🏠 Home 

Wikipedia Dark Theme

Script gives Wikipedia pages a dark color theme


Install this script?
  1. // ==UserScript==
  2. // @name Wikipedia Dark Theme
  3. // @description Script gives Wikipedia pages a dark color theme
  4. // @author Shangru Li
  5. // @version 1.63
  6. // @match *://*.wikipedia.org/*
  7. // @match *://*.mediawiki.org/*
  8. // @match *://*.wikimedia.org/*
  9. // @match *://*.wikibooks.org/*
  10. // @match *://*.wikidata.org/*
  11. // @match *://*.wikinews.org/*
  12. // @match *://*.wikiquote.org/*
  13. // @match *://*.wikisource.org/*
  14. // @match *://*.wikiversity.org/*
  15. // @match *://*.wikivoyage.org/*
  16. // @match *://*.wiktionary.org/*
  17. // @namespace https://github.com/li-shangru/WikipediaDarkTheme
  18. // @icon https://en.wikipedia.org/favicon.ico
  19. // @grant GM_setValue
  20. // @grant GM_getValue
  21. // @run-at document-start
  22. // @license MIT
  23. // ######################___Localizations___#####################################
  24. // @name:ja Wikipediaダークテーマ
  25. // @description:ja Wikipediaのサイトのバックグラウンドを黒に変更するスクリプトです
  26. // @name:zh-CN 维基百科黑色主题
  27. // @description:zh-CN 给予维基百科网页一个黑色主题
  28. // @name:zh-TW 维基百科黑色主题
  29. // @description:zh-TW 給予維基百科網頁壹個黑色主題
  30. // ==/UserScript==
  31. 'use strict';
  32. //############################################___Default_Values___######################################################
  33. const DEFAULT_RELATIVE_LUMINANCE = 0.8;
  34. const DEFAULT_FOREGROUND_COLOR = "rgb(238, 255, 255)";
  35. const DEFAULT_BACKGROUND_COLOR = "rgb(30, 30, 30)";
  36. const DEFAULT_LINK_COLOR = "rgb(249, 186, 82)";
  37. //############################################___One_Could_Alter_If_Desired___##########################################
  38. //############################################___Global_Variables___####################################################
  39. const LOCALE = window.location.href.substring(0, window.location.href.indexOf(".wiki")).slice(-2);
  40. // list of tags of the wikipedia logos and symbols to be excluded from setting backgroundColor to white
  41. const EXCLUDE_SRC_TAG = [
  42. "protection-shackle", "green_check", "symbol_support_vote",
  43. "edit-clear", "information_icon", "increase2", "decrease_positive",
  44. "steady2", "decrease2", "increase_negative", "red_question_mark",
  45. "blue_check", "x_mark", "yes_check", "twemoji", "walnut", "cscr-featured",
  46. "sound-openclipart", "folder_hexagonal_icon", "symbol_book_class2",
  47. "question_book-new", "wiktionary-logo", "commons-logo", "wikinews-logo",
  48. "wikiquote-logo", "wikivoyage-logo", "sound-icon", "wikibooks-logo",
  49. "wikiversity-logo", "ambox", "system-search", "split-arrows", "wikiversity_logo",
  50. "wikisource-logo", "wikimedia_community_logo", "wikidata-logo", "mediawiki-logo",
  51. "wikispecies-logo", "nuvola_apps", "white_flag_icon",
  52. "wiki_letter_w_cropped", "edit-copy_purple-wikiq", "acap", "portal-puzzle",
  53. "star_of_life", "disambig-dark", "gnome", "office-book", "audio-input-microphone",
  54. "hsutvald2", "hspolitic", "hsdagensdatum", "hsvissteduatt", "pl_wiki_aktualnosci_ikona",
  55. "hsbild", "wiki_aktualnosci_ikona", "hs_vdq", "hssamarbete", "hswpedia", "w-circle",
  56. "red_pencil_icon", "merge-arrow", "generic_with_pencil", "hsaktuell", "hsearth",
  57. "wikimedia-logo-circle", "wiktionary_ko_without_text", "mediawiki-notext",
  58. "wiktprintable_without_text", "dialog-information", "applications-office",
  59. "celestia", "antistub", "wiki_letter", "kit_body", "ui_icon_edit-ltr-progressive",
  60. "merge-split-transwiki", "mergefrom", "px-steady", "px-decrease", "px-increase",
  61. "question_book", "padlock-silver", "incubator-logo", "px-chinese_conversion",
  62. "px-applications-graphics", "px-pody_candidate", "px-potd-logo", "px-pd-icon",
  63. "px-dialog-warning", "px-checked_copyright_icon", "px-valued_image_seal",
  64. "px-cscr-former", "px-red_x", "px-crystal_clear_app_kedit", "px-people_icon",
  65. "kit_shorts", "kit_#####", "wikipedia-logo-v2", "phacility_phabricator_logo",
  66. "wikimedia_cloud_services_logo", "lingualibre-logo", "le_dico_des_ados_small_logo",
  67. "vikidia_v_vectorised", "sciences_humaines", "science-symbol", "history2",
  68. "vote3_final", "p_religion_world", "tecno-rueda", "notification-icon",
  69. "countries-vector", "p_history", "social_sciences", "p_culture", "p_religion_world",
  70. "p_sport", "p_train", "p_parthenon", "wiktionary_small", "wikimedia-logo",
  71. "crystal_clear_app_xmag", "1rightarrow", "emblem-qual", "hswikimedia", "hsutvald",
  72. "hstools", "hscontribs", "pl_wiki_czywiesz_ikona", "hs_geo", "p_wwii", "p_literature",
  73. "p_science", "p_globe", "p_vip", "p_mathematics", "p_chemistry", "p_medicine3",
  74. "p_technology", "datum17", "hs_liste", "hs_rtl_exclamation", "hsbook", "hshome",
  75. "hs_korganizer", "hseditor", "84_tematyczny_logo_propozycja", "hsbra",
  76. "pl_wiki_kalendarium_ikona", "pl_wiki_inm_ikona", "wiktionarypl_nodesc",
  77. "wikimedia_polska_logo", "icon_of_three_people_in_different_shades_of_grey",
  78. "wikimania", "hs_skand", "emblem-star-gray", "help-browser-red", "globe-with-clock",
  79. "records", "office-calendar", "preferences-desktop-locale", "system-users",
  80. "applications-system", "emblem-earth", "mail-closed", "tango-nosources",
  81. "emblem-scales", "mediawiki-2020"
  82. ];
  83. // list of tags of images to have color inverted, both lists are subjected to amend
  84. const INVERT_SRC_TAG = [
  85. "loudspeaker", "signature", "signatur", "chinese_characters", "/media/math/render/",
  86. "translation_to_english_arrow", "disambig_gray", "wikimedia-logo_black", "blue_pencil",
  87. "latin_alphabet_", "_cursiva", "unbalanced_scales", "question%2c_web_fundamentals",
  88. "wikipedia-tagline", "wikipedia-wordmark", "copyright/wikipedia", "ui_icon_ellipsis",
  89. "bluebg_rounded_croped", "blue-bg_rounded_cropped_right", "icon_facebook",
  90. "youtube_social_dark_circle", "instagram_circle", "icon_twitter",
  91. "%d8%a8%d8%a7%d9%84%d8%ad%d9%84%d9%8a%d8%a9", "font_awesome_5_solid_feather-alt",
  92. "font_awesome_5_solid_tree", "font_awesome_5_solid_globe", "font_awesome_5_solid_futbol",
  93. "font_awesome_5_solid_hourglass", "font_awesome_5_solid_users", "font_awesome_5_solid_palette",
  94. "font_awesome_5_solid_rocket", "font_awesome_5_solid_bong", "vlad1Trezub", "font_awesome_5_solid_flag",
  95. "font_awesome_5_solid_university", "wikipedia_wordmark", "wikipedia-logo-textonly", "mediawiki-wordmark-en.svg"
  96. ];
  97. //############################################___Controller___##########################################################
  98. // The main control function is called at every `onreadystatechange`
  99. // Document state will go from `loading` --> `interactive` --> `complete`
  100. // Metadata Block `@run-at document-start` will ensure this script start executing when `loading`
  101. (document.onreadystatechange = function () {
  102. try {
  103. if (GM_getValue("scriptEnabled")) {
  104. if ('loading' === document.readyState) {
  105. setPageVisibility("hidden");
  106. } else if ('interactive' === document.readyState) {
  107. applyDarkTheme();
  108. } else if ('complete' === document.readyState) {
  109. setPageVisibility("visible");
  110. setupThemeByHost();
  111. }
  112. } else if ('complete' === document.readyState) {
  113. setupThemeByHost();
  114. }
  115. } catch (error) {
  116. console.error(error);
  117. }
  118. })();
  119. //############################################___Functions___###########################################################
  120. function setPageVisibility(visibility) {
  121. const entirePage = document.documentElement;
  122. entirePage.style.backgroundColor = DEFAULT_BACKGROUND_COLOR;
  123. entirePage.style.visibility = visibility;
  124. }
  125. function applyDarkTheme() {
  126. // General idea is to put all elements on a wikipedia page to an array `allElements`
  127. // traverse through this array and reverse the color of each element accordingly
  128. // running time o(n), where n is the number of elements on a page
  129. document.querySelectorAll('*').forEach(function (element) {
  130. try {
  131. if (!isSpecialElement(element)) {
  132. changeForegroundColor(element);
  133. changeBackgroundColor(element);
  134. }
  135. } catch (e) {
  136. console.log(e);
  137. }
  138. });
  139. }
  140. function isSpecialElement(e) {
  141. if (elementIsWikiLogo(e)) {
  142. invertImage(e, 90);
  143. return true;
  144. } else if (elementIsImage(e)) {
  145. changeImageIfOnLists(e);
  146. return true;
  147. } else if (elementIsHyperlink(e)) {
  148. if (e.className.toLowerCase().includes("new")) {
  149. changeForegroundColor(e);
  150. } else {
  151. e.style.color = GM_getValue("linkColor");
  152. }
  153. changeBackgroundColor(e);
  154. return true;
  155. } else if (elementIsKeyboardKey(e)) {
  156. e.style.foregroundColor = DEFAULT_BACKGROUND_COLOR;
  157. return true;
  158. } else if (elementIsFamilyTree(e)) {
  159. if (e.style.borderTop) {
  160. e.style.borderTopColor = "white";
  161. }
  162. if (e.style.borderBottom) {
  163. e.style.borderBottomColor = "white";
  164. }
  165. if (e.style.borderLeft) {
  166. e.style.borderLeftColor = "white";
  167. }
  168. if (e.style.borderRight) {
  169. e.style.borderRightColor = "white";
  170. }
  171. if (e.style.border) {
  172. e.style.borderColor = "white";
  173. }
  174. return true;
  175. } else return (elementIsLegendOrPieChart(e) || elementIsIndicationArrow(e));
  176. }
  177. function elementIsImage(e) {
  178. return e.nodeName.toLowerCase() === 'img';
  179. }
  180. function changeImageIfOnLists(img) {
  181. if (!imageInExcludeList(img)) {
  182. img.style.backgroundColor = "rgb(255, 255, 255)";
  183. }
  184. if (imageInInvertList(img)) {
  185. invertImage(img, 87);
  186. }
  187. }
  188. function imageInExcludeList(img) {
  189. for (let i = 0; i < EXCLUDE_SRC_TAG.length; i++) {
  190. if (img.src.toLowerCase().includes(EXCLUDE_SRC_TAG[i])) {
  191. return true;
  192. }
  193. }
  194. return false;
  195. }
  196. function imageInInvertList(img) {
  197. for (let i = 0; i < INVERT_SRC_TAG.length; i++) {
  198. if (img.src.toLowerCase().includes(INVERT_SRC_TAG[i])) {
  199. return true;
  200. }
  201. }
  202. return false;
  203. }
  204. function invertImage(img, percent) {
  205. img.style.filter = "invert(" + percent + "%)";
  206. }
  207. function elementIsHyperlink(e) {
  208. return e.tagName.toLowerCase() === 'a' ||
  209. e.className.toLowerCase().includes("toctext") ||
  210. e.className.toLowerCase().includes("autonym");
  211. }
  212. function elementIsWikiLogo(e) {
  213. return e.className.toLowerCase().includes("mw-wiki-logo") ||
  214. e.parentElement.className.toLowerCase().includes("mw-wiki-logo");
  215. }
  216. function elementIsKeyboardKey(e) {
  217. return e.className.toLowerCase().includes('keyboard-key');
  218. }
  219. function elementIsLegendOrPieChart(e) {
  220. return e.className.toLowerCase().includes('legend') ||
  221. e.style.borderColor.toLowerCase().includes('transparent') ||
  222. (
  223. // Pie chart template
  224. (
  225. e.style.border.toLowerCase().includes("1px solid rgb(0, 0, 0)") ||
  226. e.style.border.toLowerCase().includes("1px solid black")
  227. ) &&
  228. e.style.height === "200px" &&
  229. e.style.height === "200px" &&
  230. e.style.borderRadius === "100px"
  231. ) ||
  232. (
  233. e.nodeName === "SPAN" && e.textContent.replace(/\s/g, '').length === 0
  234. ) || e.innerHTML === "&nbsp;" || e.innerHTML === "​" || e.innerHTML === "■";
  235. }
  236. function elementIsFamilyTree(e) {
  237. return e.style.borderTop.toLowerCase().includes("1px solid black") ||
  238. e.style.borderTop.toLowerCase().includes("1px dashed black") ||
  239. e.style.borderTop.toLowerCase().includes("1px dotted black") ||
  240. e.style.borderBottom.toLowerCase().includes("1px solid black") ||
  241. e.style.borderBottom.toLowerCase().includes("1px dashed black") ||
  242. e.style.borderBottom.toLowerCase().includes("1px dotted black") ||
  243. e.style.borderLeft.toLowerCase().includes("1px solid black") ||
  244. e.style.borderLeft.toLowerCase().includes("1px dashed black") ||
  245. e.style.borderLeft.toLowerCase().includes("1px dotted black") ||
  246. e.style.borderRight.toLowerCase().includes("1px solid black") ||
  247. e.style.borderRight.toLowerCase().includes("1px dashed black") ||
  248. e.style.borderRight.toLowerCase().includes("1px dotted black") ||
  249. e.style.border.toLowerCase().includes("2px solid black");
  250. }
  251. function elementIsIndicationArrow(e) {
  252. return e.innerHTML === "▼" || e.innerHTML === "▲";
  253. }
  254. function changeForegroundColor(e) {
  255. let foregroundColor = window.getComputedStyle(e, null).getPropertyValue("color");
  256. if (colorIsRGB(foregroundColor)) {
  257. const foregroundColorSplit = splitToRGB(foregroundColor);
  258. const foregroundColorInverse = inverseRBGColor(foregroundColorSplit);
  259. const foregroundColorInverseHSV = RGBtoHSL(foregroundColorInverse[0], foregroundColorInverse[1], foregroundColorInverse[2]);
  260. const defaultForegroundColorSplit = splitToRGB(DEFAULT_FOREGROUND_COLOR);
  261. const defaultForegroundColorHSL = RGBtoHSL(defaultForegroundColorSplit[0], defaultForegroundColorSplit[1], defaultForegroundColorSplit[2]);
  262. const foregroundColorHSL = increaseLuminance(foregroundColorInverseHSV, defaultForegroundColorHSL, DEFAULT_RELATIVE_LUMINANCE, 1);
  263. const foregroundColorRGB = HSLtoRGB(foregroundColorHSL[0], foregroundColorHSL[1], foregroundColorHSL[2]);
  264. e.style.color = RGBArrayToString(foregroundColorRGB);
  265. } else {
  266. e.style.color = DEFAULT_FOREGROUND_COLOR;
  267. }
  268. }
  269. function changeBackgroundColor(e) {
  270. const elementComputedStyle = window.getComputedStyle(e, null);
  271. let backgroundColor = elementComputedStyle.getPropertyValue("background-color");
  272. if (elementToChangeBackground(e)) {
  273. e.style.backgroundColor = DEFAULT_BACKGROUND_COLOR;
  274. } else if (colorIsRGB(backgroundColor)) {
  275. const backgroundColorSplit = splitToRGB(backgroundColor);
  276. const backgroundColorInverse = inverseRBGColor(backgroundColorSplit);
  277. const backgroundColorInverseHSV = RGBtoHSL(backgroundColorInverse[0], backgroundColorInverse[1], backgroundColorInverse[2]);
  278. if (backgroundColorInverseHSV[2] < 20) {
  279. backgroundColorInverseHSV[2] += 10;
  280. } else if (backgroundColorInverseHSV[2] > 80) {
  281. backgroundColorInverseHSV[2] -= 10;
  282. }
  283. const defaultBackgroundColorSplit = splitToRGB(DEFAULT_BACKGROUND_COLOR);
  284. const defaultBackgroundColorHSL = RGBtoHSL(defaultBackgroundColorSplit[0], defaultBackgroundColorSplit[1], defaultBackgroundColorSplit[2]);
  285. const backgroundColorHSL = decreaseLuminance(backgroundColorInverseHSV, defaultBackgroundColorHSL, DEFAULT_RELATIVE_LUMINANCE, 1);
  286. const backgroundColorRGB = HSLtoRGB(backgroundColorHSL[0], backgroundColorHSL[1], backgroundColorHSL[2]);
  287. e.style.backgroundColor = RGBArrayToString(backgroundColorRGB);
  288. } else if (backgroundColor !== "rgba(0, 0, 0, 0)") {
  289. e.style.backgroundColor = DEFAULT_BACKGROUND_COLOR;
  290. }
  291. const backgroundImage = elementComputedStyle.getPropertyValue("background-image");
  292. if (backgroundImageToRemove(backgroundImage)) {
  293. e.style.background = DEFAULT_BACKGROUND_COLOR;
  294. }
  295. }
  296. function backgroundImageToRemove(backgroundImage) {
  297. // This will remove the white banner on Chinese Wikipedia main page
  298. return backgroundImage && backgroundImage.includes("Zhwp_blue_banner.png");
  299. }
  300. function elementToChangeBackground(e) {
  301. return e.id.toLowerCase().includes("mw-head") || e.id === "content" ||
  302. e.id === "mw-panel" || e.parentElement.id.includes("ca-");
  303. }
  304. //############################################___Color_Utility___#######################################################
  305. function colorIsRGB(c) {
  306. return c.match(/rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)/);
  307. }
  308. function splitToRGB(c) {
  309. const rgb = colorIsRGB(c);
  310. return [rgb[1], rgb[2], rgb[3]];
  311. }
  312. function inverseRBGColor(c) {
  313. let r, g, b;
  314. r = 255 - Number(c[0]);
  315. g = 255 - Number(c[1]);
  316. b = 255 - Number(c[2]);
  317. return [r, g, b];
  318. }
  319. function increaseLuminance(colorToChange, colorToMatch, relativeLuminance, changePerLoop) {
  320. let r###lt = colorToChange;
  321. while ((r###lt[2] / colorToMatch[2]) < relativeLuminance) {
  322. r###lt[2] += changePerLoop;
  323. }
  324. return r###lt;
  325. }
  326. function decreaseLuminance(colorToChange, colorToMatch, relativeLuminance, changePerLoop) {
  327. let r###lt = colorToChange;
  328. while ((r###lt[2] / colorToMatch[2]) > relativeLuminance) {
  329. r###lt[2] -= changePerLoop;
  330. }
  331. return r###lt;
  332. }
  333. function RGBArrayToString(rgb) {
  334. return 'rgb(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ')';
  335. }
  336. function RGBToHex(r, g, b) {
  337. return "#" + ((1 << 24) + (Math.round(r) << 16) + (Math.round(g) << 8) + Math.round(b)).toString(16).slice(1);
  338. }
  339. function RGBtoHSL(r, g, b) {
  340. r /= 255;
  341. g /= 255;
  342. b /= 255;
  343. let max = Math.max(r, g, b),
  344. min = Math.min(r, g, b);
  345. let h, s, l = (max + min) / 2;
  346. if (max === min) {
  347. h = s = 0;
  348. } else {
  349. const d = max - min;
  350. s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  351. switch (max) {
  352. case r:
  353. h = (g - b) / d + (g < b ? 6 : 0);
  354. break;
  355. case g:
  356. h = (b - r) / d + 2;
  357. break;
  358. case b:
  359. h = (r - g) / d + 4;
  360. break;
  361. }
  362. h /= 6;
  363. }
  364. return [h * 360, s * 100, l * 100];
  365. }
  366. function HSLtoRGB(h, s, l) {
  367. let r, g, b;
  368. h = h / 360;
  369. s = s / 100;
  370. l = l / 100;
  371. if (s === 0) {
  372. r = g = b = l;
  373. } else {
  374. function hue2rgb(p, q, t) {
  375. if (t < 0) t += 1;
  376. if (t > 1) t -= 1;
  377. if (t < 1 / 6) return p + (q - p) * 6 * t;
  378. if (t < 1 / 2) return q;
  379. if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
  380. return p;
  381. }
  382. const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  383. const p = 2 * l - q;
  384. r = hue2rgb(p, q, h + 1 / 3);
  385. g = hue2rgb(p, q, h);
  386. b = hue2rgb(p, q, h - 1 / 3);
  387. }
  388. return [r * 255, g * 255, b * 255];
  389. }
  390. //############################################___Host_Specific___#######################################################
  391. function setupThemeByHost() {
  392. const hostname = window.location.hostname;
  393. let settingsButtonParentList;
  394. if (hostname === "etherpad.wikimedia.org") {
  395. // Etherpad
  396. settingsButtonParentList = document.querySelector("[data-key='showusers']").parentElement;
  397. if (GM_getValue("scriptEnabled")) {
  398. overrideSpecialElementStylesForEtherpad();
  399. }
  400. } else {
  401. // Default to Wikipedia pages
  402. settingsButtonParentList = getWikiPagesSettingsButtonParentList();
  403. if (GM_getValue("scriptEnabled")) {
  404. overrideSpecialElementStylesForWikiPages();
  405. }
  406. }
  407. initSettingElements(settingsButtonParentList);
  408. }
  409. function getWikiPagesSettingsButtonParentList() {
  410. let settingsButtonParentList;
  411. if (document.getElementById("pt-login")) {
  412. settingsButtonParentList = document.getElementById("pt-login").parentElement;
  413. } else if (document.getElementById("pt-logout")) {
  414. settingsButtonParentList = document.getElementById("pt-logout").parentElement;
  415. } else if (document.getElementById("p-personal")) {
  416. settingsButtonParentList = document.getElementById("p-personal").parentElement ? document.getElementById("p-personal").parentElement : document.getElementById("p-personal");
  417. }
  418. // TODO: fall back place
  419. return settingsButtonParentList;
  420. }
  421. //############################################___Special_Styles_for_Wikipedia_Pages___##################################
  422. function overrideSpecialElementStylesForWikiPages() {
  423. document.head.appendChild(getVisitedLinkStyle());
  424. document.head.appendChild(getTableStyle());
  425. document.head.appendChild(getAncestriesStyle());
  426. document.head.appendChild(getListStyle());
  427. document.head.appendChild(getLanguageSpecificStyle());
  428. document.head.appendChild(getThemeSpecificStyle());
  429. }
  430. function getVisitedLinkStyle() {
  431. const visitedLinkStyle = document.createElement('style');
  432. visitedLinkStyle.innerHTML = `
  433. a:not(.mwe-popups-extract):visited, a:not(.mwe-popups-extract).external:visited {
  434. color: ` + GM_getValue("visitedLinkColor") + ` !important;
  435. }
  436. `
  437. return visitedLinkStyle;
  438. }
  439. function getTableStyle() {
  440. const tableStyle = document.createElement('style');
  441. tableStyle.innerHTML = `
  442. tr::before {
  443. background-color: ` + DEFAULT_BACKGROUND_COLOR + ` !important;
  444. }
  445. `
  446. return tableStyle;
  447. }
  448. function getAncestriesStyle() {
  449. const ancestriesStyle = document.createElement('style');
  450. ancestriesStyle.innerHTML = `
  451. .ahnentafel-t {
  452. border-top: white solid 1px !important;
  453. border-left: white solid 1px !important;
  454. }
  455. .ahnentafel-b {
  456. border-bottom: white solid 1px !important;
  457. border-left: white solid 1px !important;
  458. }
  459. `
  460. return ancestriesStyle;
  461. }
  462. function getListStyle() {
  463. const listStyle = document.createElement('style');
  464. listStyle.innerHTML = `
  465. ul {
  466. list-style-image: none;
  467. }
  468. `
  469. return listStyle;
  470. }
  471. function getThemeSpecificStyle() {
  472. const themeStyle = document.createElement('style');
  473. if (document.body.className.includes('skin-minerva')) {
  474. // Skin `MinervaNeue` badges
  475. themeStyle.innerHTML = `
  476. .mw-echo-notifications-badge { filter: invert(100%); }
  477. `
  478. } else if (document.body.className.includes('skin-monobook')) {
  479. // Skin `MonoBook` top white bar
  480. themeStyle.innerHTML = `
  481. body { background: none; }
  482. .mw-echo-notifications-badge { filter: invert(100%); }
  483. `
  484. } else if (document.body.className.includes('skin-timeless')) {
  485. // Skin `Timeless` side bar small screen
  486. themeStyle.innerHTML = `
  487. #searchButton { filter: invert(100%); }
  488. .mw-echo-notifications-badge { filter: invert(100%); }
  489. `
  490. } else if (document.body.className.includes('skin-vector-2022')) {
  491. // Skin `Vector 2022` side bar small screen
  492. themeStyle.innerHTML = `
  493. #mw-panel { background-image: none !important; background-color: transparent !important;}
  494. .mw-echo-notifications-badge { filter: invert(100%); }
  495. .mw-ui-icon { filter: invert(100%); }
  496. #mw-head { background-color: transparent !important; }
  497. .mw-logo-icon { background-color: transparent !important; }
  498. #content { background-color: transparent !important; }
  499. `
  500. } else if (document.body.className.includes('skin-vector')) {
  501. // Skin `Vector` badges
  502. themeStyle.innerHTML = `
  503. .mw-echo-notifications-badge { filter: invert(100%); }
  504. `
  505. }
  506. return themeStyle;
  507. }
  508. function getLanguageSpecificStyle() {
  509. const langStyle = document.createElement('style');
  510. if (LOCALE === "zh") {
  511. // Chinese conversion box & main page
  512. langStyle.innerHTML = `
  513. .vectorTabs li { background-image: none; }
  514. .vectorTabs li a span { background: ` + DEFAULT_BACKGROUND_COLOR + ` !important; }
  515. .vectorTabs li a span { color: ` + DEFAULT_FOREGROUND_COLOR + ` !important; }
  516. @media (min-width: 720px) { .mw-parser-output #mp-2012-column-right-block-b { background: ` + DEFAULT_BACKGROUND_COLOR + ` !important; } }
  517. `
  518. } else if (LOCALE === "fr") {
  519. // French Main page and side bar
  520. langStyle.innerHTML = `
  521. #accueil_2017_en-tete { background: ` + DEFAULT_BACKGROUND_COLOR + ` !important; }
  522. '#mw-panel { background: ` + DEFAULT_BACKGROUND_COLOR + ` !important; }
  523. `
  524. } else if (LOCALE === "ja") {
  525. // Japanese main page headings
  526. langStyle.innerHTML = `
  527. .mw-parser-output .mainpage-heading-title { background: linear-gradient(to right,rgb(74,51,25),rgba(173,171,170,0)) !important; }
  528. `
  529. } else if (LOCALE === "es") {
  530. // Spanish main page headings
  531. langStyle.innerHTML = `
  532. @media (min-width: 1000px) { .mw-parser-output .main-top-left { background-image: linear-gradient(to right, #070605 0%,#070605 70%, rgba(7,6,5,0)100%) !important; } }
  533. `
  534. } else if (LOCALE === "pl") {
  535. // Polish main page headings
  536. langStyle.innerHTML = `
  537. h2 { background-image: none !important; background: linear-gradient(to right,rgb(74,51,25),rgba(173,171,170,0)) !important; }
  538. `
  539. } else if (LOCALE === "ru") {
  540. // Russian main page
  541. langStyle.innerHTML = `
  542. @media (min-width: 1000px) { .main-top-left { background-image: linear-gradient(to right, #070605 0%,#070605 70%, rgba(7,6,5,0)100%) !important; } }
  543. `
  544. } else if (LOCALE === "sv") {
  545. // Swedish main page headings
  546. langStyle.innerHTML = `
  547. .mw-parser-output .frontPageBlock { background: none !important; }
  548. .frontPageBlockTitle { background: linear-gradient(to right,rgb(74,51,25),rgba(173,171,170,0)) !important; }
  549. `
  550. } else if (LOCALE === "uk") {
  551. // Ukrainian main page
  552. langStyle.innerHTML = `
  553. .mw-parser-output #main-head { background: linear-gradient(rgb(30,30,30), ` + DEFAULT_BACKGROUND_COLOR + ` ) !important; }
  554. .mw-parser-output #main-bottom { background: linear-gradient(` + DEFAULT_BACKGROUND_COLOR + `, rgb(30,30,30)) !important; }
  555. `
  556. } else if (LOCALE === "ko") {
  557. // Korean main page
  558. langStyle.innerHTML = `
  559. .mw-parser-output .main-top-left { background-image: linear-gradient(to right, #070605 0%,#070605 70%, rgba(7,6,5,0)100%) !important; }
  560. `
  561. }
  562. return langStyle;
  563. }
  564. //############################################___Special_Styles_for_Etherpad___#########################################
  565. function overrideSpecialElementStylesForEtherpad() {
  566. isEtherpadLoaded().then(() => {
  567. setEtherpadTheme('#272822', '#FFF', '#FFF', '#FFF', '#FFF', '#272822', '#272822');
  568. });
  569. }
  570. async function isEtherpadLoaded() {
  571. while (document.querySelector("iframe[name='ace_outer']") === null || document.querySelector("iframe[name='ace_outer']").contentDocument.querySelector("iframe[name='ace_inner']") === null) {
  572. await new Promise(resolve => requestAnimationFrame(resolve))
  573. }
  574. }
  575. function setEtherpadTheme(light, superDark, dark, primary, middle, text, superLight) {
  576. document.body.style.setProperty('--light-color', light);
  577. document.body.style.setProperty('--super-dark-color', superDark);
  578. document.body.style.setProperty('--dark-color', dark);
  579. document.body.style.setProperty('--primary-color', primary);
  580. document.body.style.setProperty('--middle-color', middle);
  581. document.body.style.setProperty('--text-color', text);
  582. document.body.style.setProperty('--super-light-color', superLight);
  583. const outerStyle = document.querySelector("iframe[name='ace_outer']").contentDocument.body.style;
  584. outerStyle.setProperty('--primary-color', primary);
  585. outerStyle.setProperty('--super-light-color', superLight);
  586. outerStyle.setProperty('--super-dark-color', superDark);
  587. outerStyle.setProperty('--light-color', light);
  588. outerStyle.setProperty('--dark-color', dark);
  589. const innerStyle = document.querySelector("iframe[name='ace_outer']").contentDocument.querySelector("iframe[name='ace_inner']").contentDocument.body.style;
  590. innerStyle.setProperty('--super-dark-color', superDark);
  591. innerStyle.setProperty('--primary-color', primary);
  592. }
  593. //############################################___Init_Elements___#######################################################
  594. function initSettingElements(settingsButtonParentList) {
  595. initGMStorage();
  596. insertSettingsModalStyle();
  597. createSettingsModal();
  598. addButtonListeners();
  599. addSettingsButton(settingsButtonParentList);
  600. updateSettingsModal();
  601. }
  602. function initGMStorage(reset = false) {
  603. if (!GM_getValue("linkColor") || reset) {
  604. const split = splitToRGB(DEFAULT_LINK_COLOR);
  605. GM_setValue("linkColor", RGBToHex(split[0], split[1], split[2]));
  606. }
  607. if (!GM_getValue("visitedLinkColor") || reset) {
  608. const linkColorSplit = splitToRGB(DEFAULT_LINK_COLOR);
  609. let visitedLinkColorHSL = RGBtoHSL(linkColorSplit[0], linkColorSplit[1], linkColorSplit[2]);
  610. visitedLinkColorHSL[2] -= 35;
  611. const visitedLinkColorRGB = HSLtoRGB(visitedLinkColorHSL[0], visitedLinkColorHSL[1], visitedLinkColorHSL[2]);
  612. GM_setValue("visitedLinkColor", RGBToHex(visitedLinkColorRGB[0], visitedLinkColorRGB[1], visitedLinkColorRGB[2]));
  613. }
  614. }
  615. //############################################___Settings_Button___#####################################################
  616. function addSettingsButton(parentList) {
  617. // Create a list that contains settings button
  618. let settingsButtonList = document.createElement("li");
  619. settingsButtonList.innerHTML += `
  620. <a
  621. id="settingsButton"
  622. title="Change settings for Wikipedia Dark Theme."
  623. style="font-weight: bold; color: white;"
  624. onclick="document.getElementById('settingsModal').style.display = 'block'; return false;"
  625. >
  626. Settings
  627. </a>
  628. `;
  629. // Adding toggle script button to after the login/logout button
  630. parentList.appendChild(settingsButtonList);
  631. setSettingsButton();
  632. }
  633. function setSettingsButton() {
  634. let settingsButton = document.getElementById("settingsButton");
  635. let text, title;
  636. switch (LOCALE) {
  637. case "zh":
  638. text = "设置";
  639. title = "设置维基百科黑色主题。";
  640. break;
  641. case "ja":
  642. text = "設定";
  643. title = "ウィキペディアダークテーマを設定します。";
  644. break;
  645. case "fr":
  646. text = "Les paramètres";
  647. title = "Modifiez les paramètres de Wikipédia Dark Theme.";
  648. break;
  649. default:
  650. text = "Settings";
  651. title = "Change settings for Wikipedia Dark Theme.";
  652. }
  653. settingsButton.text = text;
  654. settingsButton.title = title;
  655. settingsButton.style.color = GM_getValue("scriptEnabled") ? "white" : "black";
  656. }
  657. //############################################___Settings_Modal___######################################################
  658. function createSettingsModal() {
  659. let settingsModal = `
  660. <div id="settingsModal" class="modal">
  661. <div class="modal-content">
  662. <div class="modal-header">
  663. <h5>Wikipedia Dark Theme Settings</h5>
  664. <span id="close" class="close">&times;</span>
  665. </div>
  666. <div class="modal-body">
  667. <h6>Theme preferences</h6>
  668. <div class="form-check form-check-inline">
  669. <input class="form-check-input" type="radio" name="theme" id="darkTheme" value="dark">
  670. <label class="form-check-label" for="darkTheme">Dark</label>
  671. </div>
  672. <div class="form-check form-check-inline">
  673. <input class="form-check-input" type="radio" name="theme" id="lightTheme" value="light">
  674. <label class="form-check-label" for="lightTheme">Light</label>
  675. </div>
  676. <div class="form-check form-check-inline">
  677. <input class="form-check-input" type="radio" name="theme" id="syncTheme" value="sync">
  678. <label class="form-check-label" for="syncTheme">Sync with system</label>
  679. </div>
  680. <h6>Color preferences (in dark theme)</h6>
  681. <div class="form-check-inline">
  682. <label for="linkColor" class="label">Link color: </label>
  683. <input type="color" id="linkColor" title="Choose your link color">
  684. </div>
  685. <div class="form-check-inline">
  686. <label for="visitedLinkColor" class="label">Link color (visited): </label>
  687. <input type="color" id="visitedLinkColor" title="Choose your visited link color">
  688. </div>
  689. <br>
  690. </div>
  691. <div class="modal-footer">
  692. <button class="btn btn-outline-secondary" id="restoreButton">Restore defaults</button>
  693. <button class="btn btn-outline-secondary close">Cancel</button>
  694. <button class="btn btn-outline-primary" id="saveButton">Save changes</button>
  695. </div>
  696. </div>
  697. </div>
  698. `;
  699. document.body.insertAdjacentHTML('afterend', settingsModal)
  700. }
  701. function setSettings() {
  702. if (document.getElementById("syncTheme").checked) {
  703. GM_setValue("syncTheme", true);
  704. } else {
  705. GM_setValue("syncTheme", false);
  706. }
  707. if (document.getElementById("darkTheme").checked) {
  708. GM_setValue("scriptEnabled", true);
  709. } else {
  710. GM_setValue("scriptEnabled", false);
  711. }
  712. GM_setValue("linkColor", document.getElementById("linkColor").value);
  713. GM_setValue("visitedLinkColor", document.getElementById("visitedLinkColor").value);
  714. }
  715. function updateSettingsModal() {
  716. updateThemePreferences();
  717. updateColorPreferences();
  718. }
  719. function updateThemePreferences() {
  720. if (GM_getValue("syncTheme")) {
  721. const userPrefersDark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
  722. if (userPrefersDark) {
  723. GM_setValue("scriptEnabled", true);
  724. } else {
  725. GM_setValue("scriptEnabled", false);
  726. }
  727. document.getElementById("syncTheme").checked = "checked";
  728. } else {
  729. if (GM_getValue("scriptEnabled")) {
  730. document.getElementById("darkTheme").checked = "checked";
  731. } else {
  732. document.getElementById("lightTheme").checked = "checked";
  733. }
  734. }
  735. }
  736. function updateColorPreferences() {
  737. document.getElementById("linkColor").value = GM_getValue("linkColor");
  738. document.getElementById("visitedLinkColor").value = GM_getValue("visitedLinkColor");
  739. }
  740. function dismissSettingsModal() {
  741. const settingsModal = document.getElementById("settingsModal");
  742. settingsModal.style.display = "none";
  743. updateSettingsModal();
  744. }
  745. function addButtonListeners() {
  746. const closeButtons = document.getElementsByClassName("close");
  747. for (let i = 0; i < closeButtons.length; i++) {
  748. closeButtons[i].onclick = function () {
  749. dismissSettingsModal();
  750. }
  751. }
  752. window.onclick = function (event) {
  753. if (event.target === document.getElementById("settingsModal")) {
  754. dismissSettingsModal();
  755. }
  756. }
  757. const saveButton = document.getElementById("saveButton");
  758. saveButton.onclick = function () {
  759. setSettings();
  760. dismissSettingsModal();
  761. location.reload();
  762. }
  763. const restoreButton = document.getElementById("restoreButton");
  764. restoreButton.onclick = function () {
  765. initGMStorage(true);
  766. dismissSettingsModal();
  767. location.reload();
  768. }
  769. }
  770. function insertSettingsModalStyle() {
  771. const settingsModalStyle = document.createElement('style');
  772. settingsModalStyle.innerHTML = `
  773. .modal {
  774. font-family: var(--bs-font-sans-serif);
  775. font-size: 1rem;
  776. font-weight: 400;
  777. line-height: 1.5;
  778. color: #212529;
  779. -webkit-text-size-adjust: 100%;
  780. display: none;
  781. position: fixed;
  782. z-index: 1060;
  783. padding-top: 100px;
  784. left: 0;
  785. top: 0;
  786. width: 100%;
  787. height: 100%;
  788. overflow-x: hidden;
  789. overflow-y: auto;
  790. outline: 0;
  791. background-color: rgb(0,0,0);
  792. background-color: rgba(0,0,0,0.4);
  793. }
  794. .modal-content {
  795. position: relative;
  796. display: flex;
  797. flex-direction: column;
  798. width: 450px;
  799. pointer-events: auto;
  800. background-color: #fff;
  801. background-clip: padding-box;
  802. border: 1px solid rgba(0,0,0,.2);
  803. border-radius: .3rem;
  804. outline: 0;
  805. margin: auto;
  806. -webkit-animation-name: animatetop;
  807. -webkit-animation-duration: 0.4s;
  808. animation-name: animatetop;
  809. animation-duration: 0.4s
  810. }
  811. .modal-header {
  812. display: flex;
  813. flex-shrink: 0;
  814. align-items: center;
  815. justify-content: space-between;
  816. padding: 1rem 1rem;
  817. border-bottom: 1px solid #dee2e6;
  818. border-top-left-radius: calc(.3rem - 1px);
  819. border-top-right-radius: calc(.3rem - 1px);
  820. }
  821. .modal-body {
  822. position: relative;
  823. flex: 1 1 auto;
  824. padding: 1rem;
  825. }
  826. .modal-footer {
  827. display: flex;
  828. flex-wrap: wrap;
  829. flex-shrink: 0;
  830. align-items: center;
  831. justify-content: flex-end;
  832. padding: .75rem;
  833. border-top: 1px solid #dee2e6;
  834. border-bottom-right-radius: calc(.3rem - 1px);
  835. border-bottom-left-radius: calc(.3rem - 1px);
  836. }
  837. .modal-footer > * {
  838. margin: .25rem;
  839. }
  840. @-webkit-keyframes animatetop {
  841. from {top:-300px; opacity:0}
  842. to {top:0; opacity:1}
  843. }
  844. @keyframes animatetop {
  845. from {top:-300px; opacity:0}
  846. to {top:0; opacity:1}
  847. }
  848. #close {
  849. color: grey;
  850. float: right;
  851. font-size: 28px;
  852. font-weight: bold;
  853. }
  854. #close:hover,
  855. #close:focus {
  856. color: black;
  857. text-decoration: none;
  858. cursor: pointer;
  859. }
  860. #restoreButton {
  861. margin-right: auto;
  862. }
  863. .label {
  864. margin: .4rem;
  865. display: inline-block;
  866. }
  867. .h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
  868. margin-top: 0;
  869. margin-bottom: .5rem;
  870. font-weight: 500;
  871. line-height: 1.2;
  872. }
  873. .h5, h5 {
  874. font-size: 1.25rem;
  875. }
  876. .h6, h6 {
  877. font-size: 1rem;
  878. }
  879. .btn {
  880. display: inline-block;
  881. font-weight: 400;
  882. line-height: 1.5;
  883. color: #212529;
  884. text-align: center;
  885. text-decoration: none;
  886. vertical-align: middle;
  887. cursor: pointer;
  888. -webkit-user-select: none;
  889. -moz-user-select: none;
  890. user-select: none;
  891. background-color: transparent;
  892. border: 1px solid transparent;
  893. padding: .375rem .75rem;
  894. font-size: 1rem;
  895. border-radius: .25rem;
  896. transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
  897. }
  898. .btn-outline-primary {
  899. color: #007bff;
  900. background-color: transparent;
  901. background-image: none;
  902. border-color: #007bff;
  903. }
  904. .btn-outline-primary:hover {
  905. color: #fff;
  906. background-color: #007bff;
  907. border-color: #007bff;
  908. }
  909. .btn-outline-secondary {
  910. color: #6c757d;
  911. background-color: transparent;
  912. background-image: none;
  913. border-color: #6c757d;
  914. }
  915. .btn-outline-secondary:hover {
  916. color: #fff;
  917. background-color: #6c757d;
  918. border-color: #6c757d;
  919. }
  920. .form-check-input:checked[type="radio"] {
  921. background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e");
  922. }
  923. .form-check {
  924. display: block;
  925. min-height: 1.5rem;
  926. padding-left: 1.5em;
  927. margin-bottom: .125rem;
  928. }
  929. .form-check-inline {
  930. display: inline-block;
  931. margin-right: 1rem;
  932. }
  933. .form-check-input:checked {
  934. background-color: #0d6efd;
  935. border-color: #0d6efd;
  936. }
  937. .form-check-input[type="radio"] {
  938. border-radius: 50%;
  939. }
  940. .form-check .form-check-input {
  941. float: left;
  942. margin-left: -1.5em;
  943. }
  944. .form-check-input {
  945. width: 1em;
  946. height: 1em;
  947. margin-top: .25em;
  948. vertical-align: top;
  949. background-color: #fff;
  950. background-repeat: no-repeat;
  951. background-position: center;
  952. background-size: contain;
  953. border: 1px solid rgba(0,0,0,.25);
  954. -webkit-appearance: none;
  955. -moz-appearance: none;
  956. appearance: none;
  957. -webkit-print-color-adjust: exact;
  958. color-adjust: exact;
  959. }
  960. `
  961. document.head.appendChild(settingsModalStyle);
  962. }