GreasyFork Code: Syntax Highlight by CodeMirror

To syntax highlight GreasyFork Code by CodeMirror

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         GreasyFork Code: Syntax Highlight by CodeMirror
// @namespace    Violentmonkey Scripts
// @grant        none
// @version      0.4.10
// @author       CY Fung
// @description  To syntax highlight GreasyFork Code by CodeMirror
// @run-at       document-start
// @inject-into  page
// @unwrap
// @license      MIT
// @match        https://greasyfork.org/*
// @match        https://sleazyfork.org/*
//
// ==/UserScript==


(() => {

    const cdn = 'https://cdn.jsdelivr.net/npm/[email protected]';
    const resoruces = {
        'codemirror.min.js': `${cdn}/lib/codemirror.min.js`,
        'javascript.min.js': `${cdn}/mode/javascript/javascript.min.js`,
        'css.min.js': `${cdn}/mode/css/css.min.js`,
        'stylus.min.js': `${cdn}/mode/stylus/stylus.min.js`,
        'active-line.min.js': `${cdn}/addon/selection/active-line.min.js`,
        'search.js': `${cdn}/addon/search/search.js`,
        'searchcursor.js': `${cdn}/addon/search/searchcursor.js`,
        'jump-to-line.js': `${cdn}/addon/search/jump-to-line.js`,
        'dialog.js': `${cdn}/addon/dialog/dialog.js`,
        'codemirror.min.css': `${cdn}/lib/codemirror.min.css`,
        'dialog.css': `${cdn}/addon/dialog/dialog.css`,
        'material.css': `${cdn}/theme/material.css`,
    }

    const doActionCSS = () => `

        .code-container{
            height:100vh;
        }
        .code-container .CodeMirror, .code-container textarea{
            height:100%;
        }
    `;


    const global_css = () => `

        html {
            line-height: 1.5;
            -webkit-text-size-adjust: 100%;
            -moz-tab-size: 4;
            -o-tab-size: 4;
            tab-size: 4;
            font-family: ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
            font-feature-settings: normal;
            font-variation-settings: normal
        }

        .code-container code, .code-container kbd, .code-container pre, .code-container samp {
            font-family: ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;
            font-size: 1em
        }

        #script-content > .code-container[class] {
            width: 100%;
        }

        .code-container[class] {
            border-radius: 0;
        }

        .code-container[class] {
            border-radius: 0;
        }

        .code-container > pre:only-child{
            padding:0;
        }

        code.syntax-highlighted[class] {
            font-family: monospace;
            font-size: 13px;
            font-variant-ligatures: contextual;
            line-height: 1.15rem;
            text-shadow: none !important;
        }

        .hljs-comment[class], .hljs-quote[class] {
            font-style: inherit;
            color: #259789;
        }

        .hljs-add-marker-width .marker-fixed-width[class] {
            user-select: none !important;
            width: calc(var(--hljs-marker-width, 0em) + 16px);
            background: #f4f4f4;
            padding-right: 6px;
            margin-right: 4px;
            contain: paint style;
        }

        [dark] .hljs-add-marker-width .marker-fixed-width[class] {
            background: #242424;
            color: #b6b2b2;
        }

        .marker-fixed-width[marker-text]::before {
            content: attr(marker-text);
        }


        @keyframes cmLineNumberAppear {
            from {
                background-position-x: 3px;
            }
            to {
                background-position-x: 4px;
            }
        }

        .CodeMirror-linenumber:not(:empty){
            animation: cmLineNumberAppear 1ms linear 0s 1 normal forwards;
        }

        .CodeMirror-linenumber[marker-text]::before {
            content: attr(marker-text);
        }


    `;


    const cssForCodePage = () => /\/scripts\/\d+[^\s\/\\]*\/code(\/|$)/.test(location.href) ? `

        html:not([dkkfv]) div.code-container {
            visibility: collapse;
        }

        .code-container,
        .code-container pre:only-child,
        .code-container pre:only-child code:only-child {
            max-height: calc(100vh + 4px);
            max-width: calc(100vw + 4px);
        }
    ` : '';


    const cssAdd = () => `

        ${global_css()}

        ${cssForCodePage()}

        .code-container {
            max-width: 100%;
            display: inline-flex;
            flex-direction: column;
            overflow: auto;
            border-radius: 8px;
            max-height: 100%;
            overflow: visible;
        }
        .code-container > pre:only-child {
            max-width: 100%;
            display: inline-flex;
            flex-direction: column;
            flex-grow: 1;
            height: 0;
        }
        .code-container > pre:only-child > code:only-child {
            max-width: 100%;
            flex-grow: 1;
            height: 0;
        }
        .code-container pre code {
            padding: 0;
            font-family: Consolas;
            cursor: text;
            overflow: auto;
            box-sizing: border-box;
        }
        .code-container pre code .marker {
            display: inline-block;
            color: #636d83;
            text-align: right;
            padding-right: 20px;
            user-select: none;
            cursor: auto;
        }

        .code-container[contenteditable]{
          outline: 0 !important;
          contain: strict;
          box-sizing: border-box;
          overflow: hidden;
        }

        .code-container[contenteditable]>pre[contenteditable="false"]{
          contain: strict;
          width: initial;
          box-sizing: border-box;
          overflow: hidden;
        }

        html {
            --token-color-comment: #259789;
            --cm-dialog-background-color: #fefefe;
            --cm-linenumber-background-color: #f4f4f4;
            --cm-linenumber-text-color: #636d83;
            --cm-search-color: #ffeb3ab8;
        }

        [dark] {
            --token-color-comment:#59c6b9;
            --cm-dialog-background-color: #25262d;
            --cm-linenumber-background-color: #242424;
            --cm-linenumber-text-color: #b6b2b2;
            --cm-search-color: #6068bbb8;
        }


        .CodeMirror .cm-comment[class] {
            color: var(--token-color-comment);
        }


        html .CodeMirror .CodeMirror-linenumber[class] {
            background: var(--cm-linenumber-background-color);
            color: var(--cm-linenumber-text-color);
            padding-right: 6px;
            margin-right: 4px;
            contain: paint style;
            user-select: none !important;
        }

        html .CodeMirror[class] {
            background-color: inherit;
        }

        .CodeMirror[class] .cm-searching {
            background-color: var(--cm-search-color);
        }

        .CodeMirror[class] .CodeMirror-dialog[class] {
            background-color: var(--cm-dialog-background-color);
        }

        html .CodeMirror .CodeMirror-activeline-background[class] {
            background: inherit;
            background-color: #8fd9da17;
        }

        div.code-container .CodeMirror .CodeMirror-lines {
            padding: 0;
        }

        div.code-container .CodeMirror {
            font-family: monospace;
            font-size: 13px;
            font-variant-ligatures: contextual;
            line-height: 1.15rem;
            text-shadow: none !important;
        }


    `;

    const Promise = (async function () { })().constructor;

    const delayPn = delay => new Promise((fn => setTimeout(fn, delay)));

    const PromiseExternal = ((resolve_, reject_) => {
        const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject };
        return class PromiseExternal extends Promise {
            constructor(cb = h) {
                super(cb);
                if (cb === h) {
                    /** @type {(value: any) => void} */
                    this.resolve = resolve_;
                    /** @type {(reason?: any) => void} */
                    this.reject = reject_;
                }
            }
        };
    })();

    // -------- fix requestIdleCallback issue for long coding --------

    const pud = new PromiseExternal();
    if (typeof window.requestIdleCallback === 'function' && !window.requestIdleCallback842 && window.requestIdleCallback.length === 1) {
        window.requestIdleCallback842 = window.requestIdleCallback;
        window.requestIdleCallback = function (callback, ...args) {
            return (this || window).requestIdleCallback842(async function () {
                await pud.then();
                return callback.apply(this, arguments);
            }, ...args);
        }
    }

    // -------- fix requestIdleCallback issue for long coding --------

    const pScript = new PromiseExternal();
    const pElementQuery = new PromiseExternal();

    HTMLElement.prototype.getElementsByTagName331 = HTMLElement.prototype.getElementsByTagName;
    Document.prototype.getElementsByTagName331 = Document.prototype.getElementsByTagName;

    HTMLElement.prototype.getElementsByTagName = getElementsByTagName;
    Document.prototype.getElementsByTagName = getElementsByTagName;

    let byPass = true;

    const observablePromise = (proc, timeoutPromise) => {
        let promise = null;
        return {
            obtain() {
                if (!promise) {
                    promise = new Promise(resolve => {
                        let mo = null;
                        const f = () => {
                            let t = proc();
                            if (t) {
                                mo.disconnect();
                                mo.takeRecords();
                                mo = null;
                                resolve(t);
                            }
                        }
                        mo = new MutationObserver(f);
                        mo.observe(document, { subtree: true, childList: true })
                        f();
                        timeoutPromise && timeoutPromise.then(() => {
                            resolve(null)
                        });
                    });
                }
                return promise
            }
        }
    }

    const documentReady = new Promise(resolve => {
        Promise.resolve().then(() => {
            if (document.readyState !== 'loading') {
                resolve();
            } else {
                window.addEventListener("DOMContentLoaded", resolve, false);
            }
        });
    });

    documentReady.then(async () => {
        pud.resolve();
    });

    function getElementsByTagName(tag) {
        if (byPass) {
            if (tag === 'pre' || tag === 'code' || tag === 'xmp') {
                if (location.pathname.endsWith('/code')) {
                    pElementQuery.resolve();
                    return [];
                }
            }
        }
        return this.getElementsByTagName331(tag);
    }

    async function onBodyHeadReadyAsync() {
        await observablePromise(() => document.body && document.head).obtain();
    }


    // Load CSS
    function loadJS(href) {

        return new Promise(resolve => {

            const script = document.createElement('script');
            script.src = href;
            script.onload = () => {
                resolve(script);
            };
            document.head.appendChild(script);

        });

    }

    // Load CSS
    function loadCSS(href) {
        const link = document.createElement('link');
        link.rel = 'stylesheet';
        link.href = href;
        document.head.appendChild(link);
        return link;
    }






    /** @param {HTMLElement} pre */
    async function prepareCodeAreaAsync(pre) {

        if (pre.isConnected === false) return;

        for (const li of pre.querySelectorAll('li')) {
            li.append(document.createTextNode('\n'));
        }

        const codeElement = document.createElement('code');
        // codeElement.classList.add('language-javascript');
        codeElement.innerHTML = pre.innerHTML;

        // Clearing the original code container and appending the new one
        // pre.classList = '';
        pre.innerHTML = '';
        // pre.appendChild(codeElement);

        // if (pre.querySelector('code')) return;
        const code = codeElement;

        const codeContainer = pre.closest('.code-container');
        if (codeContainer && codeContainer.querySelector('.code-container>pre:only-child')) {
            // avoid selection to the outside by mouse dragging
            codeContainer.setAttribute('contenteditable', '');
            codeContainer.querySelector('.code-container>pre:only-child').setAttribute('contenteditable', 'false');
        }


        // let parentNode = code.parentNode;
        // let nextNode = code.nextSibling;

        // code.remove();
        let parentNode = pre;
        let nextNode = null;
        await Promise.resolve().then();

        // preset language
        /*
        const text = codeElement.textContent;
        if(/(^|\n)\s*\/\/\s+==UserScript==\s*\n/.test(text)){
            codeElement.classList.add('language-javascript');
        }else if(/(^|\n)\s*\/\*\s+==UserStyle==\s*\n/.test(text)){
            codeElement.classList.add('language-css');
        }
        */


        let preLang = '';

        if (pre.classList.contains('lang-js')) {
            preLang = 'lang-js';
        } else if (pre.classList.contains('lang-css')) {
            preLang = 'lang-css';
        } else if (pre.classList.contains('uglyprint')){
            let m =/\/\/\s*={2,9}(\w+)={2,9}\s*[\r\n]/.exec(codeElement.textContent);
            if(m){
                m = m[1];
                if(m === 'UserScript') preLang = 'lang-js';
                if(m === 'UserStyle') preLang = 'lang-css';
            }
        }

        let className = '';
        if (preLang === 'lang-js') {
            className = 'language-javascript';
        } else if (preLang === 'lang-css') {

            const text = codeElement.textContent;
            let m = /\n@preprocessor\s+([-_a-zA-Z]{3,8})\s*\n/.exec(text);
            className = 'language-css'
            if (m) {
                const preprocessor = m[1];
                if (preprocessor === 'stylus') {
                    className = 'language-stylus';
                } else if (preprocessor === 'uso') {
                    className = 'language-stylus';
                } else if (preprocessor === 'less') {
                    className = 'language-less';
                } else if (preprocessor === 'default') {
                    className = 'language-stylus';
                } else {
                    className = 'language-stylus';
                }
            }


        }


        if (!className) return;

        let mode = '';
        if (className === 'language-javascript') mode = 'javascript';
        if (className === 'language-stylus') mode = 'stylus';
        if (className === 'language-less') mode = 'less';
        if (className === 'language-css') mode = 'css';

        if (!mode) return;



        let textarea = document.createElement('textarea');
        textarea.value = `${code.textContent}`;
        textarea.readOnly = true;
        textarea.id = 'editor651';


        // textarea.classList.add('code-container')

        textarea.style.width = '100%';
        textarea.style.height = '100vh';

        parentNode.insertBefore(textarea, nextNode);


        const editor651 = CodeMirror.fromTextArea(document.querySelector('#editor651'), {

            mode: mode,
            theme: document.documentElement.hasAttribute('dark') ? 'material' : 'default',

            readOnly: true,
            styleActiveLine: true,
            lineNumbers: true,
            extraKeys: { "Alt-F": "findPersistent" }
        });
        editor651.save();
        function refresh() {
            try {
                editor651.display.input.cm.refresh();
            } catch (e) {
            }
        }
        document.documentElement.addEventListener('cm-highlight-refresh', function () {
            setTimeout(refresh, 100);
            requestAnimationFrame(refresh);
            refresh();
        });

    }

    const documentBodyHeadReady = onBodyHeadReadyAsync();

    documentBodyHeadReady.then(async () => {

        if (!location.pathname.endsWith('/code')) {
            return;
        }

        document.head.appendChild(document.createElement('style')).textContent = `${cssAdd()}`;

        await loadJS(resoruces['codemirror.min.js']);

        await Promise.all([
            loadJS(resoruces['javascript.min.js']),
            loadJS(resoruces['css.min.js']),
            loadJS(resoruces['stylus.min.js']),
            loadJS(resoruces['active-line.min.js']),
            loadJS(resoruces['search.js']),
            loadJS(resoruces['searchcursor.js']),
            loadJS(resoruces['jump-to-line.js']),
            loadJS(resoruces['dialog.js'])
        ]);

        if (document.documentElement.hasAttribute('dark')) {

            // TBC
            loadCSS(resoruces['codemirror.min.css']);
            loadCSS(resoruces['dialog.css']);
            loadCSS(resoruces['material.css']);

        } else {

            loadCSS(resoruces['codemirror.min.css']);
            loadCSS(resoruces['dialog.css']);
        }


        pScript.resolve();




    });

    let keydownActive = false;

    documentReady.then(async () => {

        if (!location.pathname.endsWith('/code')) {
            byPass = false;
            return;
        }

        await pScript.then();

        await Promise.race([pElementQuery, delayPn(800)]);

        const targets = document.querySelectorAll('.code-container pre.lang-js, .code-container pre.lang-css, .code-container pre.uglyprint');

        if (targets.length === 0) return;

        await delayPn(40);

        document.head.appendChild(document.createElement('style')).textContent = doActionCSS();

        await delayPn(40);

        byPass = false;

        // Code highlighting
        const promises = [...targets].map(prepareCodeAreaAsync)
        await Promise.all(promises);

        await delayPn(40);
        document.documentElement.setAttribute('dkkfv', '');
        keydownActive = true;
        document.documentElement.dispatchEvent(new CustomEvent('cm-highlight-refresh'));

    });

    function selectAllWithinElement(element) {
        window.getSelection().removeAllRanges();
        let range = document.createRange();
        if (element) {
            range.selectNodeContents(element);
            window.getSelection().addRange(range);
        } else {
            console.error('Element not found with ID:', element);
        }
    }
    document.addEventListener('keydown', (e) => {
        if (keydownActive && e && e.code === 'KeyA' && e.isTrusted && (e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) {

            const target = e.target;
            const container = target ? target.closest('div.code-container') : null;
            const code = container ? container.querySelector('code') : null;

            if (container && code) {

                e.preventDefault();
                e.stopPropagation();
                e.stopImmediatePropagation();

                setTimeout(() => {
                    selectAllWithinElement(code);
                }, 1)

            }

        }
    }, true);


    const cmLineNumberAppearFn = (evt) => {
        const elm = evt.target;
        if (!(elm instanceof HTMLElement)) return;

        elm.setAttribute('marker-text', elm.textContent.trim());
        elm.textContent = '';
    }

    document.addEventListener('animationstart', (evt) => {
        const animationName = evt.animationName;
        if (!animationName) return;
        if (animationName === 'cmLineNumberAppear') cmLineNumberAppearFn(evt);
    }, { capture: true, passive: true });


})();