跳转到内容
打开/关闭菜单
  • 451 条目
  • 806 文件
  • 22 用户
  • 9167 编辑
Blockland Wiki
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

MediaWiki:Common.js

MediaWiki界面页面
ETTW​(留言 | 贡献)2026年4月18日 (六) 15:52的版本

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

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5或Ctrl-R(Mac为⌘-R)
  • Google Chrome:按Ctrl-Shift-R(Mac为⌘-Shift-R)
  • Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5。
/* 这里的任何JavaScript将为所有用户在每次页面加载时加载。 */

// 侧边栏广告
$(function() {
    if ($('#p-navigation').length) {
        var adHtml = '<div id="sidebar-ad"></div>';
        $('#p-navigation').after(adHtml);
        $.get(mw.util.wikiScript('api'), {
            action: 'parse',
            text: '{{CitizenAd}}',
            format: 'json'
        }, function(data) {
            $('#sidebar-ad').html(data.parse.text['*']);
        });
    }
});

/**
 * Blockland Wiki Liquid Glass Lighting System
 */
(function() {
    'use strict';
    
    var isPerformanceMode = function() {
        return document.documentElement.classList.contains('citizen-feature-performance-mode-clientpref-1');
    };

    var GLASS_SELECTORS = '#mw-panel-toc, #citizen-sidebar-lastmod, .aero-news-box, .citizen-header, .citizen-menu__card, .citizen-toc-card, .citizen-command-palette, .infobox, table.ambox, .mwe-popups-container, .oo-ui-popupWidget-popup, .oo-ui-menuSelectWidget, .voy-box';

    var lastMouseX = 0;
    var lastMouseY = 0;

    var syncLight = function(e) {
        if (isPerformanceMode()) return;

        lastMouseX = e.clientX;
        lastMouseY = e.clientY;

        var elements = document.querySelectorAll(GLASS_SELECTORS);
        elements.forEach(function(el) {
            var rect = el.getBoundingClientRect();
            
            if (rect.width > 0 && rect.top < window.innerHeight && rect.bottom > 0) {
                var x = ((e.clientX - rect.left) / rect.width) * 100;
                var y = ((e.clientY - rect.top) / rect.height) * 100;
                
                el.style.setProperty('--mouse-x', x.toFixed(2) + '%');
                el.style.setProperty('--mouse-y', y.toFixed(2) + '%');
            }
        });
    };

    var initPopupLight = function(container) {
        if (isPerformanceMode()) return;
        
        var rect = container.getBoundingClientRect();
        if (rect.width > 0) {
            var x = ((lastMouseX - rect.left) / rect.width) * 100;
            var y = ((lastMouseY - rect.top) / rect.height) * 100;
            container.style.setProperty('--mouse-x', x.toFixed(2) + '%');
            container.style.setProperty('--mouse-y', y.toFixed(2) + '%');
        }
    };

    setInterval(function() {
        if (isPerformanceMode()) return; 
        var p = document.querySelector('.citizen-command-palette');
        if (p && !p.classList.contains('glass-show')) {
            p.classList.add('glass-show');
        }
    }, 100);

    var ticking = false;
    document.addEventListener('mousemove', function(e) {
        if (isPerformanceMode()) return;
        if (!ticking) {
            window.requestAnimationFrame(function() {
                syncLight(e);
                ticking = false;
            });
            ticking = true;
        }
    }, { passive: true });

    var modeObserver = new MutationObserver(function() {
        if (isPerformanceMode()) {
            document.querySelectorAll(GLASS_SELECTORS).forEach(function(el) {
                el.style.removeProperty('--mouse-x');
                el.style.removeProperty('--mouse-y');
            });
        }
    });
    modeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });

    window._liquidGlassInitPopup = initPopupLight;
})();

/**
 * Section25. Wiki 弹出预览菜单动画控制
 */
(function() {
    'use strict';
    
    var style = document.createElement('style');
    style.textContent = `
        .mwe-popups .mwe-popups-container { 
            opacity: 0 !important; 
            transform: translateY(-12px) !important;
            transition: opacity 0.35s cubic-bezier(0.16, 1, 0.3, 1), 
                        transform 0.35s cubic-bezier(0.16, 1, 0.3, 1) !important;
        } 
        .mwe-popups.popup-show .mwe-popups-container { 
            opacity: 1 !important; 
            transform: translateY(0) !important;
        }
        .mwe-popups.flipped-y .mwe-popups-container {
            transform: translateY(12px) !important;
        }
        .mwe-popups.flipped-y.popup-show .mwe-popups-container {
            transform: translateY(0) !important;
        }
    `;
    document.head.appendChild(style);
    
    var seen = new WeakSet();
    
    new MutationObserver(function(muts) {
        muts.forEach(function(m) {
            m.addedNodes.forEach(function(node) {
                if (node.classList && node.classList.contains('mwe-popups') && !seen.has(node)) {
                    seen.add(node);
                    
                    var container = node.querySelector('.mwe-popups-container');
                    if (!container) return;
                    
                    if (window._liquidGlassInitPopup) {
                        window._liquidGlassInitPopup(container);
                    }
                    
                    node.style.pointerEvents = 'none';
                    container.offsetHeight;
                    
                    requestAnimationFrame(function() {
                        requestAnimationFrame(function() {
                            node.classList.add('popup-show');
                            setTimeout(function() {
                                node.style.pointerEvents = '';
                            }, 350);
                        });
                    });
                }
            });
        });
    }).observe(document.body, { childList: true, subtree: true });
})();
/**
 * Section19. 通知面板动画控制
 * 检测到面板后再开始动画
 */
(function() {
    'use strict';
    
    function handlePopup(popup) {
        if (popup.dataset.animatorInited) return;
        popup.dataset.animatorInited = 'true';

        const observer = new MutationObserver(() => {
            const inner = popup.querySelector('.oo-ui-popupWidget-popup');
            if (!inner) return;

            const isVisible = popup.style.display !== 'none' && popup.offsetParent !== null;
            
if (isVisible) {
    if (inner.getAttribute('data-is-active') !== 'true') {
        inner.setAttribute('data-is-active', 'true');

        // 1. 初始化:置于下方并透明
        inner.style.setProperty('transition', 'none', 'important');
        inner.style.setProperty('opacity', '0', 'important');
        inner.style.setProperty('transform', 'translateY(20px)', 'important');

        inner.offsetHeight; // 触发重绘

        requestAnimationFrame(() => {
            setTimeout(() => {
                // 2. 向上滑动并渐显
                inner.style.setProperty('transition', 'opacity 0.3s ease-out, transform 0.4s cubic-bezier(0.23, 1, 0.32, 1)', 'important');
                inner.style.setProperty('opacity', '1', 'important');
                inner.style.setProperty('transform', 'translateY(0)', 'important');
            }, 10);
        });
    }
            } else {
                // 关闭时直接重置,不加动画,确保下次打开干净利落
                if (inner.getAttribute('data-is-active') === 'true') {
                    inner.removeAttribute('data-is-active');
                    inner.style.removeProperty('opacity');
                    inner.style.removeProperty('transform');
                    inner.style.removeProperty('transition');
                    inner.style.removeProperty('transform-origin');
                }
            }
        });

        observer.observe(popup, { attributes: true, attributeFilter: ['style', 'class'] });
    }

    const scanner = new MutationObserver(() => {
        document.querySelectorAll('.mw-echo-ui-notificationBadgeButtonPopupWidget-popup').forEach(handlePopup);
    });
    scanner.observe(document.body, { childList: true, subtree: true });
    document.querySelectorAll('.mw-echo-ui-notificationBadgeButtonPopupWidget-popup').forEach(handlePopup);
})();

// 添加液态玻璃按钮
(function() {
    'use strict';
    
    function addLiquidGlassOption() {
        const form = document.querySelector('#skin-client-prefs-citizen-feature-performance-mode form');
        
        if (!form) {
            setTimeout(addLiquidGlassOption, 100);
            return;
        }
        
        // 检查是否已经添加过
        if (document.getElementById('skin-client-pref-citizen-feature-performance-mode-value-3')) {
            return;
        }
        
        // 创建Liquid选项(状态3)
        const liquidOption = document.createElement('div');
        liquidOption.className = 'citizen-client-prefs-radio';
        liquidOption.innerHTML = `
            <input name="skin-client-pref-citizen-feature-performance-mode-group" 
                   id="skin-client-pref-citizen-feature-performance-mode-value-3" 
                   type="radio" 
                   value="3" 
                   data-event-name="skin-client-pref-citizen-feature-performance-mode-value-3" 
                   class="citizen-client-prefs-radio__input">
            <span class="citizen-client-prefs-radio__icon"></span>
            <label for="skin-client-pref-citizen-feature-performance-mode-value-3" 
                   class="citizen-client-prefs-radio__label">Liquid</label>
        `;
        
        // 创建抽象玻璃选项(状态2)
        const abstractGlassOption = document.createElement('div');
        abstractGlassOption.className = 'citizen-client-prefs-radio';
        abstractGlassOption.innerHTML = `
            <input name="skin-client-pref-citizen-feature-performance-mode-group" 
                   id="skin-client-pref-citizen-feature-performance-mode-value-2" 
                   type="radio" 
                   value="2" 
                   data-event-name="skin-client-pref-citizen-feature-performance-mode-value-2" 
                   class="citizen-client-prefs-radio__input">
            <span class="citizen-client-prefs-radio__icon"></span>
            <label for="skin-client-pref-citizen-feature-performance-mode-value-2" 
                   class="citizen-client-prefs-radio__label">抽象玻璃</label>
        `;
        
        // 按顺序添加:状态3在前,状态2在后
        form.appendChild(liquidOption);
        form.appendChild(abstractGlassOption);
        
        console.log('液态玻璃按钮已添加');
        
        // 绑定所有单选按钮的事件
        const allRadios = document.querySelectorAll('input[name="skin-client-pref-citizen-feature-performance-mode-group"]');
        allRadios.forEach(radio => {
            radio.addEventListener('change', function() {
                const value = this.value;
                console.log('选中值:', value);
                localStorage.setItem('citizen-feature-performance-mode-clientpref-', value);
                
                // 更新 HTML class
                const htmlElement = document.documentElement;
                htmlElement.className = htmlElement.className.replace(/citizen-feature-performance-mode-clientpref-\d/g, '');
                htmlElement.classList.add('citizen-feature-performance-mode-clientpref-' + value);
                
                // 状态3时初始化液态玻璃
                if (value === '3') {
                    initLiquidGlassEffect();
                } else {
                    destroyLiquidGlassEffect();
                }
            });
        });
    }
    
    // 页面加载时执行
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', addLiquidGlassOption);
    } else {
        addLiquidGlassOption();
    }
    
})();

// 液态玻璃效果
function initLiquidGlassEffect() {
    if (window.liquidGlassEffect) {
        return; // 已经初始化
    }
    
    // Utility functions
    function smoothStep(a, b, t) {
        t = Math.max(0, Math.min(1, (t - a) / (b - a)));
        return t * t * (3 - 2 * t);
    }

    function length(x, y) {
        return Math.sqrt(x * x + y * y);
    }

    function roundedRectSDF(x, y, width, height, radius) {
        const qx = Math.abs(x) - width + radius;
        const qy = Math.abs(y) - height + radius;
        return Math.min(Math.max(qx, qy), 0) + length(Math.max(qx, 0), Math.max(qy, 0)) - radius;
    }

    function generateId() {
        return 'liquid-glass-' + Math.random().toString(36).substr(2, 9);
    }

    class LiquidGlassShader {
        constructor() {
            this.id = generateId();
            this.width = window.innerWidth;
            this.height = window.innerHeight;
            this.canvasDPI = 0.5; // 降低DPI以提高性能
            
            this.mouse = { x: 0.5, y: 0.5 };
            this.mouseUsed = false;
            this.animationFrame = null;
            
            this.createSVGFilter();
            this.createCanvas();
            this.setupEventListeners();
            this.updateShader();
            this.startAnimation();
        }

        createSVGFilter() {
            this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
            this.svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
            this.svg.setAttribute('width', '0');
            this.svg.setAttribute('height', '0');
            this.svg.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                pointer-events: none;
                z-index: 1;
            `;

            const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
            const filter = document.createElementNS('http://www.w3.org/2000/svg', 'filter');
            filter.setAttribute('id', `${this.id}_filter`);
            filter.setAttribute('filterUnits', 'userSpaceOnUse');
            filter.setAttribute('colorInterpolationFilters', 'sRGB');
            filter.setAttribute('x', '0');
            filter.setAttribute('y', '0');
            filter.setAttribute('width', this.width.toString());
            filter.setAttribute('height', this.height.toString());

            this.feImage = document.createElementNS('http://www.w3.org/2000/svg', 'feImage');
            this.feImage.setAttribute('id', `${this.id}_map`);
            this.feImage.setAttribute('width', this.width.toString());
            this.feImage.setAttribute('height', this.height.toString());

            this.feDisplacementMap = document.createElementNS('http://www.w3.org/2000/svg', 'feDisplacementMap');
            this.feDisplacementMap.setAttribute('in', 'SourceGraphic');
            this.feDisplacementMap.setAttribute('in2', `${this.id}_map`);
            this.feDisplacementMap.setAttribute('xChannelSelector', 'R');
            this.feDisplacementMap.setAttribute('yChannelSelector', 'G');

            filter.appendChild(this.feImage);
            filter.appendChild(this.feDisplacementMap);
            defs.appendChild(filter);
            this.svg.appendChild(defs);

            document.body.appendChild(this.svg);
        }

        createCanvas() {
            this.canvas = document.createElement('canvas');
            this.canvas.width = this.width * this.canvasDPI;
            this.canvas.height = this.height * this.canvasDPI;
            this.canvas.style.display = 'none';
            this.context = this.canvas.getContext('2d');
        }

        setupEventListeners() {
            document.addEventListener('mousemove', (e) => {
                this.mouse.x = e.clientX / this.width;
                this.mouse.y = e.clientY / this.height;
                this.mouseUsed = true;
            });

            window.addEventListener('resize', () => {
                this.width = window.innerWidth;
                this.height = window.innerHeight;
                this.canvas.width = this.width * this.canvasDPI;
                this.canvas.height = this.height * this.canvasDPI;
                this.svg.setAttribute('width', this.width.toString());
                this.svg.setAttribute('height', this.height.toString());
                this.feImage.setAttribute('width', this.width.toString());
                this.feImage.setAttribute('height', this.height.toString());
            });
        }

        updateShader() {
            const w = this.width * this.canvasDPI;
            const h = this.height * this.canvasDPI;
            const data = new Uint8ClampedArray(w * h * 4);

            let maxScale = 0;
            const rawValues = [];

            // 获取鼠标位置(归一化到0-1)
            const mouseProxy = {
                x: this.mouse.x,
                y: this.mouse.y
            };

            for (let i = 0; i < data.length; i += 4) {
                const px = (i / 4) % w;
                const py = Math.floor(i / 4 / w);
                const uvX = px / w;
                const uvY = py / h;

                // 计算距离边缘的SDF
                const ix = uvX - 0.5;
                const iy = uvY - 0.5;
                const distanceToEdge = roundedRectSDF(ix, iy, 0.3, 0.2, 0.6);
                const displacement = smoothStep(0.8, 0, distanceToEdge - 0.15);
                const scaled = smoothStep(0, 1, displacement);

                const posX = ix * scaled + 0.5;
                const posY = iy * scaled + 0.5;

                const dx = posX * w - px;
                const dy = posY * h - py;

                maxScale = Math.max(maxScale, Math.abs(dx), Math.abs(dy));
                rawValues.push(dx, dy);
            }

            maxScale *= 0.5;

            let index = 0;
            for (let i = 0; i < data.length; i += 4) {
                const r = rawValues[index++] / maxScale + 0.5;
                const g = rawValues[index++] / maxScale + 0.5;
                data[i] = Math.max(0, Math.min(255, r * 255));
                data[i + 1] = Math.max(0, Math.min(255, g * 255));
                data[i + 2] = 0;
                data[i + 3] = 255;
            }

            this.context.putImageData(new ImageData(data, w, h), 0, 0);
            this.feImage.setAttributeNS('http://www.w3.org/1999/xlink', 'href', this.canvas.toDataURL());
            this.feDisplacementMap.setAttribute('scale', (maxScale / this.canvasDPI).toString());
        }

        startAnimation() {
            const animate = () => {
                if (this.mouseUsed) {
                    this.updateShader();
                    this.mouseUsed = false;
                }
                this.animationFrame = requestAnimationFrame(animate);
            };
            animate();
        }

        destroy() {
            if (this.animationFrame) {
                cancelAnimationFrame(this.animationFrame);
            }
            this.svg.remove();
            this.canvas.remove();
        }

        getFilterId() {
            return `${this.id}_filter`;
        }
    }

    // 初始化着色器
    const shader = new LiquidGlassShader();
    window.liquidGlassEffect = shader;

    // 将SVG滤镜应用到菜单后面的内容
    const applyFilter = () => {
        const html = document.documentElement;
        if (html.classList.contains('citizen-feature-performance-mode-clientpref-3')) {
            // 应用到菜单后面的背景内容
            const menuCards = document.querySelectorAll(
                '.citizen-menu__card, ' +
                '#citizen-preferences__card, ' +
                '.citizen-command-palette, ' +
                '.mwe-popups .mwe-popups-container'
            );

            menuCards.forEach(card => {
                card.style.filter = `url(#${shader.getFilterId()})`;
            });
        }
    };

    // 延迟应用以确保SVG已加载
    setTimeout(applyFilter, 100);

    // 监听DOM变化,动态应用滤镜
    const observer = new MutationObserver(() => {
        if (document.documentElement.classList.contains('citizen-feature-performance-mode-clientpref-3')) {
            applyFilter();
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    console.log('液态玻璃效果已启用');
}

function destroyLiquidGlassEffect() {
    if (window.liquidGlassEffect) {
        window.liquidGlassEffect.destroy();
        window.liquidGlassEffect = null;

        // 移除滤镜
        const menuCards = document.querySelectorAll(
            '.citizen-menu__card, ' +
            '#citizen-preferences__card, ' +
            '.citizen-command-palette, ' +
            '.mwe-popups .mwe-popups-container'
        );

        menuCards.forEach(card => {
            card.style.filter = '';
        });

        console.log('液态玻璃效果已关闭');
    }
}