Universal Dark Theme Maker

Simple Dark Theme style for any website which you can configure per-site

安装此脚本
作者推荐脚本

您可能也喜欢Center Image & Video

安装此脚本
  1. // ==UserScript==
  2. // @name Universal Dark Theme Maker
  3. // @namespace uni_dark_theme
  4. // @version 1.41
  5. // @description Simple Dark Theme style for any website which you can configure per-site
  6. // @supportURL https://github.com/Owyn/Universal_Dark_Theme/issues
  7. // @homepage https://github.com/Owyn/Universal_Dark_Theme
  8. // @icon https://images2.imgbox.com/b3/67/Aq5XazuW_o.png
  9. // @author Owyn
  10. // @match *://*/*
  11. // @grant GM.getValue
  12. // @grant GM.setValue
  13. // @grant GM_addElement
  14. // @grant GM_registerMenuCommand
  15. // @grant unsafeWindow
  16. // @sandbox JavaScript
  17. // @compatible Chrome
  18. // @compatible Firefox
  19. // @run-at document-start
  20. // ==/UserScript==
  21.  
  22. (function() {
  23. 'use strict';
  24.  
  25. var el;
  26. var css;
  27. var cfg_color = "#c0c0c0";
  28. var cfg_bgclr = "#2e2e2e";
  29. var cfg_visclr = "#a4a4a4";
  30. var cfg_bgimg;
  31. var cfg_bgtrans;
  32. var cfg_excl;
  33. var cfg_css;
  34. var cfg_js;
  35. var cfg_match_pseudo;
  36. var cfg_active;
  37. switch(localStorage.getItem('active'))
  38. {
  39. case "true": // back-compatibility
  40. case "1":
  41. cfg_active = true;
  42. load_settings(); // start loading asap
  43. break;
  44. default:
  45. cfg_active = false;
  46. break;
  47. }
  48. async function load_settings()
  49. {
  50. cfg_excl = localStorage.getItem('excl') || "";
  51. cfg_css = localStorage.getItem('css') || "";
  52. cfg_js = localStorage.getItem('js') || "";
  53. cfg_bgimg = (localStorage.getItem('bgimg') === '1');
  54. cfg_bgtrans = (localStorage.getItem('bgtrans') === '1');
  55. cfg_match_pseudo = (localStorage.getItem('match_pseudo') === '1');
  56. let done;
  57. console.debug("UDT: GM settings started loading")
  58. if (typeof GM !== "undefined" && typeof GM.getValue === "function")
  59. {
  60. done = Promise.all([
  61. GM.getValue("Color", "#c0c0c0").then( function(result) { cfg_color = result; } , console.error),
  62. GM.getValue("bgColor", "#2e2e2e").then( function(result) { cfg_bgclr = result; } , console.error),
  63. GM.getValue("visitedColor", "#a4a4a4").then( function(result) { cfg_visclr = result; } , console.error)
  64. ]).then(
  65. function() { console.debug("UDT: GM settings loaded"); updateHTMLColorVars(); observer.observe(document.documentElement, {attributeFilter: ["style"]}); },
  66. function(error) { console.error("UDT: GM settings NOT loaded: " + error); }
  67. );
  68. }
  69. return done
  70. }
  71.  
  72. var observer = new MutationObserver((mutations) => {
  73. for (const m of mutations)
  74. {
  75. if(cfg_active && m.target.style.getPropertyValue('--cfg_visclr') === "")
  76. {
  77. updateHTMLColorVars();
  78. console.debug("UDT: color vars lost, reAdding...");
  79. }
  80. }
  81. });
  82.  
  83. function updateHTMLColorVars()
  84. {
  85. document.documentElement.style.setProperty('--cfg_color', cfg_color);
  86. document.documentElement.style.setProperty('--cfg_bgclr', cfg_bgclr);
  87. document.documentElement.style.setProperty('--cfg_visclr', cfg_visclr);
  88. }
  89.  
  90. function activate(yes, prev_active)
  91. {
  92. if(prev_active && el){console.debug("UDT: Removing dark style..."); el.remove();}
  93. if(yes)
  94. {
  95. make_css();
  96. console.debug("UDT: adding dark style...");
  97. el = GM_addElement(document.documentElement, 'style', {textContent: css});
  98. console.debug(el);
  99. if(cfg_js){eval(cfg_js);}
  100. }
  101. }
  102. function toggleDT()
  103. {
  104. cfg_active = !cfg_active;
  105. activate(cfg_active, !cfg_active);
  106. if(!cfg_active)
  107. {
  108. localStorage.removeItem('active');
  109. }
  110. else
  111. {
  112. localStorage.setItem('active', "1");
  113. updateHTMLColorVars();
  114. }
  115. }
  116.  
  117. function make_css()
  118. {
  119. let exclusions;
  120. let exc_txt = ""
  121. if(cfg_excl !== "")
  122. {
  123. exclusions = cfg_excl.split(",");
  124. for (var i = 0, len = exclusions.length; i < len; i++)
  125. {
  126. exc_txt += ":not("+exclusions[i]+")";
  127. }
  128. }
  129. let bgimg_txt = cfg_bgimg ? "-color" : "";
  130. let match_pseudo = cfg_match_pseudo ? (",*"+exc_txt+"::before,*"+exc_txt+"::after") : "";
  131. ////////////// Main thing, the style!:
  132. css = (cfg_excl !== "*" ?(cfg_bgtrans ?`
  133. :root`+exc_txt+` {
  134. color: var(--cfg_color) !important;
  135. background`+bgimg_txt+`: var(--cfg_bgclr) !important;
  136. border-color: var(--cfg_color) !important;
  137. scrollbar-color: var(--cfg_visclr) var(--cfg_bgclr) !important;
  138. color-scheme: dark;
  139. }
  140. :root `:``)+`*`+exc_txt+match_pseudo+` {
  141. color: `+(cfg_bgtrans ? `unset` : `var(--cfg_color)`)+` !important;
  142. background`+bgimg_txt+`: `+(cfg_bgtrans ? `unset` : `var(--cfg_bgclr)`)+` !important;
  143. border-color: var(--cfg_color) !important;
  144. scrollbar-color: var(--cfg_visclr) var(--cfg_bgclr) !important;
  145. color-scheme: dark;
  146. }
  147. :visited`+exc_txt+`, a:hover`+exc_txt+` {
  148. color: var(--cfg_visclr) !important;
  149. }
  150. input:focus`+exc_txt+`,textarea:focus`+exc_txt+`,select:focus`+exc_txt+`{
  151. outline: 1px solid var(--cfg_visclr) !important;
  152. }
  153. `:"")+cfg_css;
  154. //////////////
  155. }
  156.  
  157. if(cfg_active)
  158. {
  159. console.debug("UDT: Adding dark style...");
  160. make_css();
  161. el = GM_addElement(document.documentElement, 'style', {textContent: css});
  162. console.debug(unsafeWindow);
  163. console.debug(el);
  164. updateHTMLColorVars(); // placeholder colors while GM settings are still loading
  165. window.addEventListener("DOMContentLoaded", function(){
  166. if (document.documentElement.lastElementChild !== el && el.parentNode === document.documentElement) // very fast browser
  167. {
  168. while (document.documentElement.lastElementChild !== el)
  169. {
  170. document.documentElement.prepend(document.documentElement.lastElementChild);
  171. }
  172. console.debug("UDT: moved dark style to the bottom"); // actually not, cuz security restrictions
  173. }
  174. if (cfg_js)
  175. {
  176. eval(cfg_js); // execute custom JS when the page fully loads
  177. }
  178. });
  179. }
  180.  
  181. if (typeof GM_registerMenuCommand !== "undefined")
  182. {
  183. GM_registerMenuCommand("Dark Theme Configuration", ((typeof GM_addElement === "function") ? () => load_settings().then(cfg, console.error) : tell_users_they_are_wrong), "D");
  184. GM_registerMenuCommand("Toggle Dark Theme", ((typeof GM_addElement === "function") ? () => load_settings().then(toggleDT, console.error) : tell_users_they_are_wrong), "T");
  185. }
  186. else
  187. {
  188. tell_users_they_are_wrong();
  189. }
  190.  
  191. function tell_users_they_are_wrong()
  192. {
  193. let txt = "Sorry, your userscript manager is missing functionality needed for Universal Dark Theme script to work! Install TamperMonkey userscript-manager extension (the №1 most popular userscript-manager extension - how did you miss it?...)"
  194. console.error(txt);
  195. alert(txt);
  196. }
  197.  
  198. var t;
  199. var div;
  200. function cfg()
  201. {
  202. console.debug("UDT: config window open");
  203. function saveCfg()
  204. {
  205. if (typeof GM !== "undefined" && typeof GM.setValue === "function")
  206. {
  207. GM.setValue("Color", id("color").value);
  208. GM.setValue("bgColor", id("bgclr").value);
  209. GM.setValue("visitedColor", id("visitedColor").value);
  210. }
  211. localStorage.setItem('excl', id("excl").value);
  212. localStorage.setItem('css', id("css").value);
  213. localStorage.setItem('js', id("js").value);
  214. localStorage.setItem('active', id("active").checked ? "1" : "0");
  215. localStorage.setItem('match_pseudo', id("match_pseudo").checked ? "1" : "0");
  216. localStorage.setItem('bgimg', id("bgimg").checked ? "1" : "0");
  217. localStorage.setItem('bgtrans', id("bgtrans").checked ? "1" : "0");
  218. // pretty text "saved"
  219. id("cfg_save").value = "SAVED OK";
  220. clearTimeout(t);
  221. t = setTimeout(function() {id("cfg_save").value = "Save config";},1500)
  222. // update active configuration
  223. cfg_color = id("color").value;
  224. cfg_bgclr = id("bgclr").value;
  225. cfg_bgimg = id("bgimg").checked;
  226. cfg_bgtrans = id("bgtrans").checked;
  227. cfg_match_pseudo = id("match_pseudo").checked;
  228. cfg_visclr = id("visitedColor").value;
  229. cfg_excl = id("excl").value;
  230. cfg_css = id("css").value;
  231. cfg_js = id("js").value;
  232. activate(id("active").checked, cfg_active );
  233. cfg_active = id("active").checked;
  234. //
  235. updateHTMLColorVars();
  236. // clean up
  237. if(!id("active").checked) { localStorage.removeItem('active'); }
  238. if(!id("match_pseudo").checked) { localStorage.removeItem('match_pseudo'); }
  239. if(!id("bgimg").checked) { localStorage.removeItem('bgimg'); }
  240. if(!id("bgtrans").checked) { localStorage.removeItem('bgtrans'); }
  241. if(!id("excl").value) { localStorage.removeItem('excl'); }
  242. if(!id("css").value) { localStorage.removeItem('css'); }
  243. if(!id("js").value) { localStorage.removeItem('js'); }
  244. }
  245. if(div)
  246. {
  247. if(!div.isConnected) document.body.appendChild(div); // it was there but got closed
  248. return; // div already built
  249. }
  250. div = document.createElement("div");
  251. div.style = "all: initial !important; margin: auto !important; width: fit-content !important; height: fit-content !important; border: 1px solid var(--cfg_color) !important; background: var(--cfg_bgclr) !important; position: fixed !important; inset: 0 !important; z-index: 2147483647 !important; line-height: 1 !important;"; // all:initial to disable CSS inheritance
  252. div.attachShadow({mode: 'open'}); // outer CSS selectors won't affect it now (but we will still inherit styles) // we could use an iframe but extensions might block it? <iframe src='data:text/html,<strong>Hello World!</strong>'></iframe>
  253. const id = div.shadowRoot.getElementById.bind(div.shadowRoot);
  254. let shadowStyle = `* {
  255. color-scheme: dark;
  256. color: var(--cfg_color);
  257. background: var(--cfg_bgclr);
  258. border-color: var(--cfg_color);
  259. padding: initial; margin: initial;
  260. }
  261. :visited, a:hover { color: var(--cfg_visclr); }
  262. input:focus,textarea:focus,select:focus { outline: 2px solid var(--cfg_visclr); }
  263. tr,td { padding:1px; }
  264. input { padding:0px 3px; }
  265. input[type='checkbox'] { padding: 0px; }
  266. div { margin: auto; display: table; }
  267. div > input { margin:2px; width: 6em; }
  268. textarea { margin: 0px; min-width: 400px; min-height: 1em; height: 50px; resize:both; }`;
  269. div.shadowRoot.innerHTML = `<b><br><center>Configuration</center></b>
  270. <div>
  271. <br><input id='color' type='text'> Text color (empty = site default)
  272. <br><input id='bgclr' type='text'> Background color
  273. <br><input id='visitedColor' type='text'>&nbsp;<a href='' onclick='return false;'>Visited & hovered link color</a>
  274. <br><br></div><center><b>Per-site settings (stored in browser cookies called LocalStorage):</b>
  275. <table><tr><td><input id='active' type='checkbox'> Enabled for this website
  276. </td><td><input id='match_pseudo' type='checkbox'> Also color pseudo-elements
  277. </td></tr><br><br><tr><td><input id='bgimg' type='checkbox'> Keep background-images
  278. </td><td><input id='bgtrans' type='checkbox'> Make background transparent </td></tr></table>
  279. <br>Excluded css elements (e.g. \"#id1,.class2,input\"):<br><textarea id='excl'></textarea>
  280. <br><br>Custom CSS style:<br><textarea id='css'></textarea>
  281. <br><br>Custom JS Action:<br><textarea id='js'></textarea>
  282. <br><input id='cfg_save' type='button' value='Save config'> <input id='cfg_close' type='button' value='Close'></center>`;
  283. document.body.appendChild(div);
  284. GM_addElement(div.shadowRoot, 'style', {textContent: shadowStyle}); // damn CSP gets us even in our own shadow...
  285. id("color").value = cfg_color;
  286. id("bgclr").value = cfg_bgclr;
  287. id("bgimg").checked = cfg_bgimg;
  288. id("bgtrans").checked = cfg_bgtrans;
  289. id("visitedColor").value = cfg_visclr;
  290. //
  291. id("active").checked = cfg_active;
  292. id("match_pseudo").checked = cfg_match_pseudo;
  293. id("excl").value = cfg_excl;
  294. id("css").value = cfg_css;
  295. id("js").value = cfg_js;
  296. id("cfg_save").addEventListener("click", saveCfg, true);
  297. id("cfg_close").addEventListener("click", () => div.remove(), true);
  298. }
  299.  
  300. })();