From 0e531b6e3c20a6e853d7133bf445889e63230449 Mon Sep 17 00:00:00 2001 From: Anuj Kumar Sharma Date: Sat, 14 Jun 2025 22:10:19 +0530 Subject: [PATCH] Added Wikipedia's own Dark Mode solution (#1320) * Addede wikipedia's dark mode Signed-off-by: THEBOSS0369 * Fixed codefactor issue Signed-off-by: THEBOSS0369 * Removed overriding classes Signed-off-by: THEBOSS0369 * Fixed dark mode conversion Signed-off-by: THEBOSS0369 * fixed another little update Signed-off-by: THEBOSS0369 * Added wikipedia's dark theme for zim Signed-off-by: THEBOSS0369 * fixed codefactor Signed-off-by: THEBOSS0369 * adding css file in sw file Signed-off-by: THEBOSS0369 * Added feature to switch to a standard inversion dark mode if the user loads a non-Wikimedia ZIM Signed-off-by: THEBOSS0369 * added explicit check for the theme Signed-off-by: THEBOSS0369 * Fixed dropdown issue and add asterisk Signed-off-by: THEBOSS0369 * fixing white flash Signed-off-by: THEBOSS0369 * fixing eslint Signed-off-by: THEBOSS0369 * Fixing codeerror Signed-off-by: THEBOSS0369 * REVERTING Signed-off-by: THEBOSS0369 * Revert "REVERTING" This reverts commit c515392dcf1acdafdd8023989ddeb8bb0da60c74. * Fixed color * Removing unncessary code which was added to fix flash issue Signed-off-by: THEBOSS0369 * Add translations in i18n dir Signed-off-by: THEBOSS0369 * Added in description Signed-off-by: THEBOSS0369 * Update i18n/es.jsonp.js Co-authored-by: Jaifroid * Update i18n/fr.jsonp.js Co-authored-by: Jaifroid * Update i18n/fr.jsonp.js Co-authored-by: Jaifroid --------- Signed-off-by: THEBOSS0369 Co-authored-by: Jaifroid --- i18n/en.jsonp.js | 2 + i18n/es.jsonp.js | 2 + i18n/fr.jsonp.js | 2 + service-worker.js | 1 + www/css/app.css | 1 + www/css/kiwixJS_wikiVector.css | 1247 ++++++++++++++++++++++++++++++++ www/index.html | 2 + www/js/app.js | 49 ++ www/js/lib/uiUtil.js | 29 +- 9 files changed, 1331 insertions(+), 4 deletions(-) create mode 100644 www/css/kiwixJS_wikiVector.css diff --git a/i18n/en.jsonp.js b/i18n/en.jsonp.js index 4f1a3069..d6a22dc2 100644 --- a/i18n/en.jsonp.js +++ b/i18n/en.jsonp.js @@ -57,11 +57,13 @@ document.localeJson = { "configure-display-themeoption-dark-apponly": "Dark (app only)", "configure-display-themeoption-dark-invert": "Dark (generic invert)", "configure-display-themeoption-dark-mwinvert": "Dark (wikimedia invert)", + "configure-display-themeoption-dark-wikivector": "Dark (wikipedia vector)", "configure-display-themeoption-auto-apponly": "Auto (app only)", "configure-display-themeoption-auto-invert": "Auto (generic invert)", "configure-display-themeoption-auto-mwinvert": "Auto (wikimedia invert)", "configure-display-apptheme-auto-description": " (Auto themes match the dark/light mode of your device.)", "configure-display-apptheme-mwinvert-description": "* Implements workarounds specific to Wikimedia ZIMs. Try generic option if there are display errors with recent ZIMs.", + "configure-display-apptheme-wikivector-description": "* Only applies to Wikimedia ZIMs. Falls back to generic inversion when other archives are loaded.", "configure-display-apptheme-info": "[ Show article with applied theme ]", "configure-display-libzimsearchtype": "Add snippets to libzim fulltext search (if available)", "configure-display-libzimsearchtype-tip": "If checked, uses libzim fulltext search with snippet previews if available, otherwise just gets fulltext titles. Hover, click or press enter on keyboard to expand the snippet.", diff --git a/i18n/es.jsonp.js b/i18n/es.jsonp.js index 0d824b21..033b7240 100644 --- a/i18n/es.jsonp.js +++ b/i18n/es.jsonp.js @@ -57,11 +57,13 @@ document.localeJson = { "configure-display-themeoption-dark-apponly": "Oscuro (sólo aplicación)", "configure-display-themeoption-dark-invert": "Oscuro (inversión genérica)", "configure-display-themeoption-dark-mwinvert": "Oscuro (inversión wikimedia)", + "configure-display-themeoption-dark-wikivector": "Oscuro (estilo vector de wikipedia)", "configure-display-themeoption-auto-apponly": "Auto (sólo aplicación)", "configure-display-themeoption-auto-invert": "Auto (inversión genérica)", "configure-display-themeoption-auto-mwinvert": "Auto (inversión wikimedia)", "configure-display-apptheme-auto-description": " (Temas automáticos se adaptan al modo oscuro/claro de su dispositivo.)", "configure-display-apptheme-mwinvert-description": "* Implementa soluciones específicas para ZIMs de Wikimedia. Pruebe la opción genérica si hay errores de visualización con otros ZIMs.", + "configure-display-apptheme-wikivector-description": "* Solo se aplica a los ZIM de Wikimedia. Se revierte a la inversión genérica cuando se cargan otros archivos.", "configure-display-apptheme-info": "[ Mostrar artículo con tema seleccionado ]", "configure-display-libzimsearchtype": "Agregar fragmentos a la búsqueda de texto completo libzim (si están disponibles)", "configure-display-libzimsearchtype-tip": "Si está marcado, usa la búsqueda de texto completo libzim con vistas previas de fragmentos si están disponibles, de lo contrario solo obtiene títulos de texto completo. Pase el cursor, haga clic o presione enter en el teclado para expandir el fragmento.", diff --git a/i18n/fr.jsonp.js b/i18n/fr.jsonp.js index cc8d76da..6af804fe 100644 --- a/i18n/fr.jsonp.js +++ b/i18n/fr.jsonp.js @@ -57,11 +57,13 @@ document.localeJson = { "configure-display-themeoption-dark-apponly": "Sombre (application uniquement)", "configure-display-themeoption-dark-invert": "Sombre (inversion générique)", "configure-display-themeoption-dark-mwinvert": "Sombre (inversion Wikimédia)", + "configure-display-themeoption-dark-wikivector": "Sombre (style vector de Wikipédia)", "configure-display-themeoption-auto-apponly": "Automatique (application uniquement)", "configure-display-themeoption-auto-invert": "Automatique (inversion générique)", "configure-display-themeoption-auto-mwinvert": "Automatique (inversion Wikimédia)", "configure-display-apptheme-auto-description": " (Les thèmes automatiques s'adaptent au mode sombre/clair de votre appareil.)", "configure-display-apptheme-mwinvert-description": "* Implémente des solutions spécifiques aux ZIM de Wikimédia. Essayez l'option générique si vous rencontrez des problèmes d'affichage avec d'autres ZIM.", + "configure-display-apptheme-wikivector-description": "* S’applique uniquement aux ZIMs de Wikimédia. Revient à l’inversion générique lorsque d’autres archives sont chargées.", "configure-display-apptheme-info": "[ Afficher l'article avec le thème sélectionné ]", "configure-display-libzimsearchtype": "Ajouter des extraits à la recherche en texte intégral libzim (si disponible)", "configure-display-libzimsearchtype-tip": "Si coché, utilise la recherche en texte intégral libzim avec des aperçus d'extraits si disponibles, sinon obtient seulement les titres en texte intégral. Survolez, cliquez ou appuyez sur Entrée au clavier pour développer l'extrait.", diff --git a/service-worker.js b/service-worker.js index c96183ab..c0ddb2bc 100644 --- a/service-worker.js +++ b/service-worker.js @@ -116,6 +116,7 @@ const precacheFiles = [ 'www/css/app.css', 'www/css/kiwixJS_invert.css', 'www/css/kiwixJS_mwInvert.css', + 'www/css/kiwixJS_wikiVector.css', 'www/css/transition.css', 'www/img/icons/kiwix-256.png', 'www/img/icons/kiwix-32.png', diff --git a/www/css/app.css b/www/css/app.css index a494aa1f..295390f4 100644 --- a/www/css/app.css +++ b/www/css/app.css @@ -550,3 +550,4 @@ button { } } + diff --git a/www/css/kiwixJS_wikiVector.css b/www/css/kiwixJS_wikiVector.css new file mode 100644 index 00000000..67d39da6 --- /dev/null +++ b/www/css/kiwixJS_wikiVector.css @@ -0,0 +1,1247 @@ +:root { + --default-text-color: 240, 240, 240; + --default-main-color: 24, 24, 24; + --default-accent-color: 38, 38, 38; + --default-header-background-color: 74, 74, 74; + --default-redlink-color: 255, 51, 51; + --default-important-button-color: 51, 102, 204; + --default-unvisited-link-color: 107, 162, 250; + --default-visited-link-color: 115, 102, 255; + --default-timestamp-link-color: 200, 200, 200; + --default-negative-color: 255, 37, 37; + --default-positive-color: 41, 202, 44; + --default-diff-added-color: 128, 193, 255; + --default-diff-deleted-color: 251, 200, 80; + --default-accented-border-color: 170, 170, 170; + --background-color-interactive-subtle: rgb(var(--accent-color, var(--default-accent-color))); +} + +:not( + .legend-color, + .legend-content, + body, + .diffchange, + [role="img"] *, + .vjs-control, + .vjs-control *, + .mw-tmh-player *, + .minerva-icon, + .cdx-table *, + .kiwixtooltip, + .kiwixtooltip * +) { + box-shadow: none !important; + background-color: transparent !important; +} + +/* popover code */ +.kiwixtooltip, +.kiwixtooltip > div, +.kiwixtooltip > div > div +{ + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.kiwixtooltip > div > div[style*="background: black"] { + background: rgb(var(--header-background-color, var(--default-header-background-color))) !important; + opacity: 1 !important; +} + +.kiwixtooltip > span[style*="border-bottom"] { + border-bottom-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.kiwixtooltip p, +.kiwixtooltip b, +.kiwixtooltip a, +.kiwixtooltip sup { + color: rgb(var(--text-color, var(--default-text-color))) !important; + background-color: transparent !important; +} + +.kiwixtooltip a { + color: rgb(var(--unvisited-link-color, var(--default-unvisited-link-color))) !important; +} +.kiwixtooltip a:visited { + color: rgb(var(--visited-link-color, var(--default-visited-link-color))) !important; +} + +* { + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +body.mediawiki { + background-image: none !important; + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} +body.mediawiki.skin-vector-2022 { + background-color: rgb(var(--main-color, var(--default-main-color))) !important; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +td, +tr { + background: transparent !important; +} + +th { + background-color: rgb(var(--header-background-color, var(--default-header-background-color))) !important; + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +ul { + list-style-image: url('data:image/svg+xml;utf8,'); +} + +.vector-header-container { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; + border-bottom: 2px solid rgb(var(--accented-border-color, var(--default-accented-border-color))) !important; +} +.vector-sticky-header { + border: none !important; +} + +.vector-menu-portal h3 { + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.vector-dropdown-label::after, +.mw-interlanguage-selector::after { + filter: brightness(0) invert(1) !important; +} + +.vector-icon { + background-color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.vector-page-toolbar-container { + box-shadow: 0 1px rgb(var(--accented-border-color, var(--default-accented-border-color))) !important; +} + +.vector-sticky-pinned-container::after { + background: linear-gradient( + rgba(var(--main-color, var(--default-main-color)), 0), + rgb(var(--main-color, var(--default-main-color))) + ) !important; +} + +.vector-dropdown-content { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; + border: 1px solid rgb(var(--accented-border-color, var(--default-accented-border-color))) !important; +} + +.cdx-search-input input, +.cdx-search-input button { + border-width: 2px !important; +} + +.uls-menu { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +body.skin-vector-2022 .mw-logo-container { + filter: invert(1) !important; +} + +.mw-body { + color: rgb(var(--text-color, var(--default-text-color))); + background-color: rgb(var(--main-color, var(--default-main-color))) !important; +} + +#mw-page-base { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; + background-image: none; +} + +body:not(.skin-vector-2022) #mw-panel { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +body:not(.skin-vector-2022) .vector-menu-tabs li { + background-image: linear-gradient( + to bottom, + rgb(var(--accent-color, var(--default-accent-color))), + rgb(var(--main-color, var(--default-main-color))) + ); +} + +body:not(.skin-vector-2022) .vector-menu-tabs .selected { + background-image: linear-gradient( + to bottom, + rgb(var(--accent-color, var(--default-accent-color))), + rgb(var(--main-color, var(--default-main-color))) + ); +} + +body.skin-vector-2022 .vector-menu-tabs .selected span { + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.vector-menu-tabs .selected a, +.vector-menu-tabs .selected a:visited { + color: rgb(var(--text-color, var(--default-text-color))); +} + +.vector-menu-tabs .new a, +.vector-menu-tabs .new a:visited, +.vector-menu-tabs .new a *, +.vector-menu-tabs .new a:visited * { + color: rgb(var(--redlink-color, var(--default-redlink-color))) !important; +} + +a.new:visited, +a.new, +#p-personal a.new, +#p.personal a.new:visited, +a.new:visited *, +a.new * { + color: rgb(var(--redlink-color, var(--default-redlink-color))) !important; +} + +.mw-ui-icon::before { + filter: brightness(0) saturate(100%) invert(1) !important; +} + +.oo-ui-icon-tray, +.mw-ui-icon-tray::before, +.oo-ui-icon-tray::after, +.oo-ui-icon-bell, +.mw-ui-icon-bell::before, +.oo-ui-icon-bell::after { + filter: invert(1) !important; +} + +#pt-notifications-alert .mw-echo-notifications-badge.mw-echo-notifications-badge-all-read, +#pt-notifications-notice .mw-echo-notifications-badge.mw-echo-notifications-badge-all-read { + opacity: 1; +} + +#editpage-specialchars a { + background-color: rgb(var(--main-color, var(--default-main-color))) !important; + border: none; +} + +body:not(.skin-vector-2022) #searchInput { + background-color: rgb(var(--main-color, var(--default-main-color))) !important; + border: 2px solid #a2a9b1; + color: rgb(var(--text-color, var(--default-text-color))); + border-radius: 6px; +} + +body.skin-vector-2022 #simpleSearch .cdx-text-input__icon { + background-color: rgb(var(--accented-border-color, var(--default-accented-border-color))) !important; +} + +#searchButton { + display: none; + visibility: hidden; +} + +.toc, +.toccolours { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.tocnumber { + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.infobox th { + background-color: rgb(var(--header-background-color, var(--default-header-background-color))) !important; +} + +.infobox th[scope="row"], +.infobox th.infobox-label { + background-color: transparent !important; +} + +.infobox-subheader, +.infobox-header { + background-color: rgb(var(--header-background-color, var(--default-header-background-color))) !important; + border: none; +} + +.navbox, +.catlinks, +.thumbinner, +table.ambox, +table { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.navbox-list, +table:not( + .box-Multiple_issues, + .box-Citation_style, + .box-Advert, + .box-Underlinked, + .box-POV, + .ambox-protection, + .ambox-move, + .ambox-content, + .ambox-style, + .ambox-delete, + .ambox-speedy, + .ambox-notice + ) { + border-color: rgb(var(--accented-border-color, var(--default-accented-border-color))) !important; +} + +.navbox th, +.navbox-title, +.navbox-group, +.navbox-abovebelow, +.navbox-subgroup { + background-color: rgb(var(--header-background-color, var(--default-header-background-color))) !important; +} + +.navbox-even { + background-color: transparent !important; +} + +tr + tr > .navbox-abovebelow, +tr + tr > .navbox-group, +tr + tr > .navbox-image, +tr + tr > .navbox-list { + border-top: 1px solid rgb(var(--text-color, var(--default-text-color))); +} + +.wikitable > tr > th, +.wikitable > * > tr > th { + background-color: rgb(var(--header-background-color, var(--default-header-background-color))) !important; +} + +.mp-box { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; + color: rgb(var(--text-color, var(--default-text-color))) !important; + border-color: rgb(var(--accented-border-color, var(--default-accented-border-color))) !important; +} + +.mp-h2 { + background-color: rgb(var(--header-background-color, var(--default-header-background-color))) !important; + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.mw-footer li { + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.fmbox, +.tmbox, +.cmbox, +.ambox, +.imbox, +.ombox, +.mbox, +.dmbox, +.asbox, +.sistersitebox, +.wikitable, +.infobox, +.documentation, +.documentation-metadata, +.mw-advancedSearch-fieldContainer .oo-ui-labelElement > .oo-ui-fieldsetLayout-header > .oo-ui-labelElement-label, +.client-js .fmbox, +.client-js .tmbox, +.client-js .cmbox, +.client-js .ambox, +.client-js .imbox, +.client-js .ombox, +.client-js .mbox, +.client-js .dmbox, +.client-js .asbox { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.asbox, +.dmbox { + border: 1px solid rgb(var(--text-color, var(--default-text-color))); +} + +.mw-advancedSearch-namespace-selection, +.mw-advancedSearch-fieldContainer { + background: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.mw-wiki-logo { + filter: drop-shadow(0 0 1000vw white) !important; +} + +.branding-box img, +.minerva-footer-logo img { + content: url(https://upload.wikimedia.org/wikipedia/commons/e/eb/WikilogoText_White.svg) !important; +} + +.wikipediauserbox span, +.wikipediauserbox td { + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.wikipediauserbox { + border-color: rgb(var(--accented-border-color, var(--default-accented-border-color))) !important; +} + +.mwe-math-fallback-image-inline, +.mwe-math-fallback-image-display, +.oo-ui-indicatorElement-indicator, +.oo-ui-iconElement-icon { + filter: brightness(0) invert(1); +} + +.oo-ui-menuSelectWidget { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.oo-ui-toolbar-bar, +.oo-ui-toolbar-bar > * { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.oo-ui-toolbar-bar .oo-ui-indicatorElement-indicator, +.oo-ui-toolbar-bar .oo-ui-iconElement-icon { + background-color: transparent !important; +} + +.mw-notification, +.mw-notification :not(.oo-ui-iconElement-icon, .oo-ui-indicatorElement-indicator) { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.oo-ui-windowManager-modal > .oo-ui-dialog { + background-color: rgba(var(--accent-color, var(--default-accent-color)), 0.5) !important; +} + +.oo-ui-windowManager-modal > .oo-ui-dialog > .oo-ui-window-frame { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.oo-ui-windowManager-modal > .oo-ui-dialog > .oo-ui-window-frame *:not(a) { + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.oo-ui-windowManager-modal > .oo-ui-dialog > .oo-ui-window-frame .oo-ui-iconElement-icon, +.oo-ui-windowManager-modal > .oo-ui-dialog > .oo-ui-window-frame .oo-ui-indicatorElement-indicator { + background-color: transparent !important; +} + +.ve-ui-toolbar-saveButton { + background-color: rgb(var(--important-button-color, var(--default-important-button-color))) !important; +} + +.ve-ui-toolbar-saveButton * { + color: rgb(var(--text-color, var(--default-text-color))) !important; + background-color: rgb(var(--important-button-color, var(--default-important-button-color))) !important; +} + +.oo-ui-processDialog-actions-safe + .oo-ui-widget-enabled.oo-ui-iconElement:not(.oo-ui-labelElement) + > .oo-ui-buttonElement-button { + background-color: transparent !important; +} + +.oo-ui-processDialog-actions-other .oo-ui-labelElement-label { + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.oo-ui-processDialog-actions-other .oo-ui-buttonElement-button, +.oo-ui-processDialog-actions-other .oo-ui-buttonElement-button * { + background-color: transparent !important; +} + +.oo-ui-windowManager-modal + > .oo-ui-dialog + > .oo-ui-window-frame + .oo-ui-processDialog-actions-primary + .oo-ui-labelElement-label, +.oo-ui-windowManager-modal + > .oo-ui-dialog + > .oo-ui-window-frame + .oo-ui-processDialog-actions-primary + .oo-ui-buttonElement-button { + color: rgb(var(--text-color, var(--default-text-color))) !important; + background-color: rgb(var(--important-button-color, var(--default-important-button-color))) !important; +} + +.oo-ui-popupWidget-footer .oo-ui-buttonElement-button { + background-image: none !important; + background-color: transparent !important; + text-shadow: none !important; +} + +#ooui-4 { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +#wpSave { + background-color: rgb(var(--important-button-color, var(--default-important-button-color))) !important; +} + +.cx-campaign-contributionsmenu { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.cx-campaign-contributionsmenu *::marker { + filter: brightness(0) saturate(100%) invert(1) !important; +} + +.oo-ui-popupWidget-popup { + background: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.oo-ui-popupWidget-anchor { + filter: brightness(0) saturate(100%) invert(1) !important; +} + +body:not(.skin-vector-2022) .vector-menu-content-list { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.rt-tooltip { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.rt-tail::after { + background: linear-gradient( + to top right, + rgb(var(--text-color, var(--default-text-color))) 50%, + rgba(0, 0, 0, 0) 50% + ); + bottom: 0; + left: 0; +} + +.suggestions { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.cnotice, +.centralNotice { + background: rgb(var(--accent-color, var(--default-accent-color))) !important; + border: 1px solid #aaaaaa !important; +} + +.ambox table.ambox { + border: none !important; +} + +.mbox-text-span::marker, +.mbox-text-span { + list-style-image: unset !important; +} + +.references-drawer { + background: rgb(var(--accent-color, var(--default-accent-color))) !important; + border: 1px solid rgb(var(--text-color, var(--default-text-color))); +} + +.image-details { + background: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.overlay { + background: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.overlay.media-viewer { + background: rgb(var(--main-color, var(--default-main-color))) !important; +} + +.mw-widget-calendarWidget { + background: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.mw-widget-calendarWidget-header { + border-bottom: 1px solid rgb(var(--text-color, var(--default-text-color))); +} + +.mw-widget-calendarWidget-item:hover { + background: rgb(var(--main-color, var(--default-main-color))) !important; +} + +.ui-widget { + background: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.ui-widget .ui-widget-header { + background: rgb(var(--main-color, var(--default-main-color))) !important; + border: 1px solid rgb(var(--text-color, var(--default-text-color))) !important; +} + +.select2-dropdown--below { + background: rgb(var(--main-color, var(--default-main-color))) !important; +} + +.select2-results > ul > li ul > li:hover { + background: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.select2-results { + line-height: 1.7em !important; +} + +.submitButtonProxy { + border-radius: 3px; + border: 1px solid; +} + +.ui-widget fieldset { + border: 1px solid rgb(var(--text-color, var(--default-text-color))) !important; +} + +#p-personal ul { + padding-left: 0; +} + +a, +a > *, +.oo-ui-toolbar-bar > a:visited > * { + color: rgb(var(--unvisited-link-color, var(--default-unvisited-link-color))) !important; +} + +a:visited, +a:visited > *, +.oo-ui-toolbar-bar > a:visited > * { + color: rgb(var(--visited-link-color, var(--default-visited-link-color))) !important; +} + +.mw-helplink.mw-helplink { + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.mw-helplink-icon { + background-color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.diff-addedline .diffchange { + background: rgb(var(--diff-added-color, var(--default-diff-added-color))); + color: black !important; +} + +.diff-deletedline .diffchange { + background: rgb(var(--diff-deleted-color, var(--default-diff-deleted-color))); + color: black !important; +} + +.keyboard-key { + background-image: none !important; +} + +.thumbimage:not(.video-js) { + background-color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.mw-mmv-above-fold { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.mw-mmv-image-metadata { + background-color: rgb(var(--main-color, var(--default-main-color))) !important; +} + +.client-js .ambox .ambox-learn-more::before { + background: none !important; +} + +#language-settings-dialog { + background: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.uls-sub-panel { + border-top: none !important; +} + +.oo-ui-toolGroup-tools { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +#mw-fr-reviewnotice { + margin: 1em; +} + +#mw-fr-reviewnotice div { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +#mw-fr-reviewform > fieldset { + border: 1px solid rgb(var(--text-color, var(--default-text-color))) !important; +} + +.ace_tooltip, +.ace_search { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.ace-tm .ace_marker-layer .ace_selection { + background-color: #444444 !important; +} + +#mw-fr-revisiontag, +#mw-fr-revisiondetails { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +#mw-fr-submit-accept { + background-color: rgb(var(--positive-color, var(--default-positive-color))) !important; + cursor: pointer !important; +} + +#mw-fr-submit-reject, +#mw-fr-submit-unaccept { + background-color: rgb(var(--negative-color, var(--default-negative-color))) !important; + cursor: pointer !important; +} + +.tipsy-inner { + background-color: rgb(var(--main-color, var(--default-main-color))) !important; +} + +#mw-mf-page-left { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.mw-parser-output .task-center-header { + background: transparent !important; + border: 1px solid rgb(var(--text-color, var(--default-text-color))) !important; +} + +.mw-parser-output .job-description-title, +.mw-parser-output .job-description-experience { + background: transparent !important; +} + +.ext-discussiontools-ui-replyWidget-scrollback-bottom > a, +.ext-discussiontools-ui-replyWidget-scrollback-top > a { + background: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.ve-ui-findAndReplaceDialog-findResult, +.ve-ui-findAndReplaceDialog-findResult-focused { + opacity: 0.4 !important; +} + +.ve-ce-surface-selections-findResult-focused > div { + background: #fff21e !important; +} + +.ve-ce-surface-selection > div { + background: #1e90ff !important; +} + +.oo-ui-pendingElement-pending, +.cdx-lookup--pending { + background-image: linear-gradient( + 135deg, + rgb(var(--main-color, var(--default-main-color))) 25%, + transparent 25%, + transparent 50%, + rgb(var(--main-color, var(--default-main-color))) 50%, + rgb(var(--main-color, var(--default-main-color))) 75%, + transparent 75%, + transparent + ) !important; +} + +.mw-plusminus-pos { + color: rgb(var(--positive-color, var(--default-positive-color))) !important; +} + +.mw-plusminus-neg { + color: rgb(var(--negative-color, var(--default-negative-color))) !important; +} + +.mw-plusminus-null { + color: #bbbbbb !important; +} + +.SCPopupIE, +.SCPopup { + border: 1px solid rgb(var(--text-color, var(--default-text-color))) !important; + background: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.ext-growthExperiments-CPopper__popover, +#mentee-filters-form, +.cdx-menu { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.ext-related-articles-card-list h3::after { + background-image: none !important; +} + +.ext-related-articles-card-list .ext-related-articles-card { + border: 1px solid rgb(var(--text-color, var(--default-text-color))) !important; +} + +.toggle-list__list { + background: rgb(var(--accent-color, var(--default-accent-color))) !important; + box-shadow: 0 5px 17px 0 rgba(var(--text-color, var(--default-text-color)), 0.2), 0 0 1px #999 !important; +} + +.notifications-overlay.navigation-drawer { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; + box-shadow: -5px 0 5px 0 rgba(var(--text-color, var(--default-text-color)), 0.35) !important; + border-left: 1px solid rgb(var(--text-color, var(--default-text-color))) !important; +} + +.header-container.header-chrome { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; + border-bottom: 1px solid rgb(var(--text-color, var(--default-text-color))); + box-shadow: 0 0 5px 0 rgb(var(--text-color, var(--default-text-color))) !important; +} + +.overlay.search-overlay { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.list-thumb-none { + filter: invert() !important; + opacity: 0.85 !important; +} + +.overlay-footer-container { + border: none !important; +} + +label.mw-ui-icon-with-label-desktop, +a.mw-ui-icon-with-label-desktop, +a.mw-ui-icon-with-label-desktop:visited { + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.overlay.overlay-issues { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.vector-menu-dropdown .vector-menu-heading::after { + filter: brightness(0) saturate(100%) invert() !important; +} + +.minerva-icon { + filter: brightness(0) saturate(100%) invert(); +} + +.minerva-notifications ul { + list-style-image: none !important; +} + +.toc-title-icon, +.toc-title-state-icon { + filter: brightness(0) saturate(100%) invert() !important; +} + +#mp-topbanner { + background-image: linear-gradient( + to right, + rgb(var(--main-color, var(--default-main-color))) 20%, + 70%, + rgba(var(--main-color, var(--default-main-color)), 0), + rgba(var(--main-color, var(--default-main-color)), 0) + ), + url("https://upload.wikimedia.org/wikipedia/commons/e/e3/Wikipedia_logo_letters_banner.svg") !important; + background-size: cover !important; +} + +#mp-welcomecount { + text-align: left !important; +} + +#mp-welcome { + padding-left: 0 !important; +} + +.navpopup { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.popup_menu { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.mwe-popups:not(.flipped-y):not(.flipped-x-y) .mwe-popups-container { + border-top: var(--pointer-height) solid rgb(var(--text-color, var(--default-text-color))) !important; + padding-top: 0 !important; +} + +.mwe-popups.flipped-y .mwe-popups-container, +.mwe-popups.flipped-x-y .mwe-popups-container { + border-bottom: var(--pointer-height) solid rgb(var(--text-color, var(--default-text-color))) !important; + padding-bottom: 0 !important; +} + +.mwe-popups-container { + background-color: inherit !important; + color: inherit !important; +} + +.mwe-popups-extract { + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.mwe-popups-extract::after { + background: linear-gradient( + to bottom, + rgba(var(--accent-color, var(--default-accent-color)), 0), + rgb(var(--accent-color, var(--default-accent-color))) 100% + ) !important; +} + +.mwe-popups-title { + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.mwe-popups { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; + border: 1px solid rgb(var(--accented-border-color, var(--default-accented-border-color))) !important; + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.mwe-popups .mwe-popups-extract[dir="ltr"]::after { + background-image: linear-gradient( + to right, + rgba(var(--accent-color, var(--default-accent-color)), 0), + rgb(var(--accent-color, var(--default-accent-color))) 50% + ) !important; +} + +.popups-icon { + filter: brightness(0) invert(1) !important; +} + +.mwe-popups-extract * { + color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.cs1-visible-error, +.cs1-visible-error :not(a) { + color: rgb(var(--redlink-color, var(--default-redlink-color))) !important; +} + +.mw-mmv-permission-box .mw-mmv-permission-text .mw-mmv-permission-text-fader { + background-image: linear-gradient( + to bottom, + rgba(var(--main-color, var(--default-main-color)), 0) 0%, + rgb(var(--main-color, var(--default-main-color))) 100% + ) !important; +} + +.mw-mmv-title-para .mw-mmv-ttf-ellipsis::before { + background-image: linear-gradient( + to right, + rgba(var(--accent-color, var(--default-accent-color)), 0) 0%, + rgb(var(--accent-color, var(--default-accent-color))) 100% + ) !important; +} + +.mw-mmv-title-para .mw-mmv-ttf-ellipsis { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.mw-mmv-stripe-button .cdx-button__icon { + display: none !important; +} + +.ve-ui-mwTransclusionOutlineTemplateWidget-sticky { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; + border-bottom: 1px solid rgb(var(--text-color, var(--default-text-color))) !important; +} + +.ve-ui-expandableContent-fade { + background: linear-gradient( + to right, + rgba(var(--accent-color, var(--default-accent-color)), 0) 0, + rgb(var(--accent-color, var(--default-accent-color))) 90% + ); +} + +.video-js .vjs-control-bar { + background-color: rgba(var(--main-color, var(--default-main-color)), 0.7) !important; +} + +.vjs-control > .vjs-slider > .vjs-play-progress > .vjs-time-tooltip, +.vjs-menu-content > .vjs-menu-item.vjs-selected > span { + color: #000 !important; +} + +#mw-wikilove-overlay { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.archive-top-result { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.ve-ce-mwReferenceNode span.mw-reflink-text { + color: transparent !important; +} + +a.user-blocked-temp, +a.user-blocked-indef { + opacity: 1 !important; +} + +.cn-closeButton, +#cnotice-toggle-icon { + filter: invert() !important; +} + +#centralNotice, +#centralNotice > * { + background: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.mw-mmv-pre-image .mw-mmv-icon { + background-color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +kbd.button { + background: rgb(var(--accent-color, var(--default-accent-color))) !important; + border: 1px solid rgb(var(--accented-border-color, var(--default-accented-border-color))) !important; +} + +.ext-discussiontools-init-timestamplink, +.ext-discussiontools-init-timestamplink:visited, +.ext-discussiontools-init-timestamplink:active { + color: rgb(var(--timestamp-link-color, var(--default-timestamp-link-color))) !important; +} + +.ext-discussiontools-init-targetcomment { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; + border: 1px dashed rgb(var(--text-color, var(--default-text-color))) !important; +} + +.ext-discussiontools-init-highlight { + mix-blend-mode: lighten !important; +} + +div.flaggedrevs_short { + position: unset !important; +} + +.cdx-info-chip { + gap: 0 !important; +} + +div.flaggedrevs_short, +div.flaggedrevs_short_basic { + flex-wrap: wrap !important; +} + +#mw-fr-revisiondetails-wrapper { + width: 100% !important; +} + +.flaggedrevs-icon { + filter: invert() !important; +} + +#mw-fr-revision-details { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.cdx-fr-css-icon-review--status--pending { + background-color: rgb(var(--text-color, var(--default-text-color))) !important; +} + +.mw-highlight button { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +/* ===================== */ +/* Syntax highlighting */ +/* ===================== */ + +/* Uses preset colors because otherwise custom formatting might break everything */ + +.mw-highlight pre { + background-color: rgb(var(--default-accent-color)) !important; +} + +.mw-highlight pre * { + color: rgb(var(--default-text-color)) !important; +} + +.mw-content-ltr.mw-highlight-lines pre, +.mw-content-ltr.content .mw-highlight-lines pre { + box-shadow: inset 2.75em 0 0 rgb(47, 47, 47) !important; +} + +td.linenos .special, +span.linenos.special { + background-color: rgb(60, 60, 60) !important; +} + +.mw-highlight .hll { + background-color: rgb(30, 30, 30) !important; +} + +.mw-highlight .c, +.mw-highlight .ch, +.mw-highlight .cm, +.mw-highlight .cpf, +.mw-highlight .c1, +.mw-highlight .cs { + color: #4a8888 !important; +} + +.mw-highlight .k, +.mw-highlight .kc, +.mw-highlight .kd, +.mw-highlight .kn, +.mw-highlight .kp, +.mw-highlight .kr, +.mw-highlight .nb, +.mw-highlight .nt, +.mw-highlight .sx, +.mw-highlight .bp { + color: #008000 !important; +} + +.mw-highlight .s, +.mw-highlight .sa, +.mw-highlight .sb, +.mw-highlight .sc, +.mw-highlight .sd, +.mw-highlight .sh, +.mw-highlight .dl, +.mw-highlight .s1, +.mw-highlight .s2 { + color: #c84848 !important; +} + +.mw-highlight .o, +.mw-highlight .m, +.mw-highlight .mb, +.mw-highlight .mf, +.mw-highlight .mh, +.mw-highlight .mi, +.mw-highlight .mo, +.mw-highlight .il { + color: #d0d0ff !important; +} + +.mw-highlight .nd, +.mw-highlight .ow { + color: #cf41f7 !important; +} + +.mw-highlight .si, +.mw-highlight .sr { + color: #ff4b92 !important; +} + +.mw-highlight .nc, +.mw-highlight .nf, +.mw-highlight .nn, +.mw-highlight .fm { + color: #7c7cff !important; +} + +.mw-highlight .kt { + color: #e1ff8f !important; +} + +.mw-highlight .na { + color: #9ec200 !important; +} + +.mw-highlight .cp { + color: #ff8f00 !important; +} + +.mw-highlight .no { + color: #dd0000 !important; +} + +.mw-highlight .nv, +.mw-highlight .ss, +.mw-highlight .vc, +.mw-highlight .vg, +.mw-highlight .vi, +.mw-highlight .vm { + color: #8d8cb5 !important; +} + +.mw-highlight .gd, +.mw-highlight .gr, +.mw-highlight .ne { + color: #ff0000 !important; +} + +.mw-highlight .gi { + color: #00a800 !important; +} + +.mw-highlight .gu { + color: #ff00ff !important; +} + +.mw-highlight .gh, +.mw-highlight .gp { + color: #4b91ff !important; +} + +.mw-highlight .gt { + color: #1960ff !important; +} + +.mw-highlight .nl { + color: #ffff00 !important; +} + +.mw-highlight .go, +.mw-highlight .ni { + color: #bbbbbb !important; +} + +.mw-highlight .se { + color: #ff3c00 !important; +} + +/* Override code starts here */ +.mw-body-content, +.mw-parser-output, +.mw-parser-output > *, +#content, +#bodyContent, +#mw-content-text, +.references, +.reflist, +.reflist li, +.reflist a, +.citations, +.citation, +.reference, +ul.references, +ol.references, +div.reflist, +div.references { + background-color: rgb(var(--accent-color, var(--default-accent-color))) !important; +} + +.mw-body-content *, +.mw-parser-output * { + background-color: inherit !important; +} + +/* Overriding the Wikipedia footer */ +div[style*="background-image:linear-gradient"] { + background-image: linear-gradient( + 180deg, + rgb(var(--accent-color, var(--default-accent-color))), + rgb(var(--main-color, var(--default-main-color))) + ) !important; + border-top-color: rgb(var(--accented-border-color, var(--default-accented-border-color))) !important; +} diff --git a/www/index.html b/www/index.html index daeaeaa3..a8d62d82 100644 --- a/www/index.html +++ b/www/index.html @@ -567,12 +567,14 @@ +

 (Auto themes match the dark/light mode of your device.)

* Implements workarounds specific to Wikimedia ZIMs. Try generic option if there are display errors with recent ZIMs.

+

* Only applies to Wikimedia ZIMs. Falls back to generic inversion when other archives are loaded.

[ Show article with applied theme ]
diff --git a/www/js/app.js b/www/js/app.js index a40ad568..af5cbe7d 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -126,6 +126,50 @@ darkPreference.onchange = function () { uiUtil.applyAppTheme(params.appTheme); } +// Vector Dark theme update the Dropdown UI State +function updateThemeOptions() { + const vectorOption = document.getElementById('theme-vector-option'); + if (vectorOption) { + // Only disable if a ZIM is loaded and it's not a Wikimedia ZIM + const zimLoaded = selectedArchive && selectedArchive.file && selectedArchive.file.name; + vectorOption.disabled = zimLoaded && !params.isWikimediaZim; + vectorOption.title = (!zimLoaded || params.isWikimediaZim) ? "" : "Vector style only available for Wikimedia ZIMs"; + + // Check that dropdown matches actual theme + const currentTheme = document.getElementById('appThemeSelect')?.value; + if (currentTheme && currentTheme.includes('_wikiVector') && !params.isWikimediaZim) { + // If somehow Vector is selected for non Wikimedia ZIM then correct it + handleThemeFallback(); + } + } +} + +function handleThemeFallback() { + const themeSelect = document.getElementById('appThemeSelect'); + if (!themeSelect) return; + const currentTheme = themeSelect.value || settingsStore.getItem('appTheme') || 'light'; + + // When switching to Wikimedia ZIM + if (params.isWikimediaZim) { + // If current theme is invert then try to restore Vector if it was previously used + if (currentTheme.includes('_invert')) { + const baseTheme = currentTheme.replace('_invert', '_wikiVector'); + if (themeSelect.querySelector(`option[value="${baseTheme}"]`)) { + themeSelect.value = baseTheme; + uiUtil.applyAppTheme(baseTheme); + } + } + } + + // When switching from Wikimedia ZIM + else if (currentTheme.includes('_wikiVector')) { + const newTheme = currentTheme.replace('_wikiVector', '_invert'); + themeSelect.value = newTheme; + uiUtil.applyAppTheme(newTheme); + } + updateThemeOptions(); +} + /** * Resize the IFrame height, so that it fills the whole available height in the window */ @@ -1868,6 +1912,9 @@ async function archiveReadyCallback (archive) { } // This flag will be reset each time a new archive is loaded appstate.wikimediaZimLoaded = /wikipedia|wikivoyage|mdwiki|wiktionary/i.test(archive.file.name); + params.isWikimediaZim = /wikipedia|wikimedia|wikivoyage|wiktionary|wikibooks|wikiquote|wikisource|wikinews|wikiversity/i.test(archive.file.name); + updateThemeOptions(); + handleThemeFallback(); // Set contentInjectionMode to serviceWorker when opening a new archive in case the user switched to Restricted Mode/jquery Mode when opening the previous archive if (params.contentInjectionMode === 'jquery') { params.contentInjectionMode = settingsStore.getItem('contentInjectionMode'); @@ -2306,6 +2353,7 @@ function articleLoadedSW (iframeArticleContent) { // Display the iframe content iframeArticleContent.style.display = ''; articleContainer.style.display = ''; + // Deflect drag-and-drop of ZIM file on the iframe to Config if (!params.disableDragAndDrop) { var doc = iframeArticleContent.contentDocument ? iframeArticleContent.contentDocument.documentElement : null; @@ -2720,6 +2768,7 @@ function displayArticleContentInIframe (dirEntry, htmlArticle) { if (!isDirEntryExpectedToBeDisplayed(dirEntry)) { return; } + // Display Bootstrap warning alert if the landing page contains active content if (!params.hideActiveContentWarning && params.isLandingPage) { if (regexpActiveContent.test(htmlArticle) || /zimit/.test(selectedArchive.zimType)) { diff --git a/www/js/lib/uiUtil.js b/www/js/lib/uiUtil.js index 455f781e..f197d9a7 100644 --- a/www/js/lib/uiUtil.js +++ b/www/js/lib/uiUtil.js @@ -853,10 +853,21 @@ function tabTransitionToSection (toSection, isAnimationRequired = false) { * @param {String} theme The theme to apply (light|dark[_invert|_mwInvert]|auto[_invert|_mwInvert]) */ function applyAppTheme (theme) { + // Validate the theme parameter to prevent XSS + // Only allow specific valid theme formats + if (!theme.match(/^(light|dark|auto)(_invert|_mwInvert|_wikiVector)?$/)) { + console.error('Invalid theme format:', theme); + theme = 'light'; + } + // Resolve the app theme from the matchMedia preference (for auto themes) or from the theme string var appTheme = isDarkTheme(theme) ? 'dark' : 'light'; // Get contentTheme from chosen theme var contentTheme = theme.replace(/^[^_]*/, ''); + // Revert to '_invert' or default dark theme if trying to use '_wikiVector' on non-Wikimedia ZIMs + if (contentTheme === '_wikiVector' && !params.isWikimediaZim) { + contentTheme = '_invert'; + } var htmlEl = document.querySelector('html'); var footer = document.querySelector('footer'); var oldTheme = htmlEl.dataset.theme || ''; @@ -877,19 +888,28 @@ function applyAppTheme (theme) { footer.classList.add(contentTheme || '_light'); // Embed a reference to applied theme, so we can remove it generically in the future htmlEl.dataset.theme = appTheme + contentTheme; + + // Safely handle help element IDs + var safeOldContentTheme = oldContentTheme.replace(/[^a-zA-Z0-9-]/g, ''); + var safeContentTheme = contentTheme.replace(/[^a-zA-Z0-9-]/g, ''); + // Hide any previously displayed help - var oldHelp = document.getElementById(oldContentTheme.replace(/_/, '') + '-help'); + var oldHelp = document.getElementById(safeOldContentTheme.replace(/_/, '') + '-help'); if (oldHelp) oldHelp.style.display = 'none'; // Show any specific help for selected contentTheme - var help = document.getElementById(contentTheme.replace(/_/, '') + '-help'); + var help = document.getElementById(safeContentTheme.replace(/_/, '') + '-help'); if (help) help.style.display = 'block'; // Remove the contentTheme for auto themes whenever system is in light mode if (/^auto/.test(theme) && appTheme === 'light') contentTheme = null; // Hide any previously displayed description for auto themes var oldDescription = document.getElementById('kiwix-auto-description'); if (oldDescription) oldDescription.style.display = 'none'; + + // Safely handle description element IDs + var safeThemeBase = theme.replace(/_.*$/, '').replace(/[^a-zA-Z0-9-]/g, ''); + // Show description for auto themes - var description = document.getElementById('kiwix-' + theme.replace(/_.*$/, '') + '-description'); + var description = document.getElementById('kiwix-' + safeThemeBase + '-description'); if (description) description.style.display = 'block'; // If there is no ContentTheme or we are applying a different ContentTheme, remove any previously applied ContentTheme if (oldContentTheme && oldContentTheme !== contentTheme) { @@ -913,7 +933,8 @@ function applyAppTheme (theme) { link.setAttribute('id', 'kiwixJSTheme'); link.setAttribute('rel', 'stylesheet'); link.setAttribute('type', 'text/css'); - link.setAttribute('href', prefix + '/css/kiwixJS' + contentTheme + '.css'); + var safeContentThemeForURL = contentTheme.replace(/[^a-zA-Z0-9_-]/g, ''); + link.setAttribute('href', prefix + '/css/kiwixJS' + safeContentThemeForURL + '.css'); doc.head.appendChild(link); } }