跳转到内容

User:What7what8/darkmodefixtool a.js

维基百科,自由的百科全书
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google ChromeFirefoxMicrosoft EdgeSafari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
$(document).ready(function() {
    // 配置常量
    const API_URL = mw.config.get("wgServer") + mw.config.get("wgScriptPath") + "/api.php";
    const PAGE_NAME = mw.config.get("wgPageName");
    const DARK_CLASSES = ['skin-invert', 'skin-invert-image'];

    // 主函数
    async function initializeTool() {
        try {
            // 获取页面原始内容
            const { wikitext, token, baserevid, basetimestamp } = await fetchPageContent();
            
            // 解析嵌套结构
            const elements = {
                templates: parseNestedElements(wikitext, '{}'),
                links: parseNestedElements(wikitext, '[]')
            };
            
            // 批量解析图片
            const parsedData = await batchParseElements(elements);
            
            // 生成界面
            const panelHtml = generatePanel(parsedData);
            renderInterface(panelHtml, wikitext, { token, baserevid, basetimestamp });
            
            // 添加工具栏按钮
            addToolbarButton();

        } catch (error) {
            handleError("工具初始化失败", error);
        }
    }

    // 获取页面内容
    async function fetchPageContent() {
        const params = new URLSearchParams({
            action: 'query',
            prop: 'revisions',
            rvprop: 'content|ids|timestamp',
            rvslots: '*',
            titles: PAGE_NAME,
            format: 'json',
            meta: 'tokens',
            type: 'csrf'
        });

        const response = await fetch(`${API_URL}?${params}`);
        const data = await response.json();
        const page = Object.values(data.query.pages)[0];
        
        return {
            wikitext: page.revisions[0].slots.main['*'],
            token: data.query.tokens.csrftoken,
            baserevid: page.revisions[0].revid,
            basetimestamp: page.revisions[0].timestamp
        };
    }

    // 解析嵌套结构(改进版)
    function parseNestedElements(wikitext, brackets) {
        const [open, close] = brackets.split('');
        const elements = [];
        let stack = [];
        let buffer = '';
        let inComment = false;
        let inNowiki = false;

        for (let i = 0; i < wikitext.length; i++) {
            // 跳过注释区域
            if (wikitext.startsWith('<!--', i)) {
                inComment = true;
                i += 3;
                continue;
            }
            if (inComment && wikitext.startsWith('-->', i)) {
                inComment = false;
                i += 2;
                continue;
            }
            if (inComment) continue;

            // 跳过nowiki区域
            if (wikitext.startsWith('<nowiki>', i)) {
                inNowiki = true;
                i += 7;
                continue;
            }
            if (inNowiki && wikitext.startsWith('</nowiki>', i)) {
                inNowiki = false;
                i += 8;
                continue;
            }
            if (inNowiki) continue;

            const char = wikitext[i];
            if (char === open) {
                if (stack.length === 0) buffer = '';
                stack.push(i);
            } else if (char === close && stack.length > 0) {
                const start = stack.pop();
                if (stack.length === 0) {
                    elements.push({
                        content: wikitext.slice(start + 1, i),
                        start: start,
                        end: i + 1
                    });
                }
            }
        }
        return elements;
    }

    // 批量解析图片API
    async function batchParseElements(elements) {
    const parseQueue = [];
    
    // 收集需要解析的内容(优化后的匹配正则)
    const filePattern = /(?:File|Image):([^|\]\}]+\.(?:png|jpe?g|gif|svg|webp|tiff?|xcf|pdf))/i;
    
    elements.links.forEach(link => {
        const fileMatch = link.content.match(filePattern);
        if (fileMatch) {
            parseQueue.push({
                type: 'link',
                element: link,
                wikitext: `[[File:${fileMatch[1].trim()}||250px]]` // 使用双竖线避免参数干扰
            });
        }
    });

    elements.templates.forEach(template => {
        if (filePattern.test(template.content)) {
            parseQueue.push({
                type: 'template',
                element: template,
                wikitext: `{{${template.content}}}`
            });
        }
    });

    // 分块处理(每5个为一组)并添加重试机制
    const BATCH_SIZE = 5;
    const MAX_RETRIES = 3;
    const results = [];
    
    for (let i = 0; i < parseQueue.length; i += BATCH_SIZE) {
        const chunk = parseQueue.slice(i, i + BATCH_SIZE);
        let retryCount = 0;
        let success = false;

        while (retryCount < MAX_RETRIES && !success) {
            try {
                const formData = new FormData();
                formData.append('action', 'parse');
                formData.append('format', 'json');
                formData.append('text', chunk.map(c => c.wikitext).join('\n'));
                formData.append('prop', 'text');
                formData.append('disableeditsection', '1');
                formData.append('preview', '1');
                formData.append('disablelimitreport', '1'); // 禁用统计报告减少数据量

                const response = await fetch(API_URL, {
                    method: 'POST',
                    body: formData,
                    headers: {
                        // 添加API请求专用头
                        'Api-User-Agent': `DarkModeFixTool/1.0 (${mw.config.get('wgUserName') || 'Anonymous'})`
                    }
                });

                if (!response.ok) throw new Error(`HTTP ${response.status}`);
                
                const data = await response.json();
                const parsedItems = data.parse.text['*'].split(/<hr\s*\/?>/);
                
                chunk.forEach((item, index) => {
                    results.push({
                        ...item,
                        parsedHtml: parsedItems[index] || `<div class="error">解析失败 (${response.status})</div>`
                    });
                });
                
                success = true;
            } catch (error) {
                retryCount++;
                if (retryCount >= MAX_RETRIES) {
                    console.error(`分块 ${i/BATCH_SIZE} 解析失败:`, error);
                    chunk.forEach(item => {
                        results.push({
                            ...item,
                            parsedHtml: `<div class="error">解析失败: ${error.message}</div>`
                        });
                    });
                } else {
                    await new Promise(resolve => setTimeout(resolve, 2000 * retryCount)); // 指数退避
                }
            }
        }
    }

    return results;
}

    // 生成界面
    function generatePanel(parsedData) {
        let html = '<div class="darkmode-tool-container">';
        
        // 模板分组
        html += '<div class="section templates-section"><h3>模板图片</h3>';
        parsedData.filter(item => item.type === 'template').forEach(item => {
            html += `
                <div class="tool-item template-item">
                    <div class="code-preview"><nowiki>{{${item.element.content}}}</nowiki></div>
                    <div class="image-preview" 
                         data-start="${item.element.start}"
                         data-end="${item.element.end}"
                         data-inverted="${item.element.content.includes('skin-invert')}">
                        ${item.parsedHtml}
                    </div>
                    <div class="controls">
                        <label class="toggle">
                            <input type="checkbox" ${item.element.content.includes('skin-invert') ? 'checked' : ''}>
                            反色模式
                        </label>
                    </div>
                </div>
            `;
        });
        html += '</div>';

        // 文件链接分组
        html += '<div class="section links-section"><h3>直接文件链接</h3>';
        parsedData.filter(item => item.type === 'link').forEach(item => {
            html += `
                <div class="tool-item link-item">
                    <div class="code-preview"><nowiki>[[${item.element.content}}]</nowiki></div>
                    <div class="image-preview"
                         data-start="${item.element.start}"
                         data-end="${item.element.end}"
                         data-inverted="${item.element.content.includes('skin-invert-image')}">
                        ${item.parsedHtml}
                    </div>
                    <div class="controls">
                        <label class="toggle">
                            <input type="checkbox" ${item.element.content.includes('skin-invert-image') ? 'checked' : ''}>
                            反色模式
                        </label>
                        <button class="color-picker">背景测试</button>
                    </div>
                </div>
            `;
        });
        html += '</div></div>';

        return html;
    }

    // 渲染界面
    function renderInterface(html, originalWikitext, editParams) {
        const $panel = $(`
            <div id="darkmode-tool-dialog">
                <div class="tool-header">
                    <h2>暗色模式适配工具</h2>
                    <div class="tool-meta">当前页面:${mw.config.get('wgPageName')}</div>
                </div>
                <div class="tool-content">${html}</div>
                <div class="tool-footer">
                    <button class="mw-ui-button mw-ui-progressive submit-btn">保存修改</button>
                    <button class="mw-ui-button cancel-btn">取消</button>
                </div>
            </div>
        `).appendTo('body');

        // 反色开关事件
        $panel.find('input[type="checkbox"]').on('change', function() {
            const $preview = $(this).closest('.tool-item').find('.image-preview');
            $preview.toggleClass('skin-invert', this.checked);
        });

        // 取色器功能
        if (window.EyeDropper) {
            const eyeDropper = new EyeDropper();
            $panel.find('.color-picker').on('click', async function() {
                try {
                    const color = await eyeDropper.open();
                    $(this).closest('.tool-item').find('.image-preview')
                           .css('background-color', color.sRGBHex);
                } catch (e) {
                    console.log('用户取消取色');
                }
            });
        } else {
            $panel.find('.color-picker').remove();
        }

        // 初始化对话框
        $panel.dialog({
            title: '暗色模式适配工具',
            width: '85%',
            height: 700,
            modal: true,
            autoOpen: false,
            buttons: {
                "保存修改": () => applyChanges(originalWikitext, $panel, editParams),
                "取消": () => $panel.dialog('close')
            }
        });
    }

    // 应用修改
    async function applyChanges(original, $panel, params) {
        const changes = [];
        
        $panel.find('.tool-item').each(function() {
            const $item = $(this);
            const wasInverted = $item.find('input').prop('defaultChecked');
            const nowInverted = $item.find('input').prop('checked');
            if (wasInverted === nowInverted) return;

            const start = parseInt($item.find('.image-preview').data('start'));
            const end = parseInt($item.find('.image-preview').data('end'));
            const type = $item.hasClass('template-item') ? 'template' : 'link';
            
            let newContent = original.slice(start, end);
            if (type === 'template') {
                newContent = nowInverted ? 
                    newContent.replace(/(\|[^=]*?)(\||}})/, '$1{{skin-invert}}$2') :
                    newContent.replace(/\{\{skin-invert\}\}/g, '');
            } else {
                newContent = nowInverted ?
                    newContent.replace(/(\[\[File:[^|]*)(\||\]\])/, '$1|class=skin-invert-image$2') :
                    newContent.replace(/\|class=skin-invert-image/g, '');
            }
            
            changes.push({ start, end, newContent });
        });

        // 执行替换
        const newWikitext = changes
            .sort((a, b) => b.start - a.start)
            .reduce((text, change) => 
                text.slice(0, change.start) + change.newContent + text.slice(change.end),
                original
            );

        // 提交修改
        try {
            await saveChanges(newWikitext, params);
            location.reload();
        } catch (error) {
            handleError("保存失败", error);
        }
    }

    // 保存到API
    async function saveChanges(wikitext, { token, baserevid, basetimestamp }) {
        const formData = new FormData();
        formData.append('action', 'edit');
        formData.append('title', PAGE_NAME);
        formData.append('text', wikitext);
        formData.append('summary', '暗色模式适配调整');
        formData.append('token', token);
        formData.append('baserevid', baserevid);
        formData.append('basetimestamp', basetimestamp);
        formData.append('format', 'json');

        const response = await fetch(API_URL, {
            method: 'POST',
            body: formData
        });

        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        const result = await response.json();
        if (result.error) throw new Error(result.error.info);
        mw.notify('修改已成功保存');
    }

    // 添加工具栏按钮
    function addToolbarButton() {
        const portletId = 'p-custom-tools';
        if (!$(`#${portletId}`).length) {
            mw.util.addPortlet(
                portletId,
                '自定义工具',
                '#p-associated-pages'
            );
        }

        const button = mw.util.addPortletLink(
            portletId,
            '#',
            '皮肤反色工具',
            'ca-darkmode-tool',
            '调整暗色模式图片显示'
        );

        $(button)
            .prepend('<span class="mw-icon-image"></span>')
            .on('click', (e) => {
                e.preventDefault();
                $('#darkmode-tool-dialog').dialog('open');
            });
    }

    // 错误处理
    function handleError(context, error) {
        console.error(`${context}:`, error);
        mw.notify(`发生错误:${error.message}`, { type: 'error', tag: 'darkmode-tool' });
    }

    // 启动工具
    initializeTool();
});