User:What7what8/darkmodefixtool a.js
外观
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google Chrome、Firefox、Microsoft Edge及Safari:按住⇧ 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();
});