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

MediaWiki:Common.js:修订间差异

来自Dhamma.cn
法藏​(留言 | 贡献)
小无编辑摘要
法藏​(留言 | 贡献)
小无编辑摘要
 
(未显示同一用户的24个中间版本)
第1行: 第1行:
/**
/* =========================================================
* =========================================================
  Dhamma.cn 划词复制开示及 UrlShortener 短网址落款(移动端遮挡修复版)
* Dhamma.cn 阅读显示设置
  适用于:MediaWiki:Common.js
* MediaWiki 1.43
  ========================================================= */
*
* 功能:
*  - 背景主题
*  - 字号
*  - 行距
*  - 阅读宽度
*  - 字体
*  - localStorage 保存
* =========================================================
*/
 
(function () {
(function () {
    if (window.dhammaCopyLoaded) return;
    window.dhammaCopyLoaded = true;


     'use strict';
     function initDhammaCopy() {
 
         var cachedShortUrl = '';
 
    /* =====================================================
      1. 默认设置
      ===================================================== */
 
    var defaults = {
 
        theme: 'default',
 
        fontSize: 18,
 
        lineHeight: 1.9,
 
         maxWidth: 900,
 
        fontFamily: 'system'
 
    };
 
 
    /* =====================================================
      2. 从 localStorage 读取
      ===================================================== */
 
    var settings = {};
 
    try {
 
        var saved =
            localStorage.getItem('dhammaReaderSettings');
 
        if (saved) {
 
            settings =
                Object.assign(
                    {},
                    defaults,
                    JSON.parse(saved)
                );
 
        } else {
 
            settings =
                Object.assign({}, defaults);


        // 判断是否为移动端 / 触摸设备
        function isMobileDevice() {
            return ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (window.innerWidth <= 768);
         }
         }


    } catch (e) {
        // 1. 统一域名规范化:剥离 www 及所有二级域名(如 reg.dhamma.cn),强转为 https://dhamma.cn
 
        function forceMainDomain(url) {
        settings =
             if (!url) return 'https://dhamma.cn';
             Object.assign({}, defaults);
            if (url.indexOf('http') !== 0) {
 
                 return 'https://dhamma.cn' + (url.indexOf('/') === 0 ? '' : '/') + url;
    }
            }
 
             return url.replace(/^https?:\/\/[^\/]+/, 'https://dhamma.cn');
 
    /* =====================================================
      3. 保存设置
      ===================================================== */
 
    function saveSettings() {
 
        try {
 
            localStorage.setItem(
                 'dhammaReaderSettings',
                JSON.stringify(settings)
            );
 
        } catch (e) {
 
             // 浏览器禁用 localStorage 时忽略
         }
         }


    }
        // 2. 异步获取真正的当前页面短网址
        function getShortUrlAsync() {
            if (cachedShortUrl) return Promise.resolve(cachedShortUrl);


            // 仅在侧边栏/工具栏查找官方短网址,严禁扫描正文(#bodyContent)内的嵌入链接
            var tShort = document.querySelector('#t-shorturl a, #p-tb a[href*="/s/"], .mw-portlet-tb a[href*="/s/"], li[id*="shorturl"] a');
            if (tShort && tShort.href && tShort.href.indexOf('/s/') !== -1) {
                cachedShortUrl = forceMainDomain(tShort.href);
                return Promise.resolve(cachedShortUrl);
            }


    /* =====================================================
            // 获取页面规范网址
      4. 字体
            var canonicalEl = document.querySelector('link[rel="canonical"]');
      ===================================================== */
            var targetUrl = canonicalEl ? canonicalEl.href : window.location.href;


    var fonts = {
             // 优先使用 MediaWiki 原生 Api 模块(带 CSRF Token)
 
            if (window.mw && mw.Api) {
        system:
                return new mw.Api().post({
             '-apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", "Microsoft YaHei", Arial, sans-serif',
                    action: 'shortenurl',
 
                    url: targetUrl
        serif:
                }).then(function (data) {
            '"Noto Serif SC", "Source Han Serif SC", "Songti SC", "SimSun", serif',
                    if (data && data.shortenurl && data.shortenurl.shorturl) {
 
                        cachedShortUrl = forceMainDomain(data.shortenurl.shorturl);
        sans:
                    } else {
            '"Noto Sans SC", "Source Han Sans SC", "Microsoft YaHei", Arial, sans-serif'
                        cachedShortUrl = forceMainDomain(targetUrl);
 
                    }
    };
                    return cachedShortUrl;
 
                }).catch(function () {
 
                    return fallbackFetchApi(targetUrl);
    /* =====================================================
                 });
      5. 应用设置
             } else {
      ===================================================== */
                return fallbackFetchApi(targetUrl);
 
             }
    function applySettings() {
 
        var root =
            document.documentElement;
 
        var body =
            document.body;
 
 
        /* -----------------------------
          背景主题
          ----------------------------- */
 
        body.classList.remove(
            'reader-theme-default',
            'reader-theme-cream',
            'reader-theme-zen',
            'reader-theme-dark'
        );
 
        body.classList.add(
            'reader-theme-' + settings.theme
        );
 
 
        /* -----------------------------
          字号
          ----------------------------- */
 
        root.style.setProperty(
            '--reader-font-size',
            settings.fontSize + 'px'
        );
 
 
        /* -----------------------------
          行距
          ----------------------------- */
 
        root.style.setProperty(
            '--reader-line-height',
            settings.lineHeight
        );
 
 
        /* -----------------------------
          阅读宽度
          ----------------------------- */
 
        root.style.setProperty(
            '--reader-max-width',
            settings.maxWidth + 'px'
        );
 
 
        /* -----------------------------
          字体
          ----------------------------- */
 
        root.style.setProperty(
            '--reader-font-family',
            fonts[settings.fontFamily]
        );
 
    }
 
 
    /* =====================================================
      6. 创建设置按钮
      ===================================================== */
 
    function createSettingsButton() {
 
        if (
            document.getElementById(
                 'reader-settings-button'
             )
        ) {
             return;
         }
         }


        // 降级 Fetch 请求(带 CSRF Token)
        function fallbackFetchApi(targetUrl) {
            var scriptPath = (window.mw && mw.config && mw.config.get('wgScriptPath')) || '';
            var apiUrl = scriptPath + '/api.php';
            var token = (window.mw && mw.user && mw.user.tokens && mw.user.tokens.get('csrfToken')) || '+\\';


        var button =
            var params = new URLSearchParams();
             document.createElement('button');
             params.append('action', 'shortenurl');
 
            params.append('format', 'json');
        button.id =
            params.append('url', targetUrl);
             'reader-settings-button';
             params.append('token', token);


        button.type =
            return fetch(apiUrl, {
            'button';
                method: 'POST',
 
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        button.setAttribute(
                body: params.toString()
            'aria-label',
             })
            '阅读设置'
             .then(function (res) { return res.json(); })
        );
            .then(function (data) {
 
                 if (data && data.shortenurl && data.shortenurl.shorturl) {
        button.setAttribute(
                     cachedShortUrl = forceMainDomain(data.shortenurl.shorturl);
             'title',
                 } else {
            '阅读设置'
                     cachedShortUrl = forceMainDomain(targetUrl);
        );
 
        button.innerHTML =
             '⚙';
 
 
        document.body.appendChild(button);
 
 
        button.addEventListener(
            'click',
            function () {
 
                 var panel =
                     document.getElementById(
                        'reader-settings-panel'
                    );
 
                 if (!panel) {
                     return;
                 }
                 }
 
                 return cachedShortUrl;
                 panel.classList.toggle(
             })
                    'reader-settings-open'
            .catch(function () {
                );
                cachedShortUrl = forceMainDomain(targetUrl);
 
                 return cachedShortUrl;
             }
             });
        );
 
    }
 
 
    /* =====================================================
      7. 创建设置面板
      ===================================================== */
 
    function createSettingsPanel() {
 
        if (
            document.getElementById(
                 'reader-settings-panel'
             )
        ) {
            return;
         }
         }


        // 静默预取短网址
        getShortUrlAsync();


         var panel =
         // 3. 创建/获取浮窗节点
             document.createElement('div');
        function getPopover() {
 
            var pop = document.getElementById('dhamma-copy-popover');
        panel.id =
             if (!pop) {
            'reader-settings-panel';
                pop = document.createElement('div');
 
                pop.id = 'dhamma-copy-popover';
 
                pop.style.cssText = 'display:none; position:fixed; z-index:2147483647; background:#ffffff; border:1px solid rgba(0,0,0,0.15); border-radius:30px; box-shadow:0 8px 24px rgba(0,0,0,0.22); padding:6px 8px; font-size:14px; white-space:nowrap; transition:bottom 0.2s ease, opacity 0.2s ease;';
        panel.setAttribute(
                  
            'role',
                 var btn = document.createElement('button');
            'dialog'
                 btn.id = 'dhamma-copy-btn';
        );
                 btn.innerText = ' 复制该段开示(含出处)';
 
                 btn.style.cssText = 'background:#4d6b57; color:#ffffff; border:none; border-radius:20px; padding:8px 18px; font-size:14px; cursor:pointer; font-weight:600; font-family:sans-serif; -webkit-tap-highlight-color:transparent;';
        panel.setAttribute(
                  
            'aria-label',
                 btn.onmouseover = function () { if (this.innerText.indexOf('✅') === -1) this.style.background = '#3b5343'; };
            '阅读显示设置'
                 btn.onmouseout = function () { if (this.innerText.indexOf('✅') === -1) this.style.background = '#4d6b57'; };
        );
 
 
        panel.innerHTML = `
 
            <div class="reader-settings-title">
                阅读显示设置
            </div>
 
 
            <!-- =========================
                主题
                ========================= -->
 
            <div class="reader-setting-group">
 
                <span class="reader-setting-label">
                    背景
                </span>
 
                <div class="reader-theme-buttons">
 
                    <button
                        type="button"
                        class="reader-theme-button reader-theme-default-button"
                        data-theme="default">
                        默认
                    </button>
 
                    <button
                        type="button"
                        class="reader-theme-button reader-theme-cream-button"
                        data-theme="cream">
                        米白
                    </button>
 
                    <button
                        type="button"
                        class="reader-theme-button reader-theme-zen-button"
                        data-theme="zen">
                        禅意
                    </button>
 
                    <button
                        type="button"
                        class="reader-theme-button reader-theme-dark-button"
                        data-theme="dark">
                        夜间
                    </button>
 
                </div>
 
            </div>
 
 
            <!-- =========================
                字号
                ========================= -->
 
            <div class="reader-setting-group">
 
                <span class="reader-setting-label">
                    字号
                </span>
 
                <div class="reader-range-row">
 
                    <span>A−</span>
 
                    <input
                        id="reader-font-size"
                        class="reader-range"
                        type="range"
                        min="14"
                        max="26"
                        step="1">
 
                    <span>A+</span>
 
                    <span
                        id="reader-font-size-value"
                        class="reader-range-value">
                    </span>
 
                </div>
 
            </div>
 
 
            <!-- =========================
                行距
                ========================= -->
 
            <div class="reader-setting-group">
 
                <span class="reader-setting-label">
                    行距
                </span>
 
                <div class="reader-range-row">
 
                    <span>窄</span>
 
                    <input
                        id="reader-line-height"
                        class="reader-range"
                        type="range"
                        min="1.4"
                        max="2.4"
                        step="0.1">
 
                    <span>宽</span>
 
                    <span
                        id="reader-line-height-value"
                        class="reader-range-value">
                    </span>
 
                 </div>
 
            </div>
 
 
            <!-- =========================
                阅读宽度
                ========================= -->
 
            <div class="reader-setting-group">
 
                 <span class="reader-setting-label">
                    阅读宽度
                 </span>
 
                <div class="reader-range-row">
 
                    <span>窄</span>
 
                    <input
                        id="reader-max-width"
                        class="reader-range"
                        type="range"
                        min="680"
                        max="1200"
                        step="20">
 
                    <span>宽</span>
 
                    <span
                        id="reader-max-width-value"
                        class="reader-range-value">
                    </span>
 
                 </div>
 
            </div>
 
 
            <!-- =========================
                字体
                ========================= -->
 
            <div class="reader-setting-group">
 
                 <span class="reader-setting-label">
                    字体
                </span>
 
                <div class="reader-font-buttons">
 
                    <button
                        type="button"
                        class="reader-font-button"
                        data-font="system">
                        系统字体
                    </button>
 
                    <button
                        type="button"
                        class="reader-font-button"
                        data-font="serif">
                        宋体
                    </button>
 
                    <button
                        type="button"
                        class="reader-font-button"
                        data-font="sans">
                        黑体
                    </button>
 
                </div>
 
            </div>
 
 
            <!-- =========================
                恢复默认
                ========================= -->
 
            <button
                id="reader-settings-reset"
                 type="button">
 
                 恢复默认设置
 
            </button>
 
        `;
 
 
        document.body.appendChild(panel);
 
 
        bindSettingsEvents();
 
        updateSettingsUI();
 
    }
 
 
    /* =====================================================
      8. 绑定事件
      ===================================================== */
 
    function bindSettingsEvents() {
 
 
        /* -----------------------------
          主题
          ----------------------------- */
 
        document
            .querySelectorAll(
                '.reader-theme-button'
            )
            .forEach(function (button) {
 
                button.addEventListener(
                    'click',
                    function () {
 
                        settings.theme =
                            this.dataset.theme;
 
                        applySettings();
 
                        saveSettings();
 
                        updateSettingsUI();
 
                    }
                );
 
            });
 
 
        /* -----------------------------
          字号
          ----------------------------- */
 
        var fontSize =
            document.getElementById(
                 'reader-font-size'
            );
 
        fontSize.addEventListener(
            'input',
            function () {
 
                settings.fontSize =
                    parseInt(
                        this.value,
                        10
                    );
 
                applySettings();
 
                saveSettings();
 
                updateSettingsUI();


                pop.appendChild(btn);
                document.body.appendChild(pop);
             }
             }
         );
            return pop;
         }


        var currentText = '';


         /* -----------------------------
         // 4. 处理划词选择逻辑(响应式适配 PC / 移动端定位)
          行距
        function handleSelection() {
          ----------------------------- */
            var popover = getPopover();
 
             var btn = document.getElementById('dhamma-copy-btn');
        var lineHeight =
             var sel = window.getSelection();
             document.getElementById(
            var text = sel ? sel.toString().trim() : '';
                'reader-line-height'
            );
 
        lineHeight.addEventListener(
             'input',
            function () {
 
                settings.lineHeight =
                    parseFloat(
                        this.value
                    );
 
                applySettings();
 
                saveSettings();
 
                updateSettingsUI();


            if (!text || text.length < 2) {
                popover.style.display = 'none';
                return;
             }
             }
        );
        /* -----------------------------
          阅读宽度
          ----------------------------- */
        var maxWidth =
            document.getElementById(
                'reader-max-width'
            );


        maxWidth.addEventListener(
             currentText = text;
             'input',
            function () {


                 settings.maxWidth =
            if (sel.rangeCount > 0) {
                    parseInt(
                 var isMobile = isMobileDevice();
                        this.value,
                        10
                    );


                 applySettings();
                 if (isMobile) {
 
                    // 📱 移动端解决遮挡的核心方案:固定在屏幕底部中央,并兼容 iPhone 底部安全区
                saveSettings();
                    popover.style.top = 'auto';
 
                    popover.style.bottom = 'calc(25px + env(safe-area-inset-bottom, 0px))';
                updateSettingsUI();
                    popover.style.left = '50%';
 
                     popover.style.transform = 'translateX(-50%)';
            }
                    popover.style.display = 'block';
        );
                } else {
 
                    // 💻 PC 端:跟随选中区域在上方居中弹出
 
                    var range = sel.getRangeAt(0);
        /* -----------------------------
                    var rect = range.getBoundingClientRect();
          字体
          ----------------------------- */
 
        document
            .querySelectorAll(
                '.reader-font-button'
            )
            .forEach(function (button) {
 
                button.addEventListener(
                    'click',
                     function () {
 
                        settings.fontFamily =
                            this.dataset.font;
 
                        applySettings();
 
                        saveSettings();
 
                        updateSettingsUI();


                    if (rect.width === 0 || rect.height === 0) {
                        popover.style.display = 'none';
                        return;
                     }
                     }
                );
            });


                    var popWidth = popover.offsetWidth || 200;
                    var popHeight = popover.offsetHeight || 40;


        /* -----------------------------
                    var top = rect.top - popHeight - 10;
          恢复默认
                    var left = rect.left + (rect.width / 2) - (popWidth / 2);
          ----------------------------- */


        document
                    if (left < 10) left = 10;
            .getElementById(
                     if (top < 10) top = rect.bottom + 10;
                'reader-settings-reset'
            )
            .addEventListener(
                'click',
                function () {
 
                    settings =
                        Object.assign(
                            {},
                            defaults
                        );
 
                     applySettings();
 
                    saveSettings();
 
                    updateSettingsUI();


                    popover.style.bottom = 'auto';
                    popover.style.transform = 'none';
                    popover.style.top = top + 'px';
                    popover.style.left = left + 'px';
                    popover.style.display = 'block';
                 }
                 }
            );


    }
                btn.onclick = function (e) {
                    e.stopPropagation();
                    e.preventDefault();


                    getShortUrlAsync().then(function (shortUrl) {
                        var title = (window.mw && mw.config && mw.config.get('wgTitle')) || document.title;
                        var site = (window.mw && mw.config && mw.config.get('wgSiteName')) || 'Dhamma.cn';


    /* =====================================================
                        var output =  
      9. 更新界面
                            '“' + currentText + '”\n' +
      ===================================================== */
                            '---------------------\n' +
 
                            '来源:' + site + '\n' +
    function updateSettingsUI() {
                            '标题:' + title + '\n' +
 
                            '地址:' + shortUrl + '\n\n' +
 
                            '转载请保持原文,随喜分享,Sadhu!';
        /* -----------------------------
          主题按钮
          ----------------------------- */
 
        document
            .querySelectorAll(
                '.reader-theme-button'
            )
            .forEach(function (button) {
 
                button.classList.toggle(
                    'active',
                    button.dataset.theme ===
                    settings.theme
                );
 
            });
 
 
        /* -----------------------------
          字号
          ----------------------------- */
 
        var fontSize =
            document.getElementById(
                'reader-font-size'
            );
 
        var fontSizeValue =
            document.getElementById(
                'reader-font-size-value'
            );
 
        if (fontSize) {
 
            fontSize.value =
                settings.fontSize;
 
            fontSizeValue.textContent =
                settings.fontSize + 'px';


                        if (navigator.clipboard && navigator.clipboard.writeText) {
                            navigator.clipboard.writeText(output).then(function () {
                                showSuccess(btn, popover);
                            }).catch(function () {
                                fallbackCopy(output, btn, popover);
                            });
                        } else {
                            fallbackCopy(output, btn, popover);
                        }
                    });
                };
            }
         }
         }


 
         // 5. 复制成功反馈与降级机制
         /* -----------------------------
         function showSuccess(btn, popover) {
          行距
             btn.innerText = '✅ 已复制开示及出处';
          ----------------------------- */
             btn.style.background = '#2e7d32';
 
             setTimeout(function () {
         var lineHeight =
                btn.innerText = ' 复制该段开示(含出处)';
             document.getElementById(
                 btn.style.background = '#4d6b57';
                'reader-line-height'
                 popover.style.display = 'none';
            );
            }, 1200);
 
        var lineHeightValue =
             document.getElementById(
                'reader-line-height-value'
             );
 
        if (lineHeight) {
 
            lineHeight.value =
                 settings.lineHeight;
 
            lineHeightValue.textContent =
                 settings.lineHeight;
 
         }
         }


 
         function fallbackCopy(text, btn, popover) {
         /* -----------------------------
            var ta = document.createElement('textarea');
          阅读宽度
             ta.value = text;
          ----------------------------- */
            ta.style.position = 'fixed';
 
             ta.style.opacity = '0';
        var maxWidth =
             document.body.appendChild(ta);
            document.getElementById(
            ta.select();
                'reader-max-width'
             document.execCommand('copy');
             );
             document.body.removeChild(ta);
 
            showSuccess(btn, popover);
        var maxWidthValue =
             document.getElementById(
                'reader-max-width-value'
             );
 
        if (maxWidth) {
 
             maxWidth.value =
                settings.maxWidth;
 
             maxWidthValue.textContent =
                settings.maxWidth + 'px';
 
         }
         }


        document.addEventListener('mouseup', function () { setTimeout(handleSelection, 30); });
        document.addEventListener('touchend', function () { setTimeout(handleSelection, 50); });


        /* -----------------------------
         document.addEventListener('mousedown', function (e) {
          字体
             var pop = document.getElementById('dhamma-copy-popover');
          ----------------------------- */
            if (pop && !pop.contains(e.target)) {
 
                 pop.style.display = 'none';
         document
            .querySelectorAll(
                '.reader-font-button'
            )
            .forEach(function (button) {
 
                button.classList.toggle(
                    'active',
                    button.dataset.font ===
                    settings.fontFamily
                );
 
            });
 
    }
 
 
    /* =====================================================
      10. 点击页面其他地方关闭面板
      ===================================================== */
 
    function bindOutsideClick() {
 
        document.addEventListener(
            'click',
             function (event) {
 
                var panel =
                    document.getElementById(
                        'reader-settings-panel'
                    );
 
                var button =
                    document.getElementById(
                        'reader-settings-button'
                    );
 
                if (!panel || !button) {
                    return;
                }
 
 
                if (
                    panel.contains(event.target) ||
                    button.contains(event.target)
                ) {
                    return;
                }
 
 
                 panel.classList.remove(
                    'reader-settings-open'
                );
 
             }
             }
         );
         });
 
    }
 
 
    /* =====================================================
      11. 初始化
      ===================================================== */
 
    function initReaderSettings() {
 
        applySettings();
 
        createSettingsButton();
 
        createSettingsPanel();
 
        bindOutsideClick();
 
     }
     }


 
     if (document.readyState === 'loading') {
    /*
         document.addEventListener('DOMContentLoaded', initDhammaCopy);
    * MediaWiki 页面加载完成以后执行。
    */
/* =====================================================
  MediaWiki 初始化
  ===================================================== */
 
function startReaderSettings() {
 
     if (
        document.getElementById(
            'reader-settings-button'
        )
    ) {
        return;
    }
 
    initReaderSettings();
}
 
 
/*
* MediaWiki 页面加载完成后执行。
*
* 使用 mw.loader.using + jQuery ready,
* 避免 Common.js 加载时 DOMContentLoaded
* 已经执行完毕的问题。
*/
 
if (
    typeof mw !== 'undefined' &&
    mw.loader
) {
 
    mw.loader.using([], function () {
 
        $(startReaderSettings);
 
    });
 
} else {
 
    if (
        document.readyState === 'loading'
    ) {
 
         document.addEventListener(
            'DOMContentLoaded',
            startReaderSettings
        );
 
     } else {
     } else {
 
         initDhammaCopy();
         startReaderSettings();
 
    }
 
}
 
 
 
 
 
/* =========================================================
  Dhamma.cn 划词复制 - MediaWiki 1.43 + Timeless 终极兼容版
  ========================================================= */
 
mw.loader.using(['jquery', 'mediawiki.util']).then(function () {
    console.log('[Dhamma-Copy] 划词复制脚本已成功启动!');
 
    // 1. 动态注入浮窗(自带内联保底样式,防止 CSS 丢失或被覆盖)
    if ($('#dhamma-copy-popover').length === 0) {
        $('body').append(
            '<div id="dhamma-copy-popover" style="display:none; position:absolute; z-index:999999; background:#ffffff; border:1px solid rgba(0,0,0,0.15); border-radius:6px; box-shadow:0 4px 16px rgba(0,0,0,0.22); padding:6px; font-size:13px; line-height:1.4; white-space:nowrap;">' +
                '<button id="dhamma-copy-btn" style="background:#4d6b57; color:#ffffff; border:none; border-radius:4px; padding:6px 12px; cursor:pointer; font-weight:bold; display:inline-block;">📋 复制该段开示(带出处)</button>' +
            '</div>'
        );
    }
 
    var $popover = $('#dhamma-copy-popover');
    var currentSelectionText = '';
 
    // 2. 短网址获取函数
    function getShortUrl() {
        var shortlink = $('link[rel="shortlink"]').attr('href');
        if (shortlink) {
            return shortlink.indexOf('http') === 0 ? shortlink : window.location.origin + shortlink;
        }
        var sidebarShort = $('#t-shorturl a, #t-permalink a').attr('href');
        if (sidebarShort) {
            return sidebarShort.indexOf('http') === 0 ? sidebarShort : window.location.origin + sidebarShort;
        }
        if (typeof mw !== 'undefined' && mw.config && mw.config.get('wgShortUrl')) {
            return mw.config.get('wgShortUrl');
        }
        return window.location.href.split('#')[0].split('?')[0];
    }
 
    // 3. 全局监听松开鼠标/触摸事件
    $(document).on('mouseup touchend', function (e) {
        // 如果点击的是浮窗内部,不触发隐藏
        if ($(e.target).closest('#dhamma-copy-popover').length > 0) {
            return;
        }
 
        setTimeout(function () {
            var selection = window.getSelection();
            var text = selection ? selection.toString().trim() : '';
 
            // 未选中或文本少于2个字时隐藏
            if (!text || text.length < 2) {
                $popover.hide();
                return;
            }
 
            console.log('[Dhamma-Copy] 捕捉到选中文本:', text);
            currentSelectionText = text;
 
            // 定位计算
            if (selection.rangeCount > 0) {
                var range = selection.getRangeAt(0);
                var rect = range.getBoundingClientRect();
 
                if (rect.width === 0 && rect.height === 0) {
                    $popover.hide();
                    return;
                }
 
                var popWidth  = $popover.outerWidth() || 200;
                var popHeight = $popover.outerHeight() || 40;
 
                var scrollTop  = $(window).scrollTop();
                var scrollLeft = $(window).scrollLeft();
 
                var top  = rect.top + scrollTop - popHeight - 10;
                var left = rect.left + scrollLeft + (rect.width / 2) - (popWidth / 2);
 
                if (left < 10) left = 10;
                if (top < scrollTop + 10) {
                    top = rect.bottom + scrollTop + 10;
                }
 
                console.log('[Dhamma-Copy] 浮窗显示坐标 Top:', top, 'Left:', left);
 
                $popover.css({
                    top: top + 'px',
                    left: left + 'px'
                }).show();
            }
        }, 50);
    });
 
    // 4. 点击空白区域隐藏浮窗
    $(document).on('mousedown touchstart', function (e) {
        if (!$(e.target).closest('#dhamma-copy-popover').length) {
            $popover.hide();
        }
    });
 
    // 5. 点击复制逻辑
    $(document).on('click', '#dhamma-copy-btn', function (e) {
        e.stopPropagation();
        e.preventDefault();
 
        if (!currentSelectionText) return;
 
        var pageTitle = mw.config.get('wgTitle') || document.title;
        var siteName  = mw.config.get('wgSiteName') || 'Dhamma.cn';
        var shortUrl  = getShortUrl();
 
        var formattedOutput =
            '“' + currentSelectionText + '”\n\n' +
            '----------------------------------------\n' +
            '法谈摘自:' + siteName + ' 《' + pageTitle + '》\n' +
            '该篇网址:' + shortUrl + '\n' +
            '转载请保持原文,随喜分享,Sadhu!';
 
        if (navigator.clipboard && navigator.clipboard.writeText) {
            navigator.clipboard.writeText(formattedOutput).then(function () {
                showSuccessFeedback();
            }).catch(function () {
                fallbackCopy(formattedOutput);
            });
        } else {
            fallbackCopy(formattedOutput);
        }
    });
 
    function showSuccessFeedback() {
        var $btn = $('#dhamma-copy-btn');
        $btn.html('✅ 已复制开示及出处').css('background', '#2e7d32');
        setTimeout(function () {
            $btn.html('📋 复制该段开示(带出处)').css('background', '#4d6b57');
            $popover.hide();
        }, 1200);
    }
 
    function fallbackCopy(text) {
        var $temp = $('<textarea>');
        $('body').append($temp);
        $temp.val(text).select();
        document.execCommand('copy');
        $temp.remove();
        showSuccessFeedback();
     }
     }
});
})();

2026年9月20日 (日) 01:05的最新版本

/* =========================================================
   Dhamma.cn 划词复制开示及 UrlShortener 短网址落款(移动端遮挡修复版)
   适用于:MediaWiki:Common.js
   ========================================================= */
(function () {
    if (window.dhammaCopyLoaded) return;
    window.dhammaCopyLoaded = true;

    function initDhammaCopy() {
        var cachedShortUrl = '';

        // 判断是否为移动端 / 触摸设备
        function isMobileDevice() {
            return ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (window.innerWidth <= 768);
        }

        // 1. 统一域名规范化:剥离 www 及所有二级域名(如 reg.dhamma.cn),强转为 https://dhamma.cn
        function forceMainDomain(url) {
            if (!url) return 'https://dhamma.cn';
            if (url.indexOf('http') !== 0) {
                return 'https://dhamma.cn' + (url.indexOf('/') === 0 ? '' : '/') + url;
            }
            return url.replace(/^https?:\/\/[^\/]+/, 'https://dhamma.cn');
        }

        // 2. 异步获取真正的当前页面短网址
        function getShortUrlAsync() {
            if (cachedShortUrl) return Promise.resolve(cachedShortUrl);

            // 仅在侧边栏/工具栏查找官方短网址,严禁扫描正文(#bodyContent)内的嵌入链接
            var tShort = document.querySelector('#t-shorturl a, #p-tb a[href*="/s/"], .mw-portlet-tb a[href*="/s/"], li[id*="shorturl"] a');
            if (tShort && tShort.href && tShort.href.indexOf('/s/') !== -1) {
                cachedShortUrl = forceMainDomain(tShort.href);
                return Promise.resolve(cachedShortUrl);
            }

            // 获取页面规范网址
            var canonicalEl = document.querySelector('link[rel="canonical"]');
            var targetUrl = canonicalEl ? canonicalEl.href : window.location.href;

            // 优先使用 MediaWiki 原生 Api 模块(带 CSRF Token)
            if (window.mw && mw.Api) {
                return new mw.Api().post({
                    action: 'shortenurl',
                    url: targetUrl
                }).then(function (data) {
                    if (data && data.shortenurl && data.shortenurl.shorturl) {
                        cachedShortUrl = forceMainDomain(data.shortenurl.shorturl);
                    } else {
                        cachedShortUrl = forceMainDomain(targetUrl);
                    }
                    return cachedShortUrl;
                }).catch(function () {
                    return fallbackFetchApi(targetUrl);
                });
            } else {
                return fallbackFetchApi(targetUrl);
            }
        }

        // 降级 Fetch 请求(带 CSRF Token)
        function fallbackFetchApi(targetUrl) {
            var scriptPath = (window.mw && mw.config && mw.config.get('wgScriptPath')) || '';
            var apiUrl = scriptPath + '/api.php';
            var token = (window.mw && mw.user && mw.user.tokens && mw.user.tokens.get('csrfToken')) || '+\\';

            var params = new URLSearchParams();
            params.append('action', 'shortenurl');
            params.append('format', 'json');
            params.append('url', targetUrl);
            params.append('token', token);

            return fetch(apiUrl, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: params.toString()
            })
            .then(function (res) { return res.json(); })
            .then(function (data) {
                if (data && data.shortenurl && data.shortenurl.shorturl) {
                    cachedShortUrl = forceMainDomain(data.shortenurl.shorturl);
                } else {
                    cachedShortUrl = forceMainDomain(targetUrl);
                }
                return cachedShortUrl;
            })
            .catch(function () {
                cachedShortUrl = forceMainDomain(targetUrl);
                return cachedShortUrl;
            });
        }

        // 静默预取短网址
        getShortUrlAsync();

        // 3. 创建/获取浮窗节点
        function getPopover() {
            var pop = document.getElementById('dhamma-copy-popover');
            if (!pop) {
                pop = document.createElement('div');
                pop.id = 'dhamma-copy-popover';
                pop.style.cssText = 'display:none; position:fixed; z-index:2147483647; background:#ffffff; border:1px solid rgba(0,0,0,0.15); border-radius:30px; box-shadow:0 8px 24px rgba(0,0,0,0.22); padding:6px 8px; font-size:14px; white-space:nowrap; transition:bottom 0.2s ease, opacity 0.2s ease;';
                
                var btn = document.createElement('button');
                btn.id = 'dhamma-copy-btn';
                btn.innerText = ' 复制该段开示(含出处)';
                btn.style.cssText = 'background:#4d6b57; color:#ffffff; border:none; border-radius:20px; padding:8px 18px; font-size:14px; cursor:pointer; font-weight:600; font-family:sans-serif; -webkit-tap-highlight-color:transparent;';
                
                btn.onmouseover = function () { if (this.innerText.indexOf('✅') === -1) this.style.background = '#3b5343'; };
                btn.onmouseout = function () { if (this.innerText.indexOf('✅') === -1) this.style.background = '#4d6b57'; };

                pop.appendChild(btn);
                document.body.appendChild(pop);
            }
            return pop;
        }

        var currentText = '';

        // 4. 处理划词选择逻辑(响应式适配 PC / 移动端定位)
        function handleSelection() {
            var popover = getPopover();
            var btn = document.getElementById('dhamma-copy-btn');
            var sel = window.getSelection();
            var text = sel ? sel.toString().trim() : '';

            if (!text || text.length < 2) {
                popover.style.display = 'none';
                return;
            }

            currentText = text;

            if (sel.rangeCount > 0) {
                var isMobile = isMobileDevice();

                if (isMobile) {
                    // 📱 移动端解决遮挡的核心方案:固定在屏幕底部中央,并兼容 iPhone 底部安全区
                    popover.style.top = 'auto';
                    popover.style.bottom = 'calc(25px + env(safe-area-inset-bottom, 0px))';
                    popover.style.left = '50%';
                    popover.style.transform = 'translateX(-50%)';
                    popover.style.display = 'block';
                } else {
                    // 💻 PC 端:跟随选中区域在上方居中弹出
                    var range = sel.getRangeAt(0);
                    var rect = range.getBoundingClientRect();

                    if (rect.width === 0 || rect.height === 0) {
                        popover.style.display = 'none';
                        return;
                    }

                    var popWidth = popover.offsetWidth || 200;
                    var popHeight = popover.offsetHeight || 40;

                    var top = rect.top - popHeight - 10;
                    var left = rect.left + (rect.width / 2) - (popWidth / 2);

                    if (left < 10) left = 10;
                    if (top < 10) top = rect.bottom + 10;

                    popover.style.bottom = 'auto';
                    popover.style.transform = 'none';
                    popover.style.top = top + 'px';
                    popover.style.left = left + 'px';
                    popover.style.display = 'block';
                }

                btn.onclick = function (e) {
                    e.stopPropagation();
                    e.preventDefault();

                    getShortUrlAsync().then(function (shortUrl) {
                        var title = (window.mw && mw.config && mw.config.get('wgTitle')) || document.title;
                        var site = (window.mw && mw.config && mw.config.get('wgSiteName')) || 'Dhamma.cn';

                        var output = 
                            '“' + currentText + '”\n' +
                            '---------------------\n' +
                            '来源:' + site + '\n' +
                            '标题:' + title + '\n' +
                            '地址:' + shortUrl + '\n\n' +
                            '转载请保持原文,随喜分享,Sadhu!';

                        if (navigator.clipboard && navigator.clipboard.writeText) {
                            navigator.clipboard.writeText(output).then(function () {
                                showSuccess(btn, popover);
                            }).catch(function () {
                                fallbackCopy(output, btn, popover);
                            });
                        } else {
                            fallbackCopy(output, btn, popover);
                        }
                    });
                };
            }
        }

        // 5. 复制成功反馈与降级机制
        function showSuccess(btn, popover) {
            btn.innerText = '✅ 已复制开示及出处';
            btn.style.background = '#2e7d32';
            setTimeout(function () {
                btn.innerText = ' 复制该段开示(含出处)';
                btn.style.background = '#4d6b57';
                popover.style.display = 'none';
            }, 1200);
        }

        function fallbackCopy(text, btn, popover) {
            var ta = document.createElement('textarea');
            ta.value = text;
            ta.style.position = 'fixed';
            ta.style.opacity = '0';
            document.body.appendChild(ta);
            ta.select();
            document.execCommand('copy');
            document.body.removeChild(ta);
            showSuccess(btn, popover);
        }

        document.addEventListener('mouseup', function () { setTimeout(handleSelection, 30); });
        document.addEventListener('touchend', function () { setTimeout(handleSelection, 50); });

        document.addEventListener('mousedown', function (e) {
            var pop = document.getElementById('dhamma-copy-popover');
            if (pop && !pop.contains(e.target)) {
                pop.style.display = 'none';
            }
        });
    }

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