User:Kcx36/JS/CCHPTool.js
外观
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google Chrome、Firefox、Microsoft Edge及Safari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
/**
* 修改自:https://zh.wikipedia.org/wiki/User:SuperGrey/gadgets/ACGATool/main.js
* 原作者:https://zh.wikipedia.org/wiki/User:SuperGrey
* 原著作权标识:https://opensource.org/licenses/MIT MIT
*/
var CCHPTool = {
/**
* 获取完整的wikitext。
* @returns {Promise<string>} 包含完整wikitext的Promise。
*/
getFullText: async function () {
var api = new mw.Api();
var response = await api.get({
action: 'query', titles: 'WikiProject:中国文化遗产/专题奖/登记处', prop: 'revisions', rvslots: '*', rvprop: 'content', indexpageids: 1
});
var fulltext = response.query.pages[response.query.pageids[0]].revisions[0].slots.main['*'];
return fulltext;
},
/**
* Trims a token's text and adjusts its absolute boundaries (skipping leading/trailing whitespace).
* @param {object} token An object with properties { text, start, end }.
* @returns {object} An object with properties { text, start, end } after trimming.
*/
trimToken: function (token) {
const leadingMatch = token.text.match(/^\s*/);
const trailingMatch = token.text.match(/\s*$/);
const leading = leadingMatch ? leadingMatch[0].length : 0;
const trailing = trailingMatch ? trailingMatch[0].length : 0;
return {
text: token.text.trim(), start: token.start + leading, end: token.end - trailing
};
},
/**
* Splits the inner content of a template into tokens by detecting top-level "\n|" delimiters.
* It scans the inner text (tracking nested braces) and splits only when it sees a newline followed by "|".
* @param {string} innerContent The content between the outer "{{" and "}}".
* @param {number} offset The absolute start position of innerContent in the wikitext.
* @returns {Array} An array of tokens; each token is an object { text, start, end }.
*/
splitParameters: function (innerContent, offset) {
let tokens = [];
let lastIndex = 0;
let braceCount = 0;
let i = 0;
while (i < innerContent.length) {
if (innerContent.substr(i, 2) === '{{') {
braceCount++;
i += 2;
continue;
}
if (innerContent.substr(i, 2) === '}}') {
braceCount = Math.max(braceCount - 1, 0);
i += 2;
continue;
}
// At top level, if we see a newline followed by a pipe, split here.
if (braceCount === 0 && innerContent[i] === '\n' && innerContent[i + 1] === '|') {
tokens.push({
text: innerContent.slice(lastIndex, i), start: offset + lastIndex, end: offset + i
});
i += 2; // skip the "\n|"
lastIndex = i;
continue;
}
i++;
}
tokens.push({
text: innerContent.slice(lastIndex), start: offset + lastIndex, end: offset + innerContent.length
});
return tokens;
},
/**
* Finds the matching closing braces "}}" for a template that starts at the given index.
* @param {string} text The full wikitext.
* @param {number} start The starting index where "{{" is found.
* @returns {object} An object { endIndex } where endIndex is the index immediately after the closing "}}".
*/
findTemplateEnd: function (text, start) {
let braceCount = 0;
let i = start;
while (i < text.length) {
if (text.substr(i, 2) === '{{') {
braceCount++;
i += 2;
continue;
}
if (text.substr(i, 2) === '}}') {
braceCount--;
i += 2;
if (braceCount === 0) break;
continue;
}
i++;
}
return { endIndex: i };
},
/**
* Parses a template starting at the given index in the wikitext.
* For regular {{中国文化遗产专题奖提名}} templates, parameters are parsed as key=value pairs.
* For multiple entries templates, parameters are parsed as key=value pairs with numbers (e.g. 条目名称1, 被提名人1, etc.).
* @param {string} text The full wikitext.
* @param {number} start The starting index of the template (expects "{{").
* @returns {object} An object { template, endIndex }.
*/
parseTemplate: function (text, start) {
const templateStart = start;
const { endIndex: templateEnd } = CCHPTool.findTemplateEnd(text, start);
// Extract inner content (between outer "{{" and "}}")
const innerStart = start + 2;
const innerEnd = templateEnd - 2;
const innerContent = text.slice(innerStart, innerEnd);
// Split the inner content into tokens using the top-level "\n|" delimiter
const tokens = CCHPTool.splitParameters(innerContent, innerStart);
// The first token is the template name
let nameToken = CCHPTool.trimToken(tokens[0]);
let templateObj = {
name: nameToken.text, nameLocation: { start: nameToken.start, end: nameToken.end }, params: {}, location: { start: templateStart, end: templateEnd }
};
// For 中国文化遗产专题奖提名, process tokens as key=value pairs
// Group parameters by their trailing number if any
let kvGroups = {};
for (let j = 1; j < tokens.length; j++) {
let token = tokens[j];
let tokenTrim = CCHPTool.trimToken(token);
if (tokenTrim.text === "") continue;
const eqIndex = tokenTrim.text.indexOf('=');
if (eqIndex === -1) continue;
let rawKey = tokenTrim.text.substring(0, eqIndex);
let rawValue = tokenTrim.text.substring(eqIndex + 1);
let keyText = rawKey.trim();
let valueText = rawValue.trim();
let keyLeading = rawKey.match(/^\s*/)[0].length;
let keyLocation = {
start: tokenTrim.start + keyLeading, end: tokenTrim.start + keyLeading + keyText.length
};
let valueLeading = rawValue.match(/^\s*/)[0].length;
let valueLocation = {
start: tokenTrim.start + eqIndex + 1 + valueLeading, end: tokenTrim.end
};
// Check if key has a number suffix
let m = keyText.match(/^(.+?)(\d+)$/);
if (m) {
let prefix = m[1].trim();
let num = parseInt(m[2], 10);
if (!kvGroups[num]) kvGroups[num] = {};
kvGroups[num][prefix] = {
value: valueText, keyLocation: keyLocation, valueLocation: valueLocation, fullLocation: { start: token.start, end: token.end }
};
} else {
// Single entry parameters
templateObj.params[keyText] = {
value: valueText, keyLocation: keyLocation, valueLocation: valueLocation, fullLocation: { start: token.start, end: token.end }
};
}
}
// If we have numbered parameters, create entries
if (Object.keys(kvGroups).length > 0) {
templateObj.entries = [];
let groupNums = Object.keys(kvGroups).filter(k => k !== "0").map(Number).sort((a, b) => a - b);
for (let num of groupNums) {
let group = kvGroups[num];
let allTokens = Object.values(group);
let startPos = Math.min(...allTokens.map(t => t.fullLocation.start));
let endPos = Math.max(...allTokens.map(t => t.fullLocation.end));
group.fullLocation = { start: startPos, end: endPos };
templateObj.entries.push(group);
}
}
return { template: templateObj, endIndex: templateEnd };
},
/**
* Returns an array of date sections from the full wikitext.
* Each section is determined by h3 headings of the form "=== date ===".
* @param {string} text The full wikitext.
* @returns {Array} Array of sections: { date, start, end }.
*/
getDateSections: function (text) {
const regex = /^===\s*(.+?)\s*===/gm;
let sections = [];
let matches = [];
let match;
while ((match = regex.exec(text)) !== null) {
matches.push({ date: match[1].trim(), start: match.index, end: regex.lastIndex });
}
for (let i = 0; i < matches.length; i++) {
const sectionStart = matches[i].start;
const sectionDate = matches[i].date;
const sectionEnd = (i < matches.length - 1) ? matches[i + 1].start : text.length;
sections.push({ date: sectionDate, start: sectionStart, end: sectionEnd });
}
return sections;
},
/**
* In a given date section (from h3 heading to before the next h3), collects all entries.
* Each entry is either a main template ({{中国文化遗产专题奖提名}}) or an entry from multiple entries template.
* @param {string} text The full wikitext.
* @param {object} section A section object { date, start, end }.
* @returns {Array} Array of entry objects: { template, start, end, type }.
*/
collectEntriesInSection: function (text, section) {
let entries = [];
const sectionText = text.slice(section.start, section.end);
// Regex: match {{中国文化遗产专题奖提名
const regex = /{{中国文化遗产专题奖提名/g;
let match;
while ((match = regex.exec(sectionText)) !== null) {
let absolutePos = section.start + match.index;
let { template, endIndex } = CCHPTool.parseTemplate(text, absolutePos);
if (template.entries) {
// For multiple entries template
for (let entry of template.entries) {
entries.push({
template: entry,
start: entry.fullLocation.start,
end: entry.fullLocation.end,
type: 'multi'
});
}
} else {
// For single entry template
entries.push({
template: template,
start: template.location.start,
end: template.location.end,
type: 'single'
});
}
// Advance regex.lastIndex to avoid re-parsing inside this template
regex.lastIndex = endIndex - section.start;
}
// Sort entries in order of appearance
entries.sort((a, b) => a.start - b.start);
return entries;
},
/**
* Queries the full wikitext for an entry given a specific date (from the h3 heading)
* and an entry index (1-based, counting all entries in order).
* Returns the entry object including its location.
* @param {string} text The full wikitext.
* @param {string} date The date string (e.g. "2月3日").
* @param {number} index The 1-based index of the entry under that date.
* @returns {object|null} The entry object { template, start, end, type } or null if not found.
*/
queryEntry: function (text, date, index) {
const sections = CCHPTool.getDateSections(text);
const targetSection = sections.find(sec => sec.date === date);
if (!targetSection) return null;
const entries = CCHPTool.collectEntriesInSection(text, targetSection);
if (index < 1 || index > entries.length) return null;
return entries[index - 1]; // 1-based index
},
/**
* Given an entry (from queryEntry) and a set of changes (mapping parameter key to new value),
* updates only those parameter values in the original wikitext by using the precise location data.
* @param {string} original The full original wikitext.
* @param {object} entry The entry object (from queryEntry).
* @param {object} changes An object mapping parameter keys to new values.
* @returns {string} The updated wikitext.
*/
updateEntryParameters: function (original, entry, changes) {
let mods = [];
if (entry.type === 'single') {
let params = entry.template.params;
for (let key in changes) {
if (params[key] && params[key].valueLocation) {
mods.push({
start: params[key].valueLocation.start,
end: params[key].valueLocation.end,
replacement: changes[key]
});
}
}
} else if (entry.type === 'multi') {
// For multiple entries template
for (let key in changes) {
if (entry.template[key]) {
let token = entry.template[key];
mods.push({
start: token.valueLocation.start,
end: token.valueLocation.end,
replacement: changes[key]
});
}
}
}
// Apply modifications in descending order of start position
mods.sort((a, b) => b.start - a.start);
let updated = original;
for (let mod of mods) {
updated = updated.slice(0, mod.start) + mod.replacement + updated.slice(mod.end);
}
return updated;
},
/**
* Removes comments from the given text.
* @param text {string} The text to remove comments from.
* @returns {string} The text without comments.
*/
removeComments: function (text) {
text = text.replace(/<!--.*?-->/gs, '');
return text.trim();
},
/**
* 点击编辑按钮时的事件处理。
* @param date 日期(章节标题)
* @param index 该章节下第X个提名
*/
editNomination: function (date, index) {
CCHPTool.getFullText().then(function (fulltext) {
CCHPTool.queried = CCHPTool.queryEntry(fulltext, date, index);
if (!CCHPTool.queried) {
mw.notify('小工具无法读取该提名,请手动编辑。', { type: 'error', title: '错误' });
return;
}
let nomData = {};
if (CCHPTool.queried.type === 'single') {
nomData.pageName = CCHPTool.queried.template.params['条目名称']?.value || '';
nomData.awarder = CCHPTool.queried.template.params['被提名人']?.value || '';
nomData.nominationContent = CCHPTool.queried.template.params['提名内容']?.value || '';
nomData.checkContent = CCHPTool.queried.template.params['核对']?.value || '';
} else if (CCHPTool.queried.type === 'multi') {
nomData.pageName = CCHPTool.queried.template['条目名称']?.value || '';
nomData.awarder = CCHPTool.queried.template['被提名人']?.value || '';
nomData.nominationContent = CCHPTool.queried.template['提名内容']?.value || '';
nomData.checkContent = CCHPTool.queried.template['核对']?.value || '';
}
CCHPTool.showEditNominationDialog(nomData);
});
},
/**
* 解析模板参数,支持带编号的参数
* @param {object} template 模板对象
* @param {number} index 提名索引(从1开始)
* @returns {object} 包含条目名称、被提名人、提名内容的对象
*/
parseTemplateParameters: function (template, index) {
var params = {};
var suffix = index > 1 ? index.toString() : '';
if (template.params) {
// 单个提名模板
params.pageName = template.params['条目名称']?.value || template.params['条目名称1']?.value || '';
params.awarder = template.params['被提名人']?.value || template.params['被提名人1']?.value || '';
params.nominationContent = template.params['提名内容']?.value || template.params['提名内容1']?.value || '';
params.checkContent = template.params['核对']?.value || template.params['核对1']?.value || '';
} else {
// 多个提名模板
// 尝试带编号和不带编号的参数名
params.pageName = template['条目名称' + suffix]?.value || template['条目名称']?.value || '';
params.awarder = template['被提名人' + suffix]?.value || template['被提名人']?.value || '';
params.nominationContent = template['提名内容' + suffix]?.value || template['提名内容']?.value || '';
params.checkContent = template['核对' + suffix]?.value || template['核对']?.value || '';
}
return params;
},
/**
* 点击核对按钮时的事件处理。
* @param date 日期(章节标题)
* @param index 该章节下第X个提名
*/
checkNomination: function (date, index) {
CCHPTool.getFullText().then(function (fulltext) {
// 获取所有提名条目
var sections = CCHPTool.getDateSections(fulltext);
var targetSection = sections.find(sec => sec.date === date);
if (!targetSection) {
mw.notify('找不到指定日期的章节', { type: 'error', title: '错误' });
return;
}
var entries = CCHPTool.collectEntriesInSection(fulltext, targetSection);
if (index < 1 || index > entries.length) {
mw.notify('找不到指定索引的提名', { type: 'error', title: '错误' });
return;
}
var entry = entries[index - 1]; // 转换为0-based索引
CCHPTool.queried = entry;
CCHPTool.queried.date = date; // 保存日期信息
// 获取参数
var nomData = {};
if (entry.type === 'single') {
nomData = CCHPTool.parseTemplateParameters(entry.template, 1);
} else if (entry.type === 'multi') {
// 对于多个提名的情况,需要确定这是第几个提名组
var multiEntries = entries.filter(e => e.type === 'multi');
var groupIndex = multiEntries.indexOf(entry) + 1;
nomData = CCHPTool.parseTemplateParameters(entry.template, groupIndex);
}
CCHPTool.showCheckNominationDialog(nomData);
});
},
/**
* 点击登记新提名按钮时的事件处理。
*/
newNomination: function () {
CCHPTool.showNewNominationDialog();
},
/**
* 解析章节中的所有提名
* @param {string} text 完整的wikitext
* @param {object} section 章节对象 {date, start, end}
* @returns {Array} 提名数组,每个元素包含 {start, end, content}
*/
parseNominationsInSection: function (text, section) {
let nominations = [];
const sectionText = text.slice(section.start, section.end);
// 查找所有提名模板开始位置
const regex = /{{中国文化遗产专题奖提名[\s\S]*?}}/g;
let match;
let lastIndex = 0;
while ((match = regex.exec(sectionText)) !== null) {
// 从上一个提名结束到当前提名开始之间的内容
if (match.index > lastIndex) {
nominations.push({
start: section.start + lastIndex,
end: section.start + match.index,
content: sectionText.slice(lastIndex, match.index)
});
}
// 处理当前提名模板
const templateEnd = CCHPTool.findTemplateEnd(sectionText, match.index).endIndex;
nominations.push({
start: section.start + match.index,
end: section.start + templateEnd,
content: sectionText.slice(match.index, templateEnd)
});
// 更新位置
lastIndex = templateEnd;
regex.lastIndex = templateEnd;
}
// 添加最后一个提名之后的内容
if (lastIndex < sectionText.length) {
nominations.push({
start: section.start + lastIndex,
end: section.end,
content: sectionText.slice(lastIndex)
});
}
// 合并相邻的非提名内容
let mergedNominations = [];
let currentNomination = null;
for (let i = 0; i < nominations.length; i++) {
const nom = nominations[i];
if (nom.content.trim().startsWith('{{中国文化遗产专题奖提名')) {
// 提名模板
if (currentNomination) {
mergedNominations.push(currentNomination);
}
currentNomination = {
start: nom.start,
end: nom.end,
content: nom.content,
isNomination: true
};
} else {
// 非提名内容
if (currentNomination) {
// 附加到当前提名
currentNomination.end = nom.end;
currentNomination.content += nom.content;
} else {
// 开头的非提名内容
mergedNominations.push({
start: nom.start,
end: nom.end,
content: nom.content,
isNomination: false
});
}
}
}
if (currentNomination) {
mergedNominations.push(currentNomination);
}
// 过滤掉空提名
return mergedNominations.filter(nom =>
nom.isNomination ||
(nom.content.trim() !== '' && !nom.content.trim().startsWith('==='))
)
},
/**
* 存档状态对象
*/
archiveState: {
date: null,
selectedIndices: [],
isSelecting: false
},
/**
* 进入选择提名状态
*/
enterSelectionMode: function (date) {
CCHPTool.archiveState.date = date;
CCHPTool.archiveState.isSelecting = true;
// 添加CSS样式
mw.util.addCSS(
'.cchp-nomination-selection {' +
' position: relative;' +
' margin: 10px 0;' +
' padding: 5px;' +
' border: 1px dashed #aaa;' +
' transition: background-color 0.3s;' +
'}' +
'.cchp-nomination-selected {' +
' background-color: rgba(200, 255, 200, 0.3);' +
'}' +
'.cchp-archive-confirm-btn {' +
' position: fixed;' +
' bottom: 20px;' +
' right: 20px;' +
' z-index: 1000;' +
'}'
);
// 获取完整文本和提名信息
CCHPTool.getFullText().then(function (fulltext) {
var sections = CCHPTool.getDateSections(fulltext);
var targetSection = sections.find(sec => sec.date === date);
if (!targetSection) {
mw.notify('找不到指定日期的章节', { type: 'error', title: '错误' });
return;
}
// 解析提名
var nominations = CCHPTool.parseNominationsInSection(fulltext, targetSection);
CCHPTool.archiveState.selectedIndices = nominations.map((_, i) => true);
// 为每个提名添加选择框
$('div.mw-heading3').each(function () {
var h3div = $(this);
var h3 = h3div.find('h3').first();
if (h3.text().trim() === date) {
// 找到该章节下的所有表格(每个提名对应一个表格)
var tables = h3div.nextUntil('div.mw-heading3', 'table.wikitable');
tables.each(function (index) {
var table = $(this);
// 找到表格后的所有内容直到下一个表格或章节
var nextTable = tables.eq(index + 1);
var nextH3 = h3div.nextAll('div.mw-heading3').first();
var endElement = nextTable.length ? nextTable :
nextH3.length ? nextH3 :
h3div.parent().children().last();
// 创建包含表格及其后内容的选区
var selectionDiv = $('<div>').addClass('cchp-nomination-selection cchp-nomination-selected');
// 获取从表格开始到下一个表格或章节之前的所有元素
var elementsToWrap = [];
var current = table;
while (current.length && !current.is(endElement)) {
elementsToWrap.push(current[0]);
current = current.next();
}
// 用选区div包裹这些元素
table.before(selectionDiv);
selectionDiv.append(elementsToWrap);
// 添加点击事件
selectionDiv.on('click', function () {
$(this).toggleClass('cchp-nomination-selected');
CCHPTool.archiveState.selectedIndices[index] = $(this).hasClass('cchp-nomination-selected');
});
});
// 添加确认按钮
var confirmBtn = $('<button>')
.addClass('mw-ui-button mw-ui-progressive cchp-archive-confirm-btn')
.text('确认存档 (' + nominations.length + '个提名)')
.click(function () {
CCHPTool.confirmArchiveSelection(date);
});
$('body').append(confirmBtn);
}
});
});
},
/**
* 确认存档选择
*/
confirmArchiveSelection: function (date) {
var selectedCount = CCHPTool.archiveState.selectedIndices.filter(x => x).length;
if (selectedCount === 0) {
mw.notify('请至少选择一个提名进行存档', { type: 'error', title: '错误' });
return;
}
OO.ui.confirm('确定要存档「' + date + '」章节中的' + selectedCount + '个提名吗?').done(function (confirmed) {
if (confirmed) {
CCHPTool.performArchive(date);
}
});
},
/**
* 执行存档操作
*/
performArchive: function (date) {
var selectedCount = CCHPTool.archiveState.selectedIndices.filter(x => x).length;
if (selectedCount === 0) {
mw.notify('没有选择任何提名进行存档', { type: 'error', title: '错误' });
return;
}
CCHPTool.getFullText().then(function (fulltext) {
var sections = CCHPTool.getDateSections(fulltext);
var targetSection = sections.find(sec => sec.date === date);
if (!targetSection) {
mw.notify('小工具无法读取该章节,请手动存档。', { type: 'error', title: '错误' });
return;
}
// 解析提名
var nominations = CCHPTool.parseNominationsInSection(fulltext, targetSection);
// 获取选中的提名和保留的提名
var selectedNominations = [];
var remainingContentParts = [];
var lastEnd = targetSection.start;
for (var i = 0; i < nominations.length; i++) {
if (CCHPTool.archiveState.selectedIndices[i]) {
// 选中的提名
selectedNominations.push(nominations[i]);
} else {
// 未选中的提名,保留在原页面
// 添加提名前的内容
remainingContentParts.push(fulltext.slice(lastEnd, nominations[i].start));
// 添加提名内容
remainingContentParts.push(nominations[i].content);
lastEnd = nominations[i].end;
}
}
// 添加章节最后的内容
remainingContentParts.push(fulltext.slice(lastEnd, targetSection.end));
// 构建新的页面内容 - 确保正确处理章节标题
var newContent;
if (remainingContentParts.join('').trim() === '') {
// 如果章节中没有剩余内容,则完全删除该章节
newContent = fulltext.slice(0, targetSection.start) + fulltext.slice(targetSection.end);
} else {
// 保留章节标题和剩余内容
var remainingContent = remainingContentParts.join('');
// 确保章节标题存在
if (!remainingContent.includes('=== ' + date + ' ===')) {
remainingContent = '=== ' + date + ' ===\n' + remainingContent;
}
newContent = fulltext.slice(0, targetSection.start) + remainingContent + fulltext.slice(targetSection.end);
}
// 如果没有选中的提名,直接返回
if (selectedNominations.length === 0) {
mw.notify('没有选择任何提名进行存档', { type: 'error', title: '错误' });
return;
}
// 获取存档日期(从第一个选中的提名中提取)
var firstSelected = selectedNominations[0];
var utcRegex = /提名人:.+?(\d{4})年(\d{1,2})月(\d{1,2})日 \((.*?)\) (\d{1,2}:\d{2}) \(UTC\)/;
var utcMatch = firstSelected.content.match(utcRegex);
if (!utcMatch) {
mw.notify('小工具无法读取提名的UTC时间,请手动存档。', { type: 'error', title: '错误' });
return;
}
var yearMonth = utcMatch[1] + '年' + utcMatch[2] + '月';
var archiveTarget = 'WikiProject:中国文化遗产/专题奖/存档/' + yearMonth;
var archiveContent = selectedNominations.map(nom => nom.content).join('\n\n');
mw.notify('小工具正在存档中,请耐心等待。', { type: 'info', title: '提示', autoHide: false });
var api = new mw.Api();
// 先获取存档页面内容
api.get({
action: 'query',
titles: archiveTarget,
prop: 'revisions',
rvslots: '*',
rvprop: 'content'
}).then(function (data) {
var page = data.query.pages[Object.keys(data.query.pages)[0]];
var existingContent = page.revisions ? page.revisions[0].slots.main['*'] : '';
// 处理存档内容
var finalArchiveContent;
if (!existingContent || existingContent.trim() === '') {
// 新存档页面
finalArchiveContent = '{{Talk archive|WikiProject:中国文化遗产/专题奖/登记处}}\n\n=== ' + date + ' ===\n' + archiveContent;
} else if (existingContent.includes('=== ' + date + ' ===')) {
// 已有该日期的章节,追加到后面
finalArchiveContent = existingContent + '\n\n' + archiveContent;
} else {
// 按日期顺序插入新章节
var archiveSections = CCHPTool.getDateSections(existingContent);
var insertPos = existingContent.length;
var targetDate = new Date(utcMatch[1], utcMatch[2] - 1, utcMatch[3]);
for (var i = 0; i < archiveSections.length; i++) {
var secDateMatch = archiveSections[i].date.match(/(\d+)月(\d+)日/);
if (secDateMatch) {
var secDate = new Date(utcMatch[1], secDateMatch[1] - 1, secDateMatch[2]);
if (targetDate < secDate) {
insertPos = archiveSections[i].start;
break;
}
}
}
var newSection = '\n\n=== ' + date + ' ===\n' + archiveContent;
finalArchiveContent = existingContent.slice(0, insertPos) + newSection + existingContent.slice(insertPos);
}
// 提交存档
return api.postWithToken('csrf', {
action: 'edit',
title: archiveTarget,
text: finalArchiveContent,
summary: '[[User:Kcx36/JS/CCHPTool.js|存档]]「' + date + '」章节中的' + selectedNominations.length + '个提名'
});
}).then(function () {
// 更新原页面 - 删除已存档的提名
return api.postWithToken('csrf', {
action: 'edit',
title: 'WikiProject:中国文化遗产/专题奖/登记处',
text: newContent,
summary: '[[User:Kcx36/JS/CCHPTool.js|存档]]「' + date + '」章节中的' + selectedNominations.length + '个提名至「[[' + archiveTarget + ']]」',
minor: true
});
}).then(function () {
mw.notify('成功存档' + selectedNominations.length + '个提名至「[[' + archiveTarget + ']]」', { type: 'success', title: '成功' });
CCHPTool.cleanupArchiveSelection();
CCHPTool.refreshPage();
}).catch(function (error) {
console.error('存档失败:', error);
mw.notify('存档失败: ' + error, { type: 'error', title: '错误' });
});
});
},
/**
* 清理选择状态
*/
cleanupArchiveSelection: function () {
$('.cchp-nomination-selection').each(function () {
var $this = $(this);
$this.contents().insertBefore($this);
$this.remove();
});
$('.cchp-archive-confirm-btn').remove();
CCHPTool.archiveState = {
date: null,
selectedIndices: [],
isSelecting: false
};
},
/**
* 点击存档按钮时的事件处理。
*/
archiveChapter: function (date) {
if (CCHPTool.archiveState.isSelecting) {
// 如果已经在选择状态,则执行确认存档
CCHPTool.confirmArchiveSelection(date);
} else {
// 否则进入选择状态
CCHPTool.enterSelectionMode(date);
}
},
/**
* 在页面上添加编辑按钮。
*/
addEditButtonsToPage: function () {
// 找到登记新提名按钮
var newNominationButton = $('span[role="button"]').filter(function () {
return $(this).text() === '登记新提名';
});
if (newNominationButton.length > 0) {
// 修改原本按钮的文本为"手动登记新提名"
newNominationButton.text('手动登记新提名');
newNominationButton.removeClass('mw-ui-progressive');
// 添加新的登记按钮
var editUIButton = $('<span>').addClass('mw-ui-button').addClass('mw-ui-progressive').attr('role', 'button').text('登记新提名');
var editButton = $('<a>').attr('href', 'javascript:void(0)').append(editUIButton).click(CCHPTool.newNomination);
newNominationButton.parent().parent().append(' ').append(editButton);
}
// 识别所有h3
$('div.mw-heading3').each(function () {
var h3div = $(this);
var h3 = h3div.find('h3').first();
var date = h3.text().trim();
var index = 0;
// 为h3div底下的span.mw-editsection添加存档按钮
var editsection = h3div.find('span.mw-editsection').first();
var editsectionA = editsection.find('a').first();
$('<a>').attr('href', 'javascript:void(0)').click(function () {
CCHPTool.archiveChapter(date);
}).append('存档').insertAfter(editsectionA);
$('<span> | </span>').insertAfter(editsectionA);
// 处理每个提名表格
h3div.nextUntil('div.mw-heading3', 'table.wikitable').each(function () {
var table = $(this);
var rows = table.find('tr').slice(1); // 去掉表头
var title = "";
rows.each(function () {
var row = $(this);
var th = row.find('th');
if (th.length != 0) {
// 提名行
var nomEntry = th.first();
var nomEntryA = nomEntry.find('a');
if (nomEntryA.length != 0) {
title = nomEntry.find('a').first().attr('title');
} else {
title = nomEntry.text().trim();
}
index++;
// 添加编辑按钮
var editIcon = $('<img>').attr('src', 'https://upload.wikimedia.org/wikipedia/commons/8/8a/OOjs_UI_icon_edit-ltr-progressive.svg').css({ 'width': '12px' });
const currentIndex = index;
var editButton = $('<a>').attr('href', 'javascript:void(0)').append(editIcon).click(function () {
CCHPTool.editNomination(date, currentIndex);
});
nomEntry.append(' ').append(editButton);
} else {
// 核对行
var td = row.find('td').first();
var mwNoTalk = td.find('.mw-notalk').first();
// 检查是否已经核对(包含核对模板)
var isChecked = mwNoTalk.html().includes('核对人:');
if (!isChecked) {
// 只有未核对的才显示核对按钮
var checkIcon = $('<img>').attr('src', 'https://upload.wikimedia.org/wikipedia/commons/3/30/OOjs_UI_icon_highlight-progressive.svg').css({ 'width': '12px' });
const currentIndex = index;
var checkButton = $('<a>').attr('href', 'javascript:void(0)').append(checkIcon).click(function () {
CCHPTool.checkNomination(date, currentIndex);
});
mwNoTalk.append(' ').append(checkButton);
}
}
});
});
});
},
/**
* 生成提名表单。
* @param {object} nomData 提名数据(非必须)。无数据时为新提名。
* @returns {OO.ui.FieldsetLayout} 提名表单。
*/
generateNominationFieldset: function (nomData) {
var awarder, pageName, nominationContent, checkContent;
if (nomData) {
awarder = nomData.awarder;
pageName = nomData.pageName;
nominationContent = nomData.nominationContent || '';
checkContent = nomData.checkContent || '';
} else {
awarder = mw.config.get('wgUserName');
pageName = '';
nominationContent = '';
checkContent = '';
}
const currentNomidx = CCHPTool.nominations.length;
CCHPTool.nominations.push({
awarder: awarder,
pageName: pageName,
nominationContent: nominationContent,
checkContent: checkContent
});
// 被提名人
var awarderInput = new OO.ui.TextInputWidget({
value: CCHPTool.nominations[currentNomidx].awarder
});
awarderInput.on('change', function (newValue) {
CCHPTool.nominations[currentNomidx].awarder = newValue;
});
var awarderField = new OO.ui.FieldLayout(awarderInput, {
label: '被提名人',
align: 'left'
});
// 条目名称
var pageNameInput = new OO.ui.TextInputWidget({
value: CCHPTool.nominations[currentNomidx].pageName
});
pageNameInput.on('change', function (newValue) {
CCHPTool.nominations[currentNomidx].pageName = newValue;
});
var pageNameField = new OO.ui.FieldLayout(pageNameInput, {
label: '条目名称',
align: 'left'
});
// 提名内容
var nominationContentInput = new OO.ui.MultilineTextInputWidget({
value: CCHPTool.nominations[currentNomidx].nominationContent,
autosize: true,
rows: 3
});
nominationContentInput.on('change', function (newValue) {
CCHPTool.nominations[currentNomidx].nominationContent = newValue;
});
var nominationContentField = new OO.ui.FieldLayout(nominationContentInput, {
label: '提名内容',
align: 'top'
});
// 核对内容(仅在编辑时显示)
var checkContentField = null;
if (nomData) {
var checkContentInput = new OO.ui.MultilineTextInputWidget({
value: CCHPTool.nominations[currentNomidx].checkContent,
autosize: true,
rows: 3
});
checkContentInput.on('change', function (newValue) {
CCHPTool.nominations[currentNomidx].checkContent = newValue;
});
checkContentField = new OO.ui.FieldLayout(checkContentInput, {
label: '核对内容',
align: 'top'
});
}
var items = [awarderField, pageNameField, nominationContentField];
if (checkContentField) {
items.push(checkContentField);
}
var nominationFieldset = new OO.ui.FieldsetLayout({
items: items
});
nominationFieldset.$element.css({
'margin-top': 0
});
if (!nomData) {
// 顶部的 <hr>
var hr = $('<hr>').css({
'margin-top': '15px', 'margin-bottom': '15px'
});
nominationFieldset.$element.prepend(hr);
}
return nominationFieldset;
},
/**
* 生成核对表单。
* @param {object} nomData 提名数据。
* @returns {OO.ui.FieldsetLayout} 核对表单。
*/
generateCheckFieldset: function (nomData) {
var pageName = nomData.pageName;
var awarder = nomData.awarder;
var nominationContent = nomData.nominationContent;
var checkContent = nomData.checkContent;
CCHPTool.nominations.push({
pageName: pageName,
awarder: awarder,
nominationContent: nominationContent,
checkContent: checkContent
});
// 条目名称(只读)
var pageNameLabel = new OO.ui.LabelWidget({
label: $('<a>').attr('href', mw.util.getUrl(pageName)).text(pageName)
});
var pageNameField = new OO.ui.FieldLayout(pageNameLabel, {
label: '条目名称',
align: 'left'
});
// 被提名人(只读)
var awarderLabel = new OO.ui.LabelWidget({
label: $('<a>').attr('href', mw.util.getUrl('User:' + awarder)).text(awarder)
});
var awarderField = new OO.ui.FieldLayout(awarderLabel, {
label: '被提名人',
align: 'left'
});
// 提名内容(只读)
var nominationContentLabel = new OO.ui.LabelWidget({
label: nominationContent
});
var nominationContentField = new OO.ui.FieldLayout(nominationContentLabel, {
label: '提名内容',
align: 'top'
});
// 核对分数输入框(改为垂直排列)
var entryScoreInput = new OO.ui.TextInputWidget({
placeholder: '请输入条目分',
classes: ['cchp-score-input']
});
var entryScoreField = new OO.ui.FieldLayout(entryScoreInput, {
label: '条目分',
align: 'left',
classes: ['cchp-score-field']
});
var imageScoreInput = new OO.ui.TextInputWidget({
placeholder: '请输入图片分',
classes: ['cchp-score-input']
});
var imageScoreField = new OO.ui.FieldLayout(imageScoreInput, {
label: '图片分',
align: 'left',
classes: ['cchp-score-field']
});
var reviewScoreInput = new OO.ui.TextInputWidget({
placeholder: '请输入评审分',
classes: ['cchp-score-input']
});
var reviewScoreField = new OO.ui.FieldLayout(reviewScoreInput, {
label: '评审分',
align: 'left',
classes: ['cchp-score-field']
});
// 核对意见
var checkOpinionInput = new OO.ui.MultilineTextInputWidget({
placeholder: '请输入核对意见',
autosize: true,
rows: 3,
classes: ['cchp-opinion-input']
});
var checkOpinionField = new OO.ui.FieldLayout(checkOpinionInput, {
label: '核对意见',
align: 'top'
});
// 创建字段集
var checkFieldset = new OO.ui.FieldsetLayout({
items: [
pageNameField,
awarderField,
nominationContentField,
new OO.ui.FieldsetLayout({
label: '核对分数',
items: [entryScoreField, imageScoreField, reviewScoreField],
classes: ['cchp-score-fields']
}),
checkOpinionField
],
classes: ['cchp-check-fieldset']
});
// 添加CSS样式
mw.util.addCSS(`
.cchp-score-fields {
margin: 10px 0;
}
.cchp-score-field {
margin-bottom: 8px;
}
.cchp-score-input {
max-width: 300px;
}
.cchp-opinion-input {
max-width: 600px;
}
.cchp-score-fields .oo-ui-fieldLayout-label {
min-width: 60px;
display: inline-block;
}
.cchp-score-fields .oo-ui-inputWidget {
margin-left: 10px;
}
`);
// 保存输入框引用以便后续访问
CCHPTool.checkInputs = {
entryScoreInput: entryScoreInput,
imageScoreInput: imageScoreInput,
reviewScoreInput: reviewScoreInput,
checkOpinionInput: checkOpinionInput
};
return checkFieldset;
},
/**
* 保存新提名。
* @returns {Promise<boolean>} 是否成功提交。
*/
saveNewNomination: async function () {
// 生成多个提名的wikitext
var proposedWikitext = '{{中国文化遗产专题奖提名\n';
// 遍历所有提名
for (var i = 0; i < CCHPTool.nominations.length; i++) {
var nom = CCHPTool.nominations[i];
var suffix = CCHPTool.nominations.length > 1 ? (i + 1) : '';
proposedWikitext += '| 条目名称' + suffix + ' = ' + nom.pageName + '\n';
proposedWikitext += '| 被提名人' + suffix + ' = ' + nom.awarder + '\n';
proposedWikitext += '| 提名内容' + suffix + ' = ' + nom.nominationContent + '\n';
proposedWikitext += '| 核对' + suffix + ' = \n';
// 如果不是最后一个提名,添加空行分隔
if (i < CCHPTool.nominations.length - 1) {
proposedWikitext += '\n';
}
}
proposedWikitext += '}}';
// 签名
const signature = '~' + '~' + '~' + '~';
proposedWikitext += "\n'''提名人:'''" + signature;
// 附加说明
var message = CCHPTool.newNominationDialog.messageInput.getValue().trim();
if (message !== '') {
proposedWikitext += "\n: {{说明}}:" + message + '--' + signature;
}
// 检查是否已有今日的日期章节
var today = new Date();
var todayDate = (today.getMonth() + 1) + '月' + today.getDate() + '日';
var fulltext = await CCHPTool.getFullText();
if (!fulltext.includes('=== ' + todayDate + ' ===')) {
proposedWikitext = '=== ' + todayDate + ' ===\n' + proposedWikitext;
}
// 提交
var api = new mw.Api();
var response = await api.postWithToken('csrf', {
action: 'edit',
title: 'WikiProject:中国文化遗产/专题奖/登记处',
appendtext: '\n' + proposedWikitext,
summary: '[[User:Kcx36/JS/CCHPTool.js|新提名]]' +
(CCHPTool.nominations.length > 1 ? '(' + CCHPTool.nominations.length + '个条目)' : '')
});
if (response.edit.result === 'Success') {
mw.notify('新提名已成功提交!' +
(CCHPTool.nominations.length > 1 ? '(共' + CCHPTool.nominations.length + '个条目)' : ''),
{ title: '成功', autoHide: true });
CCHPTool.refreshPage();
return false;
} else {
mw.notify('新提名提交失败:' + response.edit.result, { type: 'error', title: '错误' });
return true;
}
},
/**
* 新提名对话框。
*/
NewNominationDialog: function (config) {
CCHPTool.NewNominationDialog.super.call(this, config);
},
/**
* 保存修改提名。
* @returns {Promise<boolean>} 是否成功提交。
*/
saveModifiedNomination: async function () {
var changes = {
'条目名称': CCHPTool.nominations[0].pageName,
'被提名人': CCHPTool.nominations[0].awarder,
'提名内容': CCHPTool.nominations[0].nominationContent,
'核对': CCHPTool.nominations[0].checkContent
};
var fulltext = await CCHPTool.getFullText();
var updatedText = CCHPTool.updateEntryParameters(fulltext, CCHPTool.queried, changes);
if (updatedText === fulltext) {
mw.notify('提名并未改动!', { type: 'warn', title: '提示' });
return true;
}
var api = new mw.Api();
var response = await api.postWithToken('csrf', {
action: 'edit',
title: 'WikiProject:中国文化遗产/专题奖/登记处',
text: updatedText,
summary: '[[User:Kcx36/JS/CCHPTool.js|编辑提名]]'
});
if (response.edit.result === 'Success') {
mw.notify('提名已成功修改!', { title: '成功', autoHide: true });
CCHPTool.refreshPage();
return false;
} else {
mw.notify('提名修改失败:' + response.edit.result, { type: 'error', title: '错误' });
return true;
}
},
/**
* 保存核对。
* @returns {Promise<boolean>} 是否成功提交。
*/
saveNominationCheck: async function (entryScore, imageScore, reviewScore, checkOpinion) {
entryScore = entryScore.trim();
imageScore = imageScore.trim();
reviewScore = reviewScore.trim();
checkOpinion = checkOpinion.trim();
// 检查是否至少填写了一个字段
if (!entryScore && !imageScore && !reviewScore && !checkOpinion) {
mw.notify('请至少填写一个核对字段(条目分、图片分、评审分或核对意见)', {
type: 'error',
title: '错误'
});
return true;
}
// 构建核对模板
var checkTemplate = '{{中国文化遗产专题奖提名/核对\n';
if (entryScore) checkTemplate += '| 条目分 = ' + entryScore + '\n';
if (imageScore) checkTemplate += '| 图片分 = ' + imageScore + '\n';
if (reviewScore) checkTemplate += '| 评审分 = ' + reviewScore + '\n';
if (checkOpinion) checkTemplate += '| 核对意见 = ' + checkOpinion + '\n';
checkTemplate += '| 核对人 = ' + '~' + '~' + '~' + '~';
checkTemplate += '}}';
var changes = {};
var fulltext = await CCHPTool.getFullText();
var updatedText = fulltext;
// 确定要更新的参数名称
if (CCHPTool.queried.type === 'single') {
// 单个提名模板
if (CCHPTool.queried.template.params['核对']) {
changes['核对'] = checkTemplate;
} else if (CCHPTool.queried.template.params['核对1']) {
changes['核对1'] = checkTemplate;
} else {
// 在模板结束前添加核对参数
var templateEnd = CCHPTool.queried.template.location.end - 2; // 减去"}}"的长度
updatedText = updatedText.slice(0, templateEnd) + '\n| 核对 = ' + checkTemplate + '\n}}';
}
} else if (CCHPTool.queried.type === 'multi') {
// 多个提名模板 - 直接更新现有的核对参数
var checkParamName = Object.keys(CCHPTool.queried.template).find(key =>
key.startsWith('核对') &&
(CCHPTool.queried.template[key].value === '' ||
CCHPTool.queried.template[key].value.trim() === '')
);
if (checkParamName) {
changes[checkParamName] = checkTemplate;
} else {
mw.notify('找不到可用的核对参数位置', { type: 'error', title: '错误' });
return true;
}
}
// 如果有需要更新的参数,使用精确位置更新
if (Object.keys(changes).length > 0) {
updatedText = CCHPTool.updateEntryParameters(fulltext, CCHPTool.queried, changes);
}
if (updatedText === fulltext) {
mw.notify('核对并未改动!', { type: 'warn', title: '提示' });
return true;
}
var api = new mw.Api();
var response = await api.postWithToken('csrf', {
action: 'edit',
title: 'WikiProject:中国文化遗产/专题奖/登记处',
text: updatedText,
summary: '[[User:Kcx36/JS/CCHPTool.js|核对分数]]'
});
if (response.edit.result === 'Success') {
// 更新Module:CCHPaward/list,将字符串分数转换为数字(空字符串视为0)
await CCHPTool.updateAwardModule(
CCHPTool.nominations[0].awarder,
entryScore ? parseFloat(entryScore) : 0,
imageScore ? parseFloat(imageScore) : 0,
reviewScore ? parseFloat(reviewScore) : 0
);
mw.notify('核对已成功提交!', { title: '成功', autoHide: true });
CCHPTool.refreshPage();
return false;
} else {
mw.notify('核对提交失败:' + response.edit.result, { type: 'error', title: '错误' });
return true;
}
},
/**
* 计算等级变化并提示颁奖
*/
updateAwardModule: async function (username, articleScore, imageScore, reviewScore) {
try {
var api = new mw.Api();
// 1. 获取Module:CCHPaward/list当前内容
var response = await api.get({
action: 'query',
titles: 'Module:CCHPaward/list',
prop: 'revisions',
rvslots: '*',
rvprop: 'content'
});
var page = response.query.pages[Object.keys(response.query.pages)[0]];
var originalContent = page.revisions[0].slots.main['*'];
// 2. 提取文件头部注释
var headerMatch = originalContent.match(/^(--[^\n]*\n)+/);
var header = headerMatch ? headerMatch[0].split('\n')[0] + '\n' : '';
// 3. 提取return语句前的所有内容(不含注释)
var preReturnMatch = originalContent.match(/^.*?(?=return\s*\{)/s);
var preReturnContent = preReturnMatch ? preReturnMatch[0].replace(/^--.*?\n/g, '') : '';
// 4. 提取数据部分
var dataMatch = originalContent.match(/return\s*\{([\s\S]*?)\}\s*$/);
if (!dataMatch) {
throw new Error('无法解析Module:CCHPaward/list内容');
}
// 5. 解析现有用户数据
var userData = {};
var userRegex = /\["([^"]+)"\]\s*=\s*\{([^}]+)\}/g;
var match;
while ((match = userRegex.exec(dataMatch[1])) !== null) {
var user = match[1];
var userInfo = match[2];
var articleMatch = userInfo.match(/article\s*=\s*([\d.]+)/);
var imageMatch = userInfo.match(/image\s*=\s*([\d.]+)/);
var reviewMatch = userInfo.match(/review\s*=\s*([\d.]+)/);
userData[user] = {
article: parseFloat(articleMatch ? articleMatch[1] : 0),
image: parseFloat(imageMatch ? imageMatch[1] : 0),
review: parseFloat(reviewMatch ? reviewMatch[1] : 0),
originalText: match[0]
};
}
// 6. 计算旧等级
var oldLevel = 0;
if (userData[username]) {
var oldTotal = userData[username].article + 0.2 * userData[username].image + 0.4 * userData[username].review;
oldLevel = Math.floor(oldTotal / 10);
}
// 7. 更新用户分数
if (!userData[username]) {
userData[username] = { article: 0, image: 0, review: 0, originalText: null };
}
var changes = [];
if (articleScore !== 0) {
userData[username].article += articleScore;
changes.push(`条目分${articleScore > 0 ? '+' : ''}${articleScore}=${userData[username].article}`);
}
if (imageScore !== 0) {
userData[username].image += imageScore;
changes.push(`图片分${imageScore > 0 ? '+' : ''}${imageScore}=${userData[username].image}`);
}
if (reviewScore !== 0) {
userData[username].review += reviewScore;
changes.push(`评审分${reviewScore > 0 ? '+' : ''}${reviewScore}=${userData[username].review}`);
}
// 8. 计算新等级
var newTotal = userData[username].article + 0.2 * userData[username].image + 0.4 * userData[username].review;
var newLevel = Math.floor(newTotal / 10);
// 9. 重建数据部分
var newDataContent = 'return {\n';
// 按原有顺序处理用户
var userOrder = [];
var orderRegex = /\["([^"]+)"\]\s*=/g;
while ((match = orderRegex.exec(dataMatch[1])) !== null) {
userOrder.push(match[1]);
}
// 处理原有用户
userOrder.forEach(function (user) {
if (userData[user]) {
if (user === username) {
// 更新当前用户
newDataContent += ` ["${user}"] = {\n`;
newDataContent += ` article = ${userData[user].article},\n`;
newDataContent += ` image = ${userData[user].image},\n`;
newDataContent += ` review = ${userData[user].review}\n`;
newDataContent += ' },\n';
} else {
// 保留未修改用户
newDataContent += ' ' + userData[user].originalText + ',\n';
}
}
});
// 处理新增用户
if (!userOrder.includes(username) && (articleScore !== 0 || imageScore !== 0 || reviewScore !== 0)) {
newDataContent += ` ["${username}"] = {\n`;
newDataContent += ` article = ${userData[username].article},\n`;
newDataContent += ` image = ${userData[username].image},\n`;
newDataContent += ` review = ${userData[username].review}\n`;
newDataContent += ' },\n';
}
// 移除最后一个逗号
newDataContent = newDataContent.replace(/,\n$/, '\n');
newDataContent += '}';
// 10. 组合最终内容
var newContent = header + preReturnContent + newDataContent;
// 11. 生成编辑摘要
var editSummary = '[[User:Kcx36/JS/CCHPTool.js|更新分数]]:' + username + ' ';
editSummary += changes.join(',');
if (oldLevel !== newLevel) {
editSummary += `,等级${oldLevel}→${newLevel}`;
}
// 12. 更新Module:CCHPaward/list
await api.postWithToken('csrf', {
action: 'edit',
title: 'Module:CCHPaward/list',
text: newContent,
summary: editSummary
});
// 13. 检查是否需要颁奖(修改后的部分)
if (newLevel > oldLevel) {
var levelsToAward = [];
// 确定需要颁发的所有等级
for (var level = oldLevel + 1; level <= newLevel; level++) {
if (level <= 5 || level % 5 === 0) {
levelsToAward.push(level);
}
}
if (levelsToAward.length > 0) {
// 创建窗口管理器(如果不存在)
if (!CCHPTool.awardWindowManager) {
CCHPTool.awardWindowManager = new OO.ui.WindowManager();
$(document.body).append(CCHPTool.awardWindowManager.$element);
}
// 创建颁奖对话框
var awardDialog = new OO.ui.MessageDialog();
CCHPTool.awardWindowManager.addWindows([awardDialog]);
// 设置对话框标题
awardDialog.title.$label.html("需要颁奖");
// 创建内容容器
var container = $("<div/>");
// 创建用户链接
var userLink = $('<a>')
.attr('href', mw.util.getUrl('User:' + username))
.text(username);
// 添加内容
container.append(
$("<p/>").append(
'用户 ',
userLink,
` 已达到${newLevel}级中国文化遗产专题奖,请前往讨论页进行颁奖。`
)
);
// 将内容添加到对话框
awardDialog.message.$label.append(container);
// 创建动作按钮
var awardAction = new OO.ui.ActionWidget({
action: "award",
label: "现在颁奖",
flags: ["primary", "progressive"]
});
var cancelAction = new OO.ui.ActionWidget({
action: "cancel",
label: "稍后处理",
flags: "safe"
});
// 处理按钮点击事件
var handleAction = function (action) {
return function () {
CCHPTool.awardWindowManager.closeWindow(awardDialog);
if (action === 'award') {
// 打开用户讨论页
window.open(
mw.util.getUrl('User talk:' + username, {
action: 'edit',
section: 'new',
preload: 'Template:CCHPAwardTalk/preload'
}),
'_blank'
);
}
// 刷新页面
CCHPTool.refreshPage();
};
};
// 绑定事件处理
awardAction.$element.on('click', handleAction('award'));
cancelAction.$element.on('click', handleAction('cancel'));
// 打开对话框
CCHPTool.awardWindowManager.openWindow(awardDialog, {
actions: [awardAction, cancelAction]
});
return true;
}
}
} catch (error) {
console.error('更新分数记录失败:', error);
mw.notify('更新分数记录失败:' + error.message, { type: 'error', title: '错误' });
}
},
/**
* 刷新页面。
*/
refreshPage: function () {
setTimeout(function () {
location.reload();
}, 500);
},
/**
* 新提名对话框。
*/
NewNominationDialog: function (config) {
CCHPTool.NewNominationDialog.super.call(this, config);
},
/**
* 初始化新提名对话框。
*/
initNewNominationDialog: function () {
OO.inheritClass(CCHPTool.NewNominationDialog, OO.ui.ProcessDialog);
CCHPTool.NewNominationDialog.static.name = 'NewNominationDialog';
CCHPTool.NewNominationDialog.static.title = '新提名(中国文化遗产专题奖小工具)';
CCHPTool.NewNominationDialog.static.actions = [
{
action: 'save',
label: '保存',
flags: ['primary', 'progressive']
},
{
action: 'cancel',
label: '取消',
flags: 'safe'
},
{
action: 'add',
label: '额外提名 + 1',
flags: ['constructive']
},
{
action: 'minus',
label: '额外提名 − 1',
flags: ['destructive'],
disabled: true // 初始时禁用减号按钮
}
];
CCHPTool.NewNominationDialog.prototype.initialize = function () {
CCHPTool.NewNominationDialog.super.prototype.initialize.call(this);
this.panel = new OO.ui.PanelLayout({
padded: true, expanded: false
});
this.content = new OO.ui.FieldsetLayout();
// 附加说明
this.messageInput = new OO.ui.MultilineTextInputWidget({
autosize: true, rows: 1
});
this.messageInput.connect(this, { resize: 'onMessageInputResize' });
this.messageInputField = new OO.ui.FieldLayout(this.messageInput, {
label: '附加说明',
align: 'top',
help: '可不填;无须签名',
helpInline: true
});
this.messageInputFieldSet = new OO.ui.FieldsetLayout({
items: [this.messageInputField]
});
this.content.addItems([this.messageInputFieldSet, CCHPTool.generateNominationFieldset()]);
this.panel.$element.append(this.content.$element);
this.$body.append(this.panel.$element);
};
CCHPTool.NewNominationDialog.prototype.onMessageInputResize = function () {
this.updateSize();
};
CCHPTool.NewNominationDialog.prototype.getBodyHeight = function () {
return this.panel.$element.outerHeight(true);
};
CCHPTool.NewNominationDialog.prototype.getActionProcess = function (action) {
if (action === 'save') {
return new OO.ui.Process(async function () {
let response = await CCHPTool.saveNewNomination();
if (!response) {
this.close();
}
}, this);
} else if (action === 'add') {
return new OO.ui.Process(function () {
// 新增一个提名
var newFieldset = CCHPTool.generateNominationFieldset();
this.content.addItems([newFieldset]);
// 更新减号按钮状态
var minusAction = this.actions.get({ actions: 'minus' })[0];
minusAction.setDisabled(false);
// 如果达到最大数量,禁用加号按钮
if (this.content.items.length - 1 >= 25) { // 减去说明字段
var addAction = this.actions.get({ actions: 'add' })[0];
addAction.setDisabled(true);
}
this.updateSize();
}, this);
} else if (action === 'minus') {
return new OO.ui.Process(function () {
if (this.content.items.length <= 2) { // 说明字段+1个提名
mw.notify(
'至少需要一个提名!',
{ type: 'error', title: '错误' }
);
return;
}
// 移除最后一个提名
this.content.removeItems([this.content.items[this.content.items.length - 1]]);
CCHPTool.nominations.pop();
// 更新按钮状态
var minusAction = this.actions.get({ actions: 'minus' })[0];
if (this.content.items.length <= 2) {
minusAction.setDisabled(true);
}
var addAction = this.actions.get({ actions: 'add' })[0];
addAction.setDisabled(false);
this.updateSize();
}, this);
} else if (action === 'cancel') {
return new OO.ui.Process(function () {
this.close();
}, this);
}
return CCHPTool.NewNominationDialog.super.prototype.getActionProcess.call(this, action);
};
CCHPTool.NewNominationDialog.prototype.getTearnDownProcess = function (data) {
return CCHPTool.NewNominationDialog.super.prototype.getTearnDownProcess.call(this, data);
};
},
/**
* 编辑提名对话框。
*/
EditNominationDialog: function (config) {
CCHPTool.EditNominationDialog.super.call(this, config);
},
/**
* 初始化编辑提名对话框。
*/
initEditNominationDialog: function () {
OO.inheritClass(CCHPTool.EditNominationDialog, OO.ui.ProcessDialog);
CCHPTool.EditNominationDialog.static.name = 'EditNominationDialog';
CCHPTool.EditNominationDialog.static.title = '编辑提名(中国文化遗产专题奖小工具)';
CCHPTool.EditNominationDialog.static.actions = [
{
action: 'save',
label: '保存',
flags: ['primary', 'progressive']
},
{
action: 'cancel',
label: '取消',
flags: 'safe'
}
];
CCHPTool.EditNominationDialog.prototype.initialize = function () {
CCHPTool.EditNominationDialog.super.prototype.initialize.call(this);
this.panel = new OO.ui.PanelLayout({
padded: true,
expanded: false
});
this.panel.$element.append(CCHPTool.editNominationDialogContent.$element);
this.$body.append(this.panel.$element);
};
CCHPTool.EditNominationDialog.prototype.getActionProcess = function (action) {
if (action === 'save') {
return new OO.ui.Process(async function () {
let response = await CCHPTool.saveModifiedNomination();
if (!response) {
this.close();
}
}, this);
} else if (action === 'cancel') {
return new OO.ui.Process(function () {
this.close();
}, this);
}
return CCHPTool.EditNominationDialog.super.prototype.getActionProcess.call(this, action);
};
},
/**
* 核对提名对话框。
*/
CheckNominationDialog: function (config) {
CCHPTool.CheckNominationDialog.super.call(this, config);
},
/**
* 初始化核对提名对话框。
*/
initCheckNominationDialog: function () {
OO.inheritClass(CCHPTool.CheckNominationDialog, OO.ui.ProcessDialog);
CCHPTool.CheckNominationDialog.static.name = 'CheckNominationDialog';
CCHPTool.CheckNominationDialog.static.title = '核对分数(中国文化遗产专题奖小工具)';
CCHPTool.CheckNominationDialog.static.actions = [
{
action: 'save',
label: '保存',
flags: ['primary', 'progressive']
},
{
action: 'cancel',
label: '取消',
flags: 'safe'
}
];
CCHPTool.CheckNominationDialog.prototype.initialize = function () {
CCHPTool.CheckNominationDialog.super.prototype.initialize.call(this);
this.panel = new OO.ui.PanelLayout({
padded: true,
expanded: false
});
this.panel.$element.append(CCHPTool.checkNominationDialogContent.$element);
this.$body.append(this.panel.$element);
};
CCHPTool.CheckNominationDialog.prototype.getActionProcess = function (action) {
if (action === 'save') {
return new OO.ui.Process(async function () {
let response = await CCHPTool.saveNominationCheck(
CCHPTool.checkInputs.entryScoreInput.getValue(),
CCHPTool.checkInputs.imageScoreInput.getValue(),
CCHPTool.checkInputs.reviewScoreInput.getValue(),
CCHPTool.checkInputs.checkOpinionInput.getValue()
);
if (!response) {
this.close();
}
}, this);
} else if (action === 'cancel') {
return new OO.ui.Process(function () {
this.close();
}, this);
}
return CCHPTool.CheckNominationDialog.super.prototype.getActionProcess.call(this, action);
};
},
/**
* 显示新提名对话框。
*/
showNewNominationDialog: function () {
CCHPTool.nominations = [];
CCHPTool.newNominationDialog = new CCHPTool.NewNominationDialog({ size: 'large' });
CCHPTool.windowManager.addWindows([CCHPTool.newNominationDialog]);
CCHPTool.windowManager.openWindow(CCHPTool.newNominationDialog);
},
/**
* 显示编辑提名对话框。
*/
showEditNominationDialog: function (nomData) {
CCHPTool.nominations = [];
CCHPTool.editNominationDialogContent.clearItems();
CCHPTool.editNominationDialogContent.addItems([CCHPTool.generateNominationFieldset(nomData)]);
CCHPTool.editNominationDialog = new CCHPTool.EditNominationDialog({ size: 'large' });
CCHPTool.windowManager.addWindows([CCHPTool.editNominationDialog]);
CCHPTool.windowManager.openWindow(CCHPTool.editNominationDialog);
},
/**
* 显示核对提名对话框。
*/
showCheckNominationDialog: function (nomData) {
CCHPTool.nominations = [];
CCHPTool.checkNominationDialogContent = CCHPTool.generateCheckFieldset(nomData);
CCHPTool.checkNominationDialog = new CCHPTool.CheckNominationDialog({
size: 'large'
});
CCHPTool.windowManager.addWindows([CCHPTool.checkNominationDialog]);
CCHPTool.windowManager.openWindow(CCHPTool.checkNominationDialog);
},
/**
* 脚本入口。
*/
init: function () {
CCHPTool.pageName = mw.config.get('wgPageName');
if (CCHPTool.pageName !== 'WikiProject:中国文化遗产/专题奖' &&
CCHPTool.pageName !== 'WikiProject:中国文化遗产/专题奖/登记处') {
return;
}
// 初始化对话框
CCHPTool.initNewNominationDialog();
CCHPTool.initEditNominationDialog();
CCHPTool.initCheckNominationDialog();
// 创建窗口管理器
CCHPTool.windowManager = new OO.ui.WindowManager();
$(document.body).append(CCHPTool.windowManager.$element);
// 添加编辑按钮
CCHPTool.addEditButtonsToPage();
// 初始化内容池
CCHPTool.editNominationDialogContent = new OO.ui.FieldsetLayout();
CCHPTool.checkNominationDialogContent = new OO.ui.FieldsetLayout();
},
// 全局变量
pageName: '',
windowManager: null,
newNominationDialog: null,
editNominationDialog: null,
editNominationDialogContent: null,
checkNominationDialog: null,
checkNominationDialogContent: null,
queried: null,
nominations: []
};
$(function () {
CCHPTool.init();
});