有觉性 以安住且中立的心 照见身心的实相

MediaWiki:QuoteCopy.js

来自Dhamma.cn

注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的更改的影响。

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5或Ctrl-R(Mac为⌘-R)
  • Google Chrome:按Ctrl-Shift-R(Mac为⌘-Shift-R)
  • Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5。
/* =========================================================
   Dhamma.cn
   法谈划词引用

   MediaWiki 1.43

   功能:
   1. 在隆波帕默尊者法谈正文中选择文字
   2. 松开鼠标左键
   3. 弹出唯一按钮:
      “复制该段开示及出处”
   4. 调用 UrlShortener
   5. 自动生成短网址
   6. 复制完整引用

   示例:

   觉知不是控制心,而是如实地知道身心……

   ----------------------------------------

   法谈摘自:Dhamma.cn 《隆波帕默尊者开示:觉知的要领》

   该篇网址:https://dhamma.cn/s/x9K2

   转载请保持原文,随喜分享,Sadhu!
   ========================================================= */

(function () {

    'use strict';


    /* =====================================================
       基本设置
       ===================================================== */

    var MIN_LENGTH = 2;

    var MAX_LENGTH = 8000;

    var popup = null;

    var selectedText = '';

    /*
     * 当前页面短网址缓存
     *
     * 同一页面只调用一次 UrlShortener。
     */
    var pageShortUrl = null;

    var shortUrlPromise = null;


    /* =====================================================
       1. 判断是否启用
       ===================================================== */

    function shouldEnable() {

        /*
         * 只在 Main namespace
         */
        if (
            mw.config.get(
                'wgNamespaceNumber'
            ) !== 0
        ) {
            return false;
        }


        /*
         * 只在正常阅读页面
         */
        if (
            mw.config.get(
                'wgAction'
            ) !== 'view'
        ) {
            return false;
        }


        /*
         * 如果页面属于:
         *
         * 分类:隆波帕默尊者
         *
         * 自动启用。
         */
        var categories =
            mw.config.get(
                'wgCategories'
            ) || [];


        if (
            categories.indexOf(
                '隆波帕默尊者'
            ) !== -1
        ) {
            return true;
        }


        /*
         * 允许将来通过隐藏元数据
         * 手动启用。
         */
        if (
            document.getElementById(
                'dhamma-quote-metadata'
            )
        ) {
            return true;
        }


        return false;
    }


    /* =====================================================
       2. 创建 Popup
       ===================================================== */

    function createPopup() {

        if (popup) {
            return;
        }


        popup =
            document.createElement(
                'div'
            );


        popup.id =
            'dhamma-quote-popup';


        popup.setAttribute(
            'role',
            'dialog'
        );


        popup.setAttribute(
            'aria-label',
            '复制该段开示及出处'
        );


        /*
         * 只有一个按钮。
         */
        popup.innerHTML =

            '<button ' +

                'type="button" ' +

                'class="dhamma-quote-button" ' +

                'data-action="source">' +

                '复制该段开示及出处' +

            '</button>';


        document.body.appendChild(
            popup
        );


        /*
         * 点击按钮时,
         * 不让 Selection 因失焦消失。
         */
        popup.addEventListener(
            'mousedown',
            function (event) {

                event.preventDefault();

            }
        );


        /*
         * 点击按钮
         */
        popup.addEventListener(
            'click',
            function (event) {

                var button =
                    event.target.closest(
                        '.dhamma-quote-button'
                    );


                if (!button) {
                    return;
                }


                copyWithSource();

            }
        );
    }


    /* =====================================================
       3. 获取当前页面标题
       ===================================================== */

    function getRawPageTitle() {

        /*
         * MediaWiki 当前页面标题
         */
        var title =
            mw.config.get(
                'wgTitle'
            );


        if (title) {
            return title.trim();
        }


        /*
         * 后备方案
         */
        var heading =
            document.querySelector(
                '#firstHeading, .mw-page-title-main'
            );


        if (heading) {

            return heading
                .textContent
                .trim();
        }


        return document.title
            .replace(
                /\s*-\s*.*$/,
                ''
            )
            .trim();
    }


    /* =====================================================
       4. 获取引用标题
       ===================================================== */

    function getCitationTitle() {

        /*
         * 如果页面存在明确的引用 metadata,
         * 优先使用。
         */
        var metadata =
            document.getElementById(
                'dhamma-quote-metadata'
            );


        if (metadata) {

            var customTitle =
                metadata.getAttribute(
                    'data-title'
                );


            if (
                customTitle &&
                customTitle.trim()
            ) {

                return normalizeCitationTitle(
                    customTitle
                );
            }
        }


        /*
         * 自动从页面标题判断。
         */
        var title =
            getRawPageTitle();


        return normalizeCitationTitle(
            title
        );
    }


    /* =====================================================
       5. 标题清理
       ===================================================== */

    function normalizeCitationTitle(
        title
    ) {

        if (!title) {
            return '隆波帕默尊者开示';
        }


        title =
            title.trim();


        /*
         * MediaWiki 页面标题中的下划线
         */
        title =
            title.replace(
                /_/g,
                ' '
            );


        /*
         * -------------------------------------------------
         * 情况一:
         *
         *  《觉知的要领》-隆波帕默尊者-2026年...
         *
         * 提取第一个《...》
         * -------------------------------------------------
         */

        var bookMatch =
            title.match(
                /《([^》]+)》/
            );


        if (bookMatch) {

            title =
                bookMatch[1].trim();
        }


        /*
         * -------------------------------------------------
         * 情况二:
         *
         * 隆波帕默尊者开示:觉知的要领
         * -------------------------------------------------
         */

        if (
            title.indexOf(
                '隆波帕默尊者开示:'
            ) === 0
        ) {

            return title;
        }


        /*
         * -------------------------------------------------
         * 去掉常见前缀
         *
         * D24.91
         * D24.91-
         * D24.91:
         * -------------------------------------------------
         */

        title =
            title.replace(
                /^D\d+(?:\.\d+)?[\s::._-]*/,
                ''
            );


        /*
         * 去除“隆波帕默尊者”后缀
         */
        title =
            title.replace(
                /[-–—_\s]*隆波帕默尊者.*$/u,
                ''
            );


        /*
         * 去除常见日期后缀
         *
         * 例如:
         * -2024年10月20日
         * 2024年10月20日
         */
        title =
            title.replace(
                /[-–—_\s]*\d{4}年\d{1,2}月\d{1,2}日.*$/u,
                ''
            );


        /*
         * 去除多余的连接符
         */
        title =
            title.replace(
                /^[-–—_\s]+|[-–—_\s]+$/g,
                ''
            );


        title =
            title.trim();


        /*
         * 如果清理后为空,
         * 使用原页面标题。
         */
        if (!title) {

            title =
                getRawPageTitle();
        }


        /*
         * 最终统一成:
         *
         * 隆波帕默尊者开示:XXXX
         */
        return (
            '隆波帕默尊者开示:' +
            title
        );
    }


    /* =====================================================
       6. 获取规范 URL
       ===================================================== */

    function getCanonicalUrl() {

        var canonical =
            mw.config.get(
                'wgCanonicalUrl'
            );


        if (canonical) {
            return canonical;
        }


        /*
         * 后备方案
         */
        var pageName =
            mw.config.get(
                'wgPageName'
            );


        if (
            pageName &&
            mw.util &&
            mw.util.getUrl
        ) {

            return mw.util.getUrl(
                pageName
            );
        }


        return window.location.href;
    }


    /* =====================================================
       7. 获取短网址
       ===================================================== */

    function getShortUrl() {

        /*
         * 当前页面已经有短网址
         */
        if (pageShortUrl) {

            return Promise.resolve(
                pageShortUrl
            );
        }


        /*
         * 已经正在请求
         */
        if (shortUrlPromise) {

            return shortUrlPromise;
        }


        /*
         * 调用 MediaWiki UrlShortener
         */
        shortUrlPromise =

            new mw.Api().post({

                action:
                    'shortenurl',

                url:
                    getCanonicalUrl()

            })


            .then(
                function (data) {

                    /*
                     * UrlShortener API
                     *
                     * {
                     *   shortenurl: {
                     *      shorturl: "..."
                     *   }
                     * }
                     */
                    if (
                        data &&
                        data.shortenurl &&
                        data.shortenurl.shorturl
                    ) {

                        pageShortUrl =
                            data.shortenurl.shorturl;


                        return pageShortUrl;
                    }


                    throw new Error(
                        'UrlShortener 未返回 shorturl'
                    );
                }
            )


            .catch(
                function (error) {

                    /*
                     * 允许以后重新请求
                     */
                    shortUrlPromise =
                        null;


                    throw error;
                }
            );


        return shortUrlPromise;
    }


    /* =====================================================
       8. 获取选中文字
       ===================================================== */

    function getSelectionData() {

        var selection =
            window.getSelection();


        if (
            !selection ||
            selection.rangeCount === 0
        ) {

            return null;
        }


        var text =
            selection
                .toString()
                .trim();


        if (!text) {
            return null;
        }


        if (
            text.length <
            MIN_LENGTH
        ) {

            return null;
        }


        if (
            text.length >
            MAX_LENGTH
        ) {

            return null;
        }


        var range =
            selection.getRangeAt(0);


        var container =
            range.commonAncestorContainer;


        /*
         * 如果是 Text Node,
         * 取得它的父元素。
         */
        if (
            container.nodeType ===
            Node.TEXT_NODE
        ) {

            container =
                container.parentElement;
        }


        if (!container) {
            return null;
        }


        /*
         * 只允许从正文中选择。
         *
         * 使用 .mw-parser-output,
         * 不依赖 #mw-content-text,
         * 对不同 Skin 更兼容。
         */
        var content =
            container.closest(
                '.mw-parser-output'
            );


        if (!content) {

            return null;
        }


        return {

            text:
                normalizeSelectedText(
                    text
                ),

            range:
                range.cloneRange()
        };
    }


    /* =====================================================
       9. 清理选中文本
       ===================================================== */

    function normalizeSelectedText(
        text
    ) {

        return text

            .replace(
                /\u00a0/g,
                ' '
            )

            .replace(
                /\r\n/g,
                '\n'
            )

            .replace(
                /[ \t]+\n/g,
                '\n'
            )

            .replace(
                /\n[ \t]+/g,
                '\n'
            )

            .replace(
                /\n{3,}/g,
                '\n\n'
            )

            .trim();
    }


    /* =====================================================
       10. 生成最终引用文字
       ===================================================== */

    function buildCitation(
        shortUrl
    ) {

        var citationTitle =
            getCitationTitle();


        return (

            selectedText +

            '\n\n' +

            '----------------------------------------' +

            '\n\n' +

            '法谈摘自:Dhamma.cn 《' +
            citationTitle +
            '》' +

            '\n\n' +

            '该篇网址:' +
            shortUrl +

            '\n\n' +

            '转载请保持原文,随喜分享,Sadhu!'
        );
    }


    /* =====================================================
       11. 复制到剪贴板
       ===================================================== */

    function copyText(
        text
    ) {

        /*
         * 现代浏览器
         */
        if (
            navigator.clipboard &&
            window.isSecureContext
        ) {

            return navigator.clipboard.writeText(
                text
            );
        }


        /*
         * 后备方案
         */
        return fallbackCopy(
            text
        );
    }


    /* =====================================================
       12. 后备复制
       ===================================================== */

    function fallbackCopy(
        text
    ) {

        return new Promise(
            function (
                resolve,
                reject
            ) {

                var textarea =
                    document.createElement(
                        'textarea'
                    );


                textarea.value =
                    text;


                textarea.setAttribute(
                    'readonly',
                    ''
                );


                textarea.style.position =
                    'fixed';

                textarea.style.left =
                    '-9999px';

                textarea.style.top =
                    '0';

                textarea.style.opacity =
                    '0';


                document.body.appendChild(
                    textarea
                );


                textarea.focus();

                textarea.select();


                try {

                    var success =
                        document.execCommand(
                            'copy'
                        );


                    document.body.removeChild(
                        textarea
                    );


                    if (success) {

                        resolve();

                    } else {

                        reject(
                            new Error(
                                '复制失败'
                            )
                        );
                    }


                } catch (error) {

                    document.body.removeChild(
                        textarea
                    );


                    reject(error);
                }

            }
        );
    }


    /* =====================================================
       13. 复制并附出处
       ===================================================== */

    function copyWithSource() {

        if (!selectedText) {
            return;
        }


        notify(
            '正在生成 Dhamma.cn 短网址……'
        );


        getShortUrl()

            .then(
                function (
                    shortUrl
                ) {

                    return copyText(
                        buildCitation(
                            shortUrl
                        )
                    );
                }
            )

            .then(
                function () {

                    notify(
                        '已复制该段开示及出处'
                    );


                    hidePopup();
                }
            )

            .catch(
                function (error) {

                    console.error(
                        'Dhamma.cn 引用复制失败:',
                        error
                    );


                    /*
                     * 短网址失败时,
                     * 不偷偷复制长 URL。
                     */
                    notify(
                        '短网址生成失败,请稍后再试'
                    );
                }
            );
    }


    /* =====================================================
       14. 显示 Popup
       ===================================================== */

    function showPopup(
        range
    ) {

        createPopup();


        var rect =
            range.getBoundingClientRect();


        if (
            rect.width === 0 &&
            rect.height === 0
        ) {

            return;
        }


        popup.style.display =
            'block';


        /*
         * 必须先显示,
         * 才能取得实际宽高。
         */
        var popupRect =
            popup.getBoundingClientRect();


        var left =
            rect.left +
            rect.width / 2 -
            popupRect.width / 2;


        var top =
            rect.bottom +
            10;


        var margin =
            10;


        /*
         * 左边界
         */
        if (
            left < margin
        ) {

            left =
                margin;
        }


        /*
         * 右边界
         */
        if (
            left +
            popupRect.width >
            window.innerWidth -
            margin
        ) {

            left =
                window.innerWidth -
                popupRect.width -
                margin;
        }


        /*
         * 如果下方空间不足,
         * 改到选区上方。
         */
        if (
            top +
            popupRect.height >
            window.innerHeight -
            margin
        ) {

            top =
                rect.top -
                popupRect.height -
                10;
        }


        /*
         * 顶部
         */
        if (
            top < margin
        ) {

            top =
                margin;
        }


        popup.style.left =
            Math.round(
                left +
                window.scrollX
            ) +
            'px';


        popup.style.top =
            Math.round(
                top +
                window.scrollY
            ) +
            'px';
    }


    /* =====================================================
       15. 隐藏 Popup
       ===================================================== */

    function hidePopup() {

        if (!popup) {
            return;
        }


        popup.style.display =
            'none';
    }


    /* =====================================================
       16. 鼠标松开
       ===================================================== */

    function handleMouseUp() {

        /*
         * 延迟一点,
         * 等浏览器完成 Selection。
         */
        setTimeout(
            function () {

                var data =
                    getSelectionData();


                if (!data) {

                    return;
                }


                selectedText =
                    data.text;


                showPopup(
                    data.range
                );

            },
            30
        );
    }


    /* =====================================================
       17. 点击其他地方
       ===================================================== */

    function handleDocumentMouseDown(
        event
    ) {

        if (!popup) {
            return;
        }


        /*
         * 点击 Popup 内部不关闭。
         */
        if (
            popup.contains(
                event.target
            )
        ) {

            return;
        }


        hidePopup();
    }


    /* =====================================================
       18. ESC
       ===================================================== */

    function handleKeyDown(
        event
    ) {

        if (
            event.key ===
            'Escape'
        ) {

            hidePopup();
        }
    }


    /* =====================================================
       19. 提示
       ===================================================== */

    function notify(
        message
    ) {

        if (
            typeof mw.notify ===
            'function'
        ) {

            mw.notify(
                message,
                {
                    type: 'info',
                    autoHide: true
                }
            );

        } else {

            console.log(
                message
            );
        }
    }


    /* =====================================================
       20. 初始化
       ===================================================== */

    function init() {

        if (!shouldEnable()) {

            return;
        }


        createPopup();


        document.addEventListener(
            'mouseup',
            handleMouseUp
        );


        document.addEventListener(
            'mousedown',
            handleDocumentMouseDown
        );


        document.addEventListener(
            'keydown',
            handleKeyDown
        );


        window.addEventListener(
            'scroll',
            hidePopup,
            {
                passive: true
            }
        );


        window.addEventListener(
            'resize',
            hidePopup
        );


        console.log(
            'Dhamma QuoteCopy: initialized'
        );
    }


    /* =====================================================
       21. 确保 MediaWiki API 已加载
       ===================================================== */

    mw.loader.using(
        [
            'mediawiki.api'
        ]
    ).then(
        function () {

            init();

        }
    );

})();