From 0b67ca14f35c8df2bf8c536047bfd3c5106dc009 Mon Sep 17 00:00:00 2001 From: Casvt <88994465+Casvt@users.noreply.github.com> Date: Mon, 11 Mar 2024 21:36:58 +0100 Subject: [PATCH] Sync test * First version of URL builder (#3) * Updated API docs * Added option for default notification service (#56) * Added support for args in URL Builder (#3) * Added regex checks in URL builder (#3) * Added test button when adding notification service * Updated API docs * Added recurring reminders on week days (#55) * Updated API docs --- backend/notification_service.py | 152 +++++++++++ backend/reminders.py | 6 + frontend/static/css/info.css | 6 + frontend/static/css/notification.css | 152 +++++++++++ frontend/static/js/general.js | 4 + frontend/static/js/library.js | 17 ++ frontend/static/js/notification.js | 394 +++++++++++++++++++++++++++ frontend/static/js/settings.js | 18 ++ frontend/static/js/show.js | 10 + frontend/static/js/window.js | 33 +++ frontend/templates/reminders.html | 204 ++++++++++++++ 11 files changed, 996 insertions(+) diff --git a/backend/notification_service.py b/backend/notification_service.py index 2612793..855a10b 100644 --- a/backend/notification_service.py +++ b/backend/notification_service.py @@ -174,6 +174,158 @@ def get_apprise_services() -> List[Dict[str, Union[str, Dict[str, list]]]]: return apprise_services +def _sort_tokens(t: dict) -> int: + result = [ + int(not t['required']) + ] + + if t['type'] == 'choice': + result.append(0) + elif t['type'] != 'list': + result.append(1) + else: + result.append(2) + + return result + +def get_apprise_services() -> List[Dict[str, Union[str, Dict[str, list]]]]: + apprise_services = [] + raw = Apprise().details() + for entry in raw['schemas']: + entry: Dict[str, Union[str, dict]] + result: Dict[str, Union[str, Dict[str, list]]] = { + 'name': str(entry['service_name']), + 'doc_url': entry['setup_url'], + 'details': { + 'templates': entry['details']['templates'], + 'tokens': [], + 'args': [] + } + } + + schema = entry['details']['tokens']['schema'] + result['details']['tokens'].append({ + 'name': schema['name'], + 'map_to': 'schema', + 'required': schema['required'], + 'type': 'choice', + 'options': schema['values'], + 'default': schema.get('default') + }) + + handled_tokens = {'schema'} + result['details']['tokens'] += [ + { + 'name': v['name'], + 'map_to': k, + 'required': v['required'], + 'type': 'list', + 'delim': v['delim'][0], + 'content': [ + { + 'name': content['name'], + 'required': content['required'], + 'type': content['type'], + 'prefix': content.get('prefix'), + 'regex': content.get('regex') + } + for content, _ in ((entry['details']['tokens'][e], handled_tokens.add(e)) for e in v['group']) + ] + } + for k, v in + filter( + lambda t: t[1]['type'].startswith('list:'), + entry['details']['tokens'].items() + ) + ] + handled_tokens.update( + set(map(lambda e: e[0], + filter(lambda e: e[1]['type'].startswith('list:'), + entry['details']['tokens'].items()) + )) + ) + + result['details']['tokens'] += [ + { + 'name': v['name'], + 'map_to': k, + 'required': v['required'], + 'type': v['type'].split(':')[0], + **({ + 'options': v.get('values'), + 'default': v.get('default') + } if v['type'].startswith('choice') else { + 'prefix': v.get('prefix'), + 'min': v.get('min'), + 'max': v.get('max'), + 'regex': v.get('regex') + }) + } + for k, v in + filter( + lambda t: not t[0] in handled_tokens, + entry['details']['tokens'].items() + ) + ] + + result['details']['tokens'].sort(key=_sort_tokens) + + result['details']['args'] += [ + { + 'name': v.get('name', k), + 'map_to': k, + 'required': v.get('required', False), + 'type': v['type'].split(':')[0], + **({ + 'delim': v['delim'][0], + 'content': [] + } if v['type'].startswith('list') else { + 'options': v['values'], + 'default': v.get('default') + } if v['type'].startswith('choice') else { + 'default': v['default'] + } if v['type'] == 'bool' else { + 'min': v.get('min'), + 'max': v.get('max'), + 'regex': v.get('regex') + }) + } + for k, v in + filter( + lambda a: ( + a[1].get('alias_of') is None + and not a[0] in ('cto', 'format', 'overflow', 'rto', 'verify') + ), + entry['details']['args'].items() + ) + ] + result['details']['args'].sort(key=_sort_tokens) + + apprise_services.append(result) + + apprise_services.sort(key=lambda s: s['name'].lower()) + + apprise_services.insert(0, { + 'name': 'Custom URL', + 'doc_url': 'https://github.com/caronc/apprise#supported-notifications', + 'details': { + 'templates': ['{url}'], + 'tokens': [{ + 'name': 'Apprise URL', + 'map_to': 'url', + 'required': True, + 'type': 'string', + 'prefix': None, + 'min': None, + 'max': None, + 'regex': None + }], + 'args': [] + } + }) + + return apprise_services + class NotificationService: def __init__(self, user_id: int, notification_service_id: int) -> None: self.id = notification_service_id diff --git a/backend/reminders.py b/backend/reminders.py index 5d2b0a3..38ded5d 100644 --- a/backend/reminders.py +++ b/backend/reminders.py @@ -229,6 +229,9 @@ def update( Either repeat_quantity and repeat_interval are given, weekdays is given or neither, but not both. + Note about args: + Either repeat_quantity and repeat_interval are given, weekdays is given or neither, but not both. + Raises: NotificationServiceNotFound: One of the notification services was not found. InvalidKeyValue: The value of one of the keys is not valid or @@ -510,6 +513,9 @@ def add( Either repeat_quantity and repeat_interval are given, weekdays is given or neither, but not both. + Note about args: + Either repeat_quantity and repeat_interval are given, weekdays is given or neither, but not both. + Raises: NotificationServiceNotFound: One of the notification services was not found. InvalidKeyValue: The value of one of the keys is not valid diff --git a/frontend/static/css/info.css b/frontend/static/css/info.css index da01f60..160f914 100644 --- a/frontend/static/css/info.css +++ b/frontend/static/css/info.css @@ -117,7 +117,10 @@ .form-container > form .repeat-options > button { width: calc((100% / 3) - (var(--gap) / 1.5)); min-width: min-content; +<<<<<<< HEAD +======= padding: 1rem 0rem; +>>>>>>> Development } .repeat-bar { @@ -224,7 +227,10 @@ div.options > button { .sub-inputs > input, .sub-inputs > select, .sub-inputs > button, +<<<<<<< HEAD +======= .sub-inputs > label, +>>>>>>> Development .form-container > form .repeat-options > button { width: 100%; } diff --git a/frontend/static/css/notification.css b/frontend/static/css/notification.css index 495e66e..6e77329 100644 --- a/frontend/static/css/notification.css +++ b/frontend/static/css/notification.css @@ -249,24 +249,173 @@ tr:has(input:read-only) button[data-type="save"] { display: none; } +<<<<<<< HEAD +/* */ +/* Add service */ +/* */ +#add-service-container { + display: none; +} + +.overflow-container.show-add #add-service-container { + display: block; +} + +.overflow-container.show-add > table { + display: none; +} + +#service-list { + display: flex; + gap: 1rem; + flex-wrap: wrap; + justify-content: center; +} + +#service-list button { + width: max(30%, 10rem); + height: 6rem; + + display: flex; + justify-content: center; + align-items: center; + + padding: .75rem; + border-radius: 4px; + border: 2px solid var(--color-gray); + + text-align: center; + font-size: 1.1rem; +} + +#add-service-container.show-add-window #add-service-window { + display: flex; +} + +#add-service-container.show-add-window #service-list { + display: none; +} + +/* */ +/* Add service form */ +/* */ +#add-service-window { + max-width: 30rem; + margin: auto; + + display: none; + flex-direction: column; + justify-content: center; + gap: 1rem; + + text-align: center; +} + +#add-service-window > h3 { + font-size: 1.75rem; +} + +#add-service-window > p { + margin-bottom: calc((1rem + 2px) * -1); + + border: 2px solid var(--color-gray); + border-top-left-radius: 4px; + border-top-right-radius: 4px; + padding: .75rem 1rem; + color: var(--color-gray); + + text-align: left; + + box-shadow: var(--default-shadow); +} + +#add-service-window > button { + border-radius: 4px; + border: 2px solid var(--color-gray); + padding: .75rem; +} + +#add-service-window > a, +#add-service-window > p > a { + color: var(--color-light); +} + +#add-service-window > div[data-map], +#add-service-window > div[data-map] > .entries-list { + display: flex; + flex-direction: column; + gap: inherit; +} + +#add-service-window > div[data-map] { + padding: .5rem; + border: 2px solid var(--color-gray); + border-radius: 4px; + box-shadow: var(--default-shadow); +} + +#add-service-window > div[data-map] > p { + color: var(--color-gray); + font-size: 1.1rem; +} + +.entries-list { + min-height: 5rem; + max-height: 15rem; + overflow-y: auto; + + align-items: center; + + background-color: var(--color-dark); + color: var(--color-light); + border: 2px solid var(--color-gray); + border-radius: 4px; + padding: .75rem; + box-shadow: var(--default-shadow); + + font-size: 1rem; +} + +.entries-list > p:first-child { + color: var(--color-gray); + font-size: 1.1rem; +} + +.input-entries:not(:has(div)) { + display: none; +} + +.add-row { + height: 2rem; + width: 80%; + + display: flex; +======= .add-row { width: min(100%, 21rem); display: flex; justify-content: center; flex-wrap: wrap; +>>>>>>> Development gap: 1rem; } .add-row input { flex-grow: 1; +<<<<<<< HEAD +======= height: 2rem; min-width: 0rem; +>>>>>>> Development font-size: .8rem; } .add-row button { +<<<<<<< HEAD +======= height: 2rem; +>>>>>>> Development padding: .35rem .75rem; background-color: var(--color-gray); border-radius: 4px; @@ -291,9 +440,12 @@ tr:has(input:read-only) button[data-type="save"] { height: inherit; fill: var(--color-dark); } +<<<<<<< HEAD +======= @media (max-width: 543px) { #service-list button { flex-grow: 1; } } +>>>>>>> Development diff --git a/frontend/static/js/general.js b/frontend/static/js/general.js index 547e8d8..94e3764 100644 --- a/frontend/static/js/general.js +++ b/frontend/static/js/general.js @@ -62,10 +62,14 @@ function logout() { const default_values = { 'api_key': null, 'locale': 'en-GB', +<<<<<<< HEAD + 'default_service': null +======= 'default_service': null, 'sorting_reminders': 'time', 'sorting_static': 'title', 'sorting_templates': 'title' +>>>>>>> Development }; function setupLocalStorage() { diff --git a/frontend/static/js/library.js b/frontend/static/js/library.js index 6491944..6346022 100644 --- a/frontend/static/js/library.js +++ b/frontend/static/js/library.js @@ -57,6 +57,19 @@ function getActiveTab() { // // Filling library // +function getWeekDays(locale) +{ + let baseDate = new Date(Date.UTC(2017, 0, 2)); // just a Monday + let weekDays = []; + for(i = 0; i < 7; i++) + { + weekDays.push(baseDate.toLocaleDateString(locale, { weekday: 'short' })); + baseDate.setDate(baseDate.getDate() + 1); + } + return weekDays; +}; +const week_days = getWeekDays(getLocalStorage('locale')['locale']); + function fillTable(table, results) { table.querySelectorAll('button.entry:not(.add-entry)').forEach( e => e.remove() @@ -90,7 +103,11 @@ function fillTable(table, results) { formatted_date += interval_text; } else if (r.weekdays !== null) +<<<<<<< HEAD + formatted_date += ` (each ${r.weekdays.split(',').map(d => week_days[parseInt(d)]).join(', ')})`; +======= formatted_date += ` (each ${r.weekdays.map(d => week_days[d]).join(', ')})`; +>>>>>>> Development time.innerText = formatted_date; entry.appendChild(time); diff --git a/frontend/static/js/notification.js b/frontend/static/js/notification.js index 811ee59..a021185 100644 --- a/frontend/static/js/notification.js +++ b/frontend/static/js/notification.js @@ -110,9 +110,123 @@ function fillNotificationServices() { return response.json(); }) .then(json => { +<<<<<<< HEAD + if (json.result.length) { + document.getElementById('add-reminder').classList.remove('error', 'error-icon'); + + const default_select = document.querySelector('#default-service-input'); + default_select.innerHTML = ''; + let default_service = getLocalStorage('default_service')['default_service']; + json.result.forEach(service => { + const entry = document.createElement('option'); + entry.value = service.id; + entry.innerText = service.title; + if (default_service === service.id) + entry.setAttribute('selected', ''); + default_select.appendChild(entry); + }); + if (!document.querySelector(`#default-service-input > option[value="${default_service}"]`)) + setLocalStorage({'default_service': + parseInt(document.querySelector('#default-service-input > option')?.value) + || null + }); + default_service = getLocalStorage('default_service')['default_service']; + + inputs.notification_service.innerHTML = ''; + json.result.forEach(service => { + const entry = document.createElement('div'); + + const select = document.createElement('input'); + select.dataset.id = service.id; + select.type = 'checkbox'; + entry.appendChild(select); + + const title = document.createElement('p'); + title.innerText = service.title; + entry.appendChild(title); + + inputs.notification_service.appendChild(entry); + }); + inputs.notification_service.querySelector(':first-child input').checked = true; + + const table = document.getElementById('services-list'); + table.innerHTML = ''; + json.result.forEach(service => { + const entry = document.createElement('tr'); + entry.dataset.id = service.id; + + const title_container = document.createElement('td'); + title_container.classList.add('title-column'); + const title = document.createElement('input'); + title.setAttribute('readonly', ''); + title.setAttribute('type', 'text'); + title.value = service.title; + title_container.appendChild(title); + entry.appendChild(title_container); + + const url_container = document.createElement('td'); + url_container.classList.add('url-column'); + const url = document.createElement('input'); + url.setAttribute('readonly', ''); + url.setAttribute('type', 'text'); + url.value = service.url; + url.addEventListener('keydown', e => { + if (e.key === 'Enter') + saveService(service.id); + }); + url_container.appendChild(url); + entry.appendChild(url_container); + + const actions = document.createElement('td'); + actions.classList.add('action-column'); + entry.appendChild(actions); + + const edit_button = document.createElement('button'); + edit_button.dataset.type = 'edit'; + edit_button.addEventListener('click', e => editService(service.id)); + edit_button.title = 'Edit'; + edit_button.setAttribute('aria-label', 'Edit'); + edit_button.innerHTML = icons.edit; + actions.appendChild(edit_button); + + const save_button = document.createElement('button'); + save_button.dataset.type = 'save'; + save_button.addEventListener('click', e => saveService(service.id)); + save_button.title = 'Save Edits'; + save_button.setAttribute('aria-label', 'Save Edits'); + save_button.innerHTML = icons.save; + actions.appendChild(save_button); + + const delete_button = document.createElement('button'); + delete_button.dataset.type = 'delete'; + delete_button.addEventListener('click', e => deleteService(service.id)); + delete_button.title = 'Delete'; + delete_button.setAttribute('aria-label', 'Delete'); + delete_button.innerHTML = icons.delete; + actions.appendChild(delete_button); + + table.appendChild(entry); + }); + } else { + document.getElementById('add-reminder').classList.add('error', 'error-icon'); + + inputs.notification_service.innerHTML = ''; + + const default_select = document.querySelector('#default-service-input'); + default_select.innerHTML = ''; + + const default_service = getLocalStorage('default_service')['default_service']; + if (!document.querySelector(`#default-service-input > option[value="${default_service}"]`)) + setLocalStorage({'default_service': + parseInt(document.querySelector('#default-service-input > option')?.value) + || null + }); + }; +======= fillNotificationTable(json); fillNotificationSelection(json); setNoNotificationServiceMsg(json); +>>>>>>> Development }) .catch(e => { if (e === 401) @@ -163,12 +277,18 @@ function deleteService(id, delete_reminders_using=false) { if (json.error !== null) return Promise.reject(json); row.remove(); +<<<<<<< HEAD + fillNotificationSelection(); + if (document.querySelectorAll('#services-list > tr').length === 0) + document.getElementById('add-reminder').classList.add('error', 'error-icon'); +======= fillNotificationServices(); if (delete_reminders_using) { fillLibrary(Types.reminder); fillLibrary(Types.static_reminder); fillLibrary(Types.template); }; +>>>>>>> Development }) .catch(e => { if (e.error === 'ApiKeyExpired' || e.error === 'ApiKeyInvalid') @@ -188,6 +308,80 @@ function deleteService(id, delete_reminders_using=false) { }); }; +<<<<<<< HEAD +function testService() { + const test_button = document.querySelector('#test-service'); + + // Check regexes for input's + [...document.querySelectorAll('#add-service-window > input:not([data-regex=""])[data-regex]')] + .forEach(el => el.classList.remove('error-input')); + + const faulty_inputs = + [...document.querySelectorAll('#add-service-window > input:not([data-regex=""])[data-regex]')] + .filter(el => !new RegExp + ( + el.dataset.regex.split('').reverse().join('').split(',').slice(1).join(',').split('').reverse().join(''), + el.dataset.regex.split('').reverse().join('').split(',')[0] + ).test(el.value) + ); + if (faulty_inputs.length > 0) { + faulty_inputs.forEach(el => el.classList.add('error-input')); + return; + }; + + const data = { + 'url': buildAppriseURL() + }; + if (!data.url) { + test_button.classList.add('error-input'); + test_button.title = 'Required field missing'; + return; + }; + fetch(`${url_prefix}/api/notificationservices/test?api_key=${api_key}`, { + 'method': 'POST', + 'headers': {'Content-Type': 'application/json'}, + 'body': JSON.stringify(data) + }) + .then(response => { + if (!response.ok) return Promise.reject(response.status); + + test_button.classList.remove('error-input'); + test_button.title = ''; + test_button.classList.add('show-sent'); + }) + .catch(e => { + if (e === 401) + window.location.href = `${url_prefix}/`; + else if (e === 400) { + test_button.classList.add('error-input'); + test_button.title = 'Invalid Apprise URL'; + } else + console.log(e); + }); +}; + +function toggleAddService() { + const cont = document.querySelector('.overflow-container'); + if (cont.classList.contains('show-add')) { + // Hide add + cont.classList.remove('show-add'); + hideAddServiceWindow(); + } else { + // Show add + if (notification_services === null) { + fetch(`${url_prefix}/api/notificationservices/available?api_key=${api_key}`) + .then(response => response.json()) + .then(json => { + notification_services = json.result; + const table = document.querySelector('#service-list'); + json.result.forEach((result, index) => { + const entry = document.createElement('button'); + entry.innerText = result.name; + entry.addEventListener('click', e => showAddServiceWindow(index)); + table.appendChild(entry); + }); + }); +======= // // Adding a service // @@ -310,6 +504,7 @@ function createEntriesList(token) { e.preventDefault(); e.stopImmediatePropagation(); addEntry(entries_list); +>>>>>>> Development }; }; add_row.appendChild(add_input); @@ -348,6 +543,137 @@ function addEntry(entries_list) { toggleAddRow(entries_list.querySelector('.add-row')); }; +function createTitle() { + const service_title = document.createElement('input'); + service_title.id = 'service-title'; + service_title.type = 'text'; + service_title.placeholder = 'Service Title'; + service_title.required = true; + return service_title; +}; + +function createChoice(token) { + const choice = document.createElement('select'); + choice.dataset.map = token.map_to || ''; + choice.dataset.prefix = ''; + choice.placeholder = token.name; + choice.required = token.required; + token.options.forEach(option => { + const entry = document.createElement('option'); + entry.value = option; + entry.innerText = option; + choice.appendChild(entry); + }); + if (token.default) + choice.querySelector(`option[value="${token.default}"]`).setAttribute('selected', ''); + + return choice; +}; + +function createString(token) { + const str_input = document.createElement('input'); + str_input.dataset.map = token.map_to || ''; + str_input.dataset.prefix = token.prefix || ''; + str_input.dataset.regex = token.regex || ''; + str_input.type = 'text'; + str_input.placeholder = `${token.name}${!token.required ? ' (Optional)' : ''}`; + str_input.required = token.required; + return str_input; +}; + +function createInt(token) { + const int_input = document.createElement('input'); + int_input.dataset.map = token.map_to || ''; + int_input.dataset.prefix = token.prefix || ''; + int_input.type = 'number'; + int_input.placeholder = `${token.name}${!token.required ? ' (Optional)' : ''}`; + int_input.required = token.required; + if (token.min !== null) + int_input.min = token.min; + if (token.max !== null) + int_input.max = token.max; + return int_input; +}; + +function createBool(token) { + const bool_input = document.createElement('select'); + bool_input.dataset.map = token.map_to || ''; + bool_input.dataset.prefix = ''; + bool_input.placeholder = token.name; + bool_input.required = token.required; + [['Yes', 'true'], ['No', 'false']].forEach(option => { + const entry = document.createElement('option'); + entry.value = option[1]; + entry.innerText = option[0]; + bool_input.appendChild(entry); + }); + bool_input.querySelector(`option[value="${token.default}"]`).setAttribute('selected', ''); + + return bool_input; +}; + +function createEntriesList(token) { + const entries_list = document.createElement('div'); + entries_list.classList.add('entries-list'); + entries_list.dataset.map = token.map_to || ''; + entries_list.dataset.delim = token.delim || ''; + entries_list.dataset.prefix = token.prefix || ''; + + const entries_desc = document.createElement('p'); + entries_desc.innerText = token.name; + entries_list.appendChild(entries_desc); + + const entries = document.createElement('div'); + entries.classList.add('input-entries'); + entries_list.appendChild(entries); + + const add_row = document.createElement('div'); + add_row.classList.add('add-row', 'hidden'); + const add_input = document.createElement('input'); + add_input.type = 'text'; + add_input.addEventListener('keydown', e => { + if (e.key === "Enter") { + e.preventDefault(); + e.stopImmediatePropagation(); + addEntry(entries_list); + }; + }); + add_row.appendChild(add_input); + const add_entry_button = document.createElement('button'); + add_entry_button.type = 'button'; + add_entry_button.innerText = 'Add'; + add_entry_button.addEventListener('click', e => addEntry(entries_list)); + add_row.appendChild(add_entry_button); + entries_list.appendChild(add_row); + + const add_button = document.createElement('button'); + add_button.type = 'button'; + add_button.innerHTML = icons.add; + add_button.addEventListener('click', e => toggleAddRow(add_row)); + entries_list.appendChild(add_button); + + return entries_list; +}; + +function toggleAddRow(row) { + if (row.classList.contains('hidden')) { + // Show row + row.querySelector('input').value = ''; + row.classList.remove('hidden'); + } else { + // Hide row + row.classList.add('hidden'); + }; +}; + +function addEntry(entries_list) { + const value = entries_list.querySelector('.add-row > input').value; + const entry = document.createElement('div'); + entry.innerText = value; + entries_list.querySelector('.input-entries').appendChild(entry); + toggleAddRow(entries_list.querySelector('.add-row')); +}; + function showAddServiceWindow(index) { const window = NotiEls.add_service_window; window.innerHTML = ''; @@ -359,6 +685,30 @@ function showAddServiceWindow(index) { const title = document.createElement('h3'); title.innerText = data.name; window.appendChild(title); +<<<<<<< HEAD + + const docs = document.createElement('a'); + docs.href = data.doc_url; + docs.target = '_blank'; + docs.innerText = 'Documentation'; + window.appendChild(docs); + + window.appendChild(createTitle()); + + [[data.details.tokens, 'tokens'], [data.details.args, 'args']].forEach(vars => { + if (vars[1] === 'args' && vars[0].length > 0) { + // The args are hidden behind a "Show Advanced Settings" button + const show_args = document.createElement('button'); + show_args.type = 'button'; + show_args.innerText = 'Show Advanced Settings'; + show_args.addEventListener('click', e => { + window.querySelectorAll('[data-is_arg="true"]').forEach(el => el.classList.toggle('hidden')); + show_args.innerText = show_args.innerText === 'Show Advanced Settings' ? 'Hide Advanced Settings' : 'Show Advanced Settings'; + }); + window.appendChild(show_args); + }; + +======= const docs = document.createElement('a'); docs.href = data.doc_url; @@ -381,6 +731,7 @@ function showAddServiceWindow(index) { window.appendChild(show_args); }; +>>>>>>> Development vars[0].forEach(token => { let result = null; if (token.type === 'choice') { @@ -425,9 +776,13 @@ function showAddServiceWindow(index) { }); if (vars[1] === 'args' && vars[0].length > 0) +<<<<<<< HEAD + window.querySelectorAll('[data-is_arg="true"]').forEach(el => el.classList.toggle('hidden')); +======= window.querySelectorAll('[data-is_arg="true"]').forEach( el => el.classList.toggle('hidden') ); +>>>>>>> Development }) // Bottom options @@ -443,7 +798,11 @@ function showAddServiceWindow(index) { const test = document.createElement('button'); test.id = 'test-service'; test.type = 'button'; +<<<<<<< HEAD + test.addEventListener('click', e => testService()); +======= test.onclick = e => testService(); +>>>>>>> Development options.appendChild(test); const test_text = document.createElement('div'); test_text.innerText = 'Test'; @@ -462,8 +821,13 @@ function showAddServiceWindow(index) { }; function buildAppriseURL() { +<<<<<<< HEAD + const data = notification_services[document.querySelector('#add-service-window').dataset.index]; + const inputs = document.querySelectorAll('#add-service-window > [data-map][data-is_arg="false"]'); +======= const data = notification_services[NotiEls.add_service_window.dataset.index]; const inputs = NotiEls.add_service_window.querySelectorAll('[data-map][data-is_arg="false"]'); +>>>>>>> Development const values = {}; // Gather all values and format @@ -504,9 +868,15 @@ function buildAppriseURL() { template = template.replace(`{${key}}`, value); // Add args +<<<<<<< HEAD + const args = [...document.querySelectorAll('#add-service-window > [data-map][data-is_arg="true"]')] + .map(el => { + if (['INPUT', 'SELECT'].includes(el.nodeName) && el.value) +======= const args = [...NotiEls.add_service_window.querySelectorAll('[data-map][data-is_arg="true"]')] .map(el => { if (['INPUT', 'SELECT'].includes(el.nodeName) && el.value && el.value !== el.dataset.default) +>>>>>>> Development return `${el.dataset.map}=${el.value}`; else if (el.nodeName == 'DIV') { let value = @@ -534,6 +904,24 @@ function buildAppriseURL() { console.debug(template); return template; +<<<<<<< HEAD +}; + +function addService() { + const add_button = document.querySelector('#add-service-window > .options > button[type="submit"]'); + + // Check regexes for input's + [...document.querySelectorAll('#add-service-window > input:not([data-regex=""])[data-regex]')] + .forEach(el => el.classList.remove('error-input')); + + const faulty_inputs = + [...document.querySelectorAll('#add-service-window > input:not([data-regex=""])[data-regex]')] + .filter(el => !new RegExp + ( + el.dataset.regex.split('').reverse().join('').split(',').slice(1).join(',').split('').reverse().join(''), + el.dataset.regex.split('').reverse().join('').split(',')[0] + ).test(el.value) +======= }; function testService() { @@ -605,6 +993,7 @@ function addService() { el.dataset.regex.split(',')[el.dataset.regex.split(',').length-1] ).test(el.value) ) +>>>>>>> Development ); if (faulty_inputs.length > 0) { faulty_inputs.forEach(el => el.classList.add('error-input')); @@ -652,5 +1041,10 @@ fillNotificationServices(); let notification_services = null; +<<<<<<< HEAD +document.getElementById('add-service-button').addEventListener('click', e => toggleAddService()); +document.getElementById('add-service-window').setAttribute('action', 'javascript:addService();'); +======= NotiEls.triggers.service_list.onchange = showServiceList; NotiEls.add_service_window.action = 'javascript:addService();'; +>>>>>>> Development diff --git a/frontend/static/js/settings.js b/frontend/static/js/settings.js index 1cc7e77..c4a7c7f 100644 --- a/frontend/static/js/settings.js +++ b/frontend/static/js/settings.js @@ -6,19 +6,30 @@ const SettingsEls = { }; function loadSettings() { +<<<<<<< HEAD + document.getElementById('locale-input').value = getLocalStorage('locale')['locale']; +======= // Default Service is handled by notification.fillNotificationSelection() document.getElementById('locale-input').value = getLocalStorage('locale')['locale']; +>>>>>>> Development }; function updateLocale(e) { setLocalStorage({'locale': e.target.value}); +<<<<<<< HEAD + window.location.reload(); +======= fillLibrary(Types.reminder); +>>>>>>> Development }; function updateDefaultService(e) { setLocalStorage({'default_service': parseInt(e.target.value)}); +<<<<<<< HEAD +======= // Add window is handled by show.showAdd() +>>>>>>> Development }; function changePassword() { @@ -55,7 +66,14 @@ function deleteAccount() { loadSettings(); +<<<<<<< HEAD +document.getElementById('locale-input').addEventListener('change', updateLocale); +document.querySelector('#default-service-input').addEventListener('change', updateDefaultService); +document.getElementById('change-password-form').setAttribute('action', 'javascript:changePassword()'); +document.getElementById('delete-account-button').addEventListener('click', e => deleteAccount()); +======= SettingsEls.locale_input.onchange = updateLocale; SettingsEls.default_service_input.onchange = updateDefaultService; SettingsEls.change_password_form.action = 'javascript:changePassword();'; SettingsEls.delete_account_button.onclick = e => deleteAccount(); +>>>>>>> Development diff --git a/frontend/static/js/show.js b/frontend/static/js/show.js index fcf7836..e68d4a2 100644 --- a/frontend/static/js/show.js +++ b/frontend/static/js/show.js @@ -4,6 +4,11 @@ function showAdd(type) { inputs.title.value = ''; inputs.text.value = ''; inputs.time.value = ''; +<<<<<<< HEAD + inputs.notification_service.querySelectorAll('input[type="checkbox"]').forEach(c => c.checked = false); + inputs.notification_service.querySelector(`input[type="checkbox"][data-id="${default_service}"]`).checked = true; + document.querySelectorAll('.weekday-bar > input[type="checkbox"]').forEach(el => el.checked = false); +======= inputs.notification_service.querySelectorAll('input[type="checkbox"]') .forEach(c => c.checked = false); inputs.notification_service.querySelector( @@ -11,6 +16,7 @@ function showAdd(type) { ).checked = true; document.querySelectorAll('.weekday-bar > input[type="checkbox"]') .forEach(el => el.checked = false); +>>>>>>> Development toggleNormal(); selectColor(colors[0]); inputs.color_toggle.checked = false; @@ -84,7 +90,11 @@ function showEdit(id, type) { c => c.checked = json.result.notification_services.includes(parseInt(c.dataset.id)) ); +<<<<<<< HEAD + if (type == types.reminder) { +======= if (type == Types.reminder) { +>>>>>>> Development if (json.result.repeat_interval !== null) { toggleRepeated(); type_buttons.repeat_interval.value = json.result.repeat_interval; diff --git a/frontend/static/js/window.js b/frontend/static/js/window.js index 016ba18..ab619b2 100644 --- a/frontend/static/js/window.js +++ b/frontend/static/js/window.js @@ -12,6 +12,15 @@ const inputs = { }; const type_buttons = { +<<<<<<< HEAD + 'normal_button': document.getElementById('normal-button'), + 'repeat_button': document.getElementById('repeat-button'), + 'weekday_button': document.getElementById('weekday-button'), + + 'repeat_bar': document.querySelector('.repeat-bar'), + 'repeat_interval': document.getElementById('repeat-interval'), + 'repeat_quantity': document.getElementById('repeat-quantity'), +======= 'normal_button': document.querySelector('#normal-button'), 'repeat_button': document.querySelector('#repeat-button'), 'weekday_button': document.querySelector('#weekday-button'), @@ -19,6 +28,7 @@ const type_buttons = { 'repeat_bar': document.querySelector('.repeat-bar'), 'repeat_interval': document.querySelector('#repeat-interval'), 'repeat_quantity': document.querySelector('#repeat-quantity'), +>>>>>>> Development 'weekday_bar': document.querySelector('.weekday-bar') }; @@ -61,8 +71,12 @@ function toggleNormal() { type_buttons.repeat_interval.value = ''; type_buttons.weekday_bar.classList.add('hidden'); +<<<<<<< HEAD + type_buttons.weekday_bar.querySelectorAll('input[type="checkbox"]').forEach(el => el.checked = false); +======= type_buttons.weekday_bar.querySelectorAll('input[type="checkbox"]') .forEach(el => el.checked = false); +>>>>>>> Development }; function toggleRepeated() { @@ -74,8 +88,12 @@ function toggleRepeated() { type_buttons.repeat_interval.setAttribute('required', ''); type_buttons.weekday_bar.classList.add('hidden'); +<<<<<<< HEAD + type_buttons.weekday_bar.querySelectorAll('input[type="checkbox"]').forEach(el => el.checked = false); +======= type_buttons.weekday_bar.querySelectorAll('input[type="checkbox"]') .forEach(el => el.checked = false); +>>>>>>> Development }; function toggleWeekDay() { @@ -329,6 +347,20 @@ function submitInfo() { // code run on load +<<<<<<< HEAD +loadColor(); + +document.getElementById('template-selection').addEventListener('change', e => applyTemplate()); +document.getElementById('color-toggle').addEventListener('click', e => toggleColor()); +document.getElementById('toggle-notification-service-list').addEventListener('click', e => toggleNotificationService()); +document.getElementById('normal-button').addEventListener('click', e => toggleNormal()); +document.getElementById('repeat-button').addEventListener('click', e => toggleRepeated()); +document.getElementById('weekday-button').addEventListener('click', e => toggleWeekDay()); +document.getElementById('close-info').addEventListener('click', e => hideWindow()); +document.getElementById('delete-info').addEventListener('click', e => deleteInfo()); +document.getElementById('test-reminder').addEventListener('click', e => testReminder()); +document.getElementById('info-form').setAttribute('action', 'javascript:submitInfo();'); +======= fillColors(); document.querySelector('#template-selection').onchange = e => applyTemplate(); @@ -339,3 +371,4 @@ document.querySelector('#close-info').onclick = e => showWindow("home"); document.querySelector('#delete-info').onclick = e => deleteInfo(); document.querySelector('#test-reminder').onclick = e => testReminder(); document.querySelector('#info-form').action = 'javascript:submitInfo();'; +>>>>>>> Development diff --git a/frontend/templates/reminders.html b/frontend/templates/reminders.html index 5c75db8..6d1341f 100644 --- a/frontend/templates/reminders.html +++ b/frontend/templates/reminders.html @@ -217,6 +217,90 @@ +<<<<<<< HEAD +
+

+
+
+
+ + +
+ + +
+ + +
+ +
+ + + +
+ + + +
+ + + + +
+
+=======

@@ -303,9 +387,19 @@

+>>>>>>> Development
+<<<<<<< HEAD +
+

Notification Services

+

Setup your notification providers here

+
+ +
+ + + + + + + + + + +
TitleApprise URL + + + + + +
+
+
+
+
+=======
@@ -348,11 +470,92 @@

Notification Services

+>>>>>>> Development
+<<<<<<< HEAD +
+

Settings

+
+

Locale

+ + +

Default Notification Service

+ + +

Change Password

+
+ + +
+ +

Delete Account

+ +
+

Contact and Donation

+
+ Donate to MIND + Documentation + Report an issue + Discord server +=======

Settings

@@ -441,6 +644,7 @@

Contact and Donation

Report an issueDiscord server +>>>>>>> Development