From e0236646bf6673fc3abd42f87ee7ae8772632b3b Mon Sep 17 00:00:00 2001 From: Brock Whittaker Date: Wed, 29 Nov 2017 17:31:21 -0800 Subject: [PATCH] night-mode: Add custom CSS through JS. This adds custom CSS through JavaScript for things that do not scope well and will override other inherited styles. This should ONLY be used for problematic CSS that has no obvious or easy CSS-only solution. (Specifically, we need this for the "default link" styling, which is hard to override because we don't want to start winning ties due to specificity that we would not have won in the light theme). --- .eslintrc.json | 1 + static/js/night_mode.js | 100 ++++++++++++++++++++++++++++ static/js/server_events_dispatch.js | 6 +- static/js/ui.js | 4 ++ static/js/zulip.js | 6 -- static/styles/dark.css | 4 -- zproject/settings.py | 1 + 7 files changed, 111 insertions(+), 11 deletions(-) create mode 100644 static/js/night_mode.js diff --git a/.eslintrc.json b/.eslintrc.json index 42bc9161b8..182309f2fa 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -34,6 +34,7 @@ "server_events_dispatch": false, "ui": false, "ui_report": false, + "night_mode": false, "ui_util": false, "lightbox": false, "input_pill": false, diff --git a/static/js/night_mode.js b/static/js/night_mode.js new file mode 100644 index 0000000000..cca8617690 --- /dev/null +++ b/static/js/night_mode.js @@ -0,0 +1,100 @@ +var night_mode = (function () { + +var exports = {}; + +var create_stylesheet = function () { + var stylesheet = document.createElement('style'); + stylesheet.type = "text/css"; + stylesheet.classList.add("night-mode-stylesheet"); + // disable the stylesheet by default in case the user isn't running night mode. + stylesheet.disabled = true; + + return stylesheet; +}; + +// this transforms an object into CSS. The top level keys of the object are +// the CSS selectors and the second level keys are attributes which should have +// valid CSS values. +// +// EXAMPLE: +// +// { +// h1: { // h1 { +// color: "red", // color: red; +// }, // } +// p: { // p { +// "font-size": "1em", // font-size: 1em; +// "line-spacing": 1.2, // line-spacing: 1.2; +// }, // } +// } +// +// All CSS selectors are supported, everything from `h1` to +// complex selectors like `.night:not(.test)::after`. +var object_to_css = function (object) { + var css = ""; + + // for each CSS selector. + for (var selector in object) { + // ensure the object properties are its own and not ones injected into + // the Object.prototype. + if (object.hasOwnProperty(selector)) { + // create the `h1 {` line... + css += selector + " {"; + // and for each of the properties of the selector... + for (var attr in object[selector]) { + if (object[selector].hasOwnProperty(attr)) { + // add the attribute and its value like `attr: value;`. + css += "\n\t" + attr + ": " + object[selector][attr] + ";"; + } + } + css += "\n}\n"; + } + } + + // trim the excess newline. + return css.trim(); +}; + +(function () { + // the object to be converted to CSS. + // this should ONLY be used if there is no obvious way to perform this action + // by prefixing the selector with `body.night-mode`. + var css_skeleton = { + "a:hover": { + color: "#65c0ed", + }, + }; + + // create a stylesheet that can be appended to the . + var stylesheet = create_stylesheet(); + // convert to CSS. + var css = object_to_css(css_skeleton); + + if (stylesheet.styleSheet) { + stylesheet.styleSheet.cssText = css; + } else { + stylesheet.appendChild(document.createTextNode(css)); + } + + // append the stylesheet. + (document.head || document.getElementsByTagName('head')[0]).appendChild(stylesheet); + + exports.enable = function () { + // we can also query for this in the DOM with the + // `.night-mode-stylesheet` class. + stylesheet.disabled = false; + $("body").addClass("dark-mode"); + }; + + exports.disable = function () { + stylesheet.disabled = true; + $("body").removeClass("dark-mode"); + }; +}()); + +return exports; +}()); + +if (typeof module !== 'undefined') { + module.exports = night_mode; +} diff --git a/static/js/server_events_dispatch.js b/static/js/server_events_dispatch.js index a001b1f334..493094cba2 100644 --- a/static/js/server_events_dispatch.js +++ b/static/js/server_events_dispatch.js @@ -302,7 +302,11 @@ exports.dispatch_normal_event = function dispatch_normal_event(event) { if (event.setting_name === 'night_mode') { $("body").fadeOut(300); setTimeout(function () { - $("body").toggleClass("dark-mode"); + if (event.setting === true) { + night_mode.enable(); + } else { + night_mode.disable(); + } $("body").fadeIn(300); }, 300); } diff --git a/static/js/ui.js b/static/js/ui.js index ec8200bd46..fda3ad329e 100644 --- a/static/js/ui.js +++ b/static/js/ui.js @@ -284,6 +284,10 @@ $(function () { exports.initialize = function () { i18n.ensure_i18n(_setup_info_overlay); exports.show_error_for_unsupported_platform(); + + if (page_params.night_mode) { + night_mode.enable(); + } }; return exports; diff --git a/static/js/zulip.js b/static/js/zulip.js index 5c354b6cc1..230ad8a6a0 100644 --- a/static/js/zulip.js +++ b/static/js/zulip.js @@ -10,9 +10,3 @@ var current_msg_list = home_msg_list; if (typeof module !== 'undefined') { module.exports.current_msg_list = current_msg_list; } - -$(function () { - if (page_params.night_mode) { - $("body").addClass("dark-mode"); - } -}); diff --git a/static/styles/dark.css b/static/styles/dark.css index 406ebeb129..3677f9c20f 100644 --- a/static/styles/dark.css +++ b/static/styles/dark.css @@ -5,10 +5,6 @@ body.dark-mode { -webkit-font-smoothing: antialiased; } -body.dark-mode a:hover { - color: #65c0ed; -} - body.dark-mode .app-main, body.dark-mode .header-main { background-color: #212d3b; diff --git a/zproject/settings.py b/zproject/settings.py index a377745fc9..770953db5a 100644 --- a/zproject/settings.py +++ b/zproject/settings.py @@ -1022,6 +1022,7 @@ JS_SPECS = { 'js/lightbox.js', 'js/ui_report.js', 'js/ui.js', + 'js/night_mode.js', 'js/ui_util.js', 'js/pointer.js', 'js/click_handlers.js',