User:SuperGrey/gadgets/voter/main.js
外观
(重定向自User:SuperGrey/ voter.js)
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google Chrome、Firefox、Microsoft Edge及Safari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
// Main page: [[User:SuperGrey/gadgets/voter]]
// <nowiki>
(() => {
// src/state.ts
var State = class {
constructor() {
// 簡繁轉換
this.convByVar = function(langDict) {
if (langDict && langDict.hant) {
return langDict.hant;
}
return "繁簡轉換未初始化,且 langDict 無效!";
};
// 用戶名
this.userName = mw.config.get("wgUserName") || "Example";
// 頁面名稱
this.pageName = mw.config.get("wgPageName");
/**
* 版本號
*/
this.version = "4.2.0";
// MediaWiki API 實例
this._api = null;
}
initHanAssist() {
return mw.loader.using("ext.gadget.HanAssist").then((require2) => {
const { convByVar } = require2("ext.gadget.HanAssist");
if (typeof convByVar === "function") {
this.convByVar = convByVar;
}
});
}
getApi() {
if (!this._api) {
this._api = new mw.Api({ "User-Agent": `ReviewTool/${this.version}` });
}
return this._api;
}
};
var state = new State();
var state_default = state;
// src/api.ts
async function getXToolsInfo(pageName) {
function safeToLocaleString(num) {
if (typeof num === "number" && !isNaN(num)) {
return num.toLocaleString();
}
return "0";
}
try {
const pageInfo = await $.get("https://xtools.wmcloud.org/api/page/pageinfo/" + mw.config.get("wgServerName") + "/" + pageName.replace(/["?%&+\\]/g, escape));
const project = pageInfo.project;
const pageEnc = encodeURIComponent(pageInfo.page);
const pageUrl = `https://${project}/wiki/${pageInfo.page}`;
const pageinfoUrl = `https://xtools.wmcloud.org/pageinfo/${project}/${pageEnc}`;
const permaLinkUrl = `https://${project}/wiki/Special:PermaLink%2F${pageInfo.created_rev_id}`;
const diffUrl = `https://${project}/wiki/Special:Diff%2F${pageInfo.modified_rev_id}`;
const pageviewsUrl = `https://pageviews.wmcloud.org/?project=${project}&pages=${pageEnc}&range=latest-${pageInfo.pageviews_offset}`;
const creatorLink = `https://${project}/wiki/User:${pageInfo.creator}`;
const creatorContribsUrl = `https://${project}/wiki/Special:Contributions/${pageInfo.creator}`;
const createdDate = new Date(pageInfo.created_at).toISOString().split("T")[0];
const revisionsText = safeToLocaleString(pageInfo.revisions);
const editorsText = safeToLocaleString(pageInfo.editors);
const watchersText = safeToLocaleString(pageInfo.watchers);
const pageviewsText = safeToLocaleString(pageInfo.pageviews);
const days = Math.round(pageInfo.secs_since_last_edit / 86400);
let creatorText = "";
if (pageInfo.creator_editcount) {
creatorText = `<bdi><a href="${creatorLink}" target="_blank">${pageInfo.creator}</a></bdi> (<a href="${creatorContribsUrl}" target="_blank">${safeToLocaleString(pageInfo.creator_editcount)}</a>)`;
} else {
creatorText = `<bdi><a href="${creatorContribsUrl}" target="_blank">${pageInfo.creator}</a></bdi>`;
}
let pageCreationText = `「<a target="_blank" title="評級: ${pageInfo.assessment.value}" href="${pageinfoUrl}"><img src="${pageInfo.assessment.badge}" style="height:16px !important; vertical-align:-4px; margin-right:3px"/></a><bdi><a target="_blank" href="${pageUrl}">${pageInfo.page}</a></bdi>」由 ${creatorText} 於 <bdi><a target='_blank' href='${permaLinkUrl}'>${createdDate}</a></bdi> 建立,共 ${revisionsText} 個修訂,最後修訂於 <a href="${diffUrl}">${days} 天</a>前。`;
let pageEditorsText = `共 ${editorsText} 編輯者` + (watchersText !== "0" ? `、${watchersText} 監視者` : "") + `,最近 ${pageInfo.pageviews_offset} 天共 <a target="_blank" href="${pageviewsUrl}">${pageviewsText} 瀏覽數</a>。`;
return `<span style="line-height:20px">${pageCreationText}${pageEditorsText}<a target="_blank" href="${pageinfoUrl}">檢視完整頁面統計</a>。</span>`.trim();
} catch (error) {
console.error("[Voter] Error fetching XTools data:", error);
return '<span style="color: red; font-weight: bold;">無法獲取 XTools 頁面資訊。</span>';
}
}
async function voteAPI(tracePage, destPage, sectionID, text, summary) {
var _a;
let votedPageName = ((_a = state_default.sectionTitles.find((x) => x.data === sectionID)) == null ? void 0 : _a.label) || `section ${sectionID}`;
mw.notify(`正在為「${votedPageName}」投出一票⋯⋯`);
let res = await state_default.getApi().get({
action: "query",
titles: destPage,
prop: "revisions|info",
rvslots: "*",
rvprop: "content",
rvsection: sectionID,
indexpageids: 1
});
let page = res.query.pages[res.query.pageids[0]];
let sectionText = page.revisions[0].slots.main["*"];
if (sectionText === void 0 || sectionText === "") {
console.log(`[Voter] 無法取得「${votedPageName}」的投票區段內容。區段ID:${sectionID}。API 回傳:`, res);
mw.notify(`無法取得「${votedPageName}」的投票區段內容,請刷新後重試。`);
return true;
}
if (!textMatchTitleVariants(sectionText, votedPageName)) {
console.log(`[Voter] 在「${votedPageName}」的投票區段中找不到該條目。區段文本:`, sectionText);
mw.notify(`在該章節找不到名為「${votedPageName}」的提名,請刷新後重試。`);
return true;
}
let mod = {
action: "edit",
title: destPage,
section: sectionID,
summary,
token: mw.user.tokens.get("csrfToken")
};
let innerHeadings;
if (tracePage === "Wikipedia:新条目推荐/候选") {
innerHeadings = sectionText.match(/=====.+?=====/g);
} else {
innerHeadings = sectionText.match(/===.+?===/g);
}
if (innerHeadings) {
mod.section += 1;
mod.prependtext = text + "\n";
} else {
mod.appendtext = "\n" + text;
}
await state_default.getApi().post(mod);
mw.notify(`「${votedPageName}」已完成投票。`);
return false;
}
// src/dialog.ts
var _mountedApp = null;
function loadCodexAndVue() {
return mw.loader.using("@wikimedia/codex").then((require2) => ({
Vue: require2("vue"),
Codex: require2("@wikimedia/codex")
}));
}
function createDialogMountIfNeeded() {
if (!document.getElementById("review-tool-dialog-mount")) {
const mountPoint = document.createElement("div");
mountPoint.id = "review-tool-dialog-mount";
document.body.appendChild(mountPoint);
}
return document.getElementById("review-tool-dialog-mount");
}
function mountApp(app) {
createDialogMountIfNeeded();
_mountedApp = app.mount("#review-tool-dialog-mount");
return _mountedApp;
}
function getMountedApp() {
return _mountedApp;
}
function removeDialogMount() {
const mountPoint = document.getElementById("review-tool-dialog-mount");
if (mountPoint) mountPoint.remove();
_mountedApp = null;
}
function registerCodexComponents(app, Codex) {
if (!app || !Codex) return;
try {
app.component("cdx-dialog", Codex.CdxDialog).component("cdx-text-input", Codex.CdxTextInput).component("cdx-text-area", Codex.CdxTextArea).component("cdx-checkbox", Codex.CdxCheckbox).component("cdx-select", Codex.CdxSelect).component("cdx-button", Codex.CdxButton).component("cdx-button-group", Codex.CdxButtonGroup);
} catch (e) {
}
}
// src/vote_dialog.ts
function createVoteDialog(sectionID) {
loadCodexAndVue().then(({ Vue, Codex }) => {
const app = Vue.createMwApp({
i18n: {
dialogTitle: state_default.convByVar({
hant: `投票助手 (Voter v${state_default.version})`,
hans: `投票助手 (Voter v${state_default.version})`
}),
submitting: state_default.convByVar({ hant: "儲存中…", hans: "保存中…" }),
submit: state_default.convByVar({ hant: "儲存投票", hans: "保存投票" }),
cancel: state_default.convByVar({ hant: "取消", hans: "取消" }),
next: state_default.convByVar({ hant: "下一步", hans: "下一步" }),
previous: state_default.convByVar({ hant: "上一步", hans: "上一步" }),
// Step 0: Entry Selection
selectEntries: state_default.convByVar({ hant: "投票條目", hans: "投票条目" }),
selectEntriesPlaceholder: state_default.convByVar({ hant: "選擇要投票的條目", hans: "选择要投票的条目" }),
// Step 1: Template Selection
selectTemplates: state_default.convByVar({ hant: "投票模板", hans: "投票模板" }),
selectTemplatesPlaceholder: state_default.convByVar({ hant: "選擇投票模板", hans: "选择投票模板" }),
voteReason: state_default.convByVar({ hant: "投票理由(可不填;無須簽名)", hans: "投票理由(可不填;无须签名)" }),
voteReasonPlaceholder: state_default.convByVar({ hant: "輸入投票理由…", hans: "输入投票理由…" }),
useBulleted: state_default.convByVar({ hant: "使用 * 縮排", hans: "使用 * 缩进" }),
// Step 2: Preview
previewHeading: state_default.convByVar({ hant: "預覽投票內容", hans: "预览投票内容" }),
previewInfo: state_default.convByVar({ hant: "以下是將要提交的投票內容:", hans: "以下是将要提交的投票内容:" }),
votingFor: state_default.convByVar({ hant: "投票條目:", hans: "投票条目:" }),
voteContent: state_default.convByVar({ hant: "投票內容:", hans: "投票内容:" }),
// Validation
noEntriesSelected: state_default.convByVar({ hant: "請選擇至少一個投票條目。", hans: "请选择至少一个投票条目。" }),
noTemplatesSelected: state_default.convByVar({ hant: "請選擇至少一個投票模板。", hans: "请选择至少一个投票模板。" })
},
data() {
return {
open: true,
isSubmitting: false,
currentStep: 0,
totalSteps: 3,
// Step 0: Entry selection
selectedEntries: [sectionID],
entryInfoHtml: "",
isLoadingInfo: false,
// Step 1: Template & reason
selectedTemplates: state_default.validVoteTemplates.length > 0 ? [state_default.validVoteTemplates[0].data] : [],
voteMessage: "",
useBulleted: true
};
},
computed: {
entryOptions() {
return state_default.sectionTitles.map((item) => ({ value: item.data, label: item.label }));
},
validTemplateOptions() {
return (state_default.validVoteTemplates || []).map((item) => ({ value: item.data, label: item.label }));
},
invalidTemplateOptions() {
return (state_default.invalidVoteTemplates || []).map((item) => ({ value: item.data, label: item.label }));
},
allTemplateOptions() {
return [...this.validTemplateOptions, ...this.invalidTemplateOptions];
},
selectedEntryNames() {
return this.selectedEntries.map((id) => {
const entry = state_default.sectionTitles.find((x) => x.data === id);
return entry ? entry.label : `Section ${id}`;
});
},
previewVoteText() {
let VTReason = this.selectedTemplates.map((str) => `{{${str}}}`).join(";");
const message = (this.voteMessage || "").trim();
VTReason += message ? ":" + message : "。";
VTReason += "--~~~~";
return VTReason;
},
primaryAction() {
if (this.currentStep < this.totalSteps - 1) {
return { label: this.$options.i18n.next, actionType: "primary", disabled: false };
}
return {
label: this.isSubmitting ? this.$options.i18n.submitting : this.$options.i18n.submit,
actionType: "progressive",
disabled: this.isSubmitting
};
},
defaultAction() {
if (this.currentStep > 0) {
return { label: this.$options.i18n.previous };
}
return { label: this.$options.i18n.cancel };
}
},
watch: {
selectedEntries: {
handler: "loadEntryInfo",
immediate: true
}
},
methods: {
getStepClass(step) {
return { "voter-multistep-dialog__stepper__step--active": step <= this.currentStep };
},
async loadEntryInfo() {
if (!this.selectedEntries.length) {
this.entryInfoHtml = "";
return;
}
this.isLoadingInfo = true;
const infoPromises = this.selectedEntries.map(async (id) => {
var _a;
const entryName = ((_a = state_default.sectionTitles.find((x) => x.data === id)) == null ? void 0 : _a.label) || "";
if (!entryName) return "";
return await getXToolsInfo(entryName);
});
const results = await Promise.all(infoPromises);
this.entryInfoHtml = results.filter(Boolean).map((html) => `<div style="margin-top:0.5em">${html}</div>`).join("");
this.isLoadingInfo = false;
},
validateStep0() {
if (!this.selectedEntries.length) {
mw.notify(this.$options.i18n.noEntriesSelected, { type: "error", title: "[Voter]" });
return false;
}
return true;
},
validateStep1() {
if (!this.selectedTemplates.length) {
mw.notify(this.$options.i18n.noTemplatesSelected, { type: "error", title: "[Voter]" });
return false;
}
return true;
},
onPrimaryAction() {
if (this.currentStep === 0 && !this.validateStep0()) {
return;
}
if (this.currentStep === 1 && !this.validateStep1()) {
return;
}
if (this.currentStep < this.totalSteps - 1) {
this.currentStep++;
return;
}
this.submitVote();
},
onDefaultAction() {
if (this.currentStep > 0) {
this.currentStep--;
return;
}
this.closeDialog();
},
onUpdateOpen(newValue) {
if (!newValue) {
this.closeDialog();
}
},
closeDialog() {
this.open = false;
setTimeout(() => {
removeDialogMount();
}, 300);
},
async submitVote() {
this.isSubmitting = true;
try {
const hasConflict = await vote(
this.selectedEntries,
this.selectedTemplates,
this.voteMessage,
this.useBulleted
);
if (hasConflict) {
this.isSubmitting = false;
return;
}
mw.notify(state_default.convByVar({ hant: "投票已成功提交。", hans: "投票已成功提交。" }), { tag: "voter" });
this.isSubmitting = false;
this.open = false;
setTimeout(() => {
removeDialogMount();
}, 300);
} catch (error) {
console.error("[Voter] submitVote failed:", error);
const msg = state_default.convByVar({ hant: "投票提交失敗,請稍後再試。", hans: "投票提交失败,请稍后再试。" });
mw.notify(msg, { type: "error", title: "[Voter]" });
this.isSubmitting = false;
}
}
},
template: `
<cdx-dialog
v-model:open="open"
:title="$options.i18n.dialogTitle"
:use-close-button="true"
:primary-action="primaryAction"
:default-action="defaultAction"
@primary="onPrimaryAction"
@default="onDefaultAction"
@update:open="onUpdateOpen"
class="voter-dialog voter-multistep-dialog"
>
<template #header>
<div class="voter-multistep-dialog__header-top">
<h2>{{ $options.i18n.dialogTitle }}</h2>
</div>
<div class="voter-multistep-dialog__stepper">
<div class="voter-multistep-dialog__stepper__label">{{ (currentStep + 1) + ' / ' + totalSteps }}</div>
<div class="voter-multistep-dialog__stepper__steps" aria-hidden="true">
<span
v-for="step of [0, 1, 2]"
:key="step"
class="voter-multistep-dialog__stepper__step"
:class="getStepClass(step)"
></span>
</div>
</div>
</template>
<!-- Step 0: Entry Selection -->
<div v-if="currentStep === 0" class="voter-form-section">
<label class="voter-form-label">{{ $options.i18n.selectEntries }}</label>
<div class="voter-checkbox-grid">
<cdx-checkbox
v-for="option in entryOptions"
:key="option.value"
v-model="selectedEntries"
:input-value="option.value"
>
{{ option.label }}
</cdx-checkbox>
</div>
<div
v-if="entryInfoHtml"
class="voter-entry-info"
v-html="entryInfoHtml"
></div>
<div v-else-if="isLoadingInfo" class="voter-entry-info voter-entry-info--loading">
載入中...
</div>
</div>
<!-- Step 1: Template Selection & Reason -->
<div v-else-if="currentStep === 1" class="voter-form-section">
<label class="voter-form-label">{{ $options.i18n.selectTemplates }}</label>
<div class="voter-checkbox-grid">
<cdx-checkbox
v-for="option in allTemplateOptions"
:key="option.value"
v-model="selectedTemplates"
:input-value="option.value"
>
{{ option.label }}
</cdx-checkbox>
</div>
<div class="voter-form-section">
<label class="voter-form-label">{{ $options.i18n.voteReason }}</label>
<cdx-text-area
v-model="voteMessage"
:placeholder="$options.i18n.voteReasonPlaceholder"
rows="3"
></cdx-text-area>
</div>
<div class="voter-form-section" style="padding-top: 0;">
<cdx-checkbox v-model="useBulleted">
{{ $options.i18n.useBulleted }}
</cdx-checkbox>
</div>
</div>
<!-- Step 2: Preview -->
<div v-else-if="currentStep === 2" class="voter-preview-section">
<h3>{{ $options.i18n.previewHeading }}</h3>
<p>{{ $options.i18n.previewInfo }}</p>
<div class="voter-preview-item">
<strong>{{ $options.i18n.votingFor }}</strong>
<ul>
<li v-for="name in selectedEntryNames" :key="name">{{ name }}</li>
</ul>
</div>
<div class="voter-preview-item">
<strong>{{ $options.i18n.voteContent }}</strong>
<pre class="voter-preview-code">{{ previewVoteText }}</pre>
</div>
</div>
</cdx-dialog>
`
});
registerCodexComponents(app, Codex);
mountApp(app);
}).catch((error) => {
console.error("[Voter] 無法加載 Codex:", error);
mw.notify(state_default.convByVar({ hant: "無法加載對話框組件。", hans: "无法加载对话框组件。" }), {
type: "error",
title: "[Voter]"
});
});
}
function openVoteDialog(sectionID) {
if (getMountedApp && getMountedApp()) removeDialogMount();
createVoteDialog(sectionID);
}
window.openVoteDialog = openVoteDialog;
// src/dom.ts
function addVoteButtons() {
if (document.querySelector("#voter-finished-loading")) {
return;
}
state_default.sectionTitles = [];
let headingSelector;
if (state_default.pageName === "Wikipedia:新条目推荐/候选") {
headingSelector = "div.mw-heading.mw-heading4";
} else {
headingSelector = "div.mw-heading.mw-heading2";
}
$(headingSelector).each((index, element) => {
let $element = $(element);
let anchor;
if (state_default.pageName === "Wikipedia:新条目推荐/候选") {
anchor = $element.nextUntil(headingSelector, "ul").find("li .anchor").attr("id");
} else {
anchor = $element.find("h2").attr("id");
}
if (anchor) {
let sectionID = getSectionID(index + 1);
const $voteLink = $("<a>").text(state_default.convByVar({ hant: "投票", hans: "投票" })).css({ "cursor": "pointer", "margin-left": "0.25em" });
$voteLink.on("click", (e) => {
e.preventDefault();
openVoteDialog(sectionID);
});
$('<span class="mw-editsection-bracket">|</span> ').insertAfter($element.find("span.mw-editsection > a").first());
$voteLink.insertAfter($element.find("span.mw-editsection > a").first().next());
state_default.sectionTitles.push({ data: sectionID, label: anchor.replace(/_/g, " ") });
}
});
console.log(`[Voter] 已識別可投票事項共 ${state_default.sectionTitles.length} 項。`);
let finishedLoading = document.createElement("div");
finishedLoading.id = "voter-finished-loading";
finishedLoading.style.display = "none";
document.querySelector("#mw-content-text .mw-parser-output").appendChild(finishedLoading);
}
function getSectionID(childid) {
try {
let $heading;
if (state_default.pageName === "Wikipedia:新条目推荐/候选") {
$heading = $("div.mw-heading.mw-heading4").eq(childid - 1);
} else {
$heading = $("div.mw-heading.mw-heading2").eq(childid - 1);
}
let $editlink = $heading.find("span.mw-editsection > a");
let href = $editlink.attr("href");
if (!href) throw new Error("No href found");
let match = href.match(/section=(\\d+)/);
if (match) return +match[1];
let parts = href.split("&");
for (let part of parts) {
if (part.startsWith("section=")) return +part.split("=")[1].replace(/^T-/, "");
}
} catch (e) {
console.log(`[Voter] Failed to get section ID for child ${childid}`);
throw e;
}
return 0;
}
function titleVariants(title) {
let us = title.replace(/ /g, "_");
let sp = title.replace(/_/g, " ");
return [title, us, sp, us.charAt(0).toUpperCase() + us.slice(1), sp.charAt(0).toUpperCase() + sp.slice(1)];
}
function textMatchTitleVariants(text, title) {
return titleVariants(title).some((variant) => text.includes(variant));
}
function addIndent(text, indent) {
return text.replace(/^/gm, indent);
}
function refreshPage(entryName) {
location.href = mw.util.getUrl(state_default.pageName + "#" + entryName);
location.reload();
}
async function vote(voteIDs, templates, message, useBulleted) {
var _a;
let VTReason = templates.map((str) => `{{${str}}}`).join(";");
message = message.trim();
VTReason += message ? ":" + message : "。";
VTReason += "--~~~~";
for (const id of voteIDs) {
let votedPageName = ((_a = state_default.sectionTitles.find((x) => x.data === id)) == null ? void 0 : _a.label) || `section ${id}`;
let indent = useBulleted ? "* " : ": ";
let destPage = state_default.pageName;
if (state_default.pageName === "Wikipedia:新条目推荐/候选") {
indent = useBulleted ? "** " : "*: ";
} else if (state_default.pageName === "Wikipedia:優良條目評選") {
destPage += "/提名區";
} else if (/^Wikipedia:(典范条目评选|特色列表评选)$/i.test(state_default.pageName)) {
destPage += "/提名区";
}
let text = addIndent(VTReason, indent);
let summary = `/* ${votedPageName} */ `;
summary += templates.join("、");
summary += " ([[User:SuperGrey/gadgets/voter|Voter]])";
if (await voteAPI(state_default.pageName, destPage, id, text, summary)) return true;
}
setTimeout(() => {
var _a2;
return refreshPage((_a2 = state_default.sectionTitles.find((x) => x.data === voteIDs[0])) == null ? void 0 : _a2.label);
}, 1e3);
return false;
}
// src/styles.css
var styles_default = "/* Voter Gadget Styles */\r\n\r\n/* Multi-step Dialog */\r\n.voter-dialog {\r\n max-width: 600px;\r\n}\r\n\r\n.voter-multistep-dialog__header-top {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 16px 24px 0;\r\n}\r\n\r\n.voter-multistep-dialog__header-top h2 {\r\n margin: 0;\r\n font-size: 18px;\r\n font-weight: 600;\r\n}\r\n\r\n.voter-multistep-dialog__stepper {\r\n display: flex;\r\n align-items: center;\r\n gap: 12px;\r\n padding: 12px 24px;\r\n border-bottom: 1px solid rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n.voter-multistep-dialog__stepper__label {\r\n font-size: 13px;\r\n color: #54595d;\r\n min-width: 36px;\r\n}\r\n\r\n.voter-multistep-dialog__stepper__steps {\r\n display: flex;\r\n gap: 6px;\r\n}\r\n\r\n.voter-multistep-dialog__stepper__step {\r\n display: block;\r\n width: 12px;\r\n height: 12px;\r\n border-radius: 999px;\r\n background-color: #c8ccd1;\r\n transition: background-color 0.2s ease;\r\n}\r\n\r\n.voter-multistep-dialog__stepper__step--active {\r\n background-color: #36c;\r\n}\r\n\r\n/* Form Sections */\r\n.voter-form-section {\r\n padding: 16px 0;\r\n}\r\n\r\n.voter-form-label {\r\n display: block;\r\n font-weight: 600;\r\n margin-bottom: 8px;\r\n font-size: 14px;\r\n}\r\n\r\n.voter-checkbox-grid {\r\n display: grid;\r\n grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));\r\n gap: 0 8px;\r\n}\r\n\r\n/* Entry Info */\r\n.voter-entry-info {\r\n margin-top: 12px;\r\n padding: 12px;\r\n background-color: #f8f9fa;\r\n border: 1px solid #eaecf0;\r\n border-radius: 4px;\r\n font-size: 13px;\r\n line-height: 1.5;\r\n}\r\n\r\n.voter-entry-info--loading {\r\n color: #72777d;\r\n font-style: italic;\r\n}\r\n\r\n/* Preview Section */\r\n.voter-preview-section h3 {\r\n margin: 0 0 12px;\r\n font-size: 16px;\r\n font-weight: 600;\r\n}\r\n\r\n.voter-preview-section p {\r\n margin: 0 0 16px;\r\n color: #54595d;\r\n}\r\n\r\n.voter-preview-item {\r\n margin-bottom: 16px;\r\n}\r\n\r\n.voter-preview-item strong {\r\n display: block;\r\n margin-bottom: 8px;\r\n font-size: 14px;\r\n}\r\n\r\n.voter-preview-item ul {\r\n margin: 0;\r\n padding-left: 24px;\r\n}\r\n\r\n.voter-preview-item li {\r\n font-size: 14px;\r\n line-height: 1.6;\r\n}\r\n\r\n.voter-preview-code {\r\n padding: 12px;\r\n background-color: #f8f9fa;\r\n border: 1px solid #eaecf0;\r\n border-radius: 4px;\r\n font-family: monospace;\r\n font-size: 13px;\r\n white-space: pre-wrap;\r\n word-break: break-word;\r\n margin: 0;\r\n}\r\n\r\n/* Dialog Footer - override Codex defaults */\r\n.voter-dialog footer {\r\n display: flex;\r\n align-items: center;\r\n justify-content: flex-end;\r\n gap: 12px;\r\n border-top: 1px solid rgba(0, 0, 0, 0.06);\r\n padding: 12px 24px;\r\n}\r\n";
// src/main.ts
function injectStyles(css) {
if (!css) return;
try {
const styleEl = document.createElement("style");
styleEl.appendChild(document.createTextNode(css));
document.head.appendChild(styleEl);
} catch (e) {
const div = document.createElement("div");
div.innerHTML = `<style>${css}</style>`;
document.head.appendChild(div.firstChild);
}
}
function validatePage(pageName) {
let validPages = [
{
name: "Wikipedia:新条目推荐/候选",
templates: [
{ data: "支持", label: "支持" },
{ data: "反對", label: "反對" },
{ data: "不合要求", label: "不合要求" },
{ data: "問題不當", label: "問題不當" }
]
},
{
name: "Wikipedia:優良條目評選",
templates: [
{ data: "yesGA", label: "符合優良條目標準" },
{ data: "noGA", label: "不符合優良條目標準" }
]
},
{
name: "Wikipedia:典范条目评选",
templates: [
{ data: "yesFA", label: "符合典範條目標準" },
{ data: "noFA", label: "不符合典範條目標準" }
]
},
{
name: "Wikipedia:特色列表评选",
templates: [
{ data: "yesFL", label: "符合特色列表標準" },
{ data: "noFL", label: "不符合特色列表標準" }
]
}
];
for (let page of validPages) {
if (pageName === page.name || new RegExp(`^${page.name}/`, "i").test(pageName)) {
state_default.validVoteTemplates = page.templates;
state_default.invalidVoteTemplates = [
"中立",
"意見",
"建議",
"疑問",
"同上",
"提醒"
].map((template) => ({
data: template,
label: template
}));
return true;
}
}
return false;
}
function init() {
if (typeof document !== "undefined") {
injectStyles(styles_default);
}
if (!validatePage(state_default.pageName)) {
console.log("[Voter] 不是目標頁面,小工具終止。");
return;
}
state_default.initHanAssist().then(() => {
console.log(`[Voter] 已載入,當前頁面為 ${state_default.pageName}。`);
mw.hook("wikipage.content").add(function() {
setTimeout(() => addVoteButtons(), 200);
});
});
}
init();
})();
// </nowiki>