1/** 2 * Dark Reader initialization script 3 * Uses the darkreader.js library to add dark mode functionality to WordPress sites 4 * Modern implementation with ES6+ features 5 */ 6 7(() => { 8 'use strict'; 9 10 /** 11 * Get current page ID for localStorage key 12 * @returns {string} 13 */ 14 const getCurrentPageId = () => { 15 const settings = (typeof darkReaderSettings !== 'undefined' && darkReaderSettings) ? darkReaderSettings : {}; 16 return settings.currentPageId || 'default'; 17 }; 18 19 /** 20 * Get page visibility mode 21 * @returns {string} 22 */ 23 const getPageVisibilityMode = () => { 24 const settings = (typeof darkReaderSettings !== 'undefined' && darkReaderSettings) ? darkReaderSettings : {}; 25 return settings.pageVisibilityMode || 'show_all'; 26 }; 27 28 /** 29 * Check if current mode uses global localStorage 30 * @returns {boolean} 31 */ 32 const isGlobalMode = () => { 33 const mode = getPageVisibilityMode(); 34 return mode === 'show_all' || mode === 'hide_all'; 35 }; 36 37 /** 38 * Clean up localStorage when switching modes 39 */ 40 const cleanupLocalStorage = () => { 41 try { 42 const mode = getPageVisibilityMode(); 43 const currentModeStored = localStorage.getItem('darkReaderVisibilityMode'); 44 45 // If mode has changed, clean up old localStorage entries 46 if (currentModeStored && currentModeStored !== mode) { 47 if (mode === 'show_all' || mode === 'hide_all') { 48 // Switching to global mode - remove all page-specific entries 49 Object.keys(localStorage).forEach(key => { 50 if (key.startsWith('darkReaderEnabled_')) { 51 localStorage.removeItem(key); 52 } 53 }); 54 } else { 55 // Switching to page-specific mode - remove global entry 56 localStorage.removeItem('darkReaderEnabled'); 57 } 58 } 59 60 // Store current mode 61 localStorage.setItem('darkReaderVisibilityMode', mode); 62 } catch (e) { 63 } 64 }; 65 66 /** 67 * Get dark mode preference from localStorage or default settings 68 * @returns {boolean} - Whether dark mode should be enabled 69 */ 70 const getDarkModePreference = () => { 71 try { 72 cleanupLocalStorage(); 73 74 // Check if user has manually toggled (takes precedence over everything) 75 const manualToggle = localStorage.getItem('darkReaderManualToggle'); 76 if (manualToggle === 'true') { 77 if (isGlobalMode()) { 78 const global = localStorage.getItem('darkReaderEnabled'); 79 if (global !== null) { 80 return global === '1'; 81 } 82 } else { 83 const pageId = getCurrentPageId(); 84 const pageSpecificKey = `darkReaderEnabled_${pageId}`; 85 const pageSpecific = localStorage.getItem(pageSpecificKey); 86 87 if (pageSpecific !== null) { 88 return pageSpecific === '1'; 89 } 90 } 91 } 92 93 // Check if system color scheme is enabled 94 const settings = (typeof darkReaderSettings !== 'undefined' && darkReaderSettings) ? darkReaderSettings : {}; 95 if (settings.useSystemColorScheme) { 96 // Use system preference 97 if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { 98 return true; 99 } 100 if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) { 101 return false; 102 } 103 } 104 105 // Check localStorage for saved preference (when not using system scheme or manual toggle) 106 if (isGlobalMode()) { 107 // Global modes: use single localStorage key 108 const global = localStorage.getItem('darkReaderEnabled');
109 if (global !== null) { 110 return global === '1'; 111 } 112 } else { 113 // Page-specific modes: use page-specific localStorage key 114 const pageId = getCurrentPageId(); 115 const pageSpecificKey = `darkReaderEnabled_${pageId}`; 116 const pageSpecific = localStorage.getItem(pageSpecificKey); 117 118 if (pageSpecific !== null) { 119 return pageSpecific === '1'; 120 } 121 } 122 } catch (e) { 123 } 124 125 // Check for default setting from WordPress 126 return Boolean(typeof darkReaderSettings !== 'undefined' && darkReaderSettings && darkReaderSettings.defaultDarkMode); 127 }; 128 129 /** 130 * Apply critical dark mode styles immediately to prevent FOUC 131 */ 132 const applyCriticalDarkStyles = () => { 133 // Check if dark mode should be applied on this page (Pro feature) 134 const settings = (typeof darkReaderSettings !== 'undefined' && darkReaderSettings) ? darkReaderSettings : {}; 135 const shouldApplyOnThisPage = settings.shouldApplyDarkMode !== false; 136 137 if (!shouldApplyOnThisPage) { 138 return; 139 } 140 141 // Check user preference using the new localStorage logic 142 // This will now also check system color scheme if enabled 143 if (!getDarkModePreference()) { 144 return; 145 } 146 147 // Create critical CSS to prevent flash of light mode 148 const criticalCSS = ` 149 html { 150 filter: invert(1) hue-rotate(180deg) !important; 151 transition: filter 0.1s ease !important; 152 } 153 img, video, iframe, svg, 154 [style*="background-image"], 155 canvas, embed, object { 156 filter: invert(1) hue-rotate(180deg) !important; 157 } 158 `; 159 160 const style = document.createElement('style'); 161 style.id = 'dark-reader-critical'; 162 style.textContent = criticalCSS; 163 164 // Insert immediately, even before head is fully parsed 165 if (document.head) { 166 document.head.appendChild(style); 167 } else { 168 // If head doesn't exist yet, wait for it 169 const observer = new MutationObserver((mutations, obs) => { 170 if (document.head) { 171 document.head.appendChild(style); 172 obs.disconnect(); 173 } 174 }); 175 observer.observe(document.documentElement, { 176 childList: true, 177 subtree: true 178 }); 179 } 180 181 // Set data attribute immediately 182 document.documentElement.setAttribute('data-darkreader-mode', 'critical'); 183 if (document.body) { 184 document.body.classList.add('dark-mode'); 185 } 186 }; 187 188 /** 189 * Remove critical styles when DarkReader takes over 190 */ 191 const removeCriticalStyles = () => { 192 const criticalStyle = document.getElementById('dark-reader-critical'); 193 if (criticalStyle) { 194 criticalStyle.remove(); 195 } 196 197 // Also remove fallback critical styles and classes 198 const fallbackStyle = document.getElementById('dark-reader-critical-fallback'); 199 if (fallbackStyle) { 200 fallbackStyle.remove(); 201 } 202 203 document.documentElement.classList.remove('dark-reader-critical-loading'); 204 }; 205 206 /** 207 * Create Dark Reader fixes object from exclusions 208 * @returns {Object} DarkReader fixes configuration 209 */ 210 const createDarkReaderFixes = () => { 211 const fixes = { 212 ignoreInlineStyle: [], 213 ignoreImageAnalysis: [], 214 css: '' 215 }; 216 217 // Always exclude toggle buttons from image analysis and transformations 218 const toggleButtonSelectors = [ 219 '.dark-reader-toggle', 220 '.dark-reader-toggle *', 221 '.dark-reader-toggle-slider', 222 '.dark-reader-toggle-switch', 223 '[class*="dark-reader-toggle"]', 224 '[class*="toggle-slider"]', 225 '.dark-mode-toggle', 226 '[data-dark-toggle]' 227 ]; 228 229 // Add toggle buttons to ignoreImageAnalysis 230 fixes.ignoreImageAnalysis.push(...toggleButtonSelectors); 231 232 // Check if darkReaderSettings is available 233 if (typeof darkReaderSettings !== 'undefined' && darkReaderSettings) {
234 let customCSS = ''; 235 236 237 238 // Check if darkReaderSettings has exclusions 239 if (darkReaderSettings.exclusions) { 240 241 let allExcludedSelectors = []; 242 243 // Collect custom CSS selectors 244 if (darkReaderSettings.exclusions.selectors) { 245 let selectors = []; 246 if (Array.isArray(darkReaderSettings.exclusions.selectors)) { 247 selectors = darkReaderSettings.exclusions.selectors.filter(selector => 248 typeof selector === 'string' && selector.trim() !== '' 249 ); 250 } else if (typeof darkReaderSettings.exclusions.selectors === 'string') { 251 // Handle case where selectors is a single string 252 selectors = darkReaderSettings.exclusions.selectors.split(',').map(s => s.trim()).filter(s => s !== ''); 253 } 254 255 if (selectors.length > 0) { 256 allExcludedSelectors.push(...selectors); 257 } 258 } 259 260 // Add image exclusions 261 if (darkReaderSettings.exclusions.images && Array.isArray(darkReaderSettings.exclusions.images)) { 262 const images = darkReaderSettings.exclusions.images.filter(selector => 263 typeof selector === 'string' && selector.trim() !== '' 264 ); 265 266 // Add to both CSS exclusions and DarkReader's ignoreImageAnalysis 267 allExcludedSelectors.push(...images); 268 fixes.ignoreImageAnalysis.push(...images); 269 } 270 271 // Add video exclusions 272 if (darkReaderSettings.exclusions.videos && Array.isArray(darkReaderSettings.exclusions.videos)) { 273 const videos = darkReaderSettings.exclusions.videos.filter(selector => 274 typeof selector === 'string' && selector.trim() !== '' 275 ); 276 277 // Videos should be excluded via CSS, not ignoreImageAnalysis 278 allExcludedSelectors.push(...videos); 279 } 280 281 // Create CSS to exclude elements from dark mode transformation 282 if (allExcludedSelectors.length > 0) { 283 const selectorString = allExcludedSelectors.join(', '); 284 const exclusionCSS = ` 285 /* Dark Reader Pro Element Exclusions */ 286 ${selectorString} { 287 filter: none !important; 288 backdrop-filter: none !important; 289 color-scheme: initial !important; 290 color: initial !important; 291 --darkreader-inline-color: initial !important; 292 -webkit-filter: none !important; 293 } 294 ${selectorString} * { 295 filter: none !important; 296 backdrop-filter: none !important; 297 -webkit-filter: none !important; 298 } 299 `; 300 customCSS += exclusionCSS; 301 } 302 303 // Set the final CSS 304 if (customCSS) { 305 fixes.css = customCSS; 306 } 307 } 308 309 } else { 310 } 311 312 // Always add toggle button exclusion CSS 313 const toggleExclusionCSS = ` 314 /* Dark Reader Toggle Button Exclusions */ 315 .dark-reader-toggle, 316 .dark-reader-toggle *, 317 .dark-reader-toggle-slider, 318 .dark-reader-toggle-slider:before, 319 .dark-reader-toggle-slider:after, 320 .dark-reader-toggle-switch, 321 .dark-reader-toggle-switch *, 322 [class*="dark-reader-toggle"], 323 [class*="toggle-slider"], 324 .dark-mode-toggle, 325 [data-dark-toggle] { 326 filter: none !important; 327 backdrop-filter: none !important; 328 -webkit-filter: none !important; 329 --darkreader-inline-bgcolor: initial !important; 330 --darkreader-inline-bgimage: initial !important; 331 --darkreader-inline-border: initial !important; 332 --darkreader-inline-color: initial !important; 333 } 334 `; 335 336 fixes.css = (fixes.css || '') + toggleExclusionCSS; 337 338 return fixes; 339 }; 340 341 /** 342 * Create Dark Reader configuration object 343 * Builds config from WordPress settings with sensible defaults 344 * @returns {Object} DarkReader configuration 345 */ 346 const createDarkReaderConfig = () => { 347 // Use default values if darkReaderSettings is not available 348 const settings = (typeof darkReaderSettings !== 'undefined' && darkReaderSettings) ? darkReaderSettings : {}; 349 350 const config = { 351 brightness: parseInt(settings.brightness) || 100, 352 contrast: parseInt(settings.contrast) || 90, 353 sepia: parseInt(settings.sepia) || 10, 354 grayscale: parseInt(settings.grayscale) || 0, 355 textStroke: parseFloat(settings.textStroke) || 0, 356 mode: parseInt(settings.mode) === 0 ? 0 : 1, 357 enableForPDFs: Boolean(settings.enableForPDFs), 358 immediateModify: settings.immediateModify !== false 359 }; 360 361 // Add dark scheme colors if provided
362 if (settings.darkSchemeBackgroundColor) { 363 config.darkSchemeBackgroundColor = settings.darkSchemeBackgroundColor; 364 } 365 if (settings.darkSchemeTextColor) { 366 config.darkSchemeTextColor = settings.darkSchemeTextColor; 367 } 368 369 return config; 370 }; 371 372 // Note: darkReaderFixes is created on-demand in functions to avoid timing issues 373 374 /** 375 * Store dark mode preference in localStorage 376 * @param {boolean} isDark - Whether dark mode is enabled 377 */ 378 const setDarkModePreference = (isDark) => { 379 try { 380 381 if (isGlobalMode()) { 382 // Global modes: set global preference 383 localStorage.setItem('darkReaderEnabled', isDark ? '1' : '0'); 384 } else { 385 // Page-specific modes: set page-specific preference 386 const pageId = getCurrentPageId(); 387 const pageSpecificKey = `darkReaderEnabled_${pageId}`; 388 localStorage.setItem(pageSpecificKey, isDark ? '1' : '0'); 389 } 390 391 // Set manual toggle flag 392 localStorage.setItem('darkReaderManualToggle', 'true'); 393 394 // Dispatch custom event for analytics tracking 395 document.dispatchEvent(new CustomEvent('darkReaderToggled', { 396 detail: { isDark: isDark } 397 })); 398 } catch (e) { 399 } 400 }; 401 402 /** 403 * Initialize dark mode based on user preference and page settings 404 */ 405 const initDarkMode = () => { 406 try { 407 const settings = (typeof darkReaderSettings !== 'undefined' && darkReaderSettings) ? darkReaderSettings : {}; 408 const shouldApplyOnThisPage = settings.shouldApplyDarkMode !== false; 409 const userPreference = getDarkModePreference(); 410 411 if (shouldApplyOnThisPage && userPreference) { 412 // Enable dark mode 413 try { 414 const config = createDarkReaderConfig(); 415 const fixes = createDarkReaderFixes(); 416 DarkReader.enable(config, fixes); 417 removeCriticalStyles(); 418 document.documentElement.setAttribute('data-darkreader-mode', 'dynamic'); 419 if (document.body) { 420 document.body.classList.add('dark-mode'); 421 } 422 423 // Initialize PDF dark mode if enabled 424 if (settings.enableForPDFs) { 425 initPDFDarkMode(); 426 } 427 } catch (error) { 428 // Fallback: enable without fixes 429 const config = createDarkReaderConfig(); 430 DarkReader.enable(config); 431 removeCriticalStyles(); 432 document.documentElement.setAttribute('data-darkreader-mode', 'dynamic'); 433 if (document.body) { 434 document.body.classList.add('dark-mode'); 435 } 436 437 // Initialize PDF dark mode if enabled 438 if (settings.enableForPDFs) { 439 initPDFDarkMode(); 440 } 441 } 442 } else { 443 // Disable dark mode 444 DarkReader.disable(); 445 removeCriticalStyles(); 446 document.documentElement.setAttribute('data-darkreader-mode', 'light'); 447 if (document.body) { 448 document.body.classList.remove('dark-mode'); 449 } 450 451 // Disable PDF dark mode 452 disablePDFDarkMode(); 453 } 454 455 updateToggleButton(userPreference); 456 } catch (e) { 457 // Anything thrown before the branches above would leave the critical 458 // class on <html> and the page stuck colour-inverted. 459 removeCriticalStyles(); 460 } 461 }; 462 463 /** 464 * Initialize PDF dark mode functionality 465 */ 466 const initPDFDarkMode = () => { 467 try { 468 // Check if PDF dark mode is enabled 469 const settings = (typeof darkReaderSettings !== 'undefined' && darkReaderSettings) ? darkReaderSettings : {}; 470 if (!settings.enableForPDFs) { 471 return; 472 } 473 474 // Load PDF.js if not already loaded 475 loadPDFJS().then(() => { 476 // Set up PDF viewer detection and dark mode 477 setupPDFViewerDarkMode(); 478 }).catch(error => { 479 }); 480 } catch (e) { 481 } 482 }; 483 484 /** 485 * Load PDF.js library dynamically 486 */ 487 const loadPDFJS = async () => { 488 // Check if PDF.js is already loaded 489 if (typeof pdfjsLib !== 'undefined') { 490 return Promise.resolve(); 491 } 492 493 // Load PDF.js from CDN 494 const pdfjsScript = document.createElement('script'); 495 pdfjsScript.src = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js'; 496 pdfjsScript.async = true; 497 498 return new Promise((resolve, reject) => { 499 pdfjsScript.onload = () => { 500 // Set worker path 501 if (typeof pdfjsLib !== 'undefined') { 502 pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js'; 503 resolve(); 504 } else { 505 reject(new Error('PDF.js failed to load')); 506 } 507 }; 508 pdfjsScript.onerror = () => reject(new Error('Failed to load PDF.js')); 509 document.head.appendChild(pdfjsScript); 510 }); 511 }; 512 513 /** 514 * Set up PDF viewer dark mode 515 */ 516 const setupPDFViewerDarkMode = () => { 517 try { 518 // Detect different types of PDF viewers 519 const pdfElements = document.querySelectorAll('embed[type="application/pdf"], object[type="application/pdf"], iframe[src*=".pdf"], iframe[src*="blob:"]'); 520
521 pdfElements.forEach(element => { 522 applyDarkModeToPDFElement(element); 523 }); 524 525 // Watch for dynamically added PDF elements 526 const observer = new MutationObserver((mutations) => { 527 mutations.forEach((mutation) => { 528 mutation.addedNodes.forEach((node) => { 529 if (node.nodeType === Node.ELEMENT_NODE) { 530 // Check if the added node is a PDF element 531 if (isPDFElement(node)) { 532 applyDarkModeToPDFElement(node); 533 } 534 535 // Check children of added node 536 const pdfChildren = node.querySelectorAll('embed[type="application/pdf"], object[type="application/pdf"], iframe[src*=".pdf"], iframe[src*="blob:"]'); 537 pdfChildren.forEach(element => { 538 applyDarkModeToPDFElement(element); 539 }); 540 } 541 }); 542 }); 543 }); 544 545 observer.observe(document.body, { 546 childList: true, 547 subtree: true 548 }); 549 550 // Store observer for cleanup 551 window.darkReaderPDFObserver = observer; 552 } catch (e) { 553 } 554 }; 555 556 /** 557 * Check if an element is a PDF element 558 */ 559 const isPDFElement = (element) => { 560 if (!element || !element.tagName) return false; 561 562 const tagName = element.tagName.toLowerCase(); 563 const type = element.getAttribute('type'); 564 const src = element.getAttribute('src'); 565 566 return ( 567 (tagName === 'embed' && type === 'application/pdf') || 568 (tagName === 'object' && type === 'application/pdf') || 569 (tagName === 'iframe' && (src?.includes('.pdf') || src?.startsWith('blob:'))) 570 ); 571 }; 572 573 /** 574 * Apply dark mode to a PDF element 575 */ 576 const applyDarkModeToPDFElement = (element) => { 577 try { 578 if (!element) return; 579 580 // Add dark mode class to PDF container 581 element.classList.add('dark-reader-pdf-dark'); 582 583 // Create dark mode overlay for PDF 584 createPDFDarkOverlay(element); 585 586 // Apply CSS filters for browser PDF viewers 587 applyPDFCSSFilters(element); 588 589 // Handle iframe PDF viewers (like browser built-in viewers) 590 if (element.tagName.toLowerCase() === 'iframe') { 591 handleIframePDFViewer(element); 592 } 593 } catch (e) { 594 } 595 }; 596 597 /** 598 * Create dark mode overlay for PDF elements 599 */ 600 const createPDFDarkOverlay = (element) => { 601 try { 602 // Remove existing overlay if any 603 const existingOverlay = element.parentNode?.querySelector('.dark-reader-pdf-overlay'); 604 if (existingOverlay) { 605 existingOverlay.remove(); 606 } 607 608 // Create overlay 609 const overlay = document.createElement('div'); 610 overlay.className = 'dark-reader-pdf-overlay'; 611 overlay.style.cssText = ` 612 position: absolute; 613 top: 0; 614 left: 0; 615 width: 100%; 616 height: 100%; 617 background: rgba(0, 0, 0, 0.1); 618 pointer-events: none; 619 z-index: 1; 620 opacity: 0; 621 transition: opacity 0.3s ease; 622 `; 623 624 // Position the element relatively if not already 625 const computedStyle = window.getComputedStyle(element); 626 if (computedStyle.position === 'static') { 627 element.style.position = 'relative'; 628 } 629 630 // Insert overlay 631 element.parentNode.insertBefore(overlay, element.nextSibling); 632 633 // Show overlay 634 setTimeout(() => { 635 overlay.style.opacity = '1'; 636 }, 100); 637 } catch (e) { 638 } 639 }; 640 641 /** 642 * Apply CSS filters for browser PDF viewers 643 */ 644 const applyPDFCSSFilters = (element) => { 645 try { 646 const settings = (typeof darkReaderSettings !== 'undefined' && darkReaderSettings) ? darkReaderSettings : {}; 647 648 // Create filter string based on settings 649 const brightness = settings.brightness || 100; 650 const contrast = settings.contrast || 90; 651 const sepia = settings.sepia || 10; 652 const grayscale = settings.grayscale || 0; 653 654 const filterString = ` 655 brightness(${brightness}%) 656 contrast(${contrast}%) 657 sepia(${sepia}%) 658 grayscale(${grayscale}%) 659 invert(1) 660 hue-rotate(180deg) 661 `; 662
663 element.style.filter = filterString; 664 element.style.transition = 'filter 0.3s ease'; 665 } catch (e) { 666 } 667 }; 668 669 /** 670 * Handle iframe PDF viewers (browser built-in viewers) 671 */ 672 const handleIframePDFViewer = (iframe) => { 673 try { 674 // Wait for iframe to load 675 iframe.addEventListener('load', () => { 676 try { 677 const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document; 678 if (!iframeDoc) return; 679 680 // Apply dark mode to iframe content 681 applyDarkModeToIframeContent(iframeDoc); 682 } catch (e) { 683 // Cross-origin iframe, can't access content 684 } 685 }); 686 } catch (e) { 687 } 688 }; 689 690 /** 691 * Apply dark mode to iframe content 692 */ 693 const applyDarkModeToIframeContent = (iframeDoc) => { 694 try { 695 // Add dark mode styles to iframe 696 const darkStyles = iframeDoc.createElement('style'); 697 darkStyles.textContent = ` 698 body { 699 background-color: #1a1a1a !important; 700 color: #ffffff !important; 701 } 702 * { 703 background-color: #1a1a1a !important; 704 color: #ffffff !important; 705 border-color: #333333 !important; 706 } 707 canvas { 708 filter: invert(1) hue-rotate(180deg) !important; 709 } 710 `; 711 iframeDoc.head.appendChild(darkStyles); 712 } catch (e) { 713 } 714 }; 715 716 /** 717 * Disable PDF dark mode 718 */ 719 const disablePDFDarkMode = () => { 720 try { 721 // Remove PDF dark mode classes 722 document.querySelectorAll('.dark-reader-pdf-dark').forEach(element => { 723 element.classList.remove('dark-reader-pdf-dark'); 724 element.style.filter = ''; 725 }); 726 727 // Remove overlays 728 document.querySelectorAll('.dark-reader-pdf-overlay').forEach(overlay => { 729 overlay.remove(); 730 }); 731 732 // Stop observer 733 if (window.darkReaderPDFObserver) { 734 window.darkReaderPDFObserver.disconnect(); 735 window.darkReaderPDFObserver = null; 736 } 737 } catch (e) { 738 } 739 }; 740 741 /** 742 * Toggle dark mode on/off 743 */ 744 const toggleDarkMode = () => { 745 const isDarkModeOn = getDarkModePreference(); 746 747 // Check if dark mode should be applied on this page (Pro feature) 748 const settings = (typeof darkReaderSettings !== 'undefined' && darkReaderSettings) ? darkReaderSettings : {}; 749 const shouldApplyOnThisPage = settings.shouldApplyDarkMode !== false; 750 751 try { 752 if (isDarkModeOn) { 753 // Switch to light mode 754 DarkReader.disable(); 755 removeCriticalStyles(); 756 document.documentElement.setAttribute('data-darkreader-mode', 'light'); 757 if (document.body) { 758 document.body.classList.remove('dark-mode'); 759 } 760 setDarkModePreference(false); 761 762 // Disable PDF dark mode 763 disablePDFDarkMode(); 764 } else { 765 // Check if dark mode is allowed on this page before enabling 766 if (!shouldApplyOnThisPage) { 767 // Don't enable dark mode on pages where it's not allowed 768 return; 769 } 770 771 // Switch to dark mode 772 try { 773 const config = createDarkReaderConfig(); 774 const fixes = createDarkReaderFixes(); 775 DarkReader.enable(config, fixes); 776 removeCriticalStyles(); 777 document.documentElement.setAttribute('data-darkreader-mode', 'dynamic'); 778 if (document.body) { 779 document.body.classList.add('dark-mode'); 780 } 781 setDarkModePreference(true); 782 783 // Initialize PDF dark mode if enabled 784 const settings = (typeof darkReaderSettings !== 'undefined' && darkReaderSettings) ? darkReaderSettings : {}; 785 if (settings.enableForPDFs) { 786 initPDFDarkMode(); 787 } 788 } catch (error) { 789 // Fallback: enable without fixes 790 const config = createDarkReaderConfig(); 791 DarkReader.enable(config);
792 removeCriticalStyles(); 793 document.documentElement.setAttribute('data-darkreader-mode', 'dynamic'); 794 if (document.body) { 795 document.body.classList.add('dark-mode'); 796 } 797 setDarkModePreference(true); 798 799 // Initialize PDF dark mode if enabled 800 const settings = (typeof darkReaderSettings !== 'undefined' && darkReaderSettings) ? darkReaderSettings : {}; 801 if (settings.enableForPDFs) { 802 initPDFDarkMode(); 803 } 804 } 805 } 806 807 updateToggleButton(!isDarkModeOn); 808 } catch (e) { 809 } 810 }; 811 812 // Expose for Pro accessibility (keyboard shortcut, URL params) so they use the same 813 // config, fixes, localStorage format, and manual-toggle flag as the UI toggle. 814 window.darkReaderToggle = toggleDarkMode; 815 window.darkReaderInitDarkMode = initDarkMode; 816 window.darkReaderSetPreferenceAndApply = (enabled) => { 817 setDarkModePreference(enabled); 818 initDarkMode(); 819 }; 820 821 /** 822 * Update toggle button appearance based on dark mode state 823 * @param {boolean} isDark - Whether dark mode is enabled 824 */ 825 const updateToggleButton = (isDark) => { 826 const toggleButton = document.getElementById('dark-reader-toggle'); 827 828 try { 829 // Update main toggle button if it exists 830 if (toggleButton) { 831 toggleButton.classList.toggle('dark', isDark); 832 } 833 834 // Update all other inline toggle buttons (from shortcodes) 835 document.querySelectorAll('.dark-reader-toggle-inline').forEach(container => { 836 container.classList.toggle('dark', isDark); 837 }); 838 } catch (e) { 839 } 840 }; 841 842 /** 843 * Set up event listeners 844 */ 845 const setupEventListeners = () => { 846 try { 847 // Main toggle button 848 const toggleButton = document.getElementById('dark-reader-toggle'); 849 if (toggleButton) { 850 toggleButton.addEventListener('click', toggleDarkMode); 851 } 852 853 document.querySelectorAll('.dark-reader-toggle-inline').forEach(container => { 854 container.addEventListener('click', toggleDarkMode); 855 }); 856 857 // Set up system color scheme listener if enabled 858 setupSystemColorSchemeListener(); 859 } catch (e) { 860 } 861 }; 862 863 /** 864 * Set up system color scheme listener 865 * Listens for changes in OS dark mode preference and applies them automatically 866 */ 867 const setupSystemColorSchemeListener = () => { 868 try { 869 const settings = (typeof darkReaderSettings !== 'undefined' && darkReaderSettings) ? darkReaderSettings : {}; 870 871 // Only set up listener if system color scheme is enabled 872 if (!settings.useSystemColorScheme) { 873 return; 874 } 875 876 // Check if matchMedia is supported (should be on all modern browsers including iOS) 877 if (!window.matchMedia) { 878 return; 879 } 880 881 const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)'); 882 883 // Handler for system color scheme changes 884 const handleSystemColorSchemeChange = (e) => { 885 // Don't auto-switch if user has manually toggled 886 const manualToggle = localStorage.getItem('darkReaderManualToggle'); 887 if (manualToggle === 'true') { 888 return; 889 } 890 891 const shouldApplyOnThisPage = settings.shouldApplyDarkMode !== false; 892 if (!shouldApplyOnThisPage) { 893 return; 894 } 895 896 const prefersDark = e.matches; 897 898 if (prefersDark) { 899 // System switched to dark mode - enable dark reader 900 try { 901 const config = createDarkReaderConfig(); 902 const fixes = createDarkReaderFixes(); 903 DarkReader.enable(config, fixes);
904 removeCriticalStyles(); 905 document.documentElement.setAttribute('data-darkreader-mode', 'dynamic'); 906 if (document.body) { 907 document.body.classList.add('dark-mode'); 908 } 909 910 // Store preference without setting manual toggle flag 911 try { 912 if (isGlobalMode()) { 913 localStorage.setItem('darkReaderEnabled', '1'); 914 } else { 915 const pageId = getCurrentPageId(); 916 const pageSpecificKey = `darkReaderEnabled_${pageId}`; 917 localStorage.setItem(pageSpecificKey, '1'); 918 } 919 } catch (err) { } 920 921 // Initialize PDF dark mode if enabled 922 if (settings.enableForPDFs) { 923 initPDFDarkMode(); 924 } 925 } catch (error) { 926 // Fallback: enable without fixes 927 const config = createDarkReaderConfig(); 928 DarkReader.enable(config); 929 removeCriticalStyles(); 930 document.documentElement.setAttribute('data-darkreader-mode', 'dynamic'); 931 if (document.body) { 932 document.body.classList.add('dark-mode'); 933 } 934 935 try { 936 if (isGlobalMode()) { 937 localStorage.setItem('darkReaderEnabled', '1'); 938 } else { 939 const pageId = getCurrentPageId(); 940 const pageSpecificKey = `darkReaderEnabled_${pageId}`; 941 localStorage.setItem(pageSpecificKey, '1'); 942 } 943 } catch (err) { } 944 945 if (settings.enableForPDFs) { 946 initPDFDarkMode(); 947 } 948 } 949 } else { 950 // System switched to light mode - disable dark reader 951 DarkReader.disable(); 952 removeCriticalStyles(); 953 document.documentElement.setAttribute('data-darkreader-mode', 'light'); 954 if (document.body) { 955 document.body.classList.remove('dark-mode'); 956 } 957 958 // Store preference without setting manual toggle flag 959 try { 960 if (isGlobalMode()) { 961 localStorage.setItem('darkReaderEnabled', '0'); 962 } else { 963 const pageId = getCurrentPageId(); 964 const pageSpecificKey = `darkReaderEnabled_${pageId}`; 965 localStorage.setItem(pageSpecificKey, '0'); 966 } 967 } catch (err) { } 968 969 // Disable PDF dark mode 970 disablePDFDarkMode(); 971 } 972 973 updateToggleButton(prefersDark); 974 }; 975 976 // Add listener using modern method (iOS 14+) 977 if (darkModeQuery.addEventListener) { 978 darkModeQuery.addEventListener('change', handleSystemColorSchemeChange); 979 } 980 // Fallback for older iOS versions (iOS 13 and below) 981 else if (darkModeQuery.addListener) { 982 darkModeQuery.addListener(handleSystemColorSchemeChange); 983 } 984 985 } catch (e) { 986 } 987 }; 988 989 // Apply critical styles immediately to prevent FOUC 990 applyCriticalDarkStyles(); 991 992 // Early initialization - check if we can initialize immediately 993 if (typeof DarkReader !== 'undefined') { 994 initDarkMode(); 995 setupEventListeners(); 996 } 997 998 // Initialize when DOM is fully loaded 999 document.addEventListener('DOMContentLoaded', () => { 1000 1001 // Check if DarkReader library is available 1002 if (typeof DarkReader === 'undefined') { 1003 return; 1004 } 1005 1006 // Initialize dark mode 1007 initDarkMode(); 1008 1009 // Set up event listeners 1010 setupEventListeners(); 1011 1012 // Make debug functions available globally for testing 1013 window.darkReaderDebug = { 1014 getSettings: () => typeof darkReaderSettings !== 'undefined' ? darkReaderSettings : 'undefined', 1015 getExclusions: () => { 1016 if (typeof darkReaderSettings !== 'undefined' && darkReaderSettings && darkReaderSettings.exclusions) { 1017 return darkReaderSettings.exclusions; 1018 }
1019 return 'No exclusions found'; 1020 }, 1021 testFixes: () => { 1022 const fixes = createDarkReaderFixes(); 1023 return fixes; 1024 }, 1025 reloadDarkMode: () => { 1026 initDarkMode(); 1027 }, 1028 testExclusions: () => { 1029 1030 if (typeof darkReaderSettings !== 'undefined' && darkReaderSettings.exclusions) { 1031 1032 const fixes = createDarkReaderFixes(); 1033 1034 // Test if excluded elements exist on page 1035 if (darkReaderSettings.exclusions.images) { 1036 darkReaderSettings.exclusions.images.forEach(selector => { 1037 const elements = document.querySelectorAll(selector); 1038 }); 1039 } 1040 1041 if (darkReaderSettings.exclusions.videos) { 1042 darkReaderSettings.exclusions.videos.forEach(selector => { 1043 const elements = document.querySelectorAll(selector); 1044 }); 1045 } 1046 } else { 1047 } 1048 1049 return 'Test completed'; 1050 } 1051 }; 1052 }); 1053})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.