Dropbox | Slate Editor — Copy & Markdown Render

Copy Dropbox Slate editor content with preserved line breaks, render .md files as formatted HTML or hybrid-styled Markdown, with segmented toolbar controls and persistent mode preference.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

Advertisement:

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

Advertisement:

// ==UserScript==
// @name         Dropbox | Slate Editor — Copy & Markdown Render
// @namespace    https://greasyfork.org/en/users/1462137-piknockyou
// @version      4.1
// @author       Piknockyou (vibe-coded)
// @license      AGPL-3.0
// @description  Copy Dropbox Slate editor content with preserved line breaks, render .md files as formatted HTML or hybrid-styled Markdown, with segmented toolbar controls and persistent mode preference.
// @match        *://*.dropbox.com/*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/markdown-it.min.js
// @grant        GM_setClipboard
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @run-at       document-idle
// @icon         https://www.google.com/s2/favicons?sz=64&domain=dropbox.com
// ==/UserScript==

(function () {
    'use strict';

    // ── Config ───────────────────────────────────────────────────────────────
    const CONFIG = {
        TOOLTIP_DELAY_MS:    0,      // delay before tooltip appears (0 = instant)
        TOOLTIP_MAX_WIDTH:   '220px',
        TOOLTIP_FONT_SIZE:   '12px',
        TOOLTIP_PADDING:     '7px 11px',
        TOOLTIP_BORDER_RAD:  '6px',
        TOOLTIP_GAP:         10,     // px between anchor element and tooltip
        TOOLTIP_MARGIN:      8,      // min px from viewport edges
    };

    // ── Tooltip ──────────────────────────────────────────────────────────────
    const Tooltip = (() => {
        const STYLES = {
            position:      'fixed',
            background:    'rgba(30,30,30,.96)',
            border:        '1px solid rgba(128,128,128,.35)',
            borderRadius:  CONFIG.TOOLTIP_BORDER_RAD,
            padding:       CONFIG.TOOLTIP_PADDING,
            fontFamily:    'system-ui, sans-serif',
            fontSize:      CONFIG.TOOLTIP_FONT_SIZE,
            fontWeight:    '500',
            color:         '#f0f0f0',
            boxShadow:     '0 4px 12px rgba(0,0,0,0.4)',
            zIndex:        '2147483647',
            pointerEvents: 'none',
            opacity:       '0',
            visibility:    'hidden',
            transition:    'opacity 0.15s ease, visibility 0.15s ease',
            whiteSpace:    'pre-line',
            textAlign:     'center',
            lineHeight:    '1.5',
            maxWidth:      CONFIG.TOOLTIP_MAX_WIDTH,
        };
        let _el = null, _timer = null;
        function _init() {
            if (_el) return;
            _el = document.createElement('div');
            _el.setAttribute('role', 'tooltip');
            Object.assign(_el.style, STYLES);
            document.body.appendChild(_el);
        }
        function _calculatePosition(r) {
            const MARGIN = CONFIG.TOOLTIP_MARGIN, GAP = CONFIG.TOOLTIP_GAP;
            _el.style.left = '-9999px'; _el.style.top = '-9999px';
            _el.style.visibility = 'hidden'; _el.style.opacity = '0';
            const tt = _el.getBoundingClientRect();
            const tw = tt.width, th = tt.height;
            const vw = window.innerWidth, vh = window.innerHeight;
            let left, top;
            if (r.top - MARGIN >= th + GAP) {
                top  = r.top - th - GAP;
                left = r.left + r.width / 2 - tw / 2;
            } else if (vh - r.bottom - MARGIN >= th + GAP) {
                top  = r.bottom + GAP;
                left = r.left + r.width / 2 - tw / 2;
            } else if (vw - r.right - MARGIN >= tw + GAP) {
                left = r.right + GAP;
                top  = r.top + r.height / 2 - th / 2;
            } else if (r.left - MARGIN >= tw + GAP) {
                left = r.left - tw - GAP;
                top  = r.top + r.height / 2 - th / 2;
            } else {
                left = (vw - tw) / 2;
                top  = Math.max(MARGIN, r.top - th - GAP);
            }
            left = Math.max(MARGIN, Math.min(left, vw - tw - MARGIN));
            top  = Math.max(MARGIN, Math.min(top,  vh - th - MARGIN));
            return { left, top };
        }
        return {
            show(target, text) {
                _init();
                _el.textContent = text;
                const pos = _calculatePosition(target.getBoundingClientRect());
                _el.style.left = `${pos.left}px`;
                _el.style.top  = `${pos.top}px`;
                _el.style.visibility = 'visible';
                _el.style.opacity    = '1';
            },
            scheduleShow(target, text, delay = CONFIG.TOOLTIP_DELAY_MS) {
                this.hide();
                _timer = setTimeout(() => this.show(target, text), delay);
            },
            hide() {
                if (_timer) { clearTimeout(_timer); _timer = null; }
                if (_el) { _el.style.opacity = '0'; _el.style.visibility = 'hidden'; }
            },
            destroy() {
                this.hide();
                if (_el) { _el.remove(); _el = null; }
            },
        };
    })();

    const EDITOR_SEL = 'div[data-slate-editor="true"]';
    const OVERLAY_CLS = 'dbx-md-overlay';
    const HYBRID_CLS = 'dbx-md-hybrid';
    const TOOLBAR_ID = 'dbx-md-toolbar';
    const BTN_SIZE = 28;
    const FEEDBACK_MS = 1500;

    if (typeof markdownit === 'undefined') {
        console.error('[Slate-MD] markdown-it failed to load');
        return;
    }
    const mdi = markdownit({ html: false, linkify: true, typographer: false, breaks: true });

    let activeEditor = null;
    let currentMode = 'raw';
    let toolbar = null;
    let feedbackTimer = null;
    let updateTimer = null;
    let editorObserver = null;
    let editorRetagTimer = null;

    const getStoredMode = () => GM_getValue('renderMode', 'raw');
    const storeMode = m => GM_setValue('renderMode', m);

    // Text helpers

    function getLineText(li) {
        const spans = li.querySelectorAll('span[data-slate-string="true"]');
        return spans.length
            ? Array.from(spans, s => s.textContent).join('').replace(/\uFEFF/g, '')
            : '';
    }

    function extractText(editor) {
        if (!editor) return '';
        const lines = [];
        for (const child of editor.children) {
            if (child.nodeName !== 'LI' || child.getAttribute('data-slate-node') !== 'element') continue;
            lines.push(getLineText(child));
        }
        return lines.join('\n');
    }

    // Copy

    function copyContent(showAlert = false) {
        if (!activeEditor) {
            if (showAlert) alert('No Slate editor content found.');
            return;
        }
        GM_setClipboard(extractText(activeEditor));
        if (showAlert) return;
        const btn = toolbar?.querySelector('[data-action="copy"]');
        if (!btn) return;
        btn.textContent = '✓';
        btn.disabled = true;
        clearTimeout(feedbackTimer);
        feedbackTimer = setTimeout(() => { btn.textContent = '📋'; btn.disabled = false; }, FEEDBACK_MS);
    }

    // Full render

    function getOverlay() {
        return activeEditor?.parentNode?.querySelector(`:scope > .${OVERLAY_CLS}`);
    }

    function applyFullRender() {
        if (!activeEditor) return;
        let overlay = getOverlay();
        if (!overlay) {
            overlay = document.createElement('div');
            overlay.className = OVERLAY_CLS;
            activeEditor.parentNode.insertBefore(overlay, activeEditor.nextSibling);
        }
        overlay.innerHTML = mdi.render(extractText(activeEditor));
        overlay.style.display = '';
        activeEditor.style.display = 'none';
    }

    function removeFullRender() {
        if (!activeEditor) return;
        const overlay = getOverlay();
        if (overlay) overlay.style.display = 'none';
        activeEditor.style.display = '';
    }

    // Hybrid

    const MD_ATTRS = [
        'data-md-level', 'data-md-bold', 'data-md-italic',
        'data-md-code', 'data-md-delim', 'data-md-link',
        'data-md-fence',
    ];

    function applyHybrid() {
        if (!activeEditor) return;
        activeEditor.classList.add(HYBRID_CLS);
        tagAllElements(activeEditor);
        startEditorObserver();
    }

    function removeHybrid() {
        if (!activeEditor) return;
        stopEditorObserver();
        activeEditor.classList.remove(HYBRID_CLS);
        const sel = MD_ATTRS.map(a => `[${a}]`).join(',');
        for (const el of activeEditor.querySelectorAll(sel)) {
            for (const a of MD_ATTRS) el.removeAttribute(a);
        }
        for (const el of activeEditor.querySelectorAll('[data-md-link-url]')) {
            el.removeAttribute('data-md-link-url');
            el.style.cursor = '';
        }
    }

    function startEditorObserver() {
        stopEditorObserver();
        if (!activeEditor) return;
        editorObserver = new MutationObserver(() => {
            clearTimeout(editorRetagTimer);
            editorRetagTimer = setTimeout(() => {
                if (currentMode !== 'hybrid' || !activeEditor) return;
                const sel = MD_ATTRS.map(a => `[${a}]`).join(',');
                for (const el of activeEditor.querySelectorAll(sel)) {
                    for (const a of MD_ATTRS) el.removeAttribute(a);
                }
                tagAllElements(activeEditor);
            }, 120);
        });
        editorObserver.observe(activeEditor, { childList: true, subtree: true, characterData: true });
    }

    function stopEditorObserver() {
        editorObserver?.disconnect();
        editorObserver = null;
        clearTimeout(editorRetagTimer);
    }

    function tagAllElements(editor) {
        let inFence = false;
        for (const li of editor.children) {
            if (li.nodeName !== 'LI' || li.getAttribute('data-slate-node') !== 'element') continue;
            const text = getLineText(li);

            // Fence lines: ``` or ```language
            if (/^`{3,}/.test(text)) {
                li.setAttribute('data-md-fence', '');
                inFence = !inFence;
                continue;
            }

            // Skip inline formatting inside fenced code blocks
            if (inFence) continue;

            const hMatch = text.match(/^(#{1,6})\s/);
            if (hMatch) li.setAttribute('data-md-level', hMatch[1].length);
            tagInlineFormatting(li);
        }
    }

    function tagInlineFormatting(li) {
        const leaves = Array.from(li.querySelectorAll('span[data-slate-leaf="true"]'));
        const isHeading = li.hasAttribute('data-md-level');
        let skippedFirst = false;

        // First pass: handle gray/green leaves (bold, italic, links)
        let bold = false, italic = false;
        for (let i = 0; i < leaves.length; i++) {
            const leaf = leaves[i];
            const isGray = /\b_gray_/.test(leaf.className);
            const isGreen = /\b_green_/.test(leaf.className);
            const strSpan = leaf.querySelector('span[data-slate-string="true"]');
            if (!strSpan) continue;
            const t = strSpan.textContent.replace(/\uFEFF/g, '');

            if (isGray && isHeading && !skippedFirst) {
                skippedFirst = true;
                leaf.setAttribute('data-md-delim', '');
                continue;
            }

            if (isGreen) {
                leaf.setAttribute('data-md-link', '');
                const url = extractLinkUrl(leaves, i);
                if (url) {
                    leaf.setAttribute('data-md-link-url', url);
                    leaf.style.cursor = 'pointer';
                    leaf.addEventListener('click', e => {
                        e.preventDefault();
                        e.stopPropagation();
                        window.open(url, '_blank', 'noopener');
                    });
                }
                continue;
            }

            if (isGray) {
                if (t === '**' || t === '__') {
                    bold = !bold;
                    leaf.setAttribute('data-md-delim', '');
                    leaf.setAttribute('data-md-bold', '');
                } else if (t === '***' || t === '___') {
                    bold = !bold; italic = !italic;
                    leaf.setAttribute('data-md-delim', '');
                    leaf.setAttribute('data-md-bold', '');
                    leaf.setAttribute('data-md-italic', '');
                } else if (t === '*' || t === '_') {
                    italic = !italic;
                    leaf.setAttribute('data-md-delim', '');
                    leaf.setAttribute('data-md-italic', '');
                } else {
                    leaf.setAttribute('data-md-delim', '');
                }
            } else {
                if (bold)   leaf.setAttribute('data-md-bold', '');
                if (italic) leaf.setAttribute('data-md-italic', '');
            }
        }

        // Second pass: detect code spans
        // Dropbox renders `code` as a single leaf containing backticks + content,
        // or occasionally as separate leaves. Handle both cases.
        let inCode = false;
        for (const leaf of leaves) {
            if (leaf.hasAttribute('data-md-link') || leaf.hasAttribute('data-md-delim')) continue;
            const strSpan = leaf.querySelector('span[data-slate-string="true"]');
            if (!strSpan) continue;
            const t = strSpan.textContent.replace(/\uFEFF/g, '');

            // Case 1: entire leaf is `content` (backticks inside one leaf)
            if (/^`.+`$/.test(t)) {
                leaf.setAttribute('data-md-code', '');
                continue;
            }
            // Case 2: leaf is just a lone backtick (separate leaf delimiter)
            if (t === '`') {
                inCode = !inCode;
                leaf.setAttribute('data-md-code', '');
                leaf.setAttribute('data-md-delim', '');
                continue;
            }
            // Case 3: we're between two backtick delimiters
            if (inCode) {
                leaf.setAttribute('data-md-code', '');
            }
        }
    }

    function extractLinkUrl(leaves, greenIndex) {
        for (let j = greenIndex + 1; j < leaves.length && j <= greenIndex + 3; j++) {
            const leaf = leaves[j];
            if (!/\b_gray_/.test(leaf.className)) continue;
            const strSpan = leaf.querySelector('span[data-slate-string="true"]');
            if (!strSpan) continue;
            const m = strSpan.textContent.replace(/\uFEFF/g, '').match(/\]\((.+?)\)/);
            if (m) return m[1];
        }
        return null;
    }

    // Mode switching

    function setMode(mode) {
        if (currentMode === 'full')   removeFullRender();
        if (currentMode === 'hybrid') removeHybrid();
        currentMode = mode;
        storeMode(mode);
        if (mode === 'full')   applyFullRender();
        if (mode === 'hybrid') applyHybrid();
        syncToolbar();
    }

    // Toolbar

    function syncToolbar() {
        if (!toolbar) return;
        for (const btn of toolbar.querySelectorAll('[data-mode]')) {
            btn.classList.toggle('dbx-md-seg--active', btn.dataset.mode === currentMode);
        }
    }


    function createToolbar() {
        if (toolbar && document.body.contains(toolbar)) {
            syncToolbar();
            return;
        }
        toolbar = null;
        toolbar = document.createElement('div');
        toolbar.id = TOOLBAR_ID;

        const seg = document.createElement('div');
        seg.className = 'dbx-md-seg-group';

        for (const { mode, icon, title, pos } of [
            { mode: 'raw',    icon: '📝', title: 'Raw text',          pos: 'left'  },
            { mode: 'hybrid', icon: '✨', title: 'Hybrid Markdown',   pos: 'mid'   },
            { mode: 'full',   icon: '👁',  title: 'Rendered Markdown', pos: 'right' },
        ]) {
            const btn = document.createElement('button');
            btn.className = `dbx-md-seg dbx-md-seg--${pos}`;
            btn.dataset.mode = mode;
            btn.textContent = icon;
            btn.title = '';
            btn.addEventListener('click', e => { e.stopPropagation(); setMode(mode); });
            btn.addEventListener('mouseenter', () => Tooltip.scheduleShow(btn, title));
            btn.addEventListener('mouseleave', () => Tooltip.hide());
            seg.appendChild(btn);
        }
        toolbar.appendChild(seg);

        const copy = document.createElement('button');
        copy.className = 'dbx-md-copy';
        copy.dataset.action = 'copy';
        copy.textContent = '📋';
        copy.title = '';
        copy.addEventListener('click', e => { e.stopPropagation(); copyContent(); });
        copy.addEventListener('mouseenter', () => Tooltip.scheduleShow(copy, 'Copy to clipboard'));
        copy.addEventListener('mouseleave', () => Tooltip.hide());
        toolbar.appendChild(copy);

        toolbar.classList.add('dbx-md-toolbar--fixed');
        document.body.appendChild(toolbar);

        syncToolbar();
    }

    function removeToolbar() {
        Tooltip.destroy();
        toolbar?.remove();
        toolbar = null;
    }

    // Lifecycle

    function cleanup() {
        if (currentMode === 'full')   removeFullRender();
        if (currentMode === 'hybrid') removeHybrid();
        if (activeEditor) {
            getOverlay()?.remove();
            activeEditor.style.display = '';
        }
        activeEditor = null;
        currentMode = 'raw';
    }

    function updateState() {
        const editor = document.querySelector(EDITOR_SEL);

        if (editor && editor !== activeEditor) {
            cleanup();
            activeEditor = editor;
            createToolbar();
            const stored = getStoredMode();
            if (stored !== 'raw') setMode(stored);
            else syncToolbar();
        } else if (editor && editor === activeEditor) {
            if (!toolbar || !document.body.contains(toolbar)) {
                toolbar = null;
                createToolbar();
                syncToolbar();
            }
        } else if (!editor && activeEditor) {
            cleanup();
            removeToolbar();
        }
    }

    function startObserving() {
        new MutationObserver(() => {
            clearTimeout(updateTimer);
            updateTimer = setTimeout(updateState, 150);
        }).observe(document.documentElement, { childList: true, subtree: true });
    }

    // Styles

    function injectStyles() {
        const S = BTN_SIZE;
        GM_addStyle(`
            #${TOOLBAR_ID} {
                display: inline-flex;
                align-items: center;
                gap: 6px;
                position: fixed;
                bottom: 20px;
                right: 20px;
                z-index: 2147483647;
                background: rgba(30,30,30,.92);
                backdrop-filter: blur(8px);
                padding: 6px 8px;
                border-radius: 10px;
                box-shadow: 0 4px 16px rgba(0,0,0,.3);
            }

            .dbx-md-seg-group {
                display: inline-flex;
                border: 1px solid rgba(128,128,128,.35);
                border-radius: 6px;
                overflow: hidden;
            }
            .dbx-md-seg {
                width: ${S}px; height: ${S}px;
                min-width: ${S}px; min-height: ${S}px;
                padding: 0; margin: 0;
                border: none;
                border-right: 1px solid rgba(128,128,128,.15);
                background: transparent;
                color: inherit;
                cursor: pointer;
                font-size: 13px;
                line-height: 1;
                display: inline-flex;
                align-items: center;
                justify-content: center;
                transition: background .15s;
                box-sizing: border-box;
            }
            .dbx-md-seg:last-child { border-right: none; }
            .dbx-md-seg:hover { background: rgba(0,97,254,.12); }
            .dbx-md-seg--active { background: #0061FE !important; color: #fff !important; }
            .dbx-md-seg--active:hover { background: #0052D9 !important; }

            .dbx-md-copy {
                width: ${S}px; height: ${S}px;
                min-width: ${S}px; min-height: ${S}px;
                padding: 0; margin: 0;
                border: 1px solid rgba(128,128,128,.35);
                border-radius: 6px;
                background: transparent;
                color: inherit;
                cursor: pointer;
                font-size: 13px;
                line-height: 1;
                display: inline-flex;
                align-items: center;
                justify-content: center;
                transition: background .15s;
                box-sizing: border-box;
                overflow: hidden;
            }
            .dbx-md-copy:hover { background: rgba(0,97,254,.12); }
            .dbx-md-copy:active { background: rgba(0,97,254,.2); }
            .dbx-md-copy:disabled { opacity: .5; cursor: default; }

            /* Full render overlay */
            .${OVERLAY_CLS} {
                color: inherit;
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
                font-size: 15px;
                line-height: 1.7;
                word-wrap: break-word;
                overflow-wrap: break-word;
                padding: 8px 0;
            }
            .${OVERLAY_CLS} h1, .${OVERLAY_CLS} h2 {
                border-bottom: 1px solid rgba(128,128,128,.2);
                padding-bottom: .3em;
                margin-top: 1.5em;
            }
            .${OVERLAY_CLS} h1 { font-size: 1.8em; }
            .${OVERLAY_CLS} h2 { font-size: 1.4em; }
            .${OVERLAY_CLS} h3 { font-size: 1.2em; margin-top: 1.2em; }
            .${OVERLAY_CLS} h4 { font-size: 1.05em; margin-top: 1em; }
            .${OVERLAY_CLS} code {
                background: rgba(128,128,128,.12);
                padding: 2px 5px;
                border-radius: 4px;
                font-size: .9em;
                font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
            }
            .${OVERLAY_CLS} pre {
                background: rgba(128,128,128,.1);
                padding: 14px;
                border-radius: 6px;
                overflow-x: auto;
            }
            .${OVERLAY_CLS} pre code { background: none; padding: 0; }
            .${OVERLAY_CLS} blockquote {
                border-left: 4px solid #0061FE;
                margin: 0 0 16px;
                padding-left: 16px;
                opacity: .8;
            }
            .${OVERLAY_CLS} table { border-collapse: collapse; width: 100%; margin: 16px 0; }
            .${OVERLAY_CLS} td, .${OVERLAY_CLS} th { border: 1px solid rgba(128,128,128,.25); padding: 6px 12px; }
            .${OVERLAY_CLS} th { font-weight: bold; background: rgba(128,128,128,.08); }
            .${OVERLAY_CLS} a { color: #0061FE; }
            .${OVERLAY_CLS} img { max-width: 100%; }
            .${OVERLAY_CLS} hr { border: none; border-top: 1px solid rgba(128,128,128,.2); margin: 24px 0; }
            .${OVERLAY_CLS} ul, .${OVERLAY_CLS} ol { padding-left: 2em; }
            .${OVERLAY_CLS} li { margin: 4px 0; }
            .${OVERLAY_CLS} input[type="checkbox"] { margin-right: 6px; }

            /* Hybrid mode */
            .${HYBRID_CLS} li[data-md-level] span[data-slate-string],
            .${HYBRID_CLS} li[data-md-level] [data-md-delim] span[data-slate-string] {
                color: #569CD6 !important;
                font-weight: 700 !important;
                opacity: 1 !important;
                font-size: 1em !important;
            }

            .${HYBRID_CLS} [data-md-bold] span[data-slate-string],
            .${HYBRID_CLS} [data-md-bold][data-md-delim] span[data-slate-string] {
                font-weight: 700 !important;
                color: #DCDCAA !important;
                opacity: 1 !important;
                font-size: 1em !important;
            }

            .${HYBRID_CLS} [data-md-italic] span[data-slate-string],
            .${HYBRID_CLS} [data-md-italic][data-md-delim] span[data-slate-string] {
                font-style: italic !important;
                color: #C586C0 !important;
                opacity: 1 !important;
                font-size: 1em !important;
            }

            .${HYBRID_CLS} [data-md-bold][data-md-italic] span[data-slate-string] {
                font-weight: 700 !important;
                font-style: italic !important;
                color: #C586C0 !important;
                opacity: 1 !important;
                font-size: 1em !important;
            }

            .${HYBRID_CLS} [data-md-code] {
                background: rgba(206,145,120,.15) !important;
                border-radius: 3px !important;
                padding: 0 3px !important;
            }
            .${HYBRID_CLS} [data-md-code] span[data-slate-string],
            .${HYBRID_CLS} [data-md-code][data-md-delim] span[data-slate-string] {
                font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace !important;
                color: #CE9178 !important;
                opacity: 1 !important;
                font-size: 1em !important;
            }

            .${HYBRID_CLS} [data-md-link] span[data-slate-string] {
                color: #4EC9B0 !important;
                text-decoration: underline !important;
                text-underline-offset: 2px !important;
                opacity: 1 !important;
                font-size: 1em !important;
            }
            .${HYBRID_CLS} [data-md-link]:hover span[data-slate-string] {
                color: #6EDDD1 !important;
            }

            .${HYBRID_CLS} li[data-md-fence] span[data-slate-string] {
                color: #608B4E !important;
                font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace !important;
                opacity: .7 !important;
            }
        `);
    }

    // Init

    function init() {
        injectStyles();
        startObserving();
        updateState();
        GM_registerMenuCommand('Copy Slate Content', () => copyContent(true), 'C');
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();