LLWiki正在建设中,欢迎加入我们

“MediaWiki:Gadget-inspect.js”的版本间差异

来自LLWiki
跳转到导航 跳转到搜索
第1行: 第1行:
//<nowiki>
// 由ResourceLoader直接调用,不可使用ES6语法
// 由ResourceLoader直接调用,不可使用ES6语法
/**
/**
* @Function: 在页面内快速编辑和预览
* @Author: [[User:Bhsd]]
* @Author: [[User:Bhsd]]
*/
*/
第7行: 第9行:
const id = mw.config.get( 'wgArticleId' ),
const id = mw.config.get( 'wgArticleId' ),
curRevid = mw.config.get( 'wgCurRevisionId' ),
curRevid = mw.config.get( 'wgCurRevisionId' ),
script = mw.config.get('wgScript'),
page = mw.config.get( 'wgPageName' ),
page = mw.config.get( 'wgPageName' ),
lang = mw.config.get( 'wgUserVariant' ),
gadgets = mw.gadgets || {},
gadgets = mw.gadgets || {},
inspect = gadgets.inspect || {},
inspect = gadgets.inspect || {},
rule = inspect.rule || true;
rule = inspect.rule;
// 不是模板、不是重定向、页面已存在、阅读模式、Wikitext、最新版本
if (id > 0 && (![10, 274].includes( mw.config.get( 'wgNamespaceNumber' ) ) || page.endsWith( '/doc' )) &&
mw.config.get('wgAction') == 'view' && mw.config.get( 'wgPageContentModel' ) == 'wikitext' &&
if ((mw.config.get( 'wgNamespaceNumber' ) != 10 || page.endsWith( '/doc' )) && !mw.config.get('wgIsRedirect') &&
!mw.config.get( 'wgIsRedirect' ) && mw.config.get( 'wgRevisionId' ) == curRevid && rule) {
id > 0 && mw.config.get('wgAction') == 'view' && mw.config.get( 'wgPageContentModel' ) == 'wikitext' &&
mw.config.get( 'wgRevisionId' ) == curRevid && rule === undefined ? true : rule) {
// 先提交Ajax请求,这里手动设置cache: true
const api = new mw.Api(),
const api = new mw.Api(),
getJSON = $.ajax({ dataType: 'json', cache: true,
getJSON = $.ajax({ dataType: 'json', cache: true,
url: script + '?title=mediawiki:gadget-CodeMirror.json&action=raw&ctype=application/json' });
url: '/zh?title=mediawiki:gadget-CodeMirror.json&action=raw&ctype=application/json' });
// 生成通用的API请求
mw.request = mw.request || mw.standardQuery(api);
mw.request = mw.request || mw.standardQuery(api);
// 标注<p>标签
const css = mw.util.addCSS( '#mw-content-text .mw-parser-output p { border:1px dashed; }' +
const css = mw.util.addCSS( '#mw-content-text .mw-parser-output p { border:1px dashed; }' +
// 解决<ul>等元素文字不换行的问题
'#mw-content-text .mw-parser-output { display:flow-root; overflow:hidden; word-wrap:break-word; }'),
'#mw-content-text .mw-parser-output { display:flow-root; overflow:hidden; word-wrap:break-word; }' ),
cssHide = mw.util.addCSS( '#inspector-btns { margin-bottom:0.5em; }' +
cssHide = mw.util.addCSS( '#inspector-btns { margin-bottom:0.5em; }' +
// 由于resizable,left, top, height必须加!important,width不可加!important
'#inspector { position:fixed; right:24px; left:unset !important; width:calc(50% - 7rem - 0.5px); }' );
'#inspector { position:fixed; bottom:0; right:24px; left:unset !important; top:unset !important;' +
'height:unset !important; width:calc(50% - 7rem - 0.5px); }' );
css.disabled = true;
css.disabled = true;
var dialog, actionP, actionD, text, editor, wrapper, nextid;
var dialog, actionP, actionD, text, editor, $wrapper, nextid,
lang = mw.config.get( 'wgUserVariant' );
const $content = $('#mw-content-text, #mw-imagepage-content').last(),
const states = ['loaded', 'loading', 'ready'], // 以上states表示开启对应的小工具
original = $content.children( '.mw-parser-output' ),
isBackup = states.includes( mw.loader.getState( 'ext.gadget.contentBackup' ) ),
placeholder = $('<div>', {class: "mw-parser-output"}).css('display', 'none'),
backupObj = isBackup ? Object.fromEntries( mw.storage.getObject( 'LLWiki-contentBackup' ) || [] ) : [],
urlDisplay = $('<a>', {href: "#"}).click(function(e) { e.preventDefault(); }),
backup = isBackup ? function() {
warning = $('<div>', {html: [wgULS("您确定要还原为未编辑的状态吗?", "您確認要復原為未編輯的狀態嗎?"), '<br>',
backupObj[id] = [mw.now(), editor.getValue()];
mw.storage.setObject( Object.entries(backupObj) );
} : function() {},
loadBackup = function() {
const backupContent = (backupObj[id] || [])[1];
if (!backupContent) { return; }
mw.confirm( wgULS('要加载备份吗?', '要加載備份嗎?'), 'progressive' ).then(function(confirm) {
if (confirm) { editor.setValue( backupContent ); }
});
},
$content = $('#mw-content-text, #mw-imagepage-content').last(),
$original = $content.children( '.mw-parser-output' ),
$placeholder = $('<div>', {class: "mw-parser-output"}).css('display', 'none'), // 用于替换时保留原始数据
$url = $('<a>'), // 桌面版CSS不必需href
$warning = $('<div>', {html: [wgULS("确定要还原为未编辑的状态吗?", "確認要復原為未編輯的狀態嗎?"), '<br>',
wgULS("建议您做好编辑内容备份。", "建議您做好編輯內容備份。")]}),
wgULS("建议您做好编辑内容备份。", "建議您做好編輯內容備份。")]}),
outer = $('<div>', {id: 'inspector', class: 'mw-ajax-loader'}).insertBefore( original )
$outer = $('<div>', {id: 'inspector', class: 'mw-ajax-loader'}).insertBefore( $original )
.on('contextmenu', '.cm-mw-template-name, .cm-mw-link-pagename', function(e) {
.on('contextmenu', '.cm-mw-template-name', function(e) {
e.preventDefault();
e.preventDefault();
const ele = $(this);
const template = '模板:' + $(this).text();
var url = ele.text();
var label;
// 这里增加一次确认界面是因为部分浏览器不允许JS直接打开新标签页。
if (ele.hasClass( 'cm-mw-template-name' )) { url = '模板:' + url; }
else if (ele.next( '.cm-mw-link' ).text() == '#') {
url += '#' + ele.next().next( '.cm-mw-link-tosection' ).text();
}
urlDisplay.text( url );
if (!mw.windowManager) {
mw.windowManager = new OO.ui.WindowManager();
$('body').append( mw.windowManager.$element );
}
if (!dialog) {
if (!dialog) {
dialog = new OO.ui.MessageDialog();
actionP = new OO.ui.ActionWidget({label: '是', target: '_blank', flags: 'progressive'});
actionP = new OO.ui.ActionWidget({label: '是', target: '_blank', flags: 'progressive'});
actionD = new OO.ui.ActionWidget({label: '否', flags: 'destructive'});
actionD = new OO.ui.ActionWidget({label: '否', flags: 'destructive'});
label = [wgULS('要在新标签页打开', '要在新標籤頁打開'), $url, wgULS('吗?', '嗎?')];
mw.windowManager.addWindows( [dialog] );
dialog.message.$label.html( [wgULS('要在新标签页打开', '要在新標籤頁打開'), urlDisplay, wgULS('吗?', '嗎?')] );
}
}
actionP.setHref( script + '/' + url );
$url.text( template );
actionP.setHref('/zh/' + template);
mw.windowManager.openWindow( dialog, {actions: [actionP, actionD]}).opening.then(function() {
dialog = mw.dialog(dialog, [actionP, actionD], label);
actionP.$button.off( 'click' ).click(function() { dialog.close(); });
});
}).resizable( {handles: 'w', minWidth: 350} ),
}).resizable( {handles: 'w', minWidth: 350} ),
btns = [new OO.ui.ButtonWidget({label: '提交', flags: ['primary', 'progressive']}).on('click', function() {
btns = [new OO.ui.ButtonWidget({label: '提交', flags: ['primary', 'progressive']}).on('click', function() {
backup();
// 改变CSS样式表示提交中
btns[0].setDisabled( true );
btns[0].setDisabled( true );
mw.safeEdit(api, curRevid, {pageid: id, text: editor.getValue(),
mw.safeEdit(api, curRevid, {pageid: id, text: editor.getValue(),
summary: wgULS('使用页面/文本对比查看器快速编辑', '使用頁面/文本對比察看器快速編輯')}).then(function() {
summary: wgULS('使用页面/文本对比查看器快速编辑', '使用頁面/文本對比察看器快速編輯')}).then(function() {
setTimeout(window.location.reload(), 500);
location.reload();
}, function(reason) { if (reason != 'editConflict') { btns[0].setDisabled( false ); }
}, function(reason) { if (reason != 'editConflict') { btns[0].setDisabled( false ); } }); // 编辑冲突需刷新页面
});
}), new OO.ui.ButtonWidget({label: wgULS('预览', '預覽')}).on('click', function() {
}), new OO.ui.ButtonWidget({label: wgULS('预览', '預覽')}).on('click', function() {
backup();
// 改变CSS样式表示预览中
btns[1].setDisabled( true );
btns[1].setDisabled( true );
console.log('API request: 请求预览');
console.log('API request: 请求预览');
第72行: 第86行:
.then(function(html) {
.then(function(html) {
console.log('End API request: 已生成预览,用时 ' + (mw.now() - now) + ' ms');
console.log('End API request: 已生成预览,用时 ' + (mw.now() - now) + ' ms');
if ($.contains( $content[0], original[0] )) { original.after( placeholder ).detach(); }
if ($.contains( $content[0], $original[0] )) { $original.after( $placeholder ).detach(); }
$content.children( '.mw-parser-output' ).replaceWith( html );
$content.children( '.mw-parser-output' ).replaceWith( html );
if (mw.resizeLyrics) { mw.resizeLyrics(); }
if (mw.resizeLyrics) { mw.resizeLyrics(); }
mw.hook( 'wikipage.content' ).fire($content);
mw.hook( 'wikipage.content' ).fire($content);
}, function(reason) { mw.apiFailure(reason, wgULS('预览', '預覽')); }).then(function() {
}, function(reason) { mw.apiFailure(reason, wgULS('预览', '預覽')); }) // mw.apiFailure不会抛出错误
btns[1].setDisabled( false );
.then(function() { btns[1].setDisabled( false ); });
});
}), new OO.ui.ButtonWidget({label: wgULS('还原', '復原'), flags: 'destructive'}).on('click', function() {
}), new OO.ui.ButtonWidget({label: wgULS('还原', '復原'), flags: 'destructive'}).on('click', function() {
OO.ui.confirm(warning, {actions: [{label: "否"},
mw.confirm($warning, ['primary', 'destructive']).then(function(confirm) {
{label: "是", flags: ['primary', 'destructive'], action: 'accept'}]}).then(function(confirm) {
if (!confirm) { return; }
if (!confirm) { return; }
editor.setValue( text );
editor.setValue( text );
$content.children( '.mw-parser-output' ).replaceWith( original );
$content.children( '.mw-parser-output' ).replaceWith( $original );
if (mw.resizeLyrics) { mw.resizeLyrics(); }
if (mw.resizeLyrics) { mw.resizeLyrics(); }
});
});
第90行: 第102行:
css.disabled = !css.disabled;
css.disabled = !css.disabled;
cssHide.disabled = !cssHide.disabled;
cssHide.disabled = !cssHide.disabled;
wrapper.toggle();
$wrapper.toggle();
btns[3].setLabel( css.disabled ? wgULS('显示', '顯示') : wgULS('隐藏', '隱藏'));
btns[3].setLabel( css.disabled ? wgULS('显示', '顯示') : wgULS('隐藏', '隱藏'));
btns[2].setDisabled( css.disabled ); // 折叠时无法更新textarea
if (mw.resizeLyrics) { mw.resizeLyrics(); }
if (mw.resizeLyrics) { mw.resizeLyrics(); }
}), new OO.ui.ButtonWidget({flags: 'progressive', icon: 'next'})],
}), new OO.ui.ButtonWidget({flags: 'progressive', icon: 'next'})],
list = mw.storage.getObject( 'inspect-category' );
list = mw.storage.getObject( 'inspect-category' ),
initList = function(pages) {
const currentIndex = pages[0] == id ? 1 : 0;
mw.storage.setObject( 'inspect-category', [pages, currentIndex] );
btns[4].setHref( '/?redirect=no&curid=' + pages[ currentIndex ] ).setDisabled( false );
};
if (!list) { nextid = id + 1; }
if (!list) { nextid = id + 1; }
else {
else {
第103行: 第121行:
nextid = list[0][ list[1] ];
nextid = list[0][ list[1] ];
}
}
if (isBackup) { btns[2].$element.contextmenu( loadBackup ); }
btns[4].setHref( '/?redirect=no&curid=' + nextid ).setDisabled( !nextid ).$element.contextmenu(function(e) {
btns[4].setHref( '/?redirect=no&curid=' + nextid ).setDisabled( !nextid ).$element.contextmenu(function(e) {
e.preventDefault();
e.preventDefault();
OO.ui.prompt( wgULS('请输入分类名或名字空间编号:', '請輸入分類名或名字空間編號:') ).then(function(cat) {
OO.ui.prompt( wgULS('请输入分类名或名字空间编号:', '請輸入分類名或名字空間編號:') ).then(function(cat) {
if (cat === null) { return; }
if (cat === null) { return; }
// 输入空白则清除分类
if (cat === '') {
if (cat === '') {
mw.storage.remove( 'inspect-category' );
mw.storage.remove( 'inspect-category' );
第112行: 第132行:
return;
return;
}
}
// 输入数字对应名字空间
if (!isNaN(cat)) {
if (!isNaN(cat)) {
if (!Object.keys( mw.config.get( 'wgFormattedNamespaces' ) ).includes(cat)) {
if (!Object.keys( mw.config.get( 'wgFormattedNamespaces' ) ).includes(cat)) {
第117行: 第138行:
return;
return;
}
}
mw.timedQuery(api, {list: 'allpages', apnamespace: cat, apfilterredir: 'nonredirects', aplimit: 'max'},
console.log('API request: 查询该名字空间的页面列表');
wgULS('该名字空间的页面列表', '該名字空間的頁面列表')).then(function(ap) {
now = mw.now();
api.get({action: 'query', list: 'allpages', apnamespace: cat, apfilterredir: 'nonredirects',
aplimit: 'max', formatversion: 2}).then(function(ap) {
console.log('End API request: 已获得该名字空间的页面列表,用时 ' + (mw.now() - now) + ' ms');
const pages = ap.query.allpages.map(function(ele) { return ele.pageid; })
const pages = ap.query.allpages.map(function(ele) { return ele.pageid; })
.sort(function(a, b) { return a < b; });
.sort(function(a, b) { return a < b; }); // 由新到旧排列
if (pages.length === 0) {
if (pages.length === 0) {
mw.notify( wgULS('该名字空间沒有非重定向的页面!', '該名字空間沒有非重定向的頁面'), {type: 'warn'} );
mw.notify( wgULS('该名字空间沒有非重定向的页面!', '該名字空間沒有非重定向的頁面'), {type: 'warn'} );
return;
return;
}
}
const currentIndex = pages[0] == id ? 1 : 0;
initList(pages);
}, function() {}); // mw.timedQuery已经通知过错误信息,这里只要一个空函数处理reject,下同
mw.storage.setObject( 'inspect-category', [pages, currentIndex] );
btns[4].setHref( '/?redirect=no&curid=' + pages[ currentIndex ] ).setDisabled( false );
}, function(reason) { mw.apiFailure(reason, wgULS('该名字空间的页面列表', '該名字空間的頁面列表')); });
return;
return;
}
}
if (!/^([Cc]ategory|分[类類]):/.test( cat )) { cat = 'Category:' + cat; }
if (!/^(category|分[类類]):/i.test( cat )) { cat = 'Category:' + cat; }
// cmtitle参数不可自动转换,所以需要先获得转换后的正确标题
console.log('API request: 查询标准的页面名');
mw.timedQuery(api, {titles: cat, converttitles: 1}, wgULS('标准页面名称', '標準頁面名稱')) .then(function(r) {
var now = mw.now();
const target = r.query.pages[0];
api.get({action: 'query', titles: cat, converttitles: 1, formatversion: 2}).then(function(response) {
const target = response.query.pages[0];
if (target.missing) {
if (target.missing) {
mw.notify(wgULS('错误的分类名!', '錯誤的分類名!'), {type: 'error'});
mw.notify(wgULS('错误的分类名!', '錯誤的分類名!'), {type: 'error'});
return;
return;
}
}
mw.timedQuery(api, {list: 'categorymembers', cmtitle: target.title, cmprop: 'ids', cmlimit: 'max',
console.log('End API request: 已获得标准的页面名,用时 ' + (mw.now() - now) + ' ms');
console.log('API request: 查询分类下的页面列表');
cmsort: 'timestamp', cmdir: 'older'}, wgULS('分类下的页面列表', '分類下的頁面列表')).then(function(cm) {
now = mw.now();
api.get({action: 'query', list: 'categorymembers', cmtitle: target.title, cmprop: 'ids',
cmlimit: 'max', cmsort: 'timestamp', cmdir: 'older', formatversion: 2}).then(function(cm) {
console.log('End API request: 已获得分类下的页面列表,用时 ' + (mw.now() - now) + ' ms');
const pages = cm.query.categorymembers.map(function(ele) { return ele.pageid; });
const pages = cm.query.categorymembers.map(function(ele) { return ele.pageid; });
if (pages.length === 0) {
if (pages.length === 0) {
第154行: 第165行:
return;
return;
}
}
const currentIndex = pages[0] == id ? 1 : 0;
initList(pages);
mw.storage.setObject( 'inspect-category', [pages, currentIndex] );
}, function() {});
}, function() {});
btns[4].setHref( '/?redirect=no&curid=' + pages[ currentIndex ] ).setDisabled( false );
}, function(reason) { mw.apiFailure(reason, wgULS('该分类下的页面列表', '該分類下的頁面列表')); });
}, function(reason) { mw.apiFailure(reason, wgULS('标准页面名称', '標準頁面名稱')); });
});
});
});
});
if (states.includes( mw.loader.getState( 'ext.gadget.PreviewWithVariant' ) )) {
const options = [{label: "大陆简体", data: "zh-cn"}, {label: "香港繁體", data: "zh-hk"},
{label: "澳門繁體", data: "zh-mo"}, {label: "马来西亚简体", data: "zh-my"},
{label: "新加坡简体", data: "zh-sg"}, {label: "臺灣繁體", data: "zh-tw"}],
select = new OO.ui.DropdownInputWidget({classes: ['inspector-variant'], options: options, value: lang})
.on('change', function() {
lang = select.getValue();
});
select.$element.prependTo( btns[1].$element );
btns[1].$element.contextmenu(function(e) {
e.preventDefault();
select.$element.find( '.oo-ui-menuSelectWidget' ).removeClass( 'oo-ui-element-hidden' );
}).children('a').blur(function() {
select.$element.find( '.oo-ui-menuSelectWidget' ).addClass( 'oo-ui-element-hidden' );
});
}
$.when(getJSON, mw.request).then(function(config, data) {
$.when(getJSON, mw.request).then(function(config, data) {
console.log('End API request: 已获得页面Wikitext');
console.log('End API request: 已获得页面Wikitext');
text = data[0].query.pages[0].revisions[0].content;
text = data[0].query.pages[0].revisions[0].content;
outer.removeClass( 'mw-ajax-loader' );
$outer.removeClass( 'mw-ajax-loader' );
editor = new CodeMirror(outer[0], {value: text, mode: 'text/mediawiki', mwConfig: config[0],
editor = new CodeMirror($outer[0], {value: text, mode: 'text/mediawiki', mwConfig: config[0],
lineWrapping: true, lineNumbers: true});
lineWrapping: true, lineNumbers: true});
wrapper = $( editor.getWrapperElement() ).toggle();
$wrapper = $( editor.getWrapperElement() ).toggle();
$('<div>', {id: 'inspector-btns', html: btns.map(function(ele) { return ele.$element; })}).appendTo( outer );
$('<div>', {id: 'inspector-btns', html: btns.map(function(ele) { return ele.$element; })}).appendTo( $outer );
// 处理页面上方的差异
$('.diff').click(function(e) {
$('.diff').click(function(e) {
const row = $( e.target ).closest('tr'),
const row = $( e.target ).closest('tr'),
第174行: 第200行:
rowLineno = isLineno ? row : row.prevAll( ':has(.diff-lineno)' ).first(),
rowLineno = isLineno ? row : row.prevAll( ':has(.diff-lineno)' ).first(),
n = parseInt( rowLineno.children().last().text().match(/\d+/) ) + row.index() - rowLineno.index()
n = parseInt( rowLineno.children().last().text().match(/\d+/) ) + row.index() - rowLineno.index()
- 2 + isLineno,
- 2 + isLineno, // 使用parseInt规避可能的程序错误,注意CodeMirror的行号从0开始
nLine = editor.lastLine();
nLine = editor.lastLine();
if (nLine >= n) {
if (nLine >= n) {
第184行: 第210行:
}, function(reason) { mw.apiFailure(reason, 'CodeMirror' + wgULS('设置或页面', '設置或頁面') + 'Wikitext'); });
}, function(reason) { mw.apiFailure(reason, 'CodeMirror' + wgULS('设置或页面', '設置或頁面') + 'Wikitext'); });
}
}
//</nowiki>
// [[category:维护工具]] [[category:桌面版小工具]] [[category:需要自确用户权限的小工具]]
// {{DEFAULTSORT:inspect.js}}

2021年1月7日 (四) 08:28的版本

//<nowiki>
// 由ResourceLoader直接调用,不可使用ES6语法
/**
 * @Function: 在页面内快速编辑和预览
 * @Author: [[User:Bhsd]]
 */
"use strict";
/*global mw, $, OO, CodeMirror, wgULS*/
const id = mw.config.get( 'wgArticleId' ),
    curRevid = mw.config.get( 'wgCurRevisionId' ),
    page = mw.config.get( 'wgPageName' ),
    gadgets = mw.gadgets || {},
    inspect = gadgets.inspect || {},
    rule = inspect.rule;
// 不是模板、不是重定向、页面已存在、阅读模式、Wikitext、最新版本
if ((mw.config.get( 'wgNamespaceNumber' ) != 10 || page.endsWith( '/doc' )) && !mw.config.get('wgIsRedirect') &&
    id > 0 && mw.config.get('wgAction') == 'view' && mw.config.get( 'wgPageContentModel' ) == 'wikitext' &&
    mw.config.get( 'wgRevisionId' ) == curRevid && rule === undefined ? true : rule) {
    // 先提交Ajax请求,这里手动设置cache: true
    const api = new mw.Api(),
        getJSON = $.ajax({ dataType: 'json', cache: true,
        url: '/zh?title=mediawiki:gadget-CodeMirror.json&action=raw&ctype=application/json' });
    // 生成通用的API请求
    mw.request = mw.request || mw.standardQuery(api);
    // 标注<p>标签
    const css = mw.util.addCSS( '#mw-content-text .mw-parser-output p { border:1px dashed; }' +
        // 解决<ul>等元素文字不换行的问题
        '#mw-content-text .mw-parser-output { display:flow-root; overflow:hidden; word-wrap:break-word; }' ),
        cssHide = mw.util.addCSS( '#inspector-btns { margin-bottom:0.5em; }' +
        // 由于resizable,left, top, height必须加!important,width不可加!important
        '#inspector { position:fixed; bottom:0; right:24px; left:unset !important; top:unset !important;' +
        'height:unset !important; width:calc(50% - 7rem - 0.5px); }' );
    css.disabled = true;
    var dialog, actionP, actionD, text, editor, $wrapper, nextid,
        lang = mw.config.get( 'wgUserVariant' );
    const states = ['loaded', 'loading', 'ready'], // 以上states表示开启对应的小工具
        isBackup = states.includes( mw.loader.getState( 'ext.gadget.contentBackup' ) ),
        backupObj = isBackup ? Object.fromEntries( mw.storage.getObject( 'LLWiki-contentBackup' ) || [] ) : [],
        backup = isBackup ? function() {
        backupObj[id] = [mw.now(), editor.getValue()];
        mw.storage.setObject( Object.entries(backupObj) );
    } : function() {},
        loadBackup = function() {
        const backupContent = (backupObj[id] || [])[1];
        if (!backupContent) { return; }
        mw.confirm( wgULS('要加载备份吗?', '要加載備份嗎?'), 'progressive' ).then(function(confirm) {
            if (confirm) {  editor.setValue( backupContent ); }
        });
    },
        $content = $('#mw-content-text, #mw-imagepage-content').last(),
        $original = $content.children( '.mw-parser-output' ),
        $placeholder = $('<div>', {class: "mw-parser-output"}).css('display', 'none'), // 用于替换时保留原始数据
        $url = $('<a>'), // 桌面版CSS不必需href
        $warning = $('<div>', {html: [wgULS("确定要还原为未编辑的状态吗?", "確認要復原為未編輯的狀態嗎?"), '<br>',
            wgULS("建议您做好编辑内容备份。", "建議您做好編輯內容備份。")]}),
        $outer = $('<div>', {id: 'inspector', class: 'mw-ajax-loader'}).insertBefore( $original )
        .on('contextmenu', '.cm-mw-template-name', function(e) {
        e.preventDefault();
        const template = '模板:' + $(this).text();
        var label;
        // 这里增加一次确认界面是因为部分浏览器不允许JS直接打开新标签页。
        if (!dialog) {
            actionP = new OO.ui.ActionWidget({label: '是', target: '_blank', flags: 'progressive'});
            actionD = new OO.ui.ActionWidget({label: '否', flags: 'destructive'});
            label = [wgULS('要在新标签页打开', '要在新標籤頁打開'), $url, wgULS('吗?', '嗎?')];
        }
        $url.text( template );
        actionP.setHref('/zh/' + template);
        dialog = mw.dialog(dialog, [actionP, actionD], label);
    }).resizable( {handles: 'w', minWidth: 350} ),
        btns = [new OO.ui.ButtonWidget({label: '提交', flags: ['primary', 'progressive']}).on('click', function() {
        backup();
        // 改变CSS样式表示提交中
        btns[0].setDisabled( true );
        mw.safeEdit(api, curRevid, {pageid: id, text: editor.getValue(),
            summary: wgULS('使用页面/文本对比查看器快速编辑', '使用頁面/文本對比察看器快速編輯')}).then(function() {
            location.reload();
        }, function(reason) { if (reason != 'editConflict') { btns[0].setDisabled( false ); } }); // 编辑冲突需刷新页面
    }), new OO.ui.ButtonWidget({label: wgULS('预览', '預覽')}).on('click', function() {
        backup();
        // 改变CSS样式表示预览中
        btns[1].setDisabled( true );
        console.log('API request: 请求预览');
        const now = mw.now();
        api.parse( editor.getValue(), {title: page, uselang: lang, disablelimitreport: 1, disableeditsection: 1} )
            .then(function(html) {
            console.log('End API request: 已生成预览,用时 ' + (mw.now() - now) + ' ms');
            if ($.contains( $content[0], $original[0] )) { $original.after( $placeholder ).detach(); }
            $content.children( '.mw-parser-output' ).replaceWith( html );
            if (mw.resizeLyrics) { mw.resizeLyrics(); }
            mw.hook( 'wikipage.content' ).fire($content);
        }, function(reason) { mw.apiFailure(reason, wgULS('预览', '預覽')); }) // mw.apiFailure不会抛出错误
            .then(function() { btns[1].setDisabled( false ); });
    }), new OO.ui.ButtonWidget({label: wgULS('还原', '復原'), flags: 'destructive'}).on('click', function() {
        mw.confirm($warning, ['primary', 'destructive']).then(function(confirm) {
            if (!confirm) { return; }
            editor.setValue( text );
            $content.children( '.mw-parser-output' ).replaceWith( $original );
            if (mw.resizeLyrics) { mw.resizeLyrics(); }
        });
    }), new OO.ui.ButtonWidget({label: wgULS('显示', '顯示')}).on('click', function() {
        css.disabled = !css.disabled;
        cssHide.disabled = !cssHide.disabled;
        $wrapper.toggle();
        btns[3].setLabel( css.disabled ? wgULS('显示', '顯示') : wgULS('隐藏', '隱藏'));
        btns[2].setDisabled( css.disabled ); // 折叠时无法更新textarea
        if (mw.resizeLyrics) { mw.resizeLyrics(); }
    }), new OO.ui.ButtonWidget({flags: 'progressive', icon: 'next'})],
        list = mw.storage.getObject( 'inspect-category' ),
        initList = function(pages) {
        const currentIndex = pages[0] == id ? 1 : 0;
        mw.storage.setObject( 'inspect-category', [pages, currentIndex] );
        btns[4].setHref( '/?redirect=no&curid=' + pages[ currentIndex ] ).setDisabled( false );
    };
    if (!list) { nextid = id + 1; }
    else {
        if (list[0][ list[1] ] == id) {
            list[1]++;
            mw.storage.setObject( 'inspect-category', list );
        }
        nextid = list[0][ list[1] ];
    }
    if (isBackup) { btns[2].$element.contextmenu( loadBackup ); }
    btns[4].setHref( '/?redirect=no&curid=' + nextid ).setDisabled( !nextid ).$element.contextmenu(function(e) {
        e.preventDefault();
        OO.ui.prompt( wgULS('请输入分类名或名字空间编号:', '請輸入分類名或名字空間編號:') ).then(function(cat) {
            if (cat === null) { return; }
            // 输入空白则清除分类
            if (cat === '') {
                mw.storage.remove( 'inspect-category' );
                btns[4].setHref( '/?redirect=no&curid=' + (id + 1) ).setDisabled( false );
                return;
            }
            // 输入数字对应名字空间
            if (!isNaN(cat)) {
                if (!Object.keys( mw.config.get( 'wgFormattedNamespaces' ) ).includes(cat)) {
                    mw.notify(wgULS('错误的名字空间编号!', '錯誤的名字空間編號!'), {type: 'error'});
                    return;
                }
                mw.timedQuery(api, {list: 'allpages', apnamespace: cat, apfilterredir: 'nonredirects', aplimit: 'max'},
                    wgULS('该名字空间的页面列表', '該名字空間的頁面列表')).then(function(ap) {
                    const pages = ap.query.allpages.map(function(ele) { return ele.pageid; })
                        .sort(function(a, b) { return a < b; }); // 由新到旧排列
                    if (pages.length === 0) {
                        mw.notify( wgULS('该名字空间沒有非重定向的页面!', '該名字空間沒有非重定向的頁面!'), {type: 'warn'} );
                        return;
                    }
                    initList(pages);
                }, function() {}); // mw.timedQuery已经通知过错误信息,这里只要一个空函数处理reject,下同
                return;
            }
            if (!/^(category|分[类類]):/i.test( cat )) { cat = 'Category:' + cat; }
            // cmtitle参数不可自动转换,所以需要先获得转换后的正确标题
            mw.timedQuery(api, {titles: cat, converttitles: 1}, wgULS('标准页面名称', '標準頁面名稱')) .then(function(r) {
                const target = r.query.pages[0];
                if (target.missing) {
                    mw.notify(wgULS('错误的分类名!', '錯誤的分類名!'), {type: 'error'});
                    return;
                }
                mw.timedQuery(api, {list: 'categorymembers', cmtitle: target.title, cmprop: 'ids', cmlimit: 'max',
                    cmsort: 'timestamp', cmdir: 'older'}, wgULS('分类下的页面列表', '分類下的頁面列表')).then(function(cm) {
                    const pages = cm.query.categorymembers.map(function(ele) { return ele.pageid; });
                    if (pages.length === 0) {
                        mw.notify( wgULS('该分类下无页面!', '該分類下無頁面!'), {type: 'warn'} );
                        return;
                    }
                    initList(pages);
                }, function() {});
            }, function() {});
        });
    });
    if (states.includes( mw.loader.getState( 'ext.gadget.PreviewWithVariant' ) )) {
        const options = [{label: "大陆简体", data: "zh-cn"}, {label: "香港繁體", data: "zh-hk"},
            {label: "澳門繁體", data: "zh-mo"}, {label: "马来西亚简体", data: "zh-my"},
            {label: "新加坡简体", data: "zh-sg"}, {label: "臺灣繁體", data: "zh-tw"}],
            select = new OO.ui.DropdownInputWidget({classes: ['inspector-variant'], options: options, value: lang})
            .on('change', function() {
            lang = select.getValue();
        });
        select.$element.prependTo( btns[1].$element );
        btns[1].$element.contextmenu(function(e) {
            e.preventDefault();
            select.$element.find( '.oo-ui-menuSelectWidget' ).removeClass( 'oo-ui-element-hidden' );
        }).children('a').blur(function() {
            select.$element.find( '.oo-ui-menuSelectWidget' ).addClass( 'oo-ui-element-hidden' );
        });
    }
    $.when(getJSON, mw.request).then(function(config, data) {
        console.log('End API request: 已获得页面Wikitext');
        text = data[0].query.pages[0].revisions[0].content;
        $outer.removeClass( 'mw-ajax-loader' );
        editor = new CodeMirror($outer[0], {value: text, mode: 'text/mediawiki', mwConfig: config[0],
            lineWrapping: true, lineNumbers: true});
        $wrapper = $( editor.getWrapperElement() ).toggle();
        $('<div>', {id: 'inspector-btns', html: btns.map(function(ele) { return ele.$element; })}).appendTo( $outer );
        // 处理页面上方的差异
        $('.diff').click(function(e) {
            const row = $( e.target ).closest('tr'),
                isLineno = row.children( '.diff-lineno' ).length ? 1 : 0,
                rowLineno = isLineno ? row : row.prevAll( ':has(.diff-lineno)' ).first(),
                n = parseInt( rowLineno.children().last().text().match(/\d+/) ) + row.index() - rowLineno.index()
                - 2 + isLineno, // 使用parseInt规避可能的程序错误,注意CodeMirror的行号从0开始
                nLine = editor.lastLine();
            if (nLine >= n) {
                editor.scrollIntoView(nLine);
                editor.scrollIntoView(n);
            }
            else { mw.notify( wgULS('当前不存在该行!', '當前不存在該行!'), {type: 'warn'} ); }
        });
    }, function(reason) { mw.apiFailure(reason, 'CodeMirror' + wgULS('设置或页面', '設置或頁面') + 'Wikitext'); });
}
//</nowiki>
// [[category:维护工具]] [[category:桌面版小工具]] [[category:需要自确用户权限的小工具]]
// {{DEFAULTSORT:inspect.js}}