GreasyFork Code: Syntax Highlight by PrismJS

To syntax highlight GreasyFork Code by PrismJS

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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

(() => {

    const USE_SHADOW_MODE = true; // performance fix for long coding

    const cdn = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0';
    const resoruces = {
        'prism-core.js': `${cdn}/components/prism-core.js`,
        'prism-clike.js': `${cdn}/components/prism-clike.min.js`,
        'prism-javascript.js': `${cdn}/components/prism-javascript.min.js`,
        'prism-css.js': `${cdn}/components/prism-css.min.js`,
        'prism-stylus.js': `${cdn}/components/prism-stylus.min.js`,
        'prism.css': `${cdn}/themes/prism.min.css`,
        'prism-dark.css': `${cdn}/themes/prism-dark.min.css`,
    }

    const doActionCSS = () => `

        .code-container, .code-container-shadow{
            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,
        .code-container-pre, .code-container-pre code, .code-container-pre pre {
            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], .code-container-shadow[class] {
            border-radius: 0;
        }

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

        pre.code-container-pre[class]{

            padding: 0;
            border: 0;
            margin: 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: var(--marker-color-background, #f4f4f4);
            padding-right: 6px;
            margin-right: 4px;
            contain: paint style;
            color: var(--marker-color-text, inherit);
        }

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

    `;


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

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

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


    const cssAdd = () =>`

        ${global_css()}

        ${cssForCodePage()}

        .code-container, .code-container-shadow {
            max-width: 100%;
            display: inline-flex;
            flex-direction: column;
            overflow: auto;
            border-radius: 8px;
            max-height: 100%;
            overflow: visible;
        }
        .code-container > pre:only-child, .code-container-pre {
            max-width: 100%;
            display: inline-flex;
            flex-direction: column;
            flex-grow: 1;
            height: 0;
        }
        .code-container > pre:only-child > code:only-child, .code-container-pre > code:only-child{
            max-width: 100%;
            flex-grow: 1;
            height: 0;
        }
        .code-container pre code, .code-container-pre code {
            padding: 0;
            font-family: Consolas;
            cursor: text;
            overflow: auto;
            box-sizing: border-box;
        }
        .code-container pre code .marker, .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;
        }

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




        html {

        --token-color-keyword: #07a;
        --token-color-punctuation: #1415ec;
        --token-color-comment: #259789;
        --token-color-function: #da204f;
        --token-color-atrule: #700d0d;
        --token-stylus-selector: #1373bb;
        --token-stylus-variable: #0d10cd;
        --token-stylus-unit: #d1939e;
        }

        [dark] {

        --token-color-keyword: #898af2;
        --token-color-punctuation: #fadbdb;
        --token-color-comment:#59c6b9;
        --token-color-function: #e98aa2;
        --token-color-atrule: #cc66ce;
        --token-stylus-selector: #96c7ec;
        --token-stylus-variable: #fff;
        --token-stylus-unit: #cfb9d7;

        --marker-color-background: #242424;
        --marker-color-text: #b6b2b2;

        }


        code .token.comment {
            color: var(--token-color-comment);
        }

        code .token.atrule, code .token.attr-value, code .token.keyword {
            color: #1415ec;
            color: var(--token-color-keyword);
        }

        code .token.punctuation{
        color: var(--token-color-punctuation);
        }


        code .token.variable-declaration,
        code  .token.variable {
            color: #0d10cd;
        }

        code .token.selector{
        color: #1373bb;
        }


        code .token.function {
            color:var(--token-color-function);
        }



        .language-stylus .token.variable-declaration,
        .language-stylus .token.variable {
            color: #0d10cd;
            color: var(--token-stylus-variable);
        }

        .language-stylus .token.selector{
            color: #1373bb;
            color: var(--token-stylus-selector);
        }

        .language-stylus .token.punctuation{
        color:#700d0d;
        }


        .language-stylus .token.function {
        color:#da204f
        }


        code.language-stylus .token.atrule, 
        code.language-stylus .token.attr-value, 
        code.language-stylus .token.keyword,
        code.language-stylus .token.punctuation,
        code.language-stylus .token.function {
            color: #700d0d;
            color: var(--token-color-atrule);
        }

        code.language-stylus .token.punctuation {
            opacity: 1;
        }

        code.language-stylus .property-declaration .operator {
            color: inherit;
        }

        code.language-stylus .token.unit {
            color: var(--token-stylus-unit);
        }

        .token, span.marker {
            contain: content;
        }

        .prettyprint.prettyprint {
            background-color: transparent;
        }

    `;


    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) {
            console.error(322)
            return (this || window).requestIdleCallback842(async function () {
                await pud.then();
                setTimeout(()=>{
                    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;
    }





    function getLangClassName(pre, textContent){



        let className = '';
        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(pre.textContent);
            if(m){
                m = m[1];
                if(m === 'UserScript') preLang = 'lang-js';
                if(m === 'UserStyle') preLang = 'lang-css';
            }
        }

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

            const text = 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';
                }
            }

        }

        return className;


    }

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

        if (pre.isConnected === false) return;
        const preParentNode = pre.parentNode;
        const preNextNode = pre.nextSibling;

        const codeContainer = pre.closest('.code-container');
        const doContentEditable = codeContainer && codeContainer.querySelector('.code-container>pre:only-child');

        pre.remove();

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

        const textContent = pre.textContent;

        const className = getLangClassName(pre, textContent);

        if (!className) return;

        if (doContentEditable) {
            // avoid selection to the outside by mouse dragging
            codeContainer.setAttribute('contenteditable', '');
            pre.setAttribute('contenteditable', 'false');
        }

        const code = document.createElement('code');

        code.classList.add(className);
        code.classList.add('syntax-highlighted')

        let htmlCode = '';

        if (className === 'language-javascript') {
            htmlCode = Prism.highlight(textContent, Prism.languages.javascript, 'javascript');
        } else if (className === 'language-stylus') {
            htmlCode = Prism.highlight(textContent, Prism.languages.stylus, 'stylus');
        } else if (className === 'language-less') {
            htmlCode = Prism.highlight(textContent, Prism.languages.less, 'less');
        } else {
            htmlCode = Prism.highlight(textContent, Prism.languages.css, 'css');
        }

        // Adding line numbers
        htmlCode = htmlCode || '';
        const htmlSplit = htmlCode ? htmlCode.split('\n') : [];
        const totalLines = htmlSplit.length;

        let mwStyle = '';

        if (totalLines >= 1) {
            code.classList.add('hljs-add-marker-width');
            const len = `${totalLines}`.length * 0.5;
            mwStyle = `${len}em`;
            htmlCode = htmlSplit.map((n, i) => `<span class="marker marker-fixed-width" marker-text="${i + 1}"></span>${n}`).join('\n') || '';

        } else {

            code.classList.remove('hljs-add-marker-width');
        }

        code.innerHTML = htmlCode;
        code.style.setProperty('--hljs-marker-width', mwStyle);



        if(pre.firstChild === pre.lastChild && pre.firstChild instanceof Node){
            pre.firstChild.replaceWith(code);
        }else{
            pre.innerHTML = '';
            pre.appendChild(code);
        }
        pre.classList.add('code-container-pre');


        if(USE_SHADOW_MODE){

            const shadowDiv = document.createElement("div");
            shadowDiv.classList.add('code-container-shadow')

            const shadow = shadowDiv.attachShadow({ mode: "open" });

            const styles = document.querySelectorAll('link, style');
            for(style of styles) {
                if(style.classList.contains("stylus")) continue;
                if(style.nodeName === 'LINK' && style.rel !== 'stylesheet') continue;
                shadow.appendChild(style.cloneNode(true))
            }

            shadow.appendChild(pre)
            preParentNode.insertBefore(shadowDiv, preNextNode);
        }else{

            preParentNode.insertBefore(pre, preNextNode);
        }




    }

    const documentBodyHeadReady = onBodyHeadReadyAsync();

    documentBodyHeadReady.then(async () => {

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

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

        self.Prism = self.Prism || {};
        self.Prism.manual = true;
        await loadJS(resoruces['prism-core.js']);
        await loadJS(resoruces['prism-clike.js']);
        await loadJS(resoruces['prism-javascript.js']);
        await loadJS(resoruces['prism-css.js']);
        await loadJS(resoruces['prism-stylus.js']);

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

            loadCSS(resoruces['prism-dark.css']);
        } else {

            loadCSS(resoruces['prism.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');
        // pre.uglyprint : too long code ; see https://greasyfork.org/zh-CN/scripts/24204-picviewer-ce/code

        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;

    });

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


})();