Back to "Byta tema för sjöjungfru (uppdaterad)"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

Javascript Markdown Mermaid

Byta tema för sjöjungfru (uppdaterad)

Thursday, 29 August 2024

Inledning

Jag använder Mermaid.js för att skapa knarkdiagram du ser i några inlägg. Som den där nere. Men något som irriterade mig är att det inte var reaktivt att byta teman (mörk/ljus) och det verkade finnas mycket dålig information ute om att uppnå detta.

Detta är resultatet av några timmars grävande och försök att räkna ut hur man gör detta.

Här hittar du källan för mdeswitcher: mdeswitcher.js.

OBS: Jag har uppdaterat detta väsentligt.

Diagrammet

graph LR A[Start] --> B[Initialize Mermaid with Theme] B --> C{Are there any elements matching 'div.mermaid'?} C --> |No| D[Exit] C --> |Yes| E[Save Original Data] E --> F{Did saving data succeed?} F --> |No| D[Exit] F --> |Yes| G[Set up Theme Event Listeners] G --> H[Check Local Storage for Dark Mode] H --> I{Is Dark Mode enabled?} I --> |Yes| J[Load Mermaid with Dark Theme] I --> |No| K[Load Mermaid with Default Theme] J --> L[Wait for Events] K --> L[Wait for Events] L --> M{Event Triggered?} M --> |Dark Theme Set| N[Reset Processed Data] N --> O[Load Mermaid with Dark Theme] M --> |Light Theme Set| P[Reset Processed Data] P --> Q[Load Mermaid with Default Theme] O --> L Q --> L L --> D[Exit]

Problemet

Problemet är att du måste initiera Mermaid att ställa in temat, och du kan inte ändra det efter det. HUR som helst om du vill återinitiera det på ett redan skapat diagram; det kan inte göra om diagrammet eftersom data inte lagras i DOM.

Lösningen

Så efter MYCKET gräva och försöka räkna ut hur man gör detta, hittade jag en lösning i detta GitHub nummer inlägg

Men det hade fortfarande några problem, så jag var tvungen att ändra det lite för att få det att fungera.

Teman

Denna webbplats är baserad på en Tailwind tema som kom med en ganska hemsk tema switcher.

Du kommer att se att detta gör olika saker runt att byta tema, ställa in temat för vad som lagras i lokal lagring, ändra ett par stilar för simplemde & highlight.js och sedan tillämpa temat.

export  function globalSetup() {
    const lightStylesheet = document.getElementById('light-mode');
    const darkStylesheet = document.getElementById('dark-mode');
    const simpleMdeDarkStylesheet = document.getElementById('simplemde-dark');
    const simpleMdeLightStylesheet = document.getElementById('simplemde-light');
    return {
        isMobileMenuOpen: false,
        isDarkMode: false,
        // Function to initialize the theme based on localStorage or system preference
        themeInit() {
            if (
                localStorage.theme === "dark" ||
                (!("theme" in localStorage) &&
                    window.matchMedia("(prefers-color-scheme: dark)").matches)
            ) {
                localStorage.theme = "dark";
                document.documentElement.classList.add("dark");
                document.documentElement.classList.remove("light");
                this.isDarkMode = true;
              
                this.applyTheme(); // Apply dark theme stylesheets
            } else {
                localStorage.theme = "base";
                document.documentElement.classList.remove("dark");
                document.documentElement.classList.add("light");
                this.isDarkMode = false;
                this.applyTheme(); // Apply light theme stylesheets
            }
        },

        // Function to switch the theme and update the stylesheets accordingly
        themeSwitch() {
            if (localStorage.theme === "dark") {
                localStorage.theme = "light";
                document.body.dispatchEvent(new CustomEvent('light-theme-set'));
                document.documentElement.classList.remove("dark");
                document.documentElement.classList.add("light");
                this.isDarkMode = false;
            } else {
                localStorage.theme = "dark";
                document.body.dispatchEvent(new CustomEvent('dark-theme-set'));
                document.documentElement.classList.add("dark");
                document.documentElement.classList.remove("light");
                this.isDarkMode = true;
            }
            this.applyTheme(); // Apply the theme stylesheets after switching
        },

        // Function to apply the appropriate stylesheets based on isDarkMode
        applyTheme() {
         
            if (this.isDarkMode) {
                // Enable dark mode stylesheets
                lightStylesheet.disabled = true;
                darkStylesheet.disabled = false;
                simpleMdeLightStylesheet.disabled = true;
                simpleMdeDarkStylesheet.disabled = false;
            } else {
                // Enable light mode stylesheets
                lightStylesheet.disabled = false;
                darkStylesheet.disabled = true;
                simpleMdeLightStylesheet.disabled = false;
                simpleMdeDarkStylesheet.disabled = true;
            }
        }
    };
}

Ställ in

De viktigaste tilläggen för Mermaid tema switcher är följande:

  document.body.dispatchEvent(new CustomEvent('dark-theme-set'));
    document.body.dispatchEvent(new CustomEvent('light-theme-set'));

Dessa två händelser används i vår ThemeSwitcher komponent för att återinitiera Mermaid diagram.

OnLoad/htmx:efter Swap

I min main.js fil Jag ställer in temaomkopplaren. Jag importerar också mdeswitch fil som innehåller koden för att byta teman.

//Important: Memraid will ALWAYS intialize on window.onload, so we need to make sure we disable this behaviour:
import mermaid from "mermaid";

window.mermaid=mermaid;
mermaid.initialize({startOnLoad:false});

window.mermaidinit = function() {
    mermaid.initialize({ startOnLoad: false });
    try {
        window.initMermaid().then(r => console.log('Mermaid initialized'));
    } catch (e) {
        console.error('Failed to initialize Mermaid:', e);
    }

}

document.body.addEventListener('htmx:afterSwap', function(evt) {
    mermaidinit();
    //This should be called after the mermaid diagrams have been rendered.
    hljs.highlightAll();
});

window.onload = function(ev) {
    if(document.readyState === 'complete') {
        mermaidinit();
        hljs.highlightAll();
    }
};

MDESwtich Ordförande

Detta är filen som innehåller koden för att byta teman för Mermaid. (De fruktansvärda diagram ovan visar sekvensen av händelser som inträffar när temat ändras)

(function(window) {
    'use strict';

    const elementCode = 'div.mermaid';

    const loadMermaid = async (theme) => {

        mermaid.initialize({startOnLoad: false, theme: theme });
        console.log("Loading mermaid with theme:", theme);
        await mermaid.run({
            querySelector: elementCode,
        });
    };

    const saveOriginalData = async () => {
        try {
            console.log("Saving original data");
            const elements = document.querySelectorAll(elementCode);
            const count = elements.length;

            if (count === 0) return;

            const promises = Array.from(elements).map((element) => {
                if (element.getAttribute('data-processed') != null) {
                    console.log("Element already processed");
                    return;
                }
                element.setAttribute('data-original-code', element.innerHTML);
            });

            await Promise.all(promises);
        } catch (error) {
            console.error(error);
            throw error;
        }
    };

    const resetProcessed = async () => {
        try {
            console.log("Resetting processed data");
            const elements = document.querySelectorAll(elementCode);
            const count = elements.length;

            if (count === 0) return;

            const promises = Array.from(elements).map((element) => {
                if (element.getAttribute('data-original-code') != null) {
                    element.removeAttribute('data-processed');
                    element.innerHTML = element.getAttribute('data-original-code');
                }
                else {
                    console.log("Element already reset");
                }
            });

            await Promise.all(promises);
        } catch (error) {
            console.error(error);
            throw error;
        }
    };

    window.initMermaid = async () => {
        const mermaidElements = document.querySelectorAll(elementCode);
        if (mermaidElements.length === 0) return;

        try {
            await saveOriginalData();
        } catch (error) {
            console.error("Error saving original data:", error);
            return; // Early exit if saveOriginalData fails
        }

        const handleDarkThemeSet = async () => {
            try {
                await resetProcessed();
                await loadMermaid('dark');
                console.log("Dark theme set");
            } catch (error) {
                console.error("Error during dark theme set:", error);
            }
        };

        const handleLightThemeSet = async () => {
            try {
                await resetProcessed();
                await loadMermaid('default');
                console.log("Light theme set");
            } catch (error) {
                console.error("Error during light theme set:", error);
            }
        };
        document.body.removeEventListener('dark-theme-set', handleDarkThemeSet);
        document.body.removeEventListener('light-theme-set', handleLightThemeSet);
        document.body.addEventListener('dark-theme-set', handleDarkThemeSet);
        document.body.addEventListener('light-theme-set', handleLightThemeSet);

        const isDarkMode = localStorage.theme === 'dark';
        await loadMermaid(isDarkMode ? 'dark' : 'default').then(r => console.log('Initial load complete'));


    };

})(window);

Går ner till toppen här.

  1. init - funktionen är huvudfunktionen som kallas när sidan laddas.

Det sparar först det ursprungliga innehållet i Mermaid diagram; Detta var ett problem i den version jag kopierade det från, de använde "innerHTML" som inte fungerade för mig som vissa diagram förlitar sig på nya linjer som att remsor.

Det lägger sedan till två evenemang lyssnare för dark-theme-set och light-theme-set händelser. När dessa händelser avfyras återställs den bearbetade datan och återinitierar sedan Mermaid-diagrammet med det nya temat.

Det kontrollerar sedan den lokala lagringen för temat och initierar Mermaid diagram med lämplig tema.

let isDarkMode = localStorage.theme === 'dark';
        if(isDarkMode) {
            loadMermaid('dark');
         }
         else{
             loadMermaid('default')
         }

Spara originaldata

Nyckeln till hela denna sak är att lagra sedan återställa innehållet i den renderade <div class="mermaid"><div> som innehåller sjöjungfrun markup från våra poster.

Du kommer att se detta bara sätter upp ett löfte som slingor genom alla element och lagrar det ursprungliga innehållet i en data-original-code Egenskap.

    const saveOriginalData = async () => {
    try {
        console.log("Saving original data");
        const elements = document.querySelectorAll(elementCode);
        const count = elements.length;

        if (count === 0) return;

        const promises = Array.from(elements).map((element) => {
            if (element.getAttribute('data-processed') != null) {
                console.log("Element already processed");
                return;
            }
            element.setAttribute('data-original-code', element.innerHTML);
        });

        await Promise.all(promises);
    } catch (error) {
        console.error(error);
        throw error;
    }
};

resetProcessed är samma utom i omvänd riktning där det tar pålägg från data-original-code attribut och ställer tillbaka det till elementet.

Påbörjad

Nu har vi alla dessa data vi kan återinitiera sjöjungfru för att tillämpa vårt nya tema och rendera SVG diagrammet i vår HTML-utgång.

    const elementCode = 'div.mermaid';

const loadMermaid = async (theme) => {

    mermaid.initialize({startOnLoad: false, theme: theme });
    console.log("Loading mermaid with theme:", theme);
    await mermaid.run({
        querySelector: elementCode,
    });
};

Slutsatser

Det här var lite jobbigt att lista ut, men jag är glad att jag gjorde det. Jag hoppas att detta hjälper någon annan som försöker göra samma sak.

logo

©2024 Scott Galloway