「MediaWiki:Common.js」:修訂間差異

出自Quickpedia wiki
無編輯摘要
無編輯摘要
 
(未顯示同一使用者於中間所作的 10 次修訂)
第1行: 第1行:
/**
/**
  * 所有用戶在加載任何頁面時,這裡的JavaScript都會加載
  * MediaWiki:Common.js 強化完整版
* 適用對象:Quickpedia wiki
* 功能:語系導向、手動字詞轉換、自動暫存(防 Cloudflare 斷線)、持久連線心跳包、深色模式、時鐘
  */
  */


mw.log.deprecate(window, 'JSConfig', {});
mw.loader.using(['mediawiki.util', 'mediawiki.Uri', 'mediawiki.api']).then(function () {


mw.loader.using(['ext.gadget.site-lib', 'mediawiki.util']).then(function() {
    $(function () {


        /* ============================================================
        * ① 語系自動導向與切換 (針對訪客預設 zh)
        * ============================================================ */
        var isAnon = mw.config.get('wgUserName') === null;
        var currentVariant = mw.util.getParamValue('variant');


    if (mw.config.get('wgAction') === 'edit' || mw.config.get('wgAction') === 'submit' || mw.config.get('wgCanonicalSpecialPageName') === 'Search') {
        // 訪客預設導向中文 (zh)
        /* scripts specific to editing pages */
        if (isAnon && !currentVariant && mw.config.get('wgAction') === 'view') {
        importScript('MediaWiki:Common.js\/edit.js');
            var url = new mw.Uri(window.location.href);
    } else {
            url.query.variant = 'zh';
        mw.loader.using('ext.visualEditor.desktopArticleTarget.init', function() {
             window.location.replace(url.toString());
             mw.libs.ve.addPlugin(function() {
             return;  
                importScript('MediaWiki:Common.js\/edit.js');
         }
             });
         });
    }


    /**
        // 建立右上角切換按鈕
    * Helper script for .hlist class in Common.css
        var variants = [
    * Add pseudo-selector class to last-child list items in IE8
            { code: 'zh', label: '中文' },
    * @source mediawiki.org/wiki/Snippets/Horizontal_lists
             { code: 'zh-tw', label: '繁體' },
    * @revision 6 (2014-08-23)
            { code: 'zh-cn', label: '简体' }
    * @author [[User:Edokter]]
         ];
    */
    var profile = $.client.profile();
    if (profile.name === 'msie' && profile.versionNumber === 8) {
        mw.hook('wikipage.content').add(function($content) {
             $content.find('.hlist').find('dd:last-child, dt:last-child, li:last-child').addClass('hlist-last-child');
         });
    }


    /* 避免在主條目的註腳中出現捲軸框 */
        var $langContainer = $('<span>').attr('id', 'custom-variant-switches');
    if (!mw.config.get('wgCanonicalNamespace')) {
         variants.forEach(function (v, i) {
         $(function() {
             var $link = $('<a>')
             $('div#mw-content-text ol.references').each(function() {
                 .text(v.label)
                 var needobjs=[], $curobj=$(this);
                 .css({
                 do {
                     cursor: 'pointer',
                    $curobj=$curobj.parent();
                     padding: '0 5px',
                     if (!$curobj) break;
                    color: currentVariant === v.code ? '#000' : '#0645ad',
                     if ($curobj.attr('id') === 'mw-content-text' || $curobj.prop('tagName').toLowerCase() === 'body') break;
                    fontWeight: currentVariant === v.code ? 'bold' : 'normal'
                    if ($curobj.css('overflow').match(/(?: auto|scroll)/i) || $curobj.css('overflow-x').match(/(?:auto|scroll)/i) || $curobj.css('overflow-y').match(/(?:auto|scroll)/i)) {
                })
                        /* null */
                .on('click', function (e) {
                     } else continue;
                     e.preventDefault();
                     if ((''+$curobj.attr('class')).split(' ').indexOf('noprint') >= 0) return;
                     var url = new mw.Uri(window.location.href);
                     needobjs.push($curobj.get(0));
                    url.query.variant = v.code;
                 } while (true);
                     window.location.href = url.toString();
                $(needobjs)
                 });
                    .css('overflow', 'visible')
            $langContainer.append($link);
                    .css('overflow-x', 'visible')
            if (i < variants.length - 1) $langContainer.append('|');
                    .css('overflow-y', 'visible')
                    .css('border', '')
                    .css('height', '')
                    .css('max-height', '');
            });
         });
         });
    }


    /**
        mw.util.addPortletLink('p-personal', '#', '', 'pt-variant-switch', '切換語系', '#pt-logout');
    * metaBox
        $('#pt-variant-switch').append($langContainer);
    * Funcionament de la Plantilla:Metacaixa
    * Implementat per: Usuari:Peleguer.
    * Actualitzat per Joanjoc seguint les indicacions d'en Martorell
    */
    function MetaCaixaInit() {
        // S'executa al carregar-se la pàgina, si hi ha metacaixes,
        // s'assignen els esdeveniments als botons
        //alert('MetaCaixaInit');
        var i = 0; // Inicialitzem comptador de caixes
        for (i = 0; i <= 9; i++) {
            var vMc = document.getElementById('mc' + i);
            if (!vMc) break;
            //alert('MetaCaixaInit, trobada Metacaixa mc' + i);
            var j = 1, // Inicialitzem comptador de botons dins de la caixa
                vPsIni = 0; // Pestanya visible inicial
            for (j = 1; j <= 9; j++) {
                var vBt = document.getElementById('mc' + i + 'bt' + j);
                if (!vBt) break;
                //alert('MetaCaixaInit, trobat botó mc' + i + 'bt' + j);
                vBt.onclick = MetaCaixaMostraPestanya; // A cada botó assignem l'esdeveniment onclick
                //alert(vBt.className);
                if (vBt.className === 'mcBotoSel') vPsIni = j; // Si tenim un botó seleccionat, en guardem l'index
            }
            //alert('mc=' + i + ', ps=' + j + ', psini=' + vPsIni);
            if (vPsIni === 0) { // Si no tenim cap botó seleccionat, n'agafem un aleatòriament
                vPsIni = 1 + Math.floor((j - 1) * Math.random());
                //alert('Activant Pestanya a l\'atzar; _mc' + i + 'bt' + vPsIni + '_');
                try {
                document.getElementById('mc' + i + 'ps' + vPsIni).style.display = 'block';
            document.getElementById('mc' + i + 'ps' + vPsIni).style.visibility = 'visible';
            document.getElementById('mc' + i + 'bt' + vPsIni).className = 'mcBotoSel';
                } catch(e) {
                // TypeError: null is not an object (evaluating 'document.getElementById('mc'+i+'ps'+vPsIni).style')
                }
            }
        }
    }


    function MetaCaixaMostraPestanya() {
        // S'executa al clicar una pestanya,
        // aquella es fa visible i les altres s'oculten
        var vMcNom = this.id.substr(0, 3), // A partir del nom del botó, deduïm el nom de la caixa
            vIndex = this.id.substr(5, 1), // I l'index
            i = 1;
        for (i = 1; i <= 9; i++) { // busquem totes les pestanyes d'aquella caixa
            //alert(vMcNom + 'ps' + i);
            var vPsElem = document.getElementById(vMcNom + 'ps' + i);
            if (!vPsElem) break;
            if (vIndex == i) { // Si és la pestanya bona la mostrem i canviem la classe de botó
                vPsElem.style.display = 'block';
                vPsElem.style.visibility = 'visible';
                document.getElementById(vMcNom + 'bt' + i).className = 'mcBotoSel';
            } else { // Sinó, l'ocultem i canviem la classe de botó
                vPsElem.style.display = 'none';
                vPsElem.style.visibility = 'hidden';
                document.getElementById(vMcNom + 'bt' + i).className = 'mcBoto';
            }
        }
        return false; // evitem la recàrrega de la pàgina
    }
    $(MetaCaixaInit);


    if (!+mw.user.options.get('discussiontools-newtopictool') || !+mw.user.options.get('discussiontools-betaenable')) {
        /* ============================================================
        /* 智能讨论页编辑(新建) */
        * ② 手動字詞轉換 (確保站名正確顯示)
         $(function() {
        * ============================================================ */
             var catalk = $('#ca-talk');
         function applyManualTranslation() {
            if (catalk.hasClass('new') && mw.config.get('wgNamespaceNumber') != 2) {
             var v = mw.util.getParamValue('variant') || mw.config.get('wgUserVariant') || mw.config.get('wgUserLanguage');
                 var a = $('a:first', catalk);
            if (v === 'zh-hans' || v === 'zh-cn') {
                a.attr('href', a.attr('href') + '&section=new');
                 $('h1#firstHeading, #p-logo-text, .mw-wiki-title, title').each(function() {
                    var t = $(this).text();
                    $(this).text(t.replace(/Quickpedia wiki/g, 'Quickpedia wiki'));
                });
             }
             }
        });
    }
    /**
    * Magic editintros
    * Description: Adds editintros on disambiguation pages, BLP pages, policy pages and guidlines.
    * Maintainers: [[User:RockMFR]]
    */
    function addEditIntro(name) {
        $('.mw-editsection, #ca-edit').find('a').each(function(i, el) {
            el.href = $(this).attr('href') + '&editintro=' + name;
        });
    }
    if (mw.config.get('wgNamespaceNumber') === 0) {
        $(function() {
            if (document.getElementById('disambigbox')) addEditIntro('Template:Disambig_editintro');
        });
        $(function() {
            var cats = mw.config.get('wgCategories');
            if (!cats) return;
            if ($.inArray('在世人物', cats) !== -1) addEditIntro('Template:BLP_editintro');
            if (cats.some(function(cat){return /\d{4}年台灣電視劇集/.test(cat)})) addEditIntro('Template:TVdrama_editintro');
        });
    } else if (mw.config.get('wgNamespaceNumber') === 4) {
        $(function() {
            var cats = mw.config.get('wgCategories');
            if (!cats) return;
            if ($.inArray('維基百科方針與指引完整列表', cats) !== -1) addEditIntro('Template:Policy editintro');
        });
    }
    /**
    * &withCSS= and &withJS= URL parameters
    * Allow to try custom scripts from MediaWiki space
    * without editing personal .css or .js files
    * @source www.mediawiki.org/wiki/Snippets/Load_JS_and_CSS_by_URL
    * @rev 6
    */
    var extraCSS = mw.util.getParamValue('withCSS'),
        extraJS = mw.util.getParamValue('withJS');
    if (extraCSS) {
        if (extraCSS.match(/^MediaWiki:[^&<>=%#]*\.css$/)) {
            importStylesheet(extraCSS);
        } else {
            mw.notify('只允许从MediaWiki命名空间加载。', {title: '无效的withCSS值'});
         }
         }
    }
         applyManualTranslation();
    if (extraJS) {
         if (extraJS.match(/^MediaWiki:[^&<>=%#]*\.js$/)) {
            importScript(extraJS);
        } else {
            mw.notify('只允许从MediaWiki命名空间加载。', {title: '无效的withJS值'});
        }
    }


    /* 页面历史加&hilight=高亮 */
    var hilight = mw.util.getParamValue('hilight');
    if (mw.config.get('wgAction') === 'history' && hilight) {
        $.each(hilight.split(','), function(_, v) {
            $('input[name=oldid][value=' + v + ']').parent().addClass('not-patrolled');
        });
    }


    /* Main page hacks */
        /* ============================================================
    if (mw.config.get('wgIsMainPage') && mw.config.get('wgAction') === 'view') {
        * ③ 自動暫存與持久編輯 (對抗 Cloudflare 驗證中斷)
        /* Remove red links */
        * ============================================================ */
        $('#mw-content-text a.new').contents().unwrap();
        if (['edit', 'submit'].indexOf(mw.config.get('wgAction')) !== -1) {
    }
            var pageName = mw.config.get('wgPageName');
});
            var storageKey = 'edit_cache_' + pageName;
            var $textbox = $('#wpTextbox1');


$(function() {
            // A. 心跳包:每 2 分鐘向伺服器發送請求,防止 Session 過期或驗證跳出
    /* 修正摺疊後定位變化 */
            setInterval(function() {
    if (location.hash) location.href = location.hash;
                new mw.Api().get({
                    action: 'query',
                    titles: pageName,
                    prop: 'info'
                }).done(function() {
                    console.log('Quickpedia wiki 連線維持中...');
                });
            }, 120000);


    /* 引用錯誤標籤名字解碼 */
            // B. 高頻率自動暫存:每 20 秒存一次草稿
    $('.anchordecodeme').each(function() {
            setInterval(function () {
        $(this).text(decodeURIComponent($(this).text().replace(/\.([0-9A-F]{2})/g, '%$1')));
                var content = $textbox.val();
    });
                if (content && content.length > 10) {
                    localStorage.setItem(storageKey, JSON.stringify({
                        text: content,
                        timestamp: Date.now()
                    }));
                }
            }, 20000);  


    /* Check for any client-side simplified/traditional Chinese conversion */
            // C. 檢查並還原草稿
    /* This routine must be placed here to make sure the field is inserted in time */
            var savedCache = localStorage.getItem(storageKey);
    $('#antispam-container').append(
            if (savedCache) {
        $('<input type="text" />').attr({
                var data = JSON.parse(savedCache);
            id: 'wpAntiConv',
                if (Date.now() - data.timestamp < 86400000 && $textbox.val().length < 50) {
            value: '\u6c49\u6f22'
                    if (confirm('偵測到未儲存草稿(可能因 Cloudflare 驗證中斷),是否還原?')) {
        })
                        $textbox.val(data.text);
    );
                    }
                }
            }


});
            // D. 離開頁面警告
            window.onbeforeunload = function() {
                if ($textbox.val().length > 50) return "內容尚未儲存!";
            };


// per [[Special:Diff/64919534/64925950]],展开折叠按钮的颜色
            // E. 儲存後清除暫存
$(function collapseButtonColor() {
            $('#wpSave, #wpSaveWidget, input#wpSave').on('click', function() {
    var $toggle = $('.mw-collapsible-toggle');
                localStorage.removeItem(storageKey);
    if ($toggle.length > 0) {
                window.onbeforeunload = null;  
        if ($toggle.parent()[0].style.color) $toggle.find('a').css('color', 'inherit');
            });
    }
        }
});


if (mw.config.get('wgUserName') === null) {
mw.loader.load('ext.gadget.preserve-variant');
}
(function() {
    function showClock() {
        var now = new Date();
        return now.toLocaleTimeString('zh-TW', { hour12: false });
    }


    function insertClock() {
        /* ============================================================
        if (document.getElementById('mw-header-clock')) return;
        * ④ 深色模式
        * ============================================================ */
        var darkBtn = mw.util.addPortletLink('p-personal', '#', '深色模式', 'pt-darkmode', '切換深色模式', '#pt-variant-switch');
        $(darkBtn).on('click', function (e) {
            e.preventDefault();
            $('body').toggleClass('dark-mode');
            localStorage.setItem('wiki-dark-mode', $('body').hasClass('dark-mode') ? '1' : '0');
        });


         var header = document.querySelector('#mw-panel + #mw-head .vector-user-links')  
         if (localStorage.getItem('wiki-dark-mode') === '1') {
                    || document.querySelector('#p-personal'); // 找 Vector 右上用戶選單
            $('body').addClass('dark-mode');
         if (!header) return;
         }


        var span = document.createElement('span');
        span.id = 'mw-header-clock';
        span.style.fontFamily = 'monospace';
        span.style.fontWeight = 'bold';
        span.style.marginLeft = '0.5em';
        span.style.color = '#000';
        span.innerText = showClock();


         header.appendChild(span);
         /* ============================================================
        * ⑤ 即時時鐘
        * ============================================================ */
        var $clock = $('<li>').attr('id', 'pt-clock').css({
            fontWeight: 'bold',
            marginLeft: '10px',
            color: '#54595d'
        });


         // 每秒更新
         function updateClock() {
        setInterval(function() {
             var now = new Date();
             span.innerText = showClock();
            $clock.text(now.toLocaleTimeString('zh-TW', { hour12: false }));
        }, 1000);
         }
    }
 
    mw.loader.using('mediawiki.util', function() {
        // 等待頁面元素加載
        document.addEventListener('DOMContentLoaded', insertClock);
        setTimeout(insertClock, 500); // 再保險一次
    });
})();
/**
* 在右上角個人選單中增加「深色模式」切換按鈕
* 插入位置:偏好設定 (pt-preferences) 之後
*/
mw.loader.using('mediawiki.util').then(function() {
    $(function() {
         // 1. 建立選單項目
        var portletLink = mw.util.addPortletLink(
            'p-personal',        // 目標:個人選單
            '#',                  // 連結路徑
            '切換深色模式',        // 顯示文字
            'pt-darkmode',        // 項目 ID
            '開啟或關閉深色模式',    // 懸停提示
            '',                  // 快捷鍵
            '#pt-watchlist'      // 插入在「監視清單」之前 = 「偏好設定」之後
        );


        // 2. 定義點擊後的動作
         $('#p-personal ul').append($clock);
         $(portletLink).on('click', function(e) {
        setInterval(updateClock, 1000);
            e.preventDefault();
         updateClock();
           
            // 這裡假設您是透過切換 <body> 的 class 來實現深色模式
            // 如果您是使用特定的 Darkmode 擴展,請將下方邏輯換成該擴展的 API
            $('body').toggleClass('dark-mode-active');
           
            // 存儲狀態到本地,讓重新整理後依然有效
            var isDark = $('body').hasClass('dark-mode-active');
            localStorage.setItem('user-dark-mode', isDark ? 'on' : 'off');
         });


        // 3. 頁面加載時檢查之前的設定
        if (localStorage.getItem('user-dark-mode') === 'on') {
            $('body').addClass('dark-mode-active');
        }
     });
     });
});
});

於 2026年9月9日 (三) 03:44 的最新修訂

/**
 * MediaWiki:Common.js 強化完整版
 * 適用對象:Quickpedia wiki
 * 功能:語系導向、手動字詞轉換、自動暫存(防 Cloudflare 斷線)、持久連線心跳包、深色模式、時鐘
 */

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

    $(function () {

        /* ============================================================
         * ① 語系自動導向與切換 (針對訪客預設 zh)
         * ============================================================ */
        var isAnon = mw.config.get('wgUserName') === null;
        var currentVariant = mw.util.getParamValue('variant');

        // 訪客預設導向中文 (zh)
        if (isAnon && !currentVariant && mw.config.get('wgAction') === 'view') {
            var url = new mw.Uri(window.location.href);
            url.query.variant = 'zh';
            window.location.replace(url.toString());
            return; 
        }

        // 建立右上角切換按鈕
        var variants = [
            { code: 'zh', label: '中文' },
            { code: 'zh-tw', label: '繁體' },
            { code: 'zh-cn', label: '简体' }
        ];

        var $langContainer = $('<span>').attr('id', 'custom-variant-switches');
        variants.forEach(function (v, i) {
            var $link = $('<a>')
                .text(v.label)
                .css({
                    cursor: 'pointer',
                    padding: '0 5px',
                    color: currentVariant === v.code ? '#000' : '#0645ad',
                    fontWeight: currentVariant === v.code ? 'bold' : 'normal'
                })
                .on('click', function (e) {
                    e.preventDefault();
                    var url = new mw.Uri(window.location.href);
                    url.query.variant = v.code;
                    window.location.href = url.toString();
                });
            $langContainer.append($link);
            if (i < variants.length - 1) $langContainer.append('|');
        });

        mw.util.addPortletLink('p-personal', '#', '', 'pt-variant-switch', '切換語系', '#pt-logout');
        $('#pt-variant-switch').append($langContainer);


        /* ============================================================
         * ② 手動字詞轉換 (確保站名正確顯示)
         * ============================================================ */
        function applyManualTranslation() {
            var v = mw.util.getParamValue('variant') || mw.config.get('wgUserVariant') || mw.config.get('wgUserLanguage');
            if (v === 'zh-hans' || v === 'zh-cn') {
                $('h1#firstHeading, #p-logo-text, .mw-wiki-title, title').each(function() {
                    var t = $(this).text();
                    $(this).text(t.replace(/Quickpedia wiki/g, 'Quickpedia wiki'));
                });
            }
        }
        applyManualTranslation();


        /* ============================================================
         * ③ 自動暫存與持久編輯 (對抗 Cloudflare 驗證中斷)
         * ============================================================ */
        if (['edit', 'submit'].indexOf(mw.config.get('wgAction')) !== -1) {
            var pageName = mw.config.get('wgPageName');
            var storageKey = 'edit_cache_' + pageName;
            var $textbox = $('#wpTextbox1');

            // A. 心跳包:每 2 分鐘向伺服器發送請求,防止 Session 過期或驗證跳出
            setInterval(function() {
                new mw.Api().get({
                    action: 'query',
                    titles: pageName,
                    prop: 'info'
                }).done(function() {
                    console.log('Quickpedia wiki 連線維持中...');
                });
            }, 120000);

            // B. 高頻率自動暫存:每 20 秒存一次草稿
            setInterval(function () {
                var content = $textbox.val();
                if (content && content.length > 10) {
                    localStorage.setItem(storageKey, JSON.stringify({
                        text: content,
                        timestamp: Date.now()
                    }));
                }
            }, 20000); 

            // C. 檢查並還原草稿
            var savedCache = localStorage.getItem(storageKey);
            if (savedCache) {
                var data = JSON.parse(savedCache);
                if (Date.now() - data.timestamp < 86400000 && $textbox.val().length < 50) {
                    if (confirm('偵測到未儲存草稿(可能因 Cloudflare 驗證中斷),是否還原?')) {
                        $textbox.val(data.text);
                    }
                }
            }

            // D. 離開頁面警告
            window.onbeforeunload = function() {
                if ($textbox.val().length > 50) return "內容尚未儲存!";
            };

            // E. 儲存後清除暫存
            $('#wpSave, #wpSaveWidget, input#wpSave').on('click', function() {
                localStorage.removeItem(storageKey);
                window.onbeforeunload = null; 
            });
        }


        /* ============================================================
         * ④ 深色模式
         * ============================================================ */
        var darkBtn = mw.util.addPortletLink('p-personal', '#', '深色模式', 'pt-darkmode', '切換深色模式', '#pt-variant-switch');
        $(darkBtn).on('click', function (e) {
            e.preventDefault();
            $('body').toggleClass('dark-mode');
            localStorage.setItem('wiki-dark-mode', $('body').hasClass('dark-mode') ? '1' : '0');
        });

        if (localStorage.getItem('wiki-dark-mode') === '1') {
            $('body').addClass('dark-mode');
        }


        /* ============================================================
         * ⑤ 即時時鐘
         * ============================================================ */
        var $clock = $('<li>').attr('id', 'pt-clock').css({
            fontWeight: 'bold',
            marginLeft: '10px',
            color: '#54595d'
        });

        function updateClock() {
            var now = new Date();
            $clock.text(now.toLocaleTimeString('zh-TW', { hour12: false }));
        }

        $('#p-personal ul').append($clock);
        setInterval(updateClock, 1000);
        updateClock();

    });
});