跳转到内容

User:Kcx36/JS/CCHPTool.js

维基百科,自由的百科全书
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google ChromeFirefoxMicrosoft EdgeSafari:按住⇧ 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>&nbsp;|&nbsp;</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();
});