From 427cf105a3882c6c04a479667d1c154640cfa55a Mon Sep 17 00:00:00 2001 From: tanc Date: Sun, 4 Dec 2022 13:53:58 +0100 Subject: [PATCH] 956 Code changes to enable dark mode --- changedetectionio/__init__.py | 19 ++- changedetectionio/forms.py | 4 +- changedetectionio/model/App.py | 1 + changedetectionio/static/js/diff-render.js | 168 ++++++++++---------- changedetectionio/static/js/toggle-theme.js | 26 +++ 5 files changed, 130 insertions(+), 88 deletions(-) create mode 100644 changedetectionio/static/js/toggle-theme.js diff --git a/changedetectionio/__init__.py b/changedetectionio/__init__.py index 5d22a280..3668b338 100644 --- a/changedetectionio/__init__.py +++ b/changedetectionio/__init__.py @@ -202,7 +202,8 @@ def changedetection_app(config=None, datastore_o=None): watch_api.add_resource(api_v1.SystemInfo, '/api/v1/systeminfo', resource_class_kwargs={'datastore': datastore, 'update_q': update_q}) - + def getDarkModeSetting(): + return datastore.data['settings']['application']['css_dark_mode'] # Setup cors headers to allow all domains # https://flask-cors.readthedocs.io/en/latest/ @@ -403,6 +404,7 @@ def changedetection_app(config=None, datastore_o=None): form = forms.quickWatchForm(request.form) output = render_template("watch-overview.html", + dark_mode=getDarkModeSetting(), form=form, watches=sorted_watches, tags=existing_tags, @@ -661,6 +663,7 @@ def changedetection_app(config=None, datastore_o=None): browser_steps_config=browser_step_ui_config, current_base_url=datastore.data['settings']['application']['base_url'], emailprefix=os.getenv('NOTIFICATION_MAIL_BUTTON_PREFIX', False), + dark_mode=getDarkModeSetting(), form=form, has_default_notification_urls=True if len(datastore.data['settings']['application']['notification_urls']) else False, has_empty_checktime=using_default_check_time, @@ -748,6 +751,7 @@ def changedetection_app(config=None, datastore_o=None): output = render_template("settings.html", form=form, + dark_mode=getDarkModeSetting(), current_base_url = datastore.data['settings']['application']['base_url'], hide_remove_pass=os.getenv("SALTED_PASS", False), api_key=datastore.data['settings']['application'].get('api_access_token'), @@ -788,6 +792,7 @@ def changedetection_app(config=None, datastore_o=None): # Could be some remaining, or we could be on GET output = render_template("import.html", + dark_mode=getDarkModeSetting(), import_url_list_remaining="\n".join(remaining_urls), original_distill_json='' ) @@ -865,6 +870,7 @@ def changedetection_app(config=None, datastore_o=None): newest=newest_version_file_contents, previous=previous_version_file_contents, extra_stylesheets=extra_stylesheets, + dark_mode=getDarkModeSetting(), versions=dates[:-1], # All except current/last uuid=uuid, newest_version_timestamp=dates[-1], @@ -912,6 +918,7 @@ def changedetection_app(config=None, datastore_o=None): content=content, history_n=watch.history_n, extra_stylesheets=extra_stylesheets, + dark_mode=getDarkModeSetting(), # current_diff_url=watch['url'], watch=watch, uuid=uuid, @@ -958,6 +965,7 @@ def changedetection_app(config=None, datastore_o=None): content=content, history_n=watch.history_n, extra_stylesheets=extra_stylesheets, + dark_mode=getDarkModeSetting(), ignored_line_numbers=ignored_line_numbers, triggered_line_numbers=trigger_line_numbers, current_diff_url=watch['url'], @@ -976,6 +984,7 @@ def changedetection_app(config=None, datastore_o=None): def notification_logs(): global notification_debug_log output = render_template("notification-log.html", + dark_mode=getDarkModeSetting(), logs=notification_debug_log if len(notification_debug_log) else ["Notification logs are empty - no notifications sent yet."]) return output @@ -1204,6 +1213,14 @@ def changedetection_app(config=None, datastore_o=None): flash("{} watches are queued for rechecking.".format(i)) return redirect(url_for('index', tag=tag)) + @app.route("/toggle-theme", methods=['GET']) + @login_required + def toggle_theme(): + current_mode = datastore.data['settings']['application']['css_dark_mode'] + new_mode = not current_mode + datastore.data['settings']['application']['css_dark_mode'] = new_mode + return '' + @app.route("/form/checkbox-operations", methods=['POST']) @login_required def form_watch_list_checkbox_operations(): diff --git a/changedetectionio/forms.py b/changedetectionio/forms.py index 5a42251d..e12b6917 100644 --- a/changedetectionio/forms.py +++ b/changedetectionio/forms.py @@ -207,9 +207,9 @@ class ValidateTokensList(object): if not p.strip('{}') in notification.valid_tokens: message = field.gettext('Token \'%s\' is not a valid token.') raise ValidationError(message % (p)) - + class validateURL(object): - + """ Flask wtform validators wont work with basic auth """ diff --git a/changedetectionio/model/App.py b/changedetectionio/model/App.py index daedde1b..df9393d6 100644 --- a/changedetectionio/model/App.py +++ b/changedetectionio/model/App.py @@ -27,6 +27,7 @@ class model(dict): 'base_url' : None, 'extract_title_as_title': False, 'empty_pages_are_a_change': False, + 'css_dark_mode': False, 'fetch_backend': getenv("DEFAULT_FETCH_BACKEND", "html_requests"), 'filter_failure_notification_threshold_attempts': _FILTER_FAILURE_THRESHOLD_ATTEMPTS_DEFAULT, 'global_ignore_text': [], # List of text to ignore when calculating the comparison checksum diff --git a/changedetectionio/static/js/diff-render.js b/changedetectionio/static/js/diff-render.js index 8d5e7a82..faff34b4 100644 --- a/changedetectionio/static/js/diff-render.js +++ b/changedetectionio/static/js/diff-render.js @@ -1,112 +1,110 @@ -var a = document.getElementById('a'); -var b = document.getElementById('b'); -var result = document.getElementById('result'); +var a = document.getElementById("a"); +var b = document.getElementById("b"); +var result = document.getElementById("result"); function changed() { - // https://github.com/kpdecker/jsdiff/issues/389 - // I would love to use `{ignoreWhitespace: true}` here but it breaks the formatting - options = {ignoreWhitespace: document.getElementById('ignoreWhitespace').checked}; - - var diff = Diff[window.diffType](a.textContent, b.textContent, options); - var fragment = document.createDocumentFragment(); - for (var i = 0; i < diff.length; i++) { - - if (diff[i].added && diff[i + 1] && diff[i + 1].removed) { - var swap = diff[i]; - diff[i] = diff[i + 1]; - diff[i + 1] = swap; - } - - var node; - if (diff[i].removed) { - node = document.createElement('del'); - node.classList.add("change"); - node.appendChild(document.createTextNode(diff[i].value)); - - } else if (diff[i].added) { - node = document.createElement('ins'); - node.classList.add("change"); - node.appendChild(document.createTextNode(diff[i].value)); - } else { - node = document.createTextNode(diff[i].value); - } - fragment.appendChild(node); + // https://github.com/kpdecker/jsdiff/issues/389 + // I would love to use `{ignoreWhitespace: true}` here but it breaks the formatting + options = { + ignoreWhitespace: document.getElementById("ignoreWhitespace").checked, + }; + + var diff = Diff[window.diffType](a.textContent, b.textContent, options); + var fragment = document.createDocumentFragment(); + for (var i = 0; i < diff.length; i++) { + if (diff[i].added && diff[i + 1] && diff[i + 1].removed) { + var swap = diff[i]; + diff[i] = diff[i + 1]; + diff[i + 1] = swap; } - result.textContent = ''; - result.appendChild(fragment); + var node; + if (diff[i].removed) { + node = document.createElement("del"); + node.classList.add("change"); + const wrapper = node.appendChild(document.createElement("span")); + wrapper.appendChild(document.createTextNode(diff[i].value)); + } else if (diff[i].added) { + node = document.createElement("ins"); + node.classList.add("change"); + const wrapper = node.appendChild(document.createElement("span")); + wrapper.appendChild(document.createTextNode(diff[i].value)); + } else { + node = document.createTextNode(diff[i].value); + } + fragment.appendChild(node); + } - // Jump at start - inputs.current = 0; - next_diff(); + result.textContent = ""; + result.appendChild(fragment); + + // Jump at start + inputs.current = 0; + next_diff(); } window.onload = function () { - - - /* Convert what is options from UTC time.time() to local browser time */ - var diffList = document.getElementById("diff-version"); - if (typeof (diffList) != 'undefined' && diffList != null) { - for (var option of diffList.options) { - var dateObject = new Date(option.value * 1000); - option.label = dateObject.toLocaleString(); - } + /* Convert what is options from UTC time.time() to local browser time */ + var diffList = document.getElementById("diff-version"); + if (typeof diffList != "undefined" && diffList != null) { + for (var option of diffList.options) { + var dateObject = new Date(option.value * 1000); + option.label = dateObject.toLocaleString(); } - - /* Set current version date as local time in the browser also */ - var current_v = document.getElementById("current-v-date"); - var dateObject = new Date(newest_version_timestamp*1000); - current_v.innerHTML = dateObject.toLocaleString(); - onDiffTypeChange(document.querySelector('#settings [name="diff_type"]:checked')); - changed(); + } + + /* Set current version date as local time in the browser also */ + var current_v = document.getElementById("current-v-date"); + var dateObject = new Date(newest_version_timestamp * 1000); + current_v.innerHTML = dateObject.toLocaleString(); + onDiffTypeChange( + document.querySelector('#settings [name="diff_type"]:checked'), + ); + changed(); }; -a.onpaste = a.onchange = - b.onpaste = b.onchange = changed; +a.onpaste = a.onchange = b.onpaste = b.onchange = changed; -if ('oninput' in a) { - a.oninput = b.oninput = changed; +if ("oninput" in a) { + a.oninput = b.oninput = changed; } else { - a.onkeyup = b.onkeyup = changed; + a.onkeyup = b.onkeyup = changed; } function onDiffTypeChange(radio) { - window.diffType = radio.value; -// Not necessary -// document.title = "Diff " + radio.value.slice(4); + window.diffType = radio.value; + // Not necessary + // document.title = "Diff " + radio.value.slice(4); } -var radio = document.getElementsByName('diff_type'); +var radio = document.getElementsByName("diff_type"); for (var i = 0; i < radio.length; i++) { - radio[i].onchange = function (e) { - onDiffTypeChange(e.target); - changed(); - } -} - -document.getElementById('ignoreWhitespace').onchange = function (e) { + radio[i].onchange = function (e) { + onDiffTypeChange(e.target); changed(); + }; } +document.getElementById("ignoreWhitespace").onchange = function (e) { + changed(); +}; -var inputs = document.getElementsByClassName('change'); +var inputs = document.getElementsByClassName("change"); inputs.current = 0; - function next_diff() { - - var element = inputs[inputs.current]; - var headerOffset = 80; - var elementPosition = element.getBoundingClientRect().top; - var offsetPosition = elementPosition - headerOffset + window.scrollY; - - window.scrollTo({ - top: offsetPosition, - behavior: "smooth" - }); - - inputs.current++; - if (inputs.current >= inputs.length) { - inputs.current = 0; - } + var element = inputs[inputs.current]; + var headerOffset = 80; + var elementPosition = element.getBoundingClientRect().top; + var offsetPosition = elementPosition - headerOffset + window.scrollY; + + window.scrollTo({ + top: offsetPosition, + behavior: "smooth", + }); + + inputs.current++; + if (inputs.current >= inputs.length) { + inputs.current = 0; + } } diff --git a/changedetectionio/static/js/toggle-theme.js b/changedetectionio/static/js/toggle-theme.js new file mode 100644 index 00000000..c9f73e85 --- /dev/null +++ b/changedetectionio/static/js/toggle-theme.js @@ -0,0 +1,26 @@ +/** + * @file + * Toggles theme between light and dark mode. + */ +$(document).ready(function () { + const url = "/toggle-theme"; + + const button = document.getElementsByClassName("toggle-theme")[0]; + + button.onclick = () => { + fetch(url) + .then(function () { + const htmlElement = document.getElementsByTagName("html"); + const isDarkMode = htmlElement[0].dataset.darkmode === "true"; + htmlElement[0].dataset.darkmode = !isDarkMode; + if (isDarkMode) { + button.classList.remove("dark"); + } else { + button.classList.add("dark"); + } + }) + .catch(function (e) { + console.log("Can't toggle the theme. Error was: ", e); + }); + }; +});