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();
    }
})();