User:Leiem/sandbox.js

注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google ChromeFirefoxMicrosoft EdgeSafari:按住⇧ 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:Leiem/sandbox.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 "待";
    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>