/** * Dark Mode Preview Plugin for Site Editor * Adds a toggle in the editor header to preview dark/light mode * * @since 1.5.0 */ (function () { const { registerPlugin } = wp.plugins; const { PluginMoreMenuItem } = wp.editPost || {}; const { useState, useEffect, createElement: el } = wp.element; const { MenuItem, MenuGroup } = wp.components; const { __ } = wp.i18n; // SVG icons for sun and moon const SunIcon = el( "svg", { width: 24, height: 24, viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", }, el("circle", { cx: 12, cy: 12, r: 5, stroke: "currentColor", strokeWidth: 2, }), el("path", { d: "M12 2V4M12 20V22M4 12H2M22 12H20M5.64 5.64L4.22 4.22M19.78 19.78L18.36 18.36M5.64 18.36L4.22 19.78M19.78 4.22L18.36 5.64", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", }) ); const MoonIcon = el( "svg", { width: 24, height: 24, viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", }, el("path", { d: "M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", }) ); /** * Apply color scheme to editor iframe */ function applyColorSchemeToEditor(scheme) { // Apply to main document (for any editor chrome that uses theme colors) document.documentElement.setAttribute("data-theme", scheme); document.documentElement.style.colorScheme = scheme; // Apply to editor iframe(s) const iframes = document.querySelectorAll( 'iframe[name="editor-canvas"], .edit-site-visual-editor iframe, .editor-styles-wrapper iframe' ); iframes.forEach((iframe) => { try { const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document; if (iframeDoc && iframeDoc.documentElement) { iframeDoc.documentElement.setAttribute("data-theme", scheme); iframeDoc.documentElement.style.colorScheme = scheme; } } catch (e) { // Cross-origin iframe, can't access } }); // Also apply to .editor-styles-wrapper for classic editor mode const editorWrapper = document.querySelector(".editor-styles-wrapper"); if (editorWrapper) { editorWrapper.setAttribute("data-theme", scheme); editorWrapper.style.colorScheme = scheme; } } /** * Dark Mode Preview Component */ function DarkModePreviewPlugin() { const storageKey = (window.brandyDarkMode && window.brandyDarkMode.storageKey) || "brandy-color-scheme"; const editorStorageKey = storageKey + "-editor-preview"; // Initialize state from editor-specific storage const [isDark, setIsDark] = useState(() => { const stored = localStorage.getItem(editorStorageKey); return stored === "dark"; }); // Apply scheme when state changes useEffect(() => { const scheme = isDark ? "dark" : "light"; applyColorSchemeToEditor(scheme); localStorage.setItem(editorStorageKey, scheme); }, [isDark]); // Watch for iframe loads and reapply scheme useEffect(() => { const observer = new MutationObserver(() => { const scheme = isDark ? "dark" : "light"; // Delay to allow iframe to fully load setTimeout(() => applyColorSchemeToEditor(scheme), 100); }); observer.observe(document.body, { childList: true, subtree: true, }); return () => observer.disconnect(); }, [isDark]); const toggleDarkMode = () => { setIsDark(!isDark); }; // Use PluginMoreMenuItem if available (Post Editor) // Otherwise create a floating button for Site Editor if (PluginMoreMenuItem) { return el( PluginMoreMenuItem, { icon: isDark ? MoonIcon : SunIcon, onClick: toggleDarkMode, }, isDark ? __("Preview Light Mode", "brandy") : __("Preview Dark Mode", "brandy") ); } // For Site Editor, we'll use a different approach - inject into header return null; } /** * Site Editor Header Button Component * Creates a button in the Site Editor header area */ function DarkModeHeaderButton() { const storageKey = (window.brandyDarkMode && window.brandyDarkMode.storageKey) || "brandy-color-scheme"; const editorStorageKey = storageKey + "-editor-preview"; const [isDark, setIsDark] = useState(() => { const stored = localStorage.getItem(editorStorageKey); return stored === "dark"; }); useEffect(() => { const scheme = isDark ? "dark" : "light"; applyColorSchemeToEditor(scheme); localStorage.setItem(editorStorageKey, scheme); }, [isDark]); useEffect(() => { const observer = new MutationObserver(() => { const scheme = isDark ? "dark" : "light"; setTimeout(() => applyColorSchemeToEditor(scheme), 100); }); observer.observe(document.body, { childList: true, subtree: true, }); return () => observer.disconnect(); }, [isDark]); const toggleDarkMode = () => { setIsDark(!isDark); }; return el( "button", { className: "brandy-dark-mode-editor-toggle components-button" + (isDark ? " is-dark" : ""), onClick: toggleDarkMode, "aria-label": isDark ? __("Preview Light Mode", "brandy") : __("Preview Dark Mode", "brandy"), title: isDark ? __("Preview Light Mode", "brandy") : __("Preview Dark Mode", "brandy"), }, el("span", { className: "brandy-dark-mode-editor-toggle__sun" }, SunIcon), el( "span", { className: "brandy-dark-mode-editor-toggle__moon" }, MoonIcon ) ); } // Register plugin for Post Editor menu item registerPlugin("brandy-dark-mode-preview", { render: DarkModePreviewPlugin, }); // For Site Editor, inject button into header function injectSiteEditorButton() { // Check if we're in Site Editor const siteEditorHeader = document.querySelector( ".edit-site-header-edit-mode__end, .edit-site-header__actions, .editor-header__settings" ); if ( siteEditorHeader && !document.querySelector(".brandy-dark-mode-editor-toggle") ) { const container = document.createElement("div"); container.className = "brandy-dark-mode-editor-toggle-container"; siteEditorHeader.insertBefore(container, siteEditorHeader.firstChild); // Render React component into container if (wp.element.render) { wp.element.render(el(DarkModeHeaderButton), container); } else if (wp.element.createRoot) { // WordPress 6.2+ uses createRoot const root = wp.element.createRoot(container); root.render(el(DarkModeHeaderButton)); } } } // Try to inject immediately and also watch for DOM changes if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => { setTimeout(injectSiteEditorButton, 500); }); } else { setTimeout(injectSiteEditorButton, 500); } // Watch for Site Editor navigation (SPA-like behavior) const headerObserver = new MutationObserver(() => { if (!document.querySelector(".brandy-dark-mode-editor-toggle")) { injectSiteEditorButton(); } }); headerObserver.observe(document.body, { childList: true, subtree: true, }); })();