User:Choihei/dchost.js
外观
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google Chrome、Firefox、Microsoft Edge及Safari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
// <nowiki>
/*
* 每年要更新的东西:
* topics 等变量,对应动员令的主题
** 确认各个主题在/con模板的参数名,以及整体贡献子页面的名字,对应修改topic_to_ic和topic_to_oc函数
* 批量替换项目页面名(第几次動員令、DCxx)
*/
(function($, mw) {
'use strict';
mw.loader.using(['jquery.ui', 'ext.gadget.site-lib']).done(function() {
const ic_page = 'Wikipedia:動員令/第二十二次動員令/龍虎榜';
const oc_page = 'Wikipedia:動員令/第二十二次動員令/整體貢獻';
const personal_page = 'Wikipedia:動員令/第二十二次動員令/個人貢獻';
const media_page = 'Wikipedia:動員令/第二十二次動員令/第二十二次動員令產生的多媒體項目';
const topics = [["大動員令"], ["歷史", "飲食料理", "公園、綠地及自然保護區"], ["亟需撰寫的條目", "非翻譯條目", "交通運輸", "工程技術"]];
const topics_s =[["大动员令"], ["历史", "饮食料理", "公园、绿地及自然保护区"], ["亟需撰写的条目", "非翻译条目", "交通运输", "工程技术"]]; // 仅用于显示,下同
const topics_t = [["大動員令"], ["歷史", "飲食料理", "公園、綠地及自然保護區"], ["亟需撰寫的條目", "非翻譯條目", "交通運輸", "工程技術"]];
const topic_group_name_s = ["大动员令", "中动员令", '小动员令'];
const topic_group_name_t = ["大動員令", "中動員令", '小動員令'];
const summary_postfix = ' via [[User:WhitePhosphorus/js/dchost.js|dchost.js]]';
// 统一把主题转为繁体字
const normalize_topic = function(topic) {
topic = topic.trim();
for (let [i, group] of Object.entries(topics_s)) {
for (let [j, topic_s] of Object.entries(group)) {
if (topic_s === topic) {
return topics[i][j];
}
}
}
return topic;
};
// 用主题得到/con模板中的参数名
const topic_to_ic = function(topic) {
if ("歷史" === topic) return "歷";
if ("飲食料理" === topic) return "食";
if ("公園、綠地及自然保護區" === topic) return "園";
if ("非翻譯條目" === topic) return "原";
return topic[0];
};
// 用主题得到整体贡献子页面的名字
const topic_to_oc = function(topic) {
if ("大動員令" === topic) {
return topic;
}
return topic + "類";
};
// 用主题得到/art模板中填的数字类别
const topic_to_type = function(topic) {
for (let i = 0; i < topics.length; ++i) {
if (topics[i].includes(topic)) {
return String(i+1);
}
}
return undefined;
};
// 用主题生成下拉选单
const topics_to_select = function(art) {
let html = '<select>\n';
for (let [i, group] of Object.entries(topics)) {
html += `<optgroup label="${wgULS(topic_group_name_s[i], topic_group_name_t[i])}">\n`;
for (let [j, topic] of Object.entries(group)) {
html += `<option value="${topic}"${art.topic == topic ? " selected" : ""}>${wgULS(topics_s[i][j], topics_t[i][j])}</option>`;
}
}
return html + '</select>';
};
const dctemplate = 'DC22/art';
const ictemplate = 'DC22/con';
const talktemplate = 'DC22/talk';
const url = (title) => mw.config.get('wgArticlePath').replace('$1', title);
// `type` is deprecated
const Article = function(id, title, length, type, topic, quality, qualitycheck, improve, result, result_improve, result_ref, result_spoken, result_pic, result_video, score, comment, resulttext, improvetext, ref, spoken, pic, video, refnum, spokenlen, picnum, fpicnum, pictranslen, videolen, mediafiles, autocheck, nsfail, sizefail) {
this.id = id;
this.title = title;
this.length = length || '';
this.topic = topic || '';
this.type = topic_to_type(this.topic) || "1"; // 1-3 (字符串) 分别是大中小动员令
this.quality = (quality || '').toUpperCase();
this.result_quality = qualitycheck || false;
this.improve = improve || false; // 参加者是否申报了改善条目
this.result = (typeof result === 'number' && result === result ? result : -1); // -1: 不改变; 0: 不通过; 1: 通过; 2: 待定
this.result_improve = (typeof result_improve === 'number' && result_improve === result_improve ? result_improve : -1); // result 是 -1 的时候这项的值不会被考虑,下同。
this.result_ref = (typeof result_ref === 'number' && result_ref === result_ref ? result_ref : -1);
this.result_spoken = (typeof result_spoken === 'number' && result_spoken === result_spoken ? result_spoken : -1);
this.result_pic = (typeof result_pic === 'number' && result_pic === result_pic ? result_pic : -1);
this.result_video = (typeof result_video === 'number' && result_video === result_video ? result_video : -1);
this.score = score || '';
this.comment = comment || '';
this.resulttext = resulttext || ''; // 自定义审核结果显示文字
this.improvetext = improvetext || ''; // 自定义改善条目审核结果显示文字
this.ref = ref || false; // 参考来源加分
this.spoken = spoken || false; // 有声条目加分
this.spokenlen = (typeof spokenlen === 'number' ? spokenlen||0 : 0);
this.pic = pic || false; // 原创条目加分
this.video = video || false; // 原创影片加分
this.refnum = (typeof refnum === 'number' ? refnum||0 : 0); // 有几个符合条件的参考来源
this.picnum = (typeof picnum === 'number' ? picnum||0 : 0); // 有几张一般原创图片
this.fpicnum = (typeof fpicnum === 'number' ? fpicnum||0 : 0); // 有几张特色原创图片
this.pictranslen = (typeof pictranslen === 'number' ? pictranslen||0 : 0); // 翻译图片总字节数
this.videolen = (typeof videolen === 'number' ? videolen||0 : 0); // 原创影片总长度
this.mediafiles = mediafiles.filter(f => f.trim().length) || []; // 多媒体文件列表
this.autocheck = autocheck || false;
this.nsfail = nsfail || false; // 名字空间错误
this.sizefail = sizefail || false; // 长度未达标
return this;
};
const header_html = (id) => `
<div id="p4js-dchost-dialog" title="${wgULS('动员令条目评审', '動員令條目評審')}">
<table id="${id}"><tbody style="display:block; overflow-y:scroll; overflow-x:scroll; width: 100%; height: 100%">
<tr>
<th>${wgULS('条目名', '條目名')}</th>
<th>${wgULS('类别', '類別')}</th>
<th>${wgULS('质量', '質量')}</th>
<th>${wgULS('长度', '長度')}</th>
<th>${wgULS('评审结果', '評審結果')}</th>
<th>改善工程</th>
<th>${wgULS('来源加分', '來源加分')}</th>
<th>${wgULS('有声条目', '有聲條目')}</th>
<th>${wgULS('图片加分', '圖片加分')}</th>
<th>${wgULS('视频加分', '影片加分')}</th>
<th>${wgULS('分数', '分數')}</th>
<th>${wgULS('新增多媒体项目', '新增多媒體項目')}</th>
<th>${wgULS('错误消息', '錯誤訊息')}</th>
</tr>
`;
// 可供用户调整参数
const render_article = (art) => `
<tr id="p4js-dchosttable-row${art.id}">
<th><a href="${url(art.title)}">${art.title}</a></th>
<th>${topics_to_select(art)}</th>
<th><select id="p4js-dchost-select-quality${art.id}">
<option value="FL"${art.quality === "FL" ? " selected" : ""}>特色列表</option>
<option value="FA"${art.quality === "FA" ? " selected" : ""}>${wgULS('典范条目', '典範條目')}</option>
<option value="GA"${art.quality === "GA" ? " selected" : ""}>${wgULS('优良条目', '優良條目')}</option>
<option value=""${art.quality === "" ? " selected" : ""}>${wgULS('达标条目', '達標條目')}</option>
</select>
<br><label id="p4js-dchost-label-qualitychecked${art.id}" style="${art.quality === "" ? 'display:none' : ''}"><input type="checkbox"${art.result_quality ? " checked" : ""}>${wgULS('已通过', '已通過')}</label></th>
<th><input type="text" value="${art.length}"><br><a class="p4js-dchost-rm-redundance-button" title="${art.title}" href="#">${wgULS('去除冗余', '去除冗餘')}</a></th>
<th><select>
<option value="1"${art.result === 1 ? " selected" : ""}>${wgULS('通过', '通過')}</option>
<option value="2"${art.result === 2 ? " selected" : ""}>${wgULS('待审核', '待審核')}</option>
<option value="0"${art.result === 0 ? " selected" : ""}>${wgULS('不通过', '不通過')}</option>
<option value="-1"${art.result === -1 ? " selected" : ""}>(不更改)</option>
</select>
<br><label>${wgULS('显示', '顯示')}<input type="text" value="${art.resulttext}"></label>
<br><label>${wgULS('评语', '評語')}<input type="text" value="${art.comment}"></label>
</th>
<th><label><input type="checkbox"${art.improve ? " checked" : ""}>${wgULS('是否申请', '是否申請')}</label>
<select>
<option value="1"${art.result_improve === 1 ? " selected" : ""}>${wgULS('通过', '通過')}</option>
<option value="2"${art.result_improve === 2 ? " selected" : ""}>${wgULS('待审核', '待審核')}</option>
<option value="0"${art.result_improve === 0 ? " selected" : ""}>${wgULS('不通过', '不通過')}</option>
<option value="-1"${art.result_improve === -1 ? " selected" : ""}>(不更改)</option>
</select>
<br><label>${wgULS('结果显示文字', '結果顯示文字')}<input type="text" value="${art.improvetext}"></label>
</th>
<th><label><input type="checkbox"${art.ref ? " checked" : ""}>${wgULS('是否申请', '是否申請')}</label>
<select>
<option value="1"${art.result_ref === 1 ? " selected" : ""}>${wgULS('通过', '通過')}</option>
<option value="2"${art.result_ref === 2 ? " selected" : ""}>${wgULS('待审核', '待審核')}</option>
<option value="0"${art.result_ref === 0 ? " selected" : ""}>${wgULS('不通过', '不通過')}</option>
<option value="-1"${art.result_ref === -1 ? " selected" : ""}>(不更改)</option>
</select>
<br><label>${wgULS('参考来源数', '参考來源數')}<input type="text" value="${art.refnum}"></label>
</th>
<th><label><input type="checkbox"${art.spoken ? " checked" : ""}>${wgULS('是否申请', '是否申請')}</label>
<select>
<option value="1"${art.result_spoken === 1 ? " selected" : ""}>${wgULS('通过', '通過')}</option>
<option value="2"${art.result_spoken === 2 ? " selected" : ""}>${wgULS('待审核', '待審核')}</option>
<option value="0"${art.result_spoken === 0 ? " selected" : ""}>${wgULS('不通过', '不通過')}</option>
<option value="-1"${art.result_spoken === -1 ? " selected" : ""}>(不更改)</option>
</select>
<br><label>${wgULS('正文字节数', '正文位元組數')}<input type="text" value="${art.spokenlen}"></label>
</th>
<th><label><input type="checkbox"${art.pic ? " checked" : ""}>${wgULS('是否申请', '是否申請')}</label>
<select>
<option value="1"${art.result_pic === 1 ? " selected" : ""}>${wgULS('通过', '通過')}</option>
<option value="2"${art.result_pic === 2 ? " selected" : ""}>${wgULS('待审核', '待審核')}</option>
<option value="0"${art.result_pic === 0 ? " selected" : ""}>${wgULS('不通过', '不通過')}</option>
<option value="-1"${art.result_pic === -1 ? " selected" : ""}>(不更改)</option>
</select>
<br><label>${wgULS('一般原创图片总张数', '一般原創圖片總計張數')}<input type="text" value="${art.picnum}"></label>
<br><label>${wgULS('特色原创图片总张数', '特色原創圖片總計張數')}<input type="text" value="${art.fpicnum}"></label>
<br><label>${wgULS('翻译图片字节数', '翻譯圖片位元組數')}<input type="text" value="${art.pictranslen}"></label>
</th>
<th><label><input type="checkbox"${art.video ? " checked" : ""}>${wgULS('是否申请', '是否申請')}</label>
<select>
<option value="1"${art.result_video === 1 ? " selected" : ""}>${wgULS('通过', '通過')}</option>
<option value="2"${art.result_video === 2 ? " selected" : ""}>${wgULS('待审核', '待審核')}</option>
<option value="0"${art.result_video === 0 ? " selected" : ""}>${wgULS('不通过', '不通過')}</option>
<option value="-1"${art.result_video === -1 ? " selected" : ""}>(不更改)</option>
</select>
<br><label>${wgULS('总长度', '總長度')}<input type="text" value="${art.videolen}"></label>
</th>
<th><input type="text" value="${art.score}" placeholder="${wgULS('留空则由系统计算', '留空則由系統計算')}"></th>
<th><label>${wgULS('多媒体项目列表,一行一个,不要加File:前缀:', '多媒體項目列表,一行一個,不要加File:前綴:')}</label>
<a href="#" class="p4js-dchost-load-files" title="${art.title}">${wgULS('加载所有文件', '載入所有檔案')}</a>
<textarea class="p4js-dchost-files" title="${art.title}">${art.mediafiles.join('\n')}</textarea>
</th>
<th><label><input type="checkbox"${art.autocheck ? " checked" : ""}>${wgULS('启用自动检验', '啟用自動檢驗')}</label>
<br><label><input type="checkbox"${art.nsfail ? " checked" : ""}>${wgULS('名字空间错误', '名字空間錯誤')}</label>
<br><label><input type="checkbox"${art.sizefail ? " checked" : ""}>${wgULS('长度未达标', '長度未達標')}</label>
</th>
</tr>
`;
// 不可调整参数,用于显示编辑前的情况
const show_article = (art) => `
<tr id="p4js-dchosttable-old-row${art.id}">
<th><a href="${url(art.title)}">${art.title}</a></th>
<th>${art.topic}</th>
<th>${({"FL":"特色列表","FA":wgULS('典范条目', '典範條目'),"GA":wgULS('优良条目', '優良條目'),"":wgULS('达标条目', '達標條目')})[art.quality]}${art.result_quality ? wgULS('已通过', '已通過') : wgULS('评选中', '評選中')}</th>
<th>${art.length}</th>
<th>${({"1":wgULS('通过', '通過'),"2":wgULS('待审核', '待審核'),"0":wgULS('不通过', '不通過'),"-1":"(不更改)"})[art.result]}
<br>${wgULS('自定显示:', '自定顯示:') + art.resulttext}
<br>${wgULS('评语:', '評語:') + art.comment}
</th>
<th>${(art.improve ? "已" : "未") + wgULS('申请,', '申請,')}${({"1":wgULS('通过', '通過'),"2":wgULS('待审核', '待審核'),"0":wgULS('不通过', '不通過'),"-1":"(不更改)"})[art.result_improve]}
<br>${wgULS('自定显示:', '自定顯示:') + art.improvetext}</th>
<th>${(art.ref ? "已" : "未") + wgULS('申请,', '申請,')}${({"1":wgULS('通过', '通過'),"2":wgULS('待审核', '待審核'),"0":wgULS('不通过', '不通過'),"-1":"(不更改)"})[art.result_ref]}
<br>${wgULS('参考来源数:', '参考來源數:') + art.refnum}</th>
<th>${(art.spoken ? "已" : "未") + wgULS('申请,', '申請,')}${({"1":wgULS('通过', '通過'),"2":wgULS('待审核', '待審核'),"0":wgULS('不通过', '不通過'),"-1":"(不更改)"})[art.result_spoken]}
<br>${wgULS('正文字节数:', '正文位元組數:') + art.spokenlen}</th>
<th>${(art.pic ? "已" : "未") + wgULS('申请,', '申請,')}${({"1":wgULS('通过', '通過'),"2":wgULS('待审核', '待審核'),"0":wgULS('不通过', '不通過'),"-1":"(不更改)"})[art.result_pic]}
<br>${wgULS('一般原创图片张数:', '一般原創圖片張數:') + art.picnum}
<br>${wgULS('特色原创图片张数:', '特色原創圖片張數:') + art.fpicnum}
<br>${wgULS('翻译图片字节数:', '翻譯圖片位元組數:') + art.pictranslen}</th>
<th>${(art.video ? "已" : "未") + wgULS('申请,', '申請,')}${({"1":wgULS('通过', '通過'),"2":wgULS('待审核', '待審核'),"0":wgULS('不通过', '不通過'),"-1":"(不更改)"})[art.result_video]}
<br>${wgULS('总长度:', '總長度:') + art.videolen}</th>
<th>${art.score}</th>
<th></th>
<th>${(art.autocheck ? "已" : "未") + wgULS('启用自动检验,', '啟用自動檢驗,')}
<br>${(art.nsfail ? "" : "不") + wgULS('显示错误信息“名字空间错误”', '顯示錯誤訊息「名字空間錯誤」')}
<br>${(art.sizefail ? "" : "不") + wgULS('显示错误信息“长度未达标”', '顯示錯誤訊息「長度未達標」')}</th>
</tr>
`;
const tail_html = `
</tbody></table></div>
`;
const preview_html = `
<div>
<p><a href="#" id="p4js-dchost-preview">${wgULS('预览(如果改动了上方表格,请务必点击)', '預覽(如果改動了上方表格,請務必點按)')}</a></p>
<p>${wgULS('以下是小工具生成的源码,请检查无误后再编辑,如有误可修改:', '以下是小工具生成的原始碼,請檢查無誤後再編輯,如有誤可修改:')}</p>
<textarea id="p4js-dchost-preview-source" style="width: 100%; height: 500px"></textarea>
</div>
`;
// 用于计算去除冗余源码后条目的有效长度
const rm_redundance_html = (title) => `
<div id="p4js-dchost-rm-redundance-dialog" title="${wgULS('去除冗余源码', '去除冗餘原始碼')}">
<p>${wgULS('条目名', '條目名')}:<a id="p4js-dchost-rm-redundance-article" href="${url(title)}">${title}</a>(<a id="p4js-dchost-rm-redundance-article-edit" href="${url(title)}?action=edit">${wgULS('编辑', '編輯')}</a> - <a id="p4js-dchost-rm-redundance-article-history" href="${url(title)}?action=history">${wgULS('历史', '歷史')}</a>)</p>
<div>
<textarea id="p4js-dchost-rm-redundance-new-before" style="float: left; width: 50%; height: 320px"></textarea>
<textarea id="p4js-dchost-rm-redundance-new-after" style="float: left; width: 50%; height: 320px"></textarea>
</div>
<div><p>
<a id="p4js-dchost-rm-redundance-new-load" href="#">${wgULS('载入当前版本源代码', '載入當前版本原始碼')}</a> - <a id="p4js-dchost-rm-redundance-new" href="#">${wgULS('开始', '開始')}</a><br>
${wgULS('去除前字节数', '去除前位元組數')}:<span id="p4js-dchost-rm-redundance-new-len-before">0</span>;${wgULS('去除后字节数', '去除後位元組數')}:<span id="p4js-dchost-rm-redundance-new-len-after">0</span>;${wgULS('差异', '差異')}:<span id="p4js-dchost-rm-redundance-new-len-diff">0</span>
</p></div>
<div>
<textarea id="p4js-dchost-rm-redundance-old-before" style="float: left; width: 50%; height: 320px"></textarea>
<textarea id="p4js-dchost-rm-redundance-old-after" style="float: left; width: 50%; height: 320px"></textarea>
</div>
<div><p>
<input id="p4js-dchost-rm-redundance-old-revision" value="" type="text"><a id="p4js-dchost-rm-redundance-old-load" href="#">${wgULS('载入对应版本源代码', '載入對應版本原始碼')}</a> - <a id="p4js-dchost-rm-redundance-old" href="#">${wgULS('开始', '開始')}</a><br>
${wgULS('去除前字节数', '去除前位元組數')}:<span id="p4js-dchost-rm-redundance-new-len-before">0</span>;${wgULS('去除后字节数', '去除後位元組數')}:<span id="p4js-dchost-rm-redundance-new-len-after">0</span>;${wgULS('差异', '差異')}:<span id="p4js-dchost-rm-redundance-new-len-diff">0</span><br>
<b>${wgULS('两版本处理后字节数差异', '兩版本處理後位元組數差異')}</b>:<span id="p4js-dchost-rm-redundance-len-diff">0</span>
</p></div>
</div>
`;
const comment_regex = /<!--.*?-->|<nowiki>.*?<\/nowiki>/g;
// 本脚本只支持提报时 {{DCxx/art}} 模板在同一行的情况
// 否则要修改这里的正则以及后续的处理逻辑(比如不能直接用换行符 split)
const articles_regex = /\|([^=|]+)=\s*((#.+\n)+)/g;
const topics_regex = /\|([^=|]+)= *\n?/g;
const score_regex = /(([\d\.]+)分)/;
const oc_regex = /(==\s*貢獻總計\s*==)([^]+?\n)(==\s*贡献明細\s*==\n)/;
const wikitext_to_article = function(wikitext, topic, id) {
let title, result;
let length, type, quality, qualitycheck, improve, result_improve, result_ref, result_spoken, result_pic, result_video, score, comment, resulttext, improvetext, ref, spoken, pic, video, refnum, spokenlen, picnum, fpicnum, pictranslen, videolen, nsfail, sizefail;
let match = (new RegExp(`#\\s*{{\\s*${dctemplate}\\s*\\|(.+)}}`)).exec(wikitext);
if (match === null) return null;
let autocheck = true; // default true
let params = match[1].split('|');
for (let param of params) {
let p = $.trim(param);
if (p.startsWith('type=')) {
type = p.slice('type='.length);
} else if (p.startsWith('length=')) {
length = p.slice('length='.length).replace(',', '');
} else if (p.startsWith('quality=')) {
quality = p.slice('quality='.length);
} else if (p.startsWith('qualitycheck=')) {
qualitycheck = p.slice('qualitycheck='.length) === 'O';
} else if (p.startsWith('improve=')) {
improve = p.slice('improve='.length) === '1';
} else if (p.startsWith('improvecheck=')) {
result_improve = ({'O':1,'X':0,'?':2})[p.slice('improvecheck='.length)];
} else if (p.startsWith('manualscoring=')) {
score = p.slice('manualscoring='.length);
} else if (p.startsWith('hostcomments=')) {
comment = p.slice('hostcomments='.length);
} else if (p.startsWith('improvetext=')) {
improvetext = p.slice('improvetext='.length);
} else if (p.startsWith('ref=')) {
ref = p.slice('ref='.length) === '1';
} else if (p.startsWith('refcheck=')) {
result_ref = ({'O':1,'X':0,'?':2})[p.slice('refcheck='.length)];
} else if (p.startsWith('refnum=')) {
refnum = parseInt(p.slice('refnum='.length));
} else if (p.startsWith('spoken=')) {
spoken = p.slice('spoken='.length) === '1';
} else if (p.startsWith('spokenlength=')) {
spokenlen = parseInt(p.slice('spokenlength='.length).replace(',', ''));
} else if (p.startsWith('pic=')) {
pic = p.slice('pic='.length) === '1';
} else if (p.startsWith('picnum=')) {
picnum = parseInt(p.slice('picnum='.length));
} else if (p.startsWith('fpicnum=')) {
fpicnum = parseInt(p.slice('fpicnum='.length));
} else if (p.startsWith('pictranslength=')) {
pictranslen = parseInt(p.slice('pictranslength='.length).replace(',', ''));
} else if (p.startsWith('video=')) {
video = p.slice('video='.length) === '1';
} else if (p.startsWith('videolength=')) {
videolen = parseInt(p.slice('videolength='.length).replace(',', ''));
} else if (p.startsWith('spokencheck=')) {
result_spoken = ({'O':1,'X':0,'?':2})[p.slice('spokencheck='.length)];
} else if (p.startsWith('piccheck=')) {
result_pic = ({'O':1,'X':0,'?':2})[p.slice('piccheck='.length)];
} else if (p.startsWith('videocheck=')) {
result_video = ({'O':1,'X':0,'?':2})[p.slice('videocheck='.length)];
} else if (p.startsWith('autocheck=')) {
autocheck = p.slice('autocheck='.length) !== '0';
} else if (p.startsWith('ns=')) {
nsfail = p.slice('ns='.length) === '1';
} else if (p.startsWith('size=')) {
sizefail = p.slice('size='.length) === '1';
} else {
if (title === undefined) {
title = p;
} else if (result === undefined) {
if (p === 'O') result = 1;
else if (p === 'X') result = 0;
else if (p === '?') result = 2;
else result = -1;
} else {
resulttext = p;
}
}
}
return new Article(id, title, length, type, topic, quality, qualitycheck, improve, result, result_improve, result_ref, result_spoken, result_pic, result_video, score, comment, resulttext, improvetext, ref, spoken, pic, video, refnum, spokenlen, picnum, fpicnum, pictranslen, videolen, [], autocheck, nsfail, sizefail);
};
const article_to_wikitext = function(art) {
let rst = `#{{${dctemplate}|${art.title}|type=${art.type}|length=${art.length}`;
if (art.quality) {
rst += ('|quality=' + art.quality);
if (art.result_quality)
rst += '|qualitycheck=O';
}
if (art.improve) {
rst += '|improve=1';
if (art.result_improve !== -1)
rst += ('|improvecheck=' + ({1:'O',2:'?',0:'X'})[art.result_improve]);
if (art.improvetext)
rst += ('|improvetext=' + art.improvetext);
}
if (art.ref) {
rst += '|ref=1|refnum=' + art.refnum;
if (art.result_ref !== -1)
rst += ('|refcheck=' + ({1:'O',2:'?',0:'X'})[art.result_ref]);
}
if (art.spoken) {
rst += ('|spoken=1|spokenlength=' + art.spokenlen);
if (art.result_spoken !== -1)
rst += ('|spokencheck=' + ({1:'O',2:'?',0:'X'})[art.result_spoken]);
}
if (art.pic) {
rst += '|pic=1';
if (art.picnum)
rst += ('|picnum=' + art.picnum);
if (art.fpicnum)
rst += ('|fpicnum=' + art.fpicnum);
if (art.pictranslen)
rst += ('|pictranslength=' + art.pictranslen);
if (art.result_pic !== -1)
rst += ('|piccheck=' + ({1:'O',2:'?',0:'X'})[art.result_pic]);
}
if (art.video) {
rst += ('|video=1|videolength=' + art.videolen);
if (art.result_video !== -1)
rst += ('|videocheck=' + ({1:'O',2:'?',0:'X'})[art.result_video]);
}
rst += ('|' + ({1:'O',2:'?',0:'X'})[art.result]);
if (art.resulttext)
rst += ('|' + art.resulttext);
if (art.comment)
rst += ('|hostcomments=' + art.comment);
if (art.score !== '')
rst += ('|manualscoring=' + art.score);
if (!art.autocheck && (art.nsfail || art.sizefail))
rst += ('|autocheck=0|ns=' + (art.nsfail?'1':'0') + '|size=' + (art.sizefail?'1':'0'));
return rst + '}}\n';
};
const html_to_article = function(id) {
let th = $(`#p4js-dchosttable-row${id} > th`);
let title = th[0].children[0].text;
let topic = th[1].children[0].value;
let quality = th[2].children[0].value;
let qualitycheck = th[2].children[2].children[0].checked;
let length = th[3].children[0].value.replace(',', '');
let result = parseInt(th[4].children[0].value);
let resulttext = th[4].children[2].children[0].value;
let comment = th[4].children[4].children[0].value;
let improve = th[5].children[0].children[0].checked;
let result_improve = parseInt(th[5].children[1].value);
let improvetext = th[5].children[3].children[0].value;
let ref = th[6].children[0].children[0].checked;
let result_ref = parseInt(th[6].children[1].value);
let refnum = parseInt(th[6].children[3].children[0].value.replace(',', ''));
let spoken = th[7].children[0].children[0].checked;
let result_spoken = parseInt(th[7].children[1].value);
let spokenlen = parseInt(th[7].children[3].children[0].value.replace(',', ''));
let pic = th[8].children[0].children[0].checked;
let result_pic = parseInt(th[8].children[1].value);
let picnum = parseInt(th[8].children[3].children[0].value);
let fpicnum = parseInt(th[8].children[5].children[0].value);
let pictranslen = parseInt(th[8].children[7].children[0].value.replace(',', ''));
let video = th[9].children[0].children[0].checked;
let result_video = parseInt(th[9].children[1].value);
let videolen = parseInt(th[9].children[3].children[0].value.replace(',', ''));
let score = th[10].children[0].value;
let mediafiles = th[11].children[2].value.split('\n');
let autocheck = th[12].children[0].children[0].checked;
let nsfail = th[12].children[2].children[0].checked;
let sizefail = th[12].children[4].children[0].checked;
return new Article(id, title, length, undefined, topic, quality, qualitycheck, improve, result, result_improve, result_ref, result_spoken, result_pic, result_video, score, comment, resulttext, improvetext, ref, spoken, pic, video, refnum, spokenlen, picnum, fpicnum, pictranslen, videolen, mediafiles, autocheck, nsfail, sizefail);
};
let [old_articles, new_articles] = [[], []];
let [old_wikitext, new_wikitext] = [[], []];
let item_count = 0;
let user_info = [];
let $dl = null; // 主表单
let $dl2 = null; // 去除冗余源码表单
if (mw.config.get('wgPageName').startsWith(personal_page + '/')) {
$('#DC22_links table > tbody').append(`
<tr><td>
<span class="mw-ui-button">
<span id="p4js-dchost-edit" title="${wgULS('主持人对本页个人贡献进行评审', '主持人對本頁個人貢獻進行評審')}">${wgULS('评审贡献', '評審貢獻')}</span>
</span>
</td></tr>`);
$('#p4js-dchost-edit').on('click', (e) => {e.preventDefault(); try{showDialog();}catch(e){}});
}
const report_failed_edits = function(title, content, reason) {
let span = $('<span>');
span.append(wgULS('编辑', '編輯'));
span.append($(`<a href="${mw.config.get('wgScript')+'?action=edit&title='+title}">${title}</a>`));
span.append(`${wgULS('失败', '失敗')}:${reason} - `);
let a = $(`<a>${wgULS('复制版本内容并手动编辑', '拷貝版本內容並手動編輯')}</a>`);
a.on('click', function(e) {
e.preventDefault();
$('#p4js-dchost-failed-edit-content').val(content).select();
document.execCommand("copy");
});
span.append(a);
$('#p4js-dchost-output').append(span, $('<br>'));
};
// 注意这个函数为了避免编辑,会抛出异常
const showDialog = function() {
new mw.Api()
.edit(mw.config.get('wgPageName'), function(revision) {
let oldtext = revision.content;
// 抓条目列表的时候去掉注释,不然那个正则工作不了
let efftext = oldtext.replace(comment_regex, '');
let html = '';
let match = null;
// 预处理:有些用户会把 |主题= 和 #{{DCxx/art}} 写在同一行,加个换行
for (let group of topics) {
for (let topic of group) {
let needle = `|${topic}=#`;
let tostr = `|${topic}=\n#`
efftext = efftext.replace(needle, tostr);
}
}
// 获取用户信息
for (let info of ['真用戶名|真用户名', '暱稱|昵称', '參與數|参与数', '編輯數|编辑数']) {
let match = (new RegExp(`\\|\\s*(?:${info})\\s*=\\s*([^\\n\\|]+)`)).exec(efftext);
if (!match) {
if (info === '暱稱|昵称') {
user_info.push(user_info[0]);
continue;
}
console.log(`获取${info}失败`);
user_info.push('');
continue;
}
user_info.push(match[1]);
}
while ((match = articles_regex.exec(efftext)) !== null) {
let topic = normalize_topic(match[1]);
let articles = match[2];
let items = articles.split('\n');
for (let item of items) {
let art = wikitext_to_article(item, topic, item_count);
if (art === null) continue;
++item_count;
old_articles.push(art);
old_wikitext.push(item+'\n');
html += render_article(art);
}
}
// 随便找个地方把对话框的 html 塞进去
$('.wikitable').after($(header_html('p4js-dchosttable') + html + tail_html));
$('.wikitable').after($(rm_redundance_html('')));
$('#p4js-dchost-rm-redundance-dialog').hide();
$dl = $('#p4js-dchost-dialog').dialog({
autoOpen: false, minWidth: 985, minHeight: 240,
buttons: [{
text: wgULS('查看差异', '檢視差異'),
click: function() {
render_diff();
}
}]
});
$dl.dialog('open');
// 去除冗余源码的对话框
$('.p4js-dchost-rm-redundance-button').on('click', function(e) {
e.preventDefault();
let title = $(this).attr('title');
$('#p4js-dchost-rm-redundance-article').text(title);
$('#p4js-dchost-rm-redundance-article').attr('href', url(title));
$('#p4js-dchost-rm-redundance-article-edit').attr('href', url(title) + '?action=edit');
$('#p4js-dchost-rm-redundance-article-history').attr('href', url(title) + '?action=history');
$dl2 = $('#p4js-dchost-rm-redundance-dialog').dialog({
autoOpen: false, minWidth: 985, minHeight: 800,
buttons: [{
text: wgULS('关闭', '關閉'),
click: function() {
$dl2.dialog('close');
}
}]
});
$('#p4js-dchost-rm-redundance-dialog').show();
$dl2.dialog('open');
});
// 同步两个文本框的滚轮
// https://stackoverflow.com/a/18953340
let $texts = $('#p4js-dchost-rm-redundance-new-before, #p4js-dchost-rm-redundance-new-after');
const sync = (texts) => function(e) {
let $other = texts.not(this).off('scroll'), other = $other.get(0);
let percentage = this.scrollTop / (this.scrollHeight - this.offsetHeight);
other.scrollTop = percentage * (other.scrollHeight - other.offsetHeight);
// Firefox workaround. Rebinding without delay isn't enough.
setTimeout( function(){ $other.on('scroll', sync ); }, 10);
}
$texts.on('scroll', sync($texts));
$texts = $('#p4js-dchost-rm-redundance-old-before, #p4js-dchost-rm-redundance-old-after');
$texts.on('scroll', sync($texts));
// https://stackoverflow.com/a/25994411
const countUtf8Bytes = function(s) {
let b = 0, i = 0, c
for(;c=s.charCodeAt(i++);b+=c>>11?3:c>>7?2:1);
return b;
}
$('#p4js-dchost-rm-redundance-new-before, #p4js-dchost-rm-redundance-new-after').keyup(function() {
let before = countUtf8Bytes($('#p4js-dchost-rm-redundance-new-before').val());
let after = countUtf8Bytes($('#p4js-dchost-rm-redundance-new-after').val());
let oldAfter = countUtf8Bytes($('#p4js-dchost-rm-redundance-old-after').val());
$('#p4js-dchost-rm-redundance-new-len-before').text(before);
$('#p4js-dchost-rm-redundance-new-len-after').text(after);
$('#p4js-dchost-rm-redundance-new-len-diff').text(after-before);
$('#p4js-dchost-rm-redundance-len-diff').text(after-oldAfter);
});
$('#p4js-dchost-rm-redundance-old-before, #p4js-dchost-rm-redundance-old-after').keyup(function() {
let before = countUtf8Bytes($('#p4js-dchost-rm-redundance-old-before').val());
let after = countUtf8Bytes($('#p4js-dchost-rm-redundance-old-after').val());
let newAfter = countUtf8Bytes($('#p4js-dchost-rm-redundance-new-after').val());
$('#p4js-dchost-rm-redundance-old-len-before').text(before);
$('#p4js-dchost-rm-redundance-old-len-after').text(after);
$('#p4js-dchost-rm-redundance-old-len-diff').text(after-before);
$('#p4js-dchost-rm-redundance-len-diff').text(newAfter-after);
});
// 填入条目当前版本源码
$('#p4js-dchost-rm-redundance-new-load').on('click', function(e) {
e.preventDefault();
let title = $('#p4js-dchost-rm-redundance-article').text();
new mw.Api()
.get({
action: 'query',
prop: 'revisions',
rvprop: 'content',
titles: title,
formatversion: '2'
})
.then(function(data) {
if (!data.query || !data.query.pages) {
console.log(title + ': unknown error');
return;
}
let page = data.query.pages[0];
if (page.missing) {
console.log(title + ' doesn\'t exist');
return;
} else {
let revisions = page.revisions;
if (revisions && revisions.length && revisions[0]) {
let text = revisions[0].content || '';
$('#p4js-dchost-rm-redundance-new-before').val(text);
}
}
$('#p4js-dchost-rm-redundance-new').keyup();
})
.fail(function(obj) {
console.log('fetch page failed:', title);
});
});
// 填入条目历史版本源码
$('#p4js-dchost-rm-redundance-old-load').on('click', function(e) {
e.preventDefault();
let rev = $('#p4js-dchost-rm-redundance-old-revision')[0].value;
if (!rev) return;
let title = $('#p4js-dchost-rm-redundance-article').text();
new mw.Api()
.get({
action: 'query',
prop: 'revisions',
rvprop: 'content',
revids: rev,
formatversion: '2'
})
.then(function(data) {
if (!data.query || !data.query.pages) {
console.log(rev + ': unknown error');
return;
}
let page = data.query.pages[0];
if (page.missing) {
console.log(rev + ' revision doesn\'t exist');
return;
} else {
let revisions = page.revisions;
if (revisions && revisions.length && revisions[0]) {
let text = revisions[0].content || '';
$('#p4js-dchost-rm-redundance-old-before').val(text);
}
}
$('#p4js-dchost-rm-redundance-old').keyup();
})
.fail(function(obj) {
console.log('fetch revid failed:', rev);
});
});
// 给定一段 text,从 start 开始,解析出一个完整的模板返回
// 例如:{{aaa|bbb={{ccc}}|ddd=eee}}
// 请确保 start 位置是第一个 {
const getTemplate = function(text, start) {
let braces = 0;
let lastBrace = true;
for (let i = start+1; i < text.length; ++i) {
if (text[i] === '{') {
if (lastBrace) {
++braces;
lastBrace = false;
}
else lastBrace = true;
} else if (text[i] === '}') {
if (lastBrace) {
--braces;
lastBrace = false;
if (braces === 0) {
return text.substr(start, i-start+1);
}
}
else lastBrace = true;
} else {
lastBrace = false;
}
};
// 没有完整的模板
return "";
};
// 开始去除冗余代码
// https://zh.wikipedia.org/wiki/Wikipedia_talk:動員令/第十八次動員令#计算字节数时注释、来源计算问题
// 很多条件比较粗暴,需要人工复查
const removeRedundance = function(text, callback) {
// 去除注释,注意没有后半部分的 <!-- ... EOF 也是注释
text = text.replace(/<!--.*?(-->|$)/gs, '');
// 删除 HTML 标签,如 <hr />,<b>,但不包含 <div id="asdf"> 等较复杂的标签
// text = text.replace(/<\s*\/?[a-zA-Z\s]+\/?\s*>/g, '');
// 删除模板中的空参数,如 {{xxx|yyy=|zzz=|aaa=bbb}} 中的 yyy 和 zzz
// text = text.replace(/\|[^\s=]+=(\s)*(?=(\|)|(\}\}))/g, '');
// 提取链接(内链、外链)正文
text = text.replace(/\[\[(?:[^\]|]*\|)?([^\]]*)\]\]/g, '$1');
text = text.replace(/(^|[^\[])\[(?:http|\/\/)[^\] ]*(?: ([^\]]*))?\]/g, '$1$2');
// 文内注释保留注文
text = text.replace(/<\s*\/?\s*ref[^>]*\s*>/g, '');
let notetags = [];
for (let m of text.matchAll(/{{notetag/gi)) {
notetags.push(getTemplate(text, m.index));
}
for (let notetag of notetags) {
let comment = /{{\s*notetag\s*\|(?:\s*1=)?\s*(.*?)(?:\||}})/gi.exec(notetag);
comment = (comment ? comment[1] : '');
text = text.replace(notetag, comment);
}
callback(text);
/*
// 提取 Cite 系模板正文
// 我们的方案是,提出所有 Cite 系模板,喂给 mw.Api() 进行 parse,取得到的正文
let cites = [];
for (let m of text.matchAll(/{{[Cc]ite /g)) {
cites.push(getTemplate(text, m.index));
}
let toparse = '* ' + cites.join('\n* ');
new mw.Api()
.parse(toparse)
.done(function(html) {
// 从原文中删除所有 Cite 系模板,改用解析出的参考文献代替
for (let cite of cites) {
text = text.replace(cite, '');
}
for (let li of $('li', $(html))) {
text += '\n' + $(li).text();
}
callback(text);
})
// 解析失败,返回处理到前面为止的 text,人工处理 Cite 系模板部分
.fail(function() {
console.log('parse failed');
callback(text);
});
*/
};
$('#p4js-dchost-rm-redundance-new').on('click', function(e) {
e.preventDefault();
removeRedundance($('#p4js-dchost-rm-redundance-new-before').val(), function(text) {
$('#p4js-dchost-rm-redundance-new-after').val(text);
$('#p4js-dchost-rm-redundance-new-before').keyup();
});
});
$('#p4js-dchost-rm-redundance-old').on('click', function(e) {
e.preventDefault();
removeRedundance($('#p4js-dchost-rm-redundance-old-before').val(), function(text) {
$('#p4js-dchost-rm-redundance-old-after').val(text);
$('#p4js-dchost-rm-redundance-old-before').keyup();
});
});
// 加载 CSS:禁止换行
$('#p4js-dchosttable a').css('white-space', 'nowrap');
$('#p4js-dchosttable label').css('white-space', 'nowrap');
// 加载 JS:达标条目不显示“已通过”的复选框
$('[id^=p4js-dchost-select-quality]').change(function() {
let artid = $(this).attr('id').slice('p4js-dchost-select-quality'.length);
console.log(this.value);
this.value === '' ? $(`#p4js-dchost-label-qualitychecked${artid}`).hide() : $(`#p4js-dchost-label-qualitychecked${artid}`).show();
});
// 载入条目所使用的多媒体文件列表
$('.p4js-dchost-load-files').on('click', function(e) {
e.preventDefault();
let title = $(this).attr('title');
loadArticleImages(title);
});
// 这里并不做实际更改
throw "meow";
});
};
// 获取源码,并提取其中有出现,条目也用到的多媒体文件
const loadArticleImages = function(title) {
$.ajax({
url: mw.util.wikiScript('api'),
data: {
action: 'query',
prop: 'images|revisions',
titles: title,
rvprop: 'content',
rvslots: '*',
imlimit: 'max', // 500 should be enough.
format: 'json',
}
}).done(function (data) {
let allImageTitles = [], revision = '';
if (data.query) {
let pages = data.query.pages;
for (let [pageid, page] of Object.entries(pages)) {
if ('images' in page) {
allImageTitles = page.images.map(function(x) { return x.title.substring(5, x.title.length); });
}
if (page.revisions && page.revisions.length && page.revisions[0]['slots']['main']['*']) {
revision = page.revisions[0]['slots']['main']['*'];
}
}
}
// The filename (without prefix 'File:') must appear in article text.
// Or if we failed to get the article text, do not filter anything.
allImageTitles = !revision.length ? allImageTitles : allImageTitles.filter(function (e) {
return ~revision.replace(/[ _]+/g, ' ').indexOf(e.substring(5, e.length));
});
// 显示在对应文本框中
$(`.p4js-dchost-files[title=${title}]`).val(allImageTitles.join('\n'));
}).fail(function(jqXHR, textStatus, errorThrown) {
console.log('Error when loading images of ' + title + ': ' + errorThrown);
});
};
// 开始比较差异的时候调用
const render_diff = function() {
new_articles = [];
const art_prop = ['length', 'type', 'topic', 'quality', 'result_quality', 'improve', 'result', 'result_improve', 'result_ref', 'result_spoken', 'result_pic', 'result_video', 'score', 'comment', 'resulttext', 'improvetext', 'ref', 'spoken', 'pic', 'video', 'refnum', 'spokenlen', 'picnum', 'fpicnum', 'pictranslen', 'videolen', 'autocheck', 'nsfail', 'sizefail'];
let html = '';
for (let i = 0; i < item_count; ++i) {
let art = html_to_article(i);
new_articles.push(art);
if (art_prop.some(e => old_articles[i][e] !== art[e])) {
html += show_article(old_articles[i]) + render_article(art);
}
// special case for mediafiles
if (art.mediafiles.length !== 0) {
html += show_article(old_articles[i]) + render_article(art);
}
}
$dl.html(header_html('p4js-dchosttable-diff') + html + tail_html + preview_html);
$dl.dialog("option", "buttons", [{
text: '返回修改',
click: function() {
back();
}
}, {
text: wgULS('提交编辑', '提交編輯'),
click: function() {
save();
}
}]);
// 加载 CSS:旧内容和新内容背景用不同颜色
$('#p4js-dchosttable-diff tr[id^="p4js-dchosttable-old-row"]').css('background-color', '#fbe4a5');
$('#p4js-dchosttable-diff tr[id^="p4js-dchosttable-row"]').css('background-color', '#acd2fb');
// 加载 CSS:禁止换行
$('#p4js-dchosttable-diff label').css('white-space', 'nowrap');
$('#p4js-dchosttable-diff th').css('white-space', 'nowrap');
// 加载 JS:达标条目不显示“已通过”的复选框
$('[id^=p4js-dchost-select-quality]').change(function() {
let artid = $(this).attr('id').slice('p4js-dchost-select-quality'.length);
this.value === '' ? $(`#p4js-dchost-label-qualitychecked${artid}`).hide() : $(`#p4js-dchost-label-qualitychecked${artid}`).show();
});
// 计算预计要编辑的源码
const previewSource = function() {
const row_id = 'p4js-dchosttable-row';
new_wikitext = [];
// 把用户在比较差异时的改动也加进去
for (let tr of $('#p4js-dchosttable-diff > tbody > tr')) {
if (tr.id.slice(0, row_id.length) === row_id) {
let i = parseInt(tr.id.slice(row_id.length));
new_articles[i] = html_to_article(i);
}
}
for (let [i, art] of Object.entries(new_articles)) {
// 结果为 -1(不改动)时就直接把原来的源码放上去
new_wikitext.push(art.result !== -1 ? article_to_wikitext(art) : old_wikitext[i]);
}
let newtext = '';
$('#p4js-dchost-preview-source').val('');
new mw.Api()
.get({
action: 'query',
prop: 'revisions',
rvprop: 'content',
titles: mw.config.get('wgPageName'),
formatversion: '2'
})
.then(function(data) {
let text = '';
if (!data.query || !data.query.pages) {
console.log(title + ': unknown error');
return;
}
let page = data.query.pages[0];
if (page.missing) {
console.log(title + ' doesn\'t exist');
return;
} else {
let revisions = page.revisions;
if (revisions && revisions.length && revisions[0]) {
text = revisions[0].content || '';
}
}
let oldtext = text;
for (let item of old_wikitext) {
oldtext = oldtext.replace(item, '');
}
let match = null;
let prev_last_index = 0;
while ((match = topics_regex.exec(oldtext)) !== null) {
let topic = normalize_topic(match[1]);
if (topics.every(e => !e.includes(topic))) {
continue;
}
newtext += oldtext.slice(prev_last_index, topics_regex.lastIndex);
for (let i = 0; i < new_articles.length; ++i) {
if (new_articles[i].topic === topic) {
newtext += new_wikitext[i];
}
}
prev_last_index = topics_regex.lastIndex;
}
newtext += oldtext.slice(prev_last_index);
$('#p4js-dchost-preview-source').val(newtext);
});
};
previewSource();
$('#p4js-dchost-preview').on('click', function(e) {
e.preventDefault();
previewSource();
})
};
// 点击返回的时候调用,要把用户在比较差异时的改动也加进去
const back = function() {
const row_id = 'p4js-dchosttable-row';
let html = '';
for (let tr of $('#p4js-dchosttable-diff > tbody > tr')) {
if (tr.id.slice(0, row_id.length) === row_id) {
let i = parseInt(tr.id.slice(row_id.length));
new_articles[i] = html_to_article(i);
}
}
for (let art of new_articles) {
html += render_article(art);
}
$dl.html(header_html('p4js-dchosttable') + html + tail_html);
$dl.dialog("option", "buttons", [{
text: wgULS('查看差异', '檢視差異'),
click: function() {
render_diff();
}
}]);
// 加载 CSS:禁止换行
$('#p4js-dchosttable a').css('white-space', 'nowrap');
$('#p4js-dchosttable label').css('white-space', 'nowrap');
// 加载 JS:达标条目不显示“已通过”的复选框
$('[id^=p4js-dchost-select-quality]').change(function() {
let artid = $(this).attr('id').slice('p4js-dchost-select-quality'.length);
this.value === '' ? $(`#p4js-dchost-label-qualitychecked${artid}`).hide() : $(`#p4js-dchost-label-qualitychecked${artid}`).show();
});
};
const save = function() {
const row_id = 'p4js-dchosttable-row';
new_wikitext = [];
// 同样要把用户在比较差异时的改动也加进去
for (let tr of $('#p4js-dchosttable-diff > tbody > tr')) {
if (tr.id.slice(0, row_id.length) === row_id) {
let i = parseInt(tr.id.slice(row_id.length));
new_articles[i] = html_to_article(i);
}
}
for (let [i, art] of Object.entries(new_articles)) {
// 结果为 -1(不改动)时就直接把原来的源码放上去
new_wikitext.push(art.result !== -1 ? article_to_wikitext(art) : old_wikitext[i]);
}
// 记录预览中的源码(可能已被修改过)
let newtext = $('#p4js-dchost-preview-source').val() || '';
// 在对话框中显示进度
$('#p4js-dchost-dialog')[0].outerHTML = `
<div id="p4js-dchost-dialog" title="正在提交..." style="overflow-y:auto; overflow-x:hidden">
<p>${wgULS('评审正在提交,进度如下:', '評審正在提交,進度如下:')}</p>
<li>${wgULS('编辑个人贡献页面', '編輯個人貢獻頁面')}...<span style="display:none" id="p4js-dchost-personal-complete">完成</span></li>
<li>${wgULS('编辑龙虎榜页面', '編輯龍虎榜頁面')}...<span style="display:none" id="p4js-dchost-ic-complete">完成</span></li>
<li>${wgULS('编辑整体贡献及子页面', '編輯整體貢獻及子頁面')}...<span id="p4js-dchost-oc-count">0</span>/<span id="p4js-dchost-oc-maxcount">${wgULS('获取中', '擷取中')}...</span></li>
<li>${wgULS('编辑各条目讨论页', '編輯各條目討論頁')}...<span id="p4js-dchost-talk-count">0</span>/<span id="p4js-dchost-talk-maxcount">${wgULS('获取中', '擷取中')}...</span></li>
<li>${wgULS('编辑多媒体项目页面', '編輯多媒體項目頁面')}...<span style="display:none" id="p4js-dchost-media-complete">完成</span></li>
<p style="display:none" id="p4js-dchost-complete">全部完成!</p>
<p id="p4js-dchost-output"></p>
<textarea id="p4js-dchost-failed-edit-content" style="width:1px; height:1px"></a>
</div>
`;
// 不显示任何按钮
$dl.dialog("option", "buttons", []);
new mw.Api()
.edit(mw.config.get('wgPageName'), function(revision) {
if (!newtext) {
let oldtext = revision.content;
for (let item of old_wikitext) {
oldtext = oldtext.replace(item, '');
}
let match = null;
let prev_last_index = 0;
while ((match = topics_regex.exec(oldtext)) !== null) {
let topic = normalize_topic(match[1]);
if (topics.every(e => !e.includes(topic))) {
continue;
}
newtext += oldtext.slice(prev_last_index, topics_regex.lastIndex);
for (let i = 0; i < new_articles.length; ++i) {
if (new_articles[i].topic == topic) {
newtext += new_wikitext[i];
}
}
prev_last_index = topics_regex.lastIndex;
}
newtext += oldtext.slice(prev_last_index);
}
// 更新讨论页
let c = 0;
let artc = 0;
for (let [i,art] of Object.entries(new_articles)) {
if (art.result !== -1 && (
art.result !== old_articles[i].result ||
art.result_improve !== old_articles[i].result_improve ||
art.topic !== old_articles[i].topic ||
art.quality !== old_articles[i].quality ||
art.result_quality !== old_articles[i].result_quality ||
art.result_ref !== old_articles[i].result_ref ||
art.result_pic !== old_articles[i].result_pic ||
art.result_video !== old_articles[i].result_video ||
art.result_spoken !== old_articles[i].result_spoken
)) {
++artc;
// 只有通过才不 remove,未通过和待审都 remove
setTimeout(() => update_talk(art, art.result !== 1), (++c)*3000);
}
}
// 得到需要编辑的讨论页数
$('#p4js-dchost-talk-maxcount')[0].innerHTML = artc;
update_progress('', true); // 刷新进度,如果是 0/0 就会认为完成
// 先预览,得到各个条目的分数
new mw.Api()
.parse(newtext)
.done(function(html) {
update_media();
let score_map = {};
for (let li of $('.wikitable li', $(html))) {
let title = $('a', $(li))[0].textContent;
let score = parseFloat((score_regex.exec(li.textContent) || ['',0])[1]) || 0;
score_map[title] = score;
}
for (let article of new_articles) {
article.score = score_map[article.title] || article.score;
}
// 得到分数才能更新 IC 和 OC
setTimeout(() => updateIC(), (c+2)*2000);
setTimeout(() => updateOC(), (c+4)*2000);
});
return {
text: newtext,
summary: wgULS('评审个人贡献', '評審個人貢獻') + summary_postfix
};
})
.always(() => update_progress('p4js-dchost-personal-complete', true))
.fail(function (obj) {
console.log(mw.config.get('wgPageName'), newtext, obj);
report_failed_edits(mw.config.get('wgPageName'), newtext, obj);
});
};
const update_talk = function(art, remove) {
let title = 'Talk:' + art.title;
// |topic= 填的参数应该和 ic 一样
let template = `{{${talktemplate}|topic=${topic_to_ic(art.topic)}|type=${art.result_quality ? ({"FL":"特","FA":"典","GA":"优","":"达"})[art.quality] : '达'}${art.result_ref===1 ? '|ref=1' : ''}${art.result_improve===1 ? '|improve=1' : ''}${art.result_pic===1||art.result_spoken===1||art.result_video===1 ? '|media=1'+(art.result_spoken===1?'|spoken=1':'')+(art.result_pic===1?'|pic=1':'')+(art.result_video===1?'|video=1':'') : ''}}}\n`;
new mw.Api()
.get({
action: 'query',
prop: 'revisions',
rvprop: 'content',
titles: title,
formatversion: '2'
})
.then(function(data) {
if (!data.query || !data.query.pages) {
console.log(title + ': unknown error');
return;
}
let page = data.query.pages[0];
if (page.missing && !remove) {
new mw.Api()
.create(title, {summary: `${wgULS('条目通过评审,添加动员令模板', '條目通過評審,添加動員令模板')}${summary_postfix}`}, template)
.fail(function (obj) {
console.log('create talk page failed', art, obj);
report_failed_edits(title, template, obj);
})
.always(() => update_progress('p4js-dchost-talk-count'));
} else if (!page.missing) {
let newtext = '';
new mw.Api()
.edit(title, function(revision) {
let oldtext = revision.content;
newtext = oldtext;
let template_regex = new RegExp(`\\{\\{\\s*${talktemplate}\\|([^}]*?)\\}\\}\\n?`);
let t;
// 摘掉模板
if (remove) {
newtext = oldtext.replace(template_regex, '');
return {
text: newtext,
summary: `${(art.result === 2 ? wgULS('条目待审核', '條目待審核') : wgULS('条目未通过评审', '條目未通過評審')) + wgULS(',移除动员令模板', ',移除動員令模板')}${summary_postfix}`
};
}
// 否则替换/添加模板
if (template_regex.test(oldtext)) {
newtext = oldtext.replace(template_regex, template);
} else {
let dyk_regex = /(^|\n)\{\{\s*DYKEntry\/archive/g;
let section_regex = /(^|\n)==[^=]*==/g;
let dyk_match = dyk_regex.exec(oldtext);
let section_match = section_regex.exec(oldtext);
// 有 {{DYKEntry/archive}} 且他前面没有段落,就加在该模板之前
if (dyk_match && dyk_regex.lastIndex < section_regex.lastIndex) {
t = dyk_match.lastIndex-dyk_match[0].length;
newtext = oldtext.slice(0, t) + dyk_match[1] + template + oldtext.slice(t);
} else {
// 否则加在讨论页第一个段落(== xxx ==)之前
if (!section_match) {
// 还没有的话就加在末尾
if (!newtext.endsWith('\n')) newtext += '\n';
newtext += template;
} else {
t = section_regex.lastIndex-section_match[0].length;
newtext = oldtext.slice(0, t) + section_match[1] + template + oldtext.slice(t);
}
}
}
return {
text: newtext,
summary: `${wgULS('条目通过评审,添加动员令模板', '條目通過評審,添加動員令模板')}${summary_postfix}`
};
})
.fail(function (obj) {
console.log('edit talk page failed', art, obj);
report_failed_edits(title, newtext, obj);
})
.always(() => update_progress('p4js-dchost-talk-count'));
} else {
// page.missing && remove
update_progress('p4js-dchost-talk-count');
}
})
.fail(function(obj) {
console.log('fetch talk page failed', art, obj);
update_progress('p4js-dchost-talk-count');
});
};
const updateIC = function() {
const articles_to_ic = function(info, arts) {
let types = {};
for (let art of arts) {
if (art.result !== 1) continue;
let t = topic_to_ic(art.topic);
if (art.result_improve === 1) {
types[t+'改數'] = (types[t+'改數'] ? types[t+'改數']+1 : 1);
}
types[t+'數'] = (types[t+'數'] ? types[t+'數']+1 : 1);
types[t] = (types[t] ? types[t]+art.score : art.score);
// 四舍五入到十分位
types[t] = parseInt(types[t]*10+.5)/10
}
let type_text = '';
Object.entries(types).forEach((e) => {
type_text += `|${e[0]}=${e[1]}`;
});
return `{{${ictemplate}` +
`|username=${info[0]}|nickname=${info[1]}|finish=${info[2]}|edit=${info[3]}` +
`${type_text}}}`;
};
let newtext = '';
new mw.Api()
.edit(ic_page, function(revision) {
let oldtext = revision.content;
let lines = oldtext.split('\n');
let i = lines.lastIndexOf('|}');
if (i === -1) {
console.log('未找到表格');
return revision.content;
}
let j = i-1;
for (; j > 0; --j) {
if (lines[j].includes(`{{${ictemplate}|username=${user_info[0]}`)) {
// 修改用户的条目数
lines[j] = articles_to_ic(user_info, new_articles);
break;
}
}
if (!j) {
// 说明用户还不在列表里,新增一项
lines.splice(i, 0, articles_to_ic(user_info, new_articles));
}
newtext = lines.join('\n');
return {
text: newtext,
summary: `更新 ${user_info[0]} 的${wgULS('贡献情况', '貢獻情況')}${summary_postfix}`
};
})
.fail(function (obj) {
console.log(obj);
report_failed_edits(ic_page, newtext, obj);
})
.always(() => update_progress('p4js-dchost-ic-complete', true));
};
const updateOC = function() {
let ocpages = [];
let ocpageoldtext = {};
let ocpagenewtext = {};
let ocpagemissing = {};
let c = 0;
let i = 0;
topics.forEach((e) => ocpages.push(...e));
let titles = [].concat(...ocpages.map(a => ["達標條目", "優良條目", "典範條目", "特色列表"].map(
b => oc_page + '/' + topic_to_oc(a) + b
)));
titles.push(oc_page);
new mw.Api()
.post({
action: 'query',
prop: 'revisions',
rvprop: 'content',
titles: titles,
formatversion: '2'
})
.then(function(data) {
if (!data.query || !data.query.pages) {
console.log('updateOC: unknown error');
}
for (let page of data.query.pages) {
if (page.missing) {
ocpageoldtext[page.title] = ocpagenewtext[page.title] = '';
ocpagemissing[page.title] = true;
} else {
ocpageoldtext[page.title] = ocpagenewtext[page.title] = page.revisions[0].content;
}
}
c = 0;
for (let art of new_articles) {
// 空格和下划线在这里是等价的
let art_regex = new RegExp(`(^|\\n)#\\s*\\[\\[${art.title.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&').replace(/[ _]/g, '[ _]')}\\]\\]\\s*\\*?\\s*([\\d\\.]+\\s*分)`);
for (let k of Object.keys(ocpageoldtext)) {
if (art.result === 0 || art.result === 2) {
// 未通过或者待定,那就要把条目撤下来
ocpagenewtext[k] = ocpagenewtext[k].replace(art_regex, '');
} else if (art.result === 1) {
// 通过了就加到对应类别(把其他类别的撤下来)
if (k.endsWith(art.result_quality ? ({"FL":"特色列表","FA":"典範條目","GA":"優良條目","":"達標條目"})[art.quality] : '達標條目') &&
k.includes(topic_to_oc(art.topic))) {
// 这是我们要的类别,如果没有加那就加上去
if (!art_regex.test(ocpagenewtext[k])) {
if (!ocpagenewtext[k].endsWith('\n')) ocpagenewtext[k] += '\n';
ocpagenewtext[k] += `# [[${art.title}]]${art.result_improve === 1 ? '*' : ''}(${art.score}分)`;
} else {
// 如果有了,那可能分数有变化,替换一下
ocpagenewtext[k] = ocpagenewtext[k].replace(art_regex, `$1# [[${art.title}]]${art.result_improve === 1 ? '*' : ''}(${art.score}分)`);
}
} else {
// 这不是我们要的类别,把条目撤下来
ocpagenewtext[k] = ocpagenewtext[k].replace(art_regex, '');
}
}
}
}
// ${} 里的就是数出有多少行由 # 开头
let octable_prefix = `
{| class="wikitable sortable"
!align="center"| '''主題'''
!align="center"| '''全部'''
!align="center"| '''達標條目'''(包括新條目推薦)
!align="center"| '''優良条目'''
!align="center"| '''典範條目'''
!align="center"| '''特色列表'''
|-
!'''全部'''
|[[:Category:第二十二次動員令貢獻一覽|{{PAGESINCAT:第二十二次動員令貢獻一覽}}]]
|[[:Category:第二十二次動員令達標條目|{{PAGESINCAT:第二十二次動員令達標條目}}]]
|[[:Category:第二十二次動員令優良條目|{{PAGESINCAT:第二十二次動員令優良條目}}]]
|[[:Category:第二十二次動員令典範條目|{{PAGESINCAT:第二十二次動員令典範條目}}]]
|[[:Category:第二十二次動員令特色列表|{{PAGESINCAT:第二十二次動員令特色列表}}]]
`;
let octable_topics = '';
for (let group of topics) {
for (let topic of group) {
octable_topics += `|-
!'''${topic_to_oc(topic)}'''
|[[:Category:第二十二次動員令${topic_to_oc(topic)}作品|{{PAGESINCAT:第二十二次動員令${topic_to_oc(topic)}作品}}]]
|{{#expr:({{PAGESINCAT:第二十二次動員令${topic_to_oc(topic)}作品}}-${ocpagenewtext[oc_page+'/'+topic_to_oc(topic)+'優良條目'].split('\n').filter(s=>s.startsWith('#')).length}-${ocpagenewtext[oc_page+'/'+topic_to_oc(topic)+'典範條目'].split('\n').filter(s=>s.startsWith('#')).length}-${ocpagenewtext[oc_page+'/'+topic_to_oc(topic)+'特色列表'].split('\n').filter(s=>s.startsWith('#')).length})}}
|${ocpagenewtext[oc_page+'/'+topic_to_oc(topic)+'優良條目'].split('\n').filter(s=>s.startsWith('#')).length}
|${ocpagenewtext[oc_page+'/'+topic_to_oc(topic)+'典範條目'].split('\n').filter(s=>s.startsWith('#')).length}
|${ocpagenewtext[oc_page+'/'+topic_to_oc(topic)+'特色列表'].split('\n').filter(s=>s.startsWith('#')).length}
`;
}
}
let octable_suffix = `
|-
|}
`;
ocpagenewtext[oc_page] = ocpageoldtext[oc_page].replace(oc_regex, `$1${octable_prefix + octable_topics + octable_suffix}$3`);
// 获取需要编辑的页面数
$('#p4js-dchost-oc-maxcount')[0].innerHTML = Object.entries(ocpagenewtext).filter((e)=>e[1]!==ocpageoldtext[e[0]]).length;
update_progress('', true); // 刷新进度,如果是 0/0 就会认为完成
for (let [k,v] of Object.entries(ocpagenewtext)) {
if (v === ocpageoldtext[k]) continue;
setTimeout(function() {
if (ocpagemissing[k]) {
new mw.Api()
.create(k, {summary: `更新 ${user_info[0]} 的${wgULS('贡献', '貢獻')}${summary_postfix}`}, v)
.fail(function (obj) {
console.log(obj);
report_failed_edits(k, v, obj);
})
.always(() => update_progress('p4js-dchost-oc-count'));
} else {
new mw.Api()
.edit(k, function (r) {
return {text: v, summary: `更新 ${user_info[0]} 的${wgULS('贡献', '貢獻')}${summary_postfix}`};
})
.fail(function (obj) {
console.log(obj);
report_failed_edits(k, v, obj);
})
.always(() => update_progress('p4js-dchost-oc-count'));
}
}, (++c)*3000);
}
})
.fail(obj => console.log(obj));
};
const update_media = function() {
const to_find = '<!-- 以下多媒体项目由评审小工具自动产生,请手动分类;请不要改动章节标题以及这行注释 -->\n<gallery>\n';
let to_insert = '';
let users = 0;
let files = 0;
for (let art of new_articles) {
if (art.mediafiles.length > 0) {
to_insert += art.mediafiles.map(f => `${f}|用於條目[[${art.title}]]<br>貢獻者:${user_info[0]}`).join('\n');
files += art.mediafiles.length;
++users;
}
}
to_insert += '\n';
if (!files) {
update_progress('p4js-dchost-media-complete', true);
return;
}
// to_find -> to_find + to_insert
new mw.Api()
.edit(media_page, function (revision) {
let oldtext = revision.content;
let newtext = oldtext.replace(to_find, to_find + to_insert);
return {text: newtext, summary: `添加 ${users} 个${wgULS('用户', '用戶')}的 ${files} 个${wgULS('多媒体项目', '多媒體項目')}${summary_postfix}`};
})
.fail(function (obj) {
console.log(obj);
report_failed_edits(media_page, newtext, obj);
})
.always(() => update_progress('p4js-dchost-media-complete', true));
}
const update_progress = function(id, show) {
if (id) {
if (show === true) {
$(`#${id}`).show();
} else if (show === false) {
$(`#${id}`).hide();
} else {
let count = parseInt($(`#${id}`)[0].innerHTML);
$(`#${id}`)[0].innerHTML = count+1;
}
}
if ($('#p4js-dchost-personal-complete').is(':visible') &&
$('#p4js-dchost-ic-complete').is(':visible') &&
$('#p4js-dchost-media-complete').is(':visible') &&
$('#p4js-dchost-oc-count')[0].innerHTML === $('#p4js-dchost-oc-maxcount')[0].innerHTML &&
$('#p4js-dchost-talk-count')[0].innerHTML === $('#p4js-dchost-talk-maxcount')[0].innerHTML) {
// 全部完成
$('#p4js-dchost-complete').show();
$dl.dialog("option", "buttons", [{
text: wgULS('关闭', '關閉'),
click: function() {
$dl.dialog('close');
window.location.reload(true);
}
}]);
}
};
});
})(jQuery, mediaWiki);
// <nowiki>