diff --git a/app/views/settings/_redmine_dark_settings.html.erb b/app/views/settings/_redmine_dark_settings.html.erb new file mode 100644 index 0000000..a559237 --- /dev/null +++ b/app/views/settings/_redmine_dark_settings.html.erb @@ -0,0 +1,11 @@ +
+ <%= check_box_tag 'settings[follow_browser_theme]', '1', + @settings['follow_browser_theme'].to_s == '1' || @settings['follow_browser_theme'].to_s == 'true' %> + +
++ When enabled, dark mode will automatically activate based on your browser or operating system's dark theme setting, + in addition to the manual toggle. When disabled, dark mode can only be activated using the manual toggle. +
diff --git a/assets/javascripts/dark.js b/assets/javascripts/dark.js index 257681a..55ed243 100755 --- a/assets/javascripts/dark.js +++ b/assets/javascripts/dark.js @@ -40,26 +40,36 @@ function clickdarkmode() { } $(document).ready(function() { - console.log("dark.js"); - var topmenu = document.getElementById("top-menu"); - var divdark = document.createElement("div"); - divdark.id = "dark"; - var adivdark = document.createElement("a"); - adivdark.innerText = "dark mode"; - adivdark.href = ""; - divdark.appendChild(adivdark); - topmenu.insertBefore(divdark, topmenu.firstChild) - adivdark.onclick = clickdarkmode; + // Add to desktop top menu - as a list item in the account menu + var accountUl = document.querySelector("#account ul"); + if (accountUl) { + var lidark = document.createElement("li"); + var adivdark = document.createElement("a"); + adivdark.innerText = "dark mode"; + adivdark.href = "#"; + adivdark.className = "dark-mode-toggle"; + lidark.appendChild(adivdark); + accountUl.appendChild(lidark); + adivdark.onclick = clickdarkmode; + } + + // Add to mobile flyout menu try { var ulmobil = document.querySelectorAll('#wrapper .js-profile-menu ul')[0]; - var lidark = document.createElement("li"); - var alidark = document.createElement("a"); - alidark.innerText = "dark mode"; - alidark.href = ""; - lidark.appendChild(alidark); - ulmobil.appendChild(lidark); - alidark.onclick = clickdarkmode; - } catch (e) {} + if (ulmobil) { + var lidark = document.createElement("li"); + var alidark = document.createElement("a"); + alidark.innerText = "dark mode"; + alidark.href = "#"; + lidark.appendChild(alidark); + ulmobil.appendChild(lidark); + alidark.onclick = clickdarkmode; + } + } catch (e) { + // Silent fail for mobile menu + } + + // Initialize dark mode from cookie let initdark = getCookie(getCookieName()); if (initdark == "on") { clickdarkmode(); diff --git a/assets/stylesheets/dark.css b/assets/stylesheets/dark.css index ff040d1..69e7fff 100755 --- a/assets/stylesheets/dark.css +++ b/assets/stylesheets/dark.css @@ -1,9 +1,6 @@ -#dark { - float: right; - color: #fff; - margin-right: 8px; - margin-left: 8px; - font-weight: bold; +/* Dark mode toggle styling */ +a.dark-mode-toggle { + cursor: pointer; } html.dark { @@ -144,189 +141,190 @@ body.dark.theme-Rtmaterial tr.priority-highest td.priority { border-color: #29B6F6; } -body.dark.theme-Rtmaterial tr.overdue td:first-child, +body.dark.theme-Rtmaterial tr.overdue td:first-child, body.dark.theme-Rtmaterial #my-page tr.overdue td.id { border-left: 3px solid #29B6F6 !important; } +/* Only follow browser dark theme preference when setting is enabled */ @media (prefers-color-scheme: dark) { - #dark { + body.follow-browser-theme:not(.dark) #dark { display:none; } - - html { + + body.follow-browser-theme:not(.dark) html { background-color: #191919; } - - body { + + body.follow-browser-theme:not(.dark) { filter: invert(90%); } - - body:not(.theme-Rtmaterial) .icon{ + + body.follow-browser-theme:not(.dark):not(.theme-Rtmaterial) .icon{ filter: invert(100%); color: #555 !important; } - - body .flyout-menu { + + body.follow-browser-theme:not(.dark) .flyout-menu { background-color: #ccc } - - body .badge, - body img.gravatar, - body .wiki img, - body img.filecontent.image, - body table.indicator.summary, - body .attachments .images img, - body canvas.chartjs-render-monitor { + + body.follow-browser-theme:not(.dark) .badge, + body.follow-browser-theme:not(.dark) img.gravatar, + body.follow-browser-theme:not(.dark) .wiki img, + body.follow-browser-theme:not(.dark) img.filecontent.image, + body.follow-browser-theme:not(.dark) table.indicator.summary, + body.follow-browser-theme:not(.dark) .attachments .images img, + body.follow-browser-theme:not(.dark) canvas.chartjs-render-monitor { filter: invert(100%); } - - body table.list tr.overdue td.due_date, - body div.issue.overdue .due-date .value { + + body.follow-browser-theme:not(.dark) table.list tr.overdue td.due_date, + body.follow-browser-theme:not(.dark) div.issue.overdue .due-date .value { filter: invert(100%); color: lightcoral; } - - body #sidebar a.selected { + + body.follow-browser-theme:not(.dark) #sidebar a.selected { color: #000; } - - body #header, - body #top-menu, - body #project-jump .drdn-trigger { + + body.follow-browser-theme:not(.dark) #header, + body.follow-browser-theme:not(.dark) #top-menu, + body.follow-browser-theme:not(.dark) #project-jump .drdn-trigger { color: #555; background-color: white; } - body .diff_out, - body .diff_in { + body.follow-browser-theme:not(.dark) .diff_out, + body.follow-browser-theme:not(.dark) .diff_in { filter: invert(1); } - - body #header a, body #top-menu a, - body #content h1, body h2, body h3, body h4, body h5, body h6 { + + body.follow-browser-theme:not(.dark) #header a, body.follow-browser-theme:not(.dark) #top-menu a, + body.follow-browser-theme:not(.dark) #content h1, body.follow-browser-theme:not(.dark) h2, body.follow-browser-theme:not(.dark) h3, body.follow-browser-theme:not(.dark) h4, body.follow-browser-theme:not(.dark) h5, body.follow-browser-theme:not(.dark) h6 { color: #555 !important; } - - body #main a.user { + + body.follow-browser-theme:not(.dark) #main a.user { color: #205D86; } - - body a.issue, - body a.issue:link, - body a.issue:visited, - body div.wiki .wiki-page, - body div.wiki .external { + + body.follow-browser-theme:not(.dark) a.issue, + body.follow-browser-theme:not(.dark) a.issue:link, + body.follow-browser-theme:not(.dark) a.issue:visited, + body.follow-browser-theme:not(.dark) div.wiki .wiki-page, + body.follow-browser-theme:not(.dark) div.wiki .external { color: #205D86; } - body .highlight { + body.follow-browser-theme:not(.dark) .highlight { color: #000; } - - body input[type="submit"], - body div.flash.notice, - body table.list:not(.odd-even) tbody tr:nth-child(odd):hover, - body .odd:hover, - body #issue-changesets div.changeset:nth-child(odd):hover, - body table.list:not(.odd-even) tbody tr:nth-child(even):hover, - body .even:hover, - body #issue-changesets div.changeset:nth-child(even):hover, - body #main-menu .menu-children li a:hover, - body .pagination ul.pages li.page:hover { + + body.follow-browser-theme:not(.dark) input[type="submit"], + body.follow-browser-theme:not(.dark) div.flash.notice, + body.follow-browser-theme:not(.dark) table.list:not(.odd-even) tbody tr:nth-child(odd):hover, + body.follow-browser-theme:not(.dark) .odd:hover, + body.follow-browser-theme:not(.dark) #issue-changesets div.changeset:nth-child(odd):hover, + body.follow-browser-theme:not(.dark) table.list:not(.odd-even) tbody tr:nth-child(even):hover, + body.follow-browser-theme:not(.dark) .even:hover, + body.follow-browser-theme:not(.dark) #issue-changesets div.changeset:nth-child(even):hover, + body.follow-browser-theme:not(.dark) #main-menu .menu-children li a:hover, + body.follow-browser-theme:not(.dark) .pagination ul.pages li.page:hover { background-color: darkgrey; } - - body , - body #main-menu, - body #main-menu li a, - body #main-menu li a.selected, - body #main-menu li a:hover, - body .nodata, - body .warning, - body #errorExplanation, - body #flash_error, - body a, - body a:link, - body a:visited, - body a:hover, - body a:focus, - body div.flash.notice, - body #main-menu .menu-children li a:hover { + + body.follow-browser-theme:not(.dark), + body.follow-browser-theme:not(.dark) #main-menu, + body.follow-browser-theme:not(.dark) #main-menu li a, + body.follow-browser-theme:not(.dark) #main-menu li a.selected, + body.follow-browser-theme:not(.dark) #main-menu li a:hover, + body.follow-browser-theme:not(.dark) .nodata, + body.follow-browser-theme:not(.dark) .warning, + body.follow-browser-theme:not(.dark) #errorExplanation, + body.follow-browser-theme:not(.dark) #flash_error, + body.follow-browser-theme:not(.dark) a, + body.follow-browser-theme:not(.dark) a:link, + body.follow-browser-theme:not(.dark) a:visited, + body.follow-browser-theme:not(.dark) a:hover, + body.follow-browser-theme:not(.dark) a:focus, + body.follow-browser-theme:not(.dark) div.flash.notice, + body.follow-browser-theme:not(.dark) #main-menu .menu-children li a:hover { color: rgb(17,17,17); } - - body #main-menu, - body #main-menu li a.selected, - body #main-menu li a:hover, - body #main-menu li a.new-object { + + body.follow-browser-theme:not(.dark) #main-menu, + body.follow-browser-theme:not(.dark) #main-menu li a.selected, + body.follow-browser-theme:not(.dark) #main-menu li a:hover, + body.follow-browser-theme:not(.dark) #main-menu li a.new-object { background: white; } - - body #main-menu li a.selected, - body #content .tabs ul li a.selected, - body input[type="submit"], - body input[type="submit"]:hover, - body div.flash.notice, - body .ui-widget-header, - body #main-menu .menu-children { + + body.follow-browser-theme:not(.dark) #main-menu li a.selected, + body.follow-browser-theme:not(.dark) #content .tabs ul li a.selected, + body.follow-browser-theme:not(.dark) input[type="submit"], + body.follow-browser-theme:not(.dark) input[type="submit"]:hover, + body.follow-browser-theme:not(.dark) div.flash.notice, + body.follow-browser-theme:not(.dark) .ui-widget-header, + body.follow-browser-theme:not(.dark) #main-menu .menu-children { border-color: #78909c !important; } - body input[type="date"] { + body.follow-browser-theme:not(.dark) input[type="date"] { color-scheme: dark; } - body input[type="date"]::-webkit-calendar-picker-indicator { + body.follow-browser-theme:not(.dark) input[type="date"]::-webkit-calendar-picker-indicator { filter: invert(100%); } - body .pagination ul.pages li:first-child { + body.follow-browser-theme:not(.dark) .pagination ul.pages li:first-child { border-top-left-radius: 4px; border-bottom-left-radius: 4px; } - - body .pagination ul.pages li { + + body.follow-browser-theme:not(.dark) .pagination ul.pages li { border: 1px solid #ddd; } - - body .pagination ul.pages li.current { + + body.follow-browser-theme:not(.dark) .pagination ul.pages li.current { color: white; background-color: #ddd ; border-color: #ddd; } - body select option, - body select optgroup { + body.follow-browser-theme:not(.dark) select option, + body.follow-browser-theme:not(.dark) select optgroup { color: #ccc; background-color: #191919; } - body #activity .issue a, - body #activity .issue-note a, - body #activity .issue-edit a, - body #activity .issue-closed a, - body #activity .wiki-page a, - body #activity .news a, - body #activity .attachment a, - body #search-results dt a { + body.follow-browser-theme:not(.dark) #activity .issue a, + body.follow-browser-theme:not(.dark) #activity .issue-note a, + body.follow-browser-theme:not(.dark) #activity .issue-edit a, + body.follow-browser-theme:not(.dark) #activity .issue-closed a, + body.follow-browser-theme:not(.dark) #activity .wiki-page a, + body.follow-browser-theme:not(.dark) #activity .news a, + body.follow-browser-theme:not(.dark) #activity .attachment a, + body.follow-browser-theme:not(.dark) #search-results dt a { color: #ccc; } - body.theme-Rtmaterial tr.priority-default td.priority { + body.follow-browser-theme:not(.dark).theme-Rtmaterial tr.priority-default td.priority { border-color: #AB47BC; } - - body.theme-Rtmaterial tr.priority-lowest td.priority { - border-color: #F57F17; + + body.follow-browser-theme:not(.dark).theme-Rtmaterial tr.priority-lowest td.priority { + border-color: #F57F17; } - - body.theme-Rtmaterial tr.priority-highest td.priority { + + body.follow-browser-theme:not(.dark).theme-Rtmaterial tr.priority-highest td.priority { border-color: #29B6F6; } - - body.theme-Rtmaterial tr.overdue td:first-child, - body.theme-Rtmaterial #my-page tr.overdue td.id { + + body.follow-browser-theme:not(.dark).theme-Rtmaterial tr.overdue td:first-child, + body.follow-browser-theme:not(.dark).theme-Rtmaterial #my-page tr.overdue td.id { border-left: 3px solid #29B6F6 !important; } } diff --git a/config/settings.yml b/config/settings.yml new file mode 100644 index 0000000..c46a68b --- /dev/null +++ b/config/settings.yml @@ -0,0 +1,4 @@ +# Settings for redmine_dark plugin +follow_browser_theme: + default: false + description: 'Automatically apply dark mode based on browser/OS dark theme preference' diff --git a/init.rb b/init.rb index ecae017..7eea029 100755 --- a/init.rb +++ b/init.rb @@ -47,4 +47,8 @@ def init_redmine_dark url 'https://github.com/fraoustin/redmine_dark' author_url 'https://github.com/fraoustin' requires_redmine :version_or_higher => '2.0.0' + + settings :default => { + 'follow_browser_theme' => false + }, :partial => 'settings/redmine_dark_settings' end diff --git a/lib/dark_application_hooks.rb b/lib/dark_application_hooks.rb index 654c5e6..8bf102e 100644 --- a/lib/dark_application_hooks.rb +++ b/lib/dark_application_hooks.rb @@ -2,7 +2,15 @@ class DarkApplicationHooks < Redmine::Hook::ViewListener def view_layouts_base_html_head(context = {}) # beware of http://www.redmine.org/issues/3935 - return javascript_include_tag('dark.js', :plugin => 'redmine_dark') + + javascript_include_tag('dark.js', :plugin => 'redmine_dark') + stylesheet_link_tag("dark.css", :plugin => "redmine_dark", :media => "all") end + + def view_layouts_base_body_bottom(context = {}) + # Add a body class if follow_browser_theme setting is enabled + follow_theme = Setting.plugin_redmine_dark['follow_browser_theme'] rescue false + if follow_theme.to_s == '1' || follow_theme.to_s == 'true' + javascript_tag("document.body.classList.add('follow-browser-theme');") + end + end end