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

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

来自LLWiki
跳转到导航 跳转到搜索
第8行: 第8行:
/*global OO, CodeMirror, wgULS*/
/*global OO, CodeMirror, wgULS*/
$(() => { // 避免全局变量
$(() => { // 避免全局变量
let nextid, list = mw.storage.getObject( 'inspect-category' ), flag;
let nextid, list = mw.storage.getObject( 'inspect-category' ), flag;
const id = mw.config.get( 'wgArticleId' ),
const id = mw.config.get( 'wgArticleId' ),
curRevid = mw.config.get( 'wgCurRevisionId' ),
curRevid = mw.config.get( 'wgCurRevisionId' ),
page = mw.config.get( 'wgPageName' ), // 这里未转义,后面应用时需要注意
page = mw.config.get( 'wgPageName' ), // 这里未转义,后面应用时需要注意
isCode = mw.config.get( 'wgPageContentModel' ) != 'wikitext', // javascript/css/Scribunto/json
isCode = mw.config.get( 'wgPageContentModel' ) != 'wikitext', // javascript/css/Scribunto/json
gadgets = mw.gadgets || {},
gadgets = mw.gadgets || {},
charInsert = gadgets.charinsert || {},
charInsert = gadgets.charinsert || {},
src = charInsert.src || 'special:我的用户页/edittools',
src = charInsert.src || 'special:我的用户页/edittools',
// 由于resizable,left, top, height必须加!important,width不可加!important
// 由于resizable,left, top, height必须加!important,width不可加!important
cssHide = mw.util.addCSS( `#inspector { position:fixed; bottom:0; right:24px; left:auto !important;
cssHide = mw.util.addCSS( `#inspector { position:fixed; bottom:0; right:24px; left:auto !important;
top:auto !important; height:auto !important; width:calc(50% - 7rem - 0.5px); }
top:auto !important; height:auto !important; width:calc(50% - 7rem - 0.5px); }
div.start-screen { bottom:40px; }` ),
div.start-screen { bottom:40px; }` ),
$content = $('#mw-content-text, #mw-imagepage-content').last(),
$content = $('#mw-content-text, #mw-imagepage-content').last(),
// wikitext/Scribunto对应.mw-parser-output,javascript/css对应.mw-code,json对应.mw-json
// wikitext/Scribunto对应.mw-parser-output,javascript/css对应.mw-code,json对应.mw-json
$original = $content.children('.mw-json, .mw-code, .mw-parser-output'),
$original = $content.children('.mw-json, .mw-code, .mw-parser-output'),
$outer = $('<div>', {id: 'inspector', class: 'mw-ajax-loader'}).insertBefore( $original ),
$outer = $('<div>', {id: 'inspector', class: 'mw-ajax-loader'}).insertBefore( $original ),
api = new mw.Api();
api = new mw.Api();
if (!list) { nextid = id + 1; }
if (!list) { nextid = id + 1; }
else {
else {
flag = list[0][ list[1] ] == id;
flag = list[0][ list[1] ] == id;
if (flag) { list[1]++; }
if (flag) { list[1]++; }
nextid = list[0][ list[1] ];
nextid = list[0][ list[1] ];
}
}
const btnGo = new OO.ui.ButtonWidget({flags: 'progressive', icon: 'next', href: `/zh?redirect=no&curid=${nextid}`,
const btnGo = new OO.ui.ButtonWidget({flags: 'progressive', icon: 'next', href: `/zh?redirect=no&curid=${nextid}`,
disabled: !nextid}),
disabled: !nextid}),
initList = (pages, ctn) => {
initList = (pages, ctn) => {
console.info({pages: pages, ctn: ctn, id: id});
console.info({pages: pages, ctn: ctn, id: id});
flag = pages[0] == id || ctn && pages.includes(id); // &&的优先级高于||
flag = pages[0] == id || ctn && pages.includes(id); // &&的优先级高于||
list = [pages, flag ? pages.indexOf(id) + 1 : 0];
list = [pages, flag ? pages.indexOf(id) + 1 : 0];
if (ctn && !flag) { mw.notify(wgULS('当前页面并不属于该分类或名字空间!', '當前頁面並不屬於該分類或名字空間!'),
if (ctn && !flag) { mw.notify(wgULS('当前页面并不属于该分类或名字空间!', '當前頁面並不屬於該分類或名字空間!'),
{type: 'warn'}); }
{type: 'warn'}); }
mw.storage.setObject( 'inspect-category', [pages, list[1] - flag] );
mw.storage.setObject( 'inspect-category', [pages, list[1] - flag] );
btnGo.setHref( `/zh?redirect=no&curid=${pages[ list[1] ]}` ).setDisabled( false );
btnGo.setHref( `/zh?redirect=no&curid=${pages[ list[1] ]}` ).setDisabled( false );
},
},
dialog = new OO.ui.MessageDialog(),
dialog = new OO.ui.MessageDialog(),
actionC = new OO.ui.ActionWidget({action: 'cancel', label: '取消'}),
actionC = new OO.ui.ActionWidget({action: 'cancel', label: '取消'}),
actionG = new OO.ui.ActionWidget({action: 'go', label: wgULS('确定', '確認'), flags: 'progressive'}),
actionG = new OO.ui.ActionWidget({action: 'go', label: wgULS('确定', '確認'), flags: 'progressive'}),
textInput = new OO.ui.TextInputWidget(),
textInput = new OO.ui.TextInputWidget(),
fromHere = new OO.ui.CheckboxInputWidget(),
fromHere = new OO.ui.CheckboxInputWidget(),
message = [ $('<p>', {text: wgULS('请输入分类名或名字空间编号:', '請輸入分類名或名字空間編號:')}),
message = [ $('<p>', {text: wgULS('请输入分类名或名字空间编号:', '請輸入分類名或名字空間編號:')}),
textInput.$element,
textInput.$element,
new OO.ui.FieldLayout(fromHere, {label: wgULS('从当前页面继续', '從當前頁面繼續'), align: 'inline'}).$element
new OO.ui.FieldLayout(fromHere, {label: wgULS('从当前页面继续', '從當前頁面繼續'), align: 'inline'}).$element
],
],
updateCat = function(cat, ctn) {
updateCat = function(cat, ctn) {
// 输入空白则清除分类
// 输入空白则清除分类
if (cat === '') {
if (cat === '') {
mw.storage.remove( 'inspect-category' );
mw.storage.remove( 'inspect-category' );
btnGo.setHref( `/zh?redirect=no&curid=${id + 1}` ).setDisabled( false );
btnGo.setHref( `/zh?redirect=no&curid=${id + 1}` ).setDisabled( false );
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)) {
mw.notify(wgULS('错误的名字空间编号!', '錯誤的名字空間編號!'), {type: 'error'});
mw.notify(wgULS('错误的名字空间编号!', '錯誤的名字空間編號!'), {type: 'error'});
return;
return;
}
}
mw.timedQuery(api, {list: 'allpages', apnamespace: cat, apfilterredir: 'nonredirects', aplimit: 'max'},
mw.timedQuery(api, {list: 'allpages', apnamespace: cat, apfilterredir: 'nonredirects', aplimit: 'max'},
wgULS('该名字空间的页面列表', '該名字空間的頁面列表')).then(ap => {
wgULS('该名字空间的页面列表', '該名字空間的頁面列表')).then(ap => {
const pages = ap.query.allpages.map(ele => ele.pageid).sort((a, b) => a < b); // 由新到旧排列
const pages = ap.query.allpages.map(ele => ele.pageid).sort((a, b) => a < b); // 由新到旧排列
if (pages.length === 0) {
if (pages.length === 0) {
mw.notify( wgULS('该名字空间沒有非重定向的页面!', '該名字空間沒有非重定向的頁面!'), {type: 'warn'} );
mw.notify( wgULS('该名字空间沒有非重定向的页面!', '該名字空間沒有非重定向的頁面!'), {type: 'warn'} );
return;
return;
}
}
initList(pages, ctn);
initList(pages, ctn);
}, () => {}); // mw.timedQuery已经通知过错误信息,这里只要一个空函数处理reject,下同
}, () => {}); // mw.timedQuery已经通知过错误信息,这里只要一个空函数处理reject,下同
return;
return;
}
}
if (!/^(category|分[类類]):/i.test( cat )) { cat = `Category:${cat}`; }
if (!/^(category|分[类類]):/i.test( cat )) { cat = `Category:${cat}`; }
// cmtitle参数不可自动转换,所以需要先获得转换后的正确标题
// cmtitle参数不可自动转换,所以需要先获得转换后的正确标题
mw.timedQuery(api, {titles: cat, converttitles: 1}, wgULS('标准页面名称', '標準頁面名稱')).then(r => {
mw.timedQuery(api, {titles: cat, converttitles: 1}, wgULS('标准页面名称', '標準頁面名稱')).then(r => {
const target = r.query.pages[0];
const target = r.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',
mw.timedQuery(api, {list: 'categorymembers', cmtitle: target.title, cmprop: 'ids', cmlimit: 'max',
cmsort: 'timestamp', cmdir: 'older'}, wgULS('分类下的页面列表', '分類下的頁面列表')).then(cm => {
cmsort: 'timestamp', cmdir: 'older'}, wgULS('分类下的页面列表', '分類下的頁面列表')).then(cm => {
const pages = cm.query.categorymembers.map(ele => ele.pageid);
const pages = cm.query.categorymembers.map(ele => ele.pageid);
if (pages.length === 0) {
if (pages.length === 0) {
mw.notify( wgULS('该分类下无页面!', '該分類下無頁面!'), {type: 'warn'} );
mw.notify( wgULS('该分类下无页面!', '該分類下無頁面!'), {type: 'warn'} );
return;
return;
}
}
initList(pages, ctn);
initList(pages, ctn);
}, () => {});
}, () => {});
}, () => {});
}, () => {});
};
};
// 这个click事件不能绑到btnGo上,否则会禁用href跳转
// 这个click事件不能绑到btnGo上,否则会禁用href跳转
btnGo.$element.click(() => { if (list && flag) { mw.storage.setObject( 'inspect-category', list ); } })
btnGo.$element.click(() => { if (list && flag) { mw.storage.setObject( 'inspect-category', list ); } })
.contextmenu(e => {
.contextmenu(e => {
e.preventDefault();
e.preventDefault();
mw.dialog(dialog, [actionC, actionG], message).then(action => {
mw.dialog(dialog, [actionC, actionG], message).then(action => {
if (action == 'go') { updateCat( textInput.getValue(), fromHere.isSelected() ); }
if (action == 'go') { updateCat( textInput.getValue(), fromHere.isSelected() ); }
});
});
});
});
// 模板、JavaScript、CSS、Lua、JSON只需要箭头按钮
// 模板、JavaScript、CSS、Lua、JSON只需要箭头按钮
if (mw.config.get( 'wgNamespaceNumber' ) == 10 && !page.endsWith( '/doc' ) || isCode) {
if (mw.config.get( 'wgNamespaceNumber' ) == 10 && !page.endsWith( '/doc' ) || isCode) {
$outer.removeClass( 'mw-ajax-loader' ).append( $('<div>', {id: 'inspector-btns', html: btnGo.$element}) );
$outer.removeClass( 'mw-ajax-loader' ).append( $('<div>', {id: 'inspector-btns', html: btnGo.$element}) );
mw.loader.addStyleTag( '#inspector-btns { width:auto; }' );
mw.loader.addStyleTag( '#inspector-btns { width:auto; }' );
return;
return;
}
}
// 先提交Ajax请求,这里手动设置cache: true
// 先提交Ajax请求,这里手动设置cache: true
const getJSON = $.get({ dataType: 'json', cache: true,
const getJSON = $.get({ dataType: 'json', cache: true,
url: '//cdn.jsdelivr.net/gh/bhsd-harry/LLWiki@1.6/json/gadget-CodeMirror.json' }),
url: '//cdn.jsdelivr.net/gh/bhsd-harry/LLWiki@1.6/json/gadget-CodeMirror.json' }),
getExt = mw.loader.using(['ext.CodeMirror.lib', 'ext.CodeMirror.mode.mediawiki']);
getExt = mw.loader.using(['ext.CodeMirror.lib', 'ext.CodeMirror.mode.mediawiki']);
// 生成通用的API请求
// 生成通用的API请求
mw.standardQuery(api);
mw.standardQuery(api);
let text, editor, $wrapper, $replace, $charinsert, prefix, ns, section, secTitle, summary,
let text, editor, $wrapper, $replace, $charinsert, prefix, ns, section, secTitle, summary,
lang = mw.config.get( 'wgUserVariant' );
lang = mw.config.get( 'wgUserVariant' );
$original.find( ':header:has(.mw-editsection)' ).dblclick(function() {
$original.find( ':header:has(.mw-editsection)' ).dblclick(function() {
section = mw.util.getParamValue( 'section', $(this).find( '.mw-editsection > a' ).attr('href') );
section = mw.util.getParamValue( 'section', $(this).find( '.mw-editsection > a' ).attr('href') );
mw.sectionQuery(api, section).then(r => {
mw.sectionQuery(api, section).then(r => {
editor.setValue( r.parse.wikitext );
editor.setValue( r.parse.wikitext );
secTitle = r.parse.sections[0].line.replaceAll(' ', '_'); // 此处有bug,Wikitext可接受的HTML标记应被剥离
secTitle = r.parse.sections[0].line.replaceAll(' ', '_'); // 此处有bug,Wikitext可接受的HTML标记应被剥离
}, () => {});
}, () => {});
});
});
// 标注<p>标签
// 标注<p>标签
const css = mw.util.addCSS( `.empty { border:1px solid rgba(253,220,154,0.5); box-shadow:0 0 0.5em #fddc9a; }
const css = mw.util.addCSS( `.empty { border:1px solid rgba(253,220,154,0.5); box-shadow:0 0 0.5em #fddc9a; }
#mw-content-text .mw-parser-output { display:flow-root; max-height:100vh; overflow:auto;
#mw-content-text .mw-parser-output { display:flow-root; max-height:100vh; overflow:auto;
word-wrap:break-word; }` );
word-wrap:break-word; }` );
css.disabled = true;
css.disabled = true;
// 也可以使用user.options,这里使用其他方法绕过
// 也可以使用user.options,这里使用其他方法绕过
const isGadget = (name) => ['loaded', 'loading', 'ready'].includes( mw.loader.getState(`ext.gadget.${name}`) ),
const isGadget = (name) => ['loaded', 'loading', 'ready'].includes( mw.loader.getState(`ext.gadget.${name}`) ),
isBackup = isGadget('contentBackup'),
isBackup = isGadget('contentBackup'),
backupObj = isBackup ? Object.fromEntries( mw.storage.getObject( 'LLWiki-contentBackup' ) || [] ) : {},
backupObj = isBackup ? Object.fromEntries( mw.storage.getObject( 'LLWiki-contentBackup' ) || [] ) : {},
backup = isBackup ? () => {
backup = isBackup ? () => {
backupObj[id] = [mw.now(), editor.getValue()];
backupObj[id] = [mw.now(), editor.getValue()];
mw.storage.setObject( 'LLWiki-contentBackup', Object.entries(backupObj) );
mw.storage.setObject( 'LLWiki-contentBackup', Object.entries(backupObj) );
} : () => {},
} : () => {},
loadBackup = () => {
loadBackup = () => {
const backupContent = (backupObj[id] || [])[1];
const backupContent = (backupObj[id] || [])[1];
if (!backupContent) {
if (!backupContent) {
mw.notify(wgULS('当前页面尚未备份!', '當前頁面尚未備份!'), {type: 'warn'});
mw.notify(wgULS('当前页面尚未备份!', '當前頁面尚未備份!'), {type: 'warn'});
return;
return;
}
}
mw.confirm( wgULS('要加载备份吗?', '要加載備份嗎?'), 'progressive' ).then(confirm => {
mw.confirm( wgULS('要加载备份吗?', '要加載備份嗎?'), 'progressive' ).then(confirm => {
if (!confirm) { return; }
if (!confirm) { return; }
editor.setValue( backupContent );
editor.setValue( backupContent );
mw.notify(wgULS('已还原备份!', '已復原備份!'), {type: 'success'});
mw.notify(wgULS('已还原备份!', '已復原備份!'), {type: 'success'});
});
});
},
},
pEmpty = ($div) => {
pEmpty = ($div) => {
$div.find( 'p:not(:has(script))' ).filter(function() { return !/\S/.test( this.textContent ); })
$div.find( 'p:not(:has(script))' ).filter(function() { return !/\S/.test( this.textContent ); })
.addClass('empty');
.addClass('empty');
},
},
placeholder = [
placeholder = [
$('<div>', {class: "mw-parser-output"}).css('display', 'none'),
$('<div>', {class: "mw-parser-output"}).css('display', 'none'),
$('<h1>', {id: 'firstHeading', class: 'firstHeading'}),
$('<h1>', {id: 'firstHeading', class: 'firstHeading'}),
$('<div>', {id: 'catlinks'}).css('display', 'none'),
$('<div>', {id: 'catlinks'}).css('display', 'none'),
$('.mw-indicators')
$('.mw-indicators')
], // 用于替换时保留原始数据
], // 用于替换时保留原始数据
$warning = $('<div>', {html: [wgULS("确定要还原为未编辑的状态吗?", "確認要復原為未編輯的狀態嗎?"), '<br>',
$warning = $('<div>', {html: [wgULS("确定要还原为未编辑的状态吗?", "確認要復原為未編輯的狀態嗎?"), '<br>',
wgULS("建议您做好编辑内容备份。", "建議您做好編輯內容備份。")]}),
wgULS("建议您做好编辑内容备份。", "建議您做好編輯內容備份。")]}),
$heading = $('#firstHeading'),
$heading = $('#firstHeading'),
$cat = $('#catlinks'),
$cat = $('#catlinks'),
$indicator = $('.mw-indicator'),
$indicator = $('.mw-indicator'),
btns = [new OO.ui.ButtonWidget({label: '提交', flags: ['primary', 'progressive']}).on('click', () => {
btns = [new OO.ui.ButtonWidget({label: '提交', flags: ['primary', 'progressive']}).on('click', () => {
backup();
backup();
// 改变CSS样式表示提交中
// 改变CSS样式表示提交中
btns[0].setDisabled( true );
btns[0].setDisabled( true );
mw.safeEdit(api, curRevid, {pageid: id, text: editor.getValue(), section: section,
mw.safeEdit(api, curRevid, {pageid: id, text: editor.getValue(), section: section,
summary: (section === undefined ? '' : `/* ${secTitle} */ `) + (summary ? `${summary} //` : '//') +
summary: (section === undefined ? '' : `/* ${secTitle} */ `) + (summary ? `${summary} //` : '//') +
wgULS( '使用[[help:小工具/页面文本对比查看器|页面/文本对比查看器]]快速编辑',
wgULS( '使用[[help:小工具/页面文本对比查看器|页面/文本对比查看器]]快速编辑',
'使用[[help:小工具/页面文本对比查看器|頁面/文本對比察看器]]快速編輯')}, isBackup)
'使用[[help:小工具/页面文本对比查看器|頁面/文本對比察看器]]快速編輯')}, isBackup)
.then(() => { location.href = mw.util.getUrl( page ); },
.then(() => { location.href = mw.util.getUrl( page ); },
reason => { btns[0].setDisabled( reason == 'editConflict' ); }); // 编辑冲突必须刷新页面,不应重复提交
reason => { btns[0].setDisabled( reason == 'editConflict' ); }); // 编辑冲突必须刷新页面,不应重复提交
}), new OO.ui.ButtonWidget({label: wgULS('预览', '預覽')}).on('click', () => {
}), new OO.ui.ButtonWidget({label: wgULS('预览', '預覽')}).on('click', () => {
backup();
backup();
// 改变CSS样式表示预览中
// 改变CSS样式表示预览中
btns[1].setDisabled( true );
btns[1].setDisabled( true );
mw.timedParse(api, {text: editor.getValue(), uselang: lang, prop: 'text|categories|displaytitle|indicators'},
mw.timedParse(api, {text: editor.getValue(), uselang: lang, prop: 'text|categories|displaytitle|indicators'},
wgULS('预览', '預覽')).then(r => {
wgULS('预览', '預覽')).then(r => {
if ($.contains( $content[0], $original[0] )) {
if ($.contains( $content[0], $original[0] )) {
$original.after( placeholder[0] ).detach();
$original.after( placeholder[0] ).detach();
$heading.after( placeholder[1] ).detach();
$heading.after( placeholder[1] ).detach();
$cat.after( placeholder[2] ).detach();
$cat.after( placeholder[2] ).detach();
$indicator.detach();
$indicator.detach();
}
}
$content.children( '.mw-parser-output' ).replaceWith( r.parse.text );
$content.children( '.mw-parser-output' ).replaceWith( r.parse.text );
pEmpty( $content.children( '.mw-parser-output' ) );
pEmpty( $content.children( '.mw-parser-output' ) );
if (mw.resizeLyrics) { mw.resizeLyrics(); }
if (mw.resizeLyrics) { mw.resizeLyrics(); }
mw.hook( 'wikipage.content' ).fire($content);
mw.hook( 'wikipage.content' ).fire($content);
placeholder[1].html( r.parse.displaytitle );
placeholder[1].html( r.parse.displaytitle );
placeholder[3].html( Object.entries( r.parse.indicators ).map(ele =>
placeholder[3].html( Object.entries( r.parse.indicators ).map(ele =>
$('<div>', {class: 'mw-indicator', id: `mw-indicator-${ele[0]}`, html: ele[1]})) );
$('<div>', {class: 'mw-indicator', id: `mw-indicator-${ele[0]}`, html: ele[1]})) );
const showhidden = mw.user.options.get( 'showhiddencats' ),
const showhidden = mw.user.options.get( 'showhiddencats' ),
normalCats = r.parse.categories.filter(ele => !ele.hidden).map(ele => ele.category),
normalCats = r.parse.categories.filter(ele => !ele.hidden).map(ele => ele.category),
hiddenCats = (showhidden ? r.parse.categories.filter(ele => ele.hidden) : []).map(ele => ele.category);
hiddenCats = (showhidden ? r.parse.categories.filter(ele => ele.hidden) : []).map(ele => ele.category);
if (normalCats.length + hiddenCats.length === 0) { return; }
if (normalCats.length + hiddenCats.length === 0) { return; }
const $parseCat = $('<div>', {id: 'catlinks', class: 'catlinks', 'data-mw': 'interface'});
const $parseCat = $('<div>', {id: 'catlinks', class: 'catlinks', 'data-mw': 'interface'});
if (normalCats.length) {
if (normalCats.length) {
$parseCat.append( $('<div>', {id: 'mw-normal-catlinks', class: 'mw-normal-catlinks', html: [
$parseCat.append( $('<div>', {id: 'mw-normal-catlinks', class: 'mw-normal-catlinks', html: [
$('<a>', {title: wgULS('页面分类', '頁面分類'), text: wgULS('分类', '分類'),
$('<a>', {title: wgULS('页面分类', '頁面分類'), text: wgULS('分类', '分類'),
href: '/zh/special:页面分类'}),
href: '/zh/special:页面分类'}),
':',
':',
$('<ul>', {html: normalCats.map(ele => $('<li>', {html: $('<a>',
$('<ul>', {html: normalCats.map(ele => $('<li>', {html: $('<a>',
{href: mw.util.getUrl( `Category:${ele}` ), title: `Category:${ele}`, text: ele})
{href: mw.util.getUrl( `Category:${ele}` ), title: `Category:${ele}`, text: ele})
}))})
}))})
]}) );
]}) );
}
}
if (hiddenCats.length) {
if (hiddenCats.length) {
$parseCat.append( $('<div>', {id: 'mw-hidden-catlinks',
$parseCat.append( $('<div>', {id: 'mw-hidden-catlinks',
class: 'mw-hidden-catlinks mw-hidden-cats-user-shown', html: [
class: 'mw-hidden-catlinks mw-hidden-cats-user-shown', html: [
wgULS('隐藏分类:', '隱藏分類:'),
wgULS('隐藏分类:', '隱藏分類:'),
$('<ul>', {html: hiddenCats.map(ele => $('<li>', {html: $('<a>', {href: mw.util.getUrl(ele),
$('<ul>', {html: hiddenCats.map(ele => $('<li>', {html: $('<a>', {href: mw.util.getUrl(ele),
title: `Category:${ele}`, text: ele})}))})
title: `Category:${ele}`, text: ele})}))})
]}) );
]}) );
}
}
$('#catlinks').replaceWith( $parseCat );
$('#catlinks').replaceWith( $parseCat );
}, () => {}).then(() => { btns[1].setDisabled( false ); });
}, () => {}).then(() => { btns[1].setDisabled( false ); });
}), new OO.ui.ButtonWidget({label: wgULS('还原', '復原'), flags: 'destructive', disabled: true}).on('click', () => {
}), new OO.ui.ButtonWidget({label: wgULS('还原', '復原'), flags: 'destructive', disabled: true}).on('click', () => {
mw.confirm($warning, ['primary', 'destructive']).then(confirm => {
mw.confirm($warning, ['primary', 'destructive']).then(confirm => {
if (!confirm) { return; }
if (!confirm) { return; }
section = undefined;
section = undefined;
editor.setValue( text );
editor.setValue( text );
if ($.contains( $content[0], $original[0] )) { return; } // 无事发生
if ($.contains( $content[0], $original[0] )) { return; } // 无事发生
$content.children( '.mw-parser-output' ).replaceWith( $original );
$content.children( '.mw-parser-output' ).replaceWith( $original );
placeholder[1].replaceWith( $heading );
placeholder[1].replaceWith( $heading );
$('#catlinks').replaceWith( $cat );
$('#catlinks').replaceWith( $cat );
placeholder[3].html( $indicator );
placeholder[3].html( $indicator );
if (mw.resizeLyrics) { mw.resizeLyrics(); }
if (mw.resizeLyrics) { mw.resizeLyrics(); }
});
});
}), new OO.ui.ButtonWidget({label: wgULS('显示', '顯示')}).on('click', () => {
}), new OO.ui.ButtonWidget({label: wgULS('显示', '顯示')}).on('click', () => {
css.disabled = !css.disabled;
css.disabled = !css.disabled;
cssHide.disabled = !cssHide.disabled;
cssHide.disabled = !cssHide.disabled;
$wrapper.toggle();
$wrapper.toggle();
editor.setSize();
editor.setSize();
btns[3].setLabel( css.disabled ? wgULS('显示', '顯示') : wgULS('隐藏', '隱藏'));
btns[3].setLabel( css.disabled ? wgULS('显示', '顯示') : wgULS('隐藏', '隱藏'));
btns[2].setDisabled( css.disabled ); // 折叠时无法更新textarea
btns[2].setDisabled( css.disabled ); // 折叠时无法更新textarea
if (mw.resizeLyrics) { mw.resizeLyrics(); }
if (mw.resizeLyrics) { mw.resizeLyrics(); }
}), btnGo],
}), btnGo],
options = $('<div>', {text: wgULS('查找替换', '查找替換')}).click(() => {
options = $('<div>', {text: wgULS('查找替换', '查找替換')}).click(() => {
if ($replace) {
if ($replace) {
$replace.show();
$replace.show();
return;
return;
}
}
let replaceBackup;
let replaceBackup;
const enable = () => { replaceBtn.setDisabled( false ); },
const enable = () => { replaceBtn.setDisabled( false ); },
ptn = new OO.ui.TextInputWidget().on('change', enable),
ptn = new OO.ui.TextInputWidget().on('change', enable),
val = new OO.ui.MultilineTextInputWidget({autosize: true, maxRows: 3}).on('change', enable),
val = new OO.ui.MultilineTextInputWidget({autosize: true, maxRows: 3}).on('change', enable),
regex = new OO.ui.CheckboxInputWidget().on('change', enable),
regex = new OO.ui.CheckboxInputWidget().on('change', enable),
modifier = new OO.ui.CheckboxInputWidget().on('change', enable),
modifier = new OO.ui.CheckboxInputWidget().on('change', enable),
undoBtn = new OO.ui.ButtonWidget({label: wgULS('撤销', '撤銷'), disabled: true, flags: ['destructive']})
undoBtn = new OO.ui.ButtonWidget({label: wgULS('撤销', '撤銷'), disabled: true, flags: ['destructive']})
.on('click', () => {
.on('click', () => {
editor.setValue( replaceBackup );
editor.setValue( replaceBackup );
enable();
enable();
}),
}),
replaceBtn = new OO.ui.ButtonWidget({label: wgULS('替换', '替換'), flags: ['progressive']})
replaceBtn = new OO.ui.ButtonWidget({label: wgULS('替换', '替換'), flags: ['progressive']})
.on('click', () => {
.on('click', () => {
const pattern = ptn.getValue(),
const pattern = ptn.getValue(),
regexp = new RegExp(regex.isSelected() ? pattern : mw.util.escapeRegExp(pattern),
regexp = new RegExp(regex.isSelected() ? pattern : mw.util.escapeRegExp(pattern),
'g' + (modifier.isSelected() ? 'i' : ''));
'g' + (modifier.isSelected() ? 'i' : ''));
replaceBackup = editor.getValue();
replaceBackup = editor.getValue();
undoBtn.setDisabled( false );
undoBtn.setDisabled( false );
replaceBtn.setDisabled( true ); // 防止连续点击造成无法撤销
replaceBtn.setDisabled( true ); // 防止连续点击造成无法撤销
editor.setValue( replaceBackup.replace(regexp, val.getValue()) );
editor.setValue( replaceBackup.replace(regexp, val.getValue()) );
}),
}),
hideBtn = new OO.ui.ButtonWidget({label: wgULS('关闭', '關閉')})
hideBtn = new OO.ui.ButtonWidget({label: wgULS('关闭', '關閉')})
.on('click', () => {
.on('click', () => {
$replace.hide();
$replace.hide();
enable();
enable();
});
});
$replace = $('<div>', {class: 'inspector-field', html: [
$replace = $('<div>', {class: 'inspector-field', html: [
$('<div>', {html: ['查找:', ptn.$element]}),
$('<div>', {html: ['查找:', ptn.$element]}),
$('<div>', {html: [wgULS('替换:', '替換:'), val.$element]}),
$('<div>', {html: [wgULS('替换:', '替換:'), val.$element]}),
$('<div>', {html: [
$('<div>', {html: [
new OO.ui.FieldLayout(regex, {label: wgULS('正则', '正則'), align: 'inline'}).$element,
new OO.ui.FieldLayout(regex, {label: wgULS('正则', '正則'), align: 'inline'}).$element,
new OO.ui.FieldLayout(modifier, {label: $('<i>', {text: 'i'}), align: 'inline'}).$element,
new OO.ui.FieldLayout(modifier, {label: $('<i>', {text: 'i'}), align: 'inline'}).$element,
replaceBtn.$element, undoBtn.$element, hideBtn.$element
replaceBtn.$element, undoBtn.$element, hideBtn.$element
]})
]})
]}).appendTo( 'body' ).draggable();
]}).appendTo( 'body' ).draggable();
}).add(isGadget('charinsert') ? $('<div>', {text: '快速插入'}).click(() => {
}).add(isGadget('charinsert') ? $('<div>', {text: '快速插入'}).click(() => {
if ($charinsert) {
if ($charinsert) {
$charinsert.show();
$charinsert.show();
return;
return;
}
}
$charinsert = $('<div>', {html: $('<div>', {text: '快速插入工具', id: 'inspector-field-title'}),
$charinsert = $('<div>', {html: $('<div>', {text: '快速插入工具', id: 'inspector-field-title'}),
class: 'inspector-field mw-ajax-loader'}).appendTo('body').draggable().contextmenu(e => {
class: 'inspector-field mw-ajax-loader'}).appendTo('body').draggable().contextmenu(e => {
e.preventDefault();
e.preventDefault();
$charinsert.hide();
$charinsert.hide();
}).on('click', '.mw-charinsert-item', function() {
}).on('click', '.mw-charinsert-item', function() {
const $this = $(this),
const $this = $(this),
start = $this.data('mw-charinsert-start') || $this.data('start') || '',
start = $this.data('mw-charinsert-start') || $this.data('start') || '',
end = $this.data('mw-charinsert-end') || $this.data('end') || '';
end = $this.data('mw-charinsert-end') || $this.data('end') || '';
editor.replaceSelection( start + editor.getSelection() + end );
editor.replaceSelection( start + editor.getSelection() + end );
editor.focus();
editor.focus();
});
});
// 为了充分利用浏览器缓存,这里不使用API;注意url转义,这里不需要简化'/mediawiki/index.php'
// 为了充分利用浏览器缓存,这里不使用API;注意url转义,这里不需要简化'/mediawiki/index.php'
$.get({url: mw.util.getUrl(src, {variant: mw.config.get( 'wgUserLanguage' )}),
$.get({url: mw.util.getUrl(src, {variant: mw.config.get( 'wgUserLanguage' )}),
cache: charInsert.cache !== false, dataType: 'text'}).then(doc => {
cache: charInsert.cache !== false, dataType: 'text'}).then(doc => {
$charinsert.append( $( doc.match(/<body[\s\S]+<\/body>/)[0] ).find( '.mw-parser-output' ) );
$charinsert.append( $( doc.match(/<body[\s\S]+<\/body>/)[0] ).find( '.mw-parser-output' ) );
}, () => {
}, () => {
mw.notify([
mw.notify([
wgULS('无法加载', '無法加載'),
wgULS('无法加载', '無法加載'),
$('<a>', {text: src, href: mw.util.getUrl(src)}),
$('<a>', {text: src, href: mw.util.getUrl(src)}),
wgULS('!请检查该页面是否存在。', '!請檢查該頁面是否存在。')
wgULS('!请检查该页面是否存在。', '!請檢查該頁面是否存在。')
], {type: 'error', autoHideSeconds: 'long'});
], {type: 'error', autoHideSeconds: 'long'});
}).then(() => { $charinsert.removeClass( 'mw-ajax-loader' ); });
}).then(() => { $charinsert.removeClass( 'mw-ajax-loader' ); });
}) : null),
}) : null),
$dropdown = $('<div>', {class: "inspector-menu", html: options}),
$dropdown = $('<div>', {class: "inspector-menu", html: options}),
$hints = $('<div>', {class: "inspector-menu"}).on('click', '.inspector-hint', function() {
$hints = $('<div>', {class: "inspector-menu"}).on('click', '.inspector-hint', function() {
const cursor = editor.getCursor();
const cursor = editor.getCursor();
editor.replaceRange(this.textContent + (ns > 0 ? '}}' : ']]'),
editor.replaceRange(this.textContent + (ns > 0 ? '}}' : ']]'),
{line: cursor.line, ch: cursor.ch - prefix.length + 1}, cursor);
{line: cursor.line, ch: cursor.ch - prefix.length + 1}, cursor);
// 随后再次执行updateHints时会自行stop
// 随后再次执行updateHints时会自行stop
editor.focus();
editor.focus();
}),
}),
// 判别模板时可能会误处理解析器函数
// 判别模板时可能会误处理解析器函数
regexps = {0: /\[\[\s*:?([^|[\]{}<>]*)$/i, 10: /{{\s*([^|[\]{}<>#]*)$/i,
regexps = {0: /\[\[\s*:?([^|[\]{}<>]*)$/i, 10: /{{\s*([^|[\]{}<>#]*)$/i,
828: /{{\s*#invoke:\s*([^|[\]{}<>#:]*)$/i, 274: /{{\s*#widget:\s*([^|[\]{}<>#:]*)$/i},
828: /{{\s*#invoke:\s*([^|[\]{}<>#:]*)$/i, 274: /{{\s*#widget:\s*([^|[\]{}<>#:]*)$/i},
$noHint = $('<span>', {class: 'error'}),
$noHint = $('<span>', {class: 'error'}),
nsPrefixes = {0: '', 10: 'Template:', 828: '模块:', 274: 'Widget:'},
nsPrefixes = {0: '', 10: 'Template:', 828: '模块:', 274: 'Widget:'},
autocomplete = isGadget('autocomplete') ? e => {
autocomplete = isGadget('autocomplete') ? e => {
if (![9, 27].includes( e.keyCode )) { return; } // tab和esc
if (![9, 27].includes( e.keyCode )) { return; } // tab和esc
let $cursor, before, sections, title;
let $cursor, before, sections, title;
const cursor = editor.getCursor(),
const cursor = editor.getCursor(),
line = editor.getLine( cursor.line ),
line = editor.getLine( cursor.line ),
after = line.slice( cursor.ch );
after = line.slice( cursor.ch );
before = line.slice(0, cursor.ch);
before = line.slice(0, cursor.ch);
const cursorActivity = () => {
const cursorActivity = () => {
const newCursor = editor.getCursor(),
const newCursor = editor.getCursor(),
newLine = editor.getLine( newCursor.line ),
newLine = editor.getLine( newCursor.line ),
newAfter = newLine.slice( newCursor.ch );
newAfter = newLine.slice( newCursor.ch );
// 如果光标改变过位置
// 如果光标改变过位置
if (newCursor.line != cursor.line || newAfter != after) {
if (newCursor.line != cursor.line || newAfter != after) {
stop();
stop();
return;
return;
}
}
before = newLine.slice(0, newCursor.ch);
before = newLine.slice(0, newCursor.ch);
updateHints();
updateHints();
},
},
stop = () => {
stop = () => {
$hints.slideUp('fast');
$hints.slideUp('fast');
editor.off('cursorActivity', cursorActivity);
editor.off('cursorActivity', cursorActivity);
},
},
updateHints = mw.util.debounce(500, () => {
updateHints = mw.util.debounce(500, () => {
if (!regexps[ns].test( before )) {
if (!regexps[ns].test( before )) {
stop();
stop();
return;
return;
}
}
$cursor = $wrapper.find( '.CodeMirror-cursor' );
$cursor = $wrapper.find( '.CodeMirror-cursor' );
prefix = before.match( regexps[ns] )[1];
prefix = before.match( regexps[ns] )[1];
if (prefix === "" || prefix.startsWith('#')) { return; }
if (prefix === "" || prefix.startsWith('#')) { return; }
if (ns == '0' && prefix.includes('#')) {
if (ns == '0' && prefix.includes('#')) {
const capture = prefix.match(/^(.+)#(.*)$/);
const capture = prefix.match(/^(.+)#(.*)$/);
prefix = capture[2];
prefix = capture[2];
(title == capture[1] ? Promise.resolve() : mw.timedQuery(api, {action: 'parse', page: capture[1],
(title == capture[1] ? Promise.resolve() : mw.timedQuery(api, {action: 'parse', page: capture[1],
prop: 'sections'}, wgULS('段落标题', '段落標題')).then(r => {
prop: 'sections'}, wgULS('段落标题', '段落標題')).then(r => {
title = capture[1];
title = capture[1];
sections = r.parse.sections.map(ele => $('<div>', {class: 'inspector-hint', text: ele.line}));
sections = r.parse.sections.map(ele => $('<div>', {class: 'inspector-hint', text: ele.line}));
}, () => { throw null; })).then(() => {
}, () => { throw null; })).then(() => {
$hints.html( sections.filter(ele => ele.text().startsWith( prefix )) )
$hints.html( sections.filter(ele => ele.text().startsWith( prefix )) )
.show().position({my: 'left top', of: $cursor, within: $wrapper, collision: 'fit'});
.show().position({my: 'left top', of: $cursor, within: $wrapper, collision: 'fit'});
if ($hints.children().length === 0) {
if ($hints.children().length === 0) {
$noHint.text(wgULS('不存在对应段落!', '不存在對應段落!')).appendTo( $hints );
$noHint.text(wgULS('不存在对应段落!', '不存在對應段落!')).appendTo( $hints );
}
}
}, stop);
}, stop);
return;
return;
}
}
mw.timedQuery(api, {list: 'prefixsearch', pssearch: prefix, psnamespace: ns},
mw.timedQuery(api, {list: 'prefixsearch', pssearch: prefix, psnamespace: ns},
wgULS('前缀相符的页面', '前綴相符的頁面')).then(r => {
wgULS('前缀相符的页面', '前綴相符的頁面')).then(r => {
$hints.html( r.query.prefixsearch.map(ele =>
$hints.html( r.query.prefixsearch.map(ele =>
$('<div>', {class: 'inspector-hint', text: ele.title.slice( nsPrefixes[ns].length )})
$('<div>', {class: 'inspector-hint', text: ele.title.slice( nsPrefixes[ns].length )})
) ).show().position({my: 'left top', of: $cursor, within: $wrapper, collision: 'fit'});
) ).show().position({my: 'left top', of: $cursor, within: $wrapper, collision: 'fit'});
if ($hints.children().length === 0) {
if ($hints.children().length === 0) {
$noHint.text(wgULS('不存在对应页面!', '不存在對應頁面!')).appendTo( $hints );
$noHint.text(wgULS('不存在对应页面!', '不存在對應頁面!')).appendTo( $hints );
}
}
}, stop);
}, stop);
});
});
if (e.keyCode == 27) {
if (e.keyCode == 27) {
stop();
stop();
return;
return;
}
}
editor.execCommand( 'delCharBefore' ); // 删掉新增的\t
editor.execCommand( 'delCharBefore' ); // 删掉新增的\t
$.each(regexps, (k, v) => { if (v.test(before)) { ns = k; } });
$.each(regexps, (k, v) => { if (v.test(before)) { ns = k; } });
if (!ns) { return; }
if (!ns) { return; }
mw.notify(wgULS('开启自动补全提示。', '開啟自動補全提示。'), {tag: 'autocomplete'});
mw.notify(wgULS('开启自动补全提示。', '開啟自動補全提示。'), {tag: 'autocomplete'});
editor.on('cursorActivity', cursorActivity);
editor.on('cursorActivity', cursorActivity);
// 实际上updateHints总是延迟500ms执行,因此下一行代码的位置影响不大
// 实际上updateHints总是延迟500ms执行,因此下一行代码的位置影响不大
updateHints();
updateHints();
} : () => {};
} : () => {};
mw.loader.using(['jquery.ui', 'user.options']).then(() => {
mw.loader.using(['jquery.ui', 'user.options']).then(() => {
const actionP = new OO.ui.ActionWidget({label: '是', target: '_blank', flags: 'progressive'}),
const actionP = new OO.ui.ActionWidget({label: '是', target: '_blank', flags: 'progressive'}),
actionD = new OO.ui.ActionWidget({label: '否'}),
actionD = new OO.ui.ActionWidget({label: '否'}),
$url = $('<a>'), // 桌面版CSS不必需href
$url = $('<a>'), // 桌面版CSS不必需href
label = [wgULS('要在新标签页打开', '要在新標籤頁打開'), $url, wgULS('吗?', '嗎?')];
label = [wgULS('要在新标签页打开', '要在新標籤頁打開'), $url, wgULS('吗?', '嗎?')];
$outer.on('contextmenu', '.cm-mw-template-name', function() {
$outer.on('contextmenu', '.cm-mw-template-name', function() {
const template = '模板:' + $(this).text();
const template = '模板:' + $(this).text();
// 这里增加一次确认界面是因为部分浏览器不允许JS直接打开新标签页。
// 这里增加一次确认界面是因为部分浏览器不允许JS直接打开新标签页。
$url.text( template );
$url.text( template );
actionP.setHref( mw.util.getUrl(template) ); // 注意url转义
actionP.setHref( mw.util.getUrl(template) ); // 注意url转义
mw.dialog(dialog, [actionD, actionP], label).catch(() => {});
mw.dialog(dialog, [actionD, actionP], label).catch(() => {});
return false; // 这里需要同时执行stopImmediatePropagation()和preventDefault()
return false; // 这里需要同时执行stopImmediatePropagation()和preventDefault()
}).on('contextmenu', '.CodeMirror', e => {
}).on('contextmenu', '.CodeMirror', e => {
e.preventDefault();
e.preventDefault();
// 使用jquery.ui让菜单总是落在CodeMirror内,此时slideDown效果不生效
// 使用jquery.ui让菜单总是落在CodeMirror内,此时slideDown效果不生效
$dropdown.show().position({my: 'left top', of: e, within: $wrapper, collision: 'fit'});
$dropdown.show().position({my: 'left top', of: e, within: $wrapper, collision: 'fit'});
}).on('keydown', '.CodeMirror', autocomplete).resizable( {handles: 'w', minWidth: 350} );
}).on('keydown', '.CodeMirror', autocomplete).resizable( {handles: 'w', minWidth: 350} );
});
});
$('body').click(() => { $dropdown.slideUp('fast'); });
$('body').click(() => { $dropdown.slideUp('fast'); });
btns[0].$element.contextmenu(e => {
btns[0].$element.contextmenu(e => {
e.preventDefault();
e.preventDefault();
OO.ui.prompt( '请输入编辑摘要:' , {textInput: {value: summary}} ).then(input => {
OO.ui.prompt( '请输入编辑摘要:' , {textInput: {value: summary}} ).then(input => {
if (input !== null) { summary = input; }
if (input !== null) { summary = input; }
});
});
});
});
if (isBackup) {
if (isBackup) {
btns[2].$element.contextmenu(e => {
btns[2].$element.contextmenu(e => {
e.preventDefault();
e.preventDefault();
loadBackup();
loadBackup();
});
});
}
}
if (isGadget( 'PreviewWithVariant' ) ) {
if (isGadget( 'PreviewWithVariant' ) ) {
const options = [{label: "大陆简体", data: "zh-cn"}, {label: "香港繁體", data: "zh-hk"},
const options = [{label: "大陆简体", data: "zh-cn"}, {label: "香港繁體", data: "zh-hk"},
{label: "澳門繁體", data: "zh-mo"}, {label: "马来西亚简体", data: "zh-my"},
{label: "澳門繁體", data: "zh-mo"}, {label: "马来西亚简体", data: "zh-my"},
{label: "新加坡简体", data: "zh-sg"}, {label: "臺灣繁體", data: "zh-tw"}],
{label: "新加坡简体", data: "zh-sg"}, {label: "臺灣繁體", data: "zh-tw"}],
select = new OO.ui.DropdownInputWidget({classes: ['inspector-variant'], options: options, value: lang})
select = new OO.ui.DropdownInputWidget({classes: ['inspector-variant'], options: options, value: lang})
.on('change', () => { lang = select.getValue(); }),
.on('change', () => { lang = select.getValue(); }),
menu = select.$element.find( '.oo-ui-menuSelectWidget' )
menu = select.$element.find( '.oo-ui-menuSelectWidget' )
.click(() => { menu.addClass( 'oo-ui-element-hidden' ); });
.click(() => { menu.addClass( 'oo-ui-element-hidden' ); });
select.$element.prependTo( btns[1].$element );
select.$element.prependTo( btns[1].$element );
btns[1].$element.contextmenu(e => {
btns[1].$element.contextmenu(e => {
e.preventDefault();
e.preventDefault();
menu.removeClass( 'oo-ui-element-hidden' );
menu.removeClass( 'oo-ui-element-hidden' );
}).children('a').blur(() => { menu.addClass( 'oo-ui-element-hidden' ); });
}).children('a').blur(() => { menu.addClass( 'oo-ui-element-hidden' ); });
}
}
pEmpty( $original );
pEmpty( $original );
// $.when很容易出错,这里保险起见用Promise.all
// $.when很容易出错,这里保险起见用Promise.all
Promise.all([getJSON, mw.request, getExt]).then(data => {
Promise.all([getJSON, mw.request, getExt]).then(data => {
mw.config.set('extCodeMirrorConfig', data[0]);
mw.config.set('extCodeMirrorConfig', data[0]);
mw.hook( 'codemirror.config' ).fire();
mw.hook( 'codemirror.config' ).fire();
text = data[1].query.pages[0].revisions[0].content;
text = data[1].query.pages[0].revisions[0].content;
editor = new CodeMirror($outer[0], {value: text, mode: 'text/mediawiki', mwConfig: data[0],
editor = new CodeMirror($outer[0], {value: text, mode: 'text/mediawiki', mwConfig: data[0],
lineWrapping: true, lineNumbers: true});
lineWrapping: true, lineNumbers: true, resetSelectionOnContextMenu: false});
$wrapper = $( editor.getWrapperElement() ).toggle().append( [$dropdown, $hints] );
$wrapper = $( editor.getWrapperElement() ).toggle().append( [$dropdown, $hints] );
$('<div>', {id: 'inspector-btns', html: btns.map(ele => ele.$element)}).appendTo( $outer );
$('<div>', {id: 'inspector-btns', html: btns.map(ele => ele.$element)}).appendTo( $outer );
// 处理页面上方的差异
// 处理页面上方的差异
$('.diff').click(e => {
$('.diff').click(e => {
const row = $( e.target ).closest('tr');
const row = $( e.target ).closest('tr');
if (row.hasClass( 'diff-title' )) { return; }
if (row.hasClass( 'diff-title' )) { return; }
const isLineno = row.children( '.diff-lineno' ).length ? 1 : 0,
const isLineno = row.children( '.diff-lineno' ).length ? 1 : 0,
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, // 使用parseInt规避可能的程序错误,注意CodeMirror的行号从0开始
- 2 + isLineno, // 使用parseInt规避可能的程序错误,注意CodeMirror的行号从0开始
nLine = editor.lastLine();
nLine = editor.lastLine();
if (nLine >= n) {
if (nLine >= n) {
editor.scrollIntoView(nLine);
editor.scrollIntoView(nLine);
editor.scrollIntoView(n);
editor.scrollIntoView(n);
}
}
else { mw.notify( wgULS('当前不存在该行!', '當前不存在該行!'), {type: 'warn'} ); }
else { mw.notify( wgULS('当前不存在该行!', '當前不存在該行!'), {type: 'warn'} ); }
});
});
}, reason => { mw.apiFailure(reason, `CodeMirror${wgULS('设置或页面', '設置或頁面')}Wikitext`); })
}, reason => { mw.apiFailure(reason, `CodeMirror${wgULS('设置或页面', '設置或頁面')}Wikitext`); })
.then(() => { $outer.removeClass( 'mw-ajax-loader' ); });
.then(() => { $outer.removeClass( 'mw-ajax-loader' ); });
});
});
//</nowiki>
//</nowiki>

2021年9月22日 (三) 10:19的版本

//<nowiki>
// 由[[mediawiki:gadget-inspect-loader.js]]调用,可以使用ES6语法
/**
 * @Function: 在页面内快速编辑和预览
 * @Author: [[User:Bhsd]]
 */
"use strict";
/*global OO, CodeMirror, wgULS*/
$(() => { // 避免全局变量
	let nextid, list = mw.storage.getObject( 'inspect-category' ), flag;
	const id = mw.config.get( 'wgArticleId' ),
		curRevid = mw.config.get( 'wgCurRevisionId' ),
		page = mw.config.get( 'wgPageName' ), // 这里未转义,后面应用时需要注意
		isCode = mw.config.get( 'wgPageContentModel' ) != 'wikitext', // javascript/css/Scribunto/json
		gadgets = mw.gadgets || {},
		charInsert = gadgets.charinsert || {},
		src = charInsert.src || 'special:我的用户页/edittools',
		// 由于resizable,left, top, height必须加!important,width不可加!important
		cssHide = mw.util.addCSS( `#inspector { position:fixed; bottom:0; right:24px; left:auto !important;
		top:auto !important; height:auto !important; width:calc(50% - 7rem - 0.5px); }
		div.start-screen { bottom:40px; }` ),
		$content = $('#mw-content-text, #mw-imagepage-content').last(),
		// wikitext/Scribunto对应.mw-parser-output,javascript/css对应.mw-code,json对应.mw-json
		$original = $content.children('.mw-json, .mw-code, .mw-parser-output'),
		$outer = $('<div>', {id: 'inspector', class: 'mw-ajax-loader'}).insertBefore( $original ),
		api = new mw.Api();
	if (!list) { nextid = id + 1; }
	else {
		flag = list[0][ list[1] ] == id;
		if (flag) { list[1]++; }
		nextid = list[0][ list[1] ];
	}
	const btnGo = new OO.ui.ButtonWidget({flags: 'progressive', icon: 'next', href: `/zh?redirect=no&curid=${nextid}`,
		disabled: !nextid}),
		initList = (pages, ctn) => {
		console.info({pages: pages, ctn: ctn, id: id});
		flag = pages[0] == id || ctn && pages.includes(id); // &&的优先级高于||
		list = [pages, flag ? pages.indexOf(id) + 1 : 0];
		if (ctn && !flag) { mw.notify(wgULS('当前页面并不属于该分类或名字空间!', '當前頁面並不屬於該分類或名字空間!'),
			{type: 'warn'}); }
		mw.storage.setObject( 'inspect-category', [pages, list[1] - flag] );
		btnGo.setHref( `/zh?redirect=no&curid=${pages[ list[1] ]}` ).setDisabled( false );
	},
		dialog = new OO.ui.MessageDialog(),
		actionC = new OO.ui.ActionWidget({action: 'cancel', label: '取消'}),
		actionG = new OO.ui.ActionWidget({action: 'go', label: wgULS('确定', '確認'), flags: 'progressive'}),
		textInput = new OO.ui.TextInputWidget(),
		fromHere = new OO.ui.CheckboxInputWidget(),
		message = [ $('<p>', {text: wgULS('请输入分类名或名字空间编号:', '請輸入分類名或名字空間編號:')}),
		textInput.$element,
		new OO.ui.FieldLayout(fromHere, {label: wgULS('从当前页面继续', '從當前頁面繼續'), align: 'inline'}).$element
	],
		updateCat = function(cat, ctn) {
		// 输入空白则清除分类
		if (cat === '') {
			mw.storage.remove( 'inspect-category' );
			btnGo.setHref( `/zh?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(ap => {
				const pages = ap.query.allpages.map(ele => ele.pageid).sort((a, b) => a < b); // 由新到旧排列
				if (pages.length === 0) {
					mw.notify( wgULS('该名字空间沒有非重定向的页面!', '該名字空間沒有非重定向的頁面!'), {type: 'warn'} );
					return;
				}
				initList(pages, ctn);
			}, () => {}); // mw.timedQuery已经通知过错误信息,这里只要一个空函数处理reject,下同
			return;
		}
		if (!/^(category|分[类類]):/i.test( cat )) { cat = `Category:${cat}`; }
		// cmtitle参数不可自动转换,所以需要先获得转换后的正确标题
		mw.timedQuery(api, {titles: cat, converttitles: 1}, wgULS('标准页面名称', '標準頁面名稱')).then(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(cm => {
				const pages = cm.query.categorymembers.map(ele => ele.pageid);
				if (pages.length === 0) {
					mw.notify( wgULS('该分类下无页面!', '該分類下無頁面!'), {type: 'warn'} );
					return;
				}
				initList(pages, ctn);
			}, () => {});
		}, () => {});
	};
	// 这个click事件不能绑到btnGo上,否则会禁用href跳转
	btnGo.$element.click(() => { if (list && flag) { mw.storage.setObject( 'inspect-category', list ); } })
		.contextmenu(e => {
		e.preventDefault();
		mw.dialog(dialog, [actionC, actionG], message).then(action => {
			if (action == 'go') { updateCat( textInput.getValue(), fromHere.isSelected() ); }
		});
	});
	// 模板、JavaScript、CSS、Lua、JSON只需要箭头按钮
	if (mw.config.get( 'wgNamespaceNumber' ) == 10 && !page.endsWith( '/doc' ) || isCode) {
		$outer.removeClass( 'mw-ajax-loader' ).append( $('<div>', {id: 'inspector-btns', html: btnGo.$element}) );
		mw.loader.addStyleTag( '#inspector-btns { width:auto; }' );
		return;
	}
	// 先提交Ajax请求,这里手动设置cache: true
	const getJSON = $.get({ dataType: 'json', cache: true,
		url: '//cdn.jsdelivr.net/gh/bhsd-harry/LLWiki@1.6/json/gadget-CodeMirror.json' }),
		getExt = mw.loader.using(['ext.CodeMirror.lib', 'ext.CodeMirror.mode.mediawiki']);
	// 生成通用的API请求
	mw.standardQuery(api);
	let text, editor, $wrapper, $replace, $charinsert, prefix, ns, section, secTitle, summary,
		lang = mw.config.get( 'wgUserVariant' );
	$original.find( ':header:has(.mw-editsection)' ).dblclick(function() {
		section = mw.util.getParamValue( 'section', $(this).find( '.mw-editsection > a' ).attr('href') );
		mw.sectionQuery(api, section).then(r => {
			editor.setValue( r.parse.wikitext );
			secTitle = r.parse.sections[0].line.replaceAll(' ', '_'); // 此处有bug,Wikitext可接受的HTML标记应被剥离
		}, () => {});
	});
	// 标注<p>标签
	const css = mw.util.addCSS( `.empty { border:1px solid rgba(253,220,154,0.5); box-shadow:0 0 0.5em #fddc9a; }
		#mw-content-text .mw-parser-output { display:flow-root; max-height:100vh; overflow:auto;
		word-wrap:break-word; }` );
	css.disabled = true;
	// 也可以使用user.options,这里使用其他方法绕过
	const isGadget = (name) => ['loaded', 'loading', 'ready'].includes( mw.loader.getState(`ext.gadget.${name}`) ),
		isBackup = isGadget('contentBackup'),
		backupObj = isBackup ? Object.fromEntries( mw.storage.getObject( 'LLWiki-contentBackup' ) || [] ) : {},
		backup = isBackup ? () => {
		backupObj[id] = [mw.now(), editor.getValue()];
		mw.storage.setObject( 'LLWiki-contentBackup', Object.entries(backupObj) );
	} : () => {},
		loadBackup = () => {
		const backupContent = (backupObj[id] || [])[1];
		if (!backupContent) {
			mw.notify(wgULS('当前页面尚未备份!', '當前頁面尚未備份!'), {type: 'warn'});
			return;
		}
		mw.confirm( wgULS('要加载备份吗?', '要加載備份嗎?'), 'progressive' ).then(confirm => {
			if (!confirm) { return; }
			editor.setValue( backupContent );
			mw.notify(wgULS('已还原备份!', '已復原備份!'), {type: 'success'});
		});
	},
		pEmpty = ($div) => {
		$div.find( 'p:not(:has(script))' ).filter(function() { return !/\S/.test( this.textContent ); })
			.addClass('empty');
	},
		placeholder = [
		$('<div>', {class: "mw-parser-output"}).css('display', 'none'),
		$('<h1>', {id: 'firstHeading', class: 'firstHeading'}),
		$('<div>', {id: 'catlinks'}).css('display', 'none'),
		$('.mw-indicators')
	], // 用于替换时保留原始数据
		$warning = $('<div>', {html: [wgULS("确定要还原为未编辑的状态吗?", "確認要復原為未編輯的狀態嗎?"), '<br>',
			wgULS("建议您做好编辑内容备份。", "建議您做好編輯內容備份。")]}),
		$heading = $('#firstHeading'),
		$cat = $('#catlinks'),
		$indicator = $('.mw-indicator'),
		btns = [new OO.ui.ButtonWidget({label: '提交', flags: ['primary', 'progressive']}).on('click', () => {
		backup();
		// 改变CSS样式表示提交中
		btns[0].setDisabled( true );
		mw.safeEdit(api, curRevid, {pageid: id, text: editor.getValue(), section: section,
			summary: (section === undefined ? '' : `/* ${secTitle} */ `) + (summary ? `${summary} //` : '//') +
				wgULS( '使用[[help:小工具/页面文本对比查看器|页面/文本对比查看器]]快速编辑',
				'使用[[help:小工具/页面文本对比查看器|頁面/文本對比察看器]]快速編輯')}, isBackup)
			.then(() => { location.href = mw.util.getUrl( page ); },
			reason => { btns[0].setDisabled( reason == 'editConflict' ); }); // 编辑冲突必须刷新页面,不应重复提交
	}), new OO.ui.ButtonWidget({label: wgULS('预览', '預覽')}).on('click', () => {
		backup();
		// 改变CSS样式表示预览中
		btns[1].setDisabled( true );
		mw.timedParse(api, {text: editor.getValue(), uselang: lang, prop: 'text|categories|displaytitle|indicators'},
			wgULS('预览', '預覽')).then(r => {
			if ($.contains( $content[0], $original[0] )) {
				$original.after( placeholder[0] ).detach();
				$heading.after( placeholder[1] ).detach();
				$cat.after( placeholder[2] ).detach();
				$indicator.detach();
			}
			$content.children( '.mw-parser-output' ).replaceWith( r.parse.text );
			pEmpty( $content.children( '.mw-parser-output' ) );
			if (mw.resizeLyrics) { mw.resizeLyrics(); }
			mw.hook( 'wikipage.content' ).fire($content);
			placeholder[1].html( r.parse.displaytitle );
			placeholder[3].html( Object.entries( r.parse.indicators ).map(ele =>
				$('<div>', {class: 'mw-indicator', id: `mw-indicator-${ele[0]}`, html: ele[1]})) );
			const showhidden = mw.user.options.get( 'showhiddencats' ),
				normalCats = r.parse.categories.filter(ele => !ele.hidden).map(ele => ele.category),
				hiddenCats = (showhidden ? r.parse.categories.filter(ele => ele.hidden) : []).map(ele => ele.category);
			if (normalCats.length + hiddenCats.length === 0) { return; }
			const $parseCat = $('<div>', {id: 'catlinks', class: 'catlinks', 'data-mw': 'interface'});
			if (normalCats.length) {
				$parseCat.append( $('<div>', {id: 'mw-normal-catlinks', class: 'mw-normal-catlinks', html: [
					$('<a>', {title: wgULS('页面分类', '頁面分類'), text: wgULS('分类', '分類'),
						href: '/zh/special:页面分类'}),
					':',
					$('<ul>', {html: normalCats.map(ele => $('<li>', {html: $('<a>',
						{href: mw.util.getUrl( `Category:${ele}` ), title: `Category:${ele}`, text: ele})
					}))})
				]}) );
			}
			if (hiddenCats.length) {
				$parseCat.append( $('<div>', {id: 'mw-hidden-catlinks',
					class: 'mw-hidden-catlinks mw-hidden-cats-user-shown', html: [
					wgULS('隐藏分类:', '隱藏分類:'),
					$('<ul>', {html: hiddenCats.map(ele => $('<li>', {html: $('<a>', {href: mw.util.getUrl(ele),
						title: `Category:${ele}`, text: ele})}))})
				]}) );
			}
			$('#catlinks').replaceWith( $parseCat );
		}, () => {}).then(() => { btns[1].setDisabled( false ); });
	}), new OO.ui.ButtonWidget({label: wgULS('还原', '復原'), flags: 'destructive', disabled: true}).on('click', () => {
		mw.confirm($warning, ['primary', 'destructive']).then(confirm => {
			if (!confirm) { return; }
			section = undefined;
			editor.setValue( text );
			if ($.contains( $content[0], $original[0] )) { return; } // 无事发生
			$content.children( '.mw-parser-output' ).replaceWith( $original );
			placeholder[1].replaceWith( $heading );
			$('#catlinks').replaceWith( $cat );
			placeholder[3].html( $indicator );
			if (mw.resizeLyrics) { mw.resizeLyrics(); }
		});
	}), new OO.ui.ButtonWidget({label: wgULS('显示', '顯示')}).on('click', () => {
		css.disabled = !css.disabled;
		cssHide.disabled = !cssHide.disabled;
		$wrapper.toggle();
		editor.setSize();
		btns[3].setLabel( css.disabled ? wgULS('显示', '顯示') : wgULS('隐藏', '隱藏'));
		btns[2].setDisabled( css.disabled ); // 折叠时无法更新textarea
		if (mw.resizeLyrics) { mw.resizeLyrics(); }
	}), btnGo],
		options = $('<div>', {text: wgULS('查找替换', '查找替換')}).click(() => {
		if ($replace) {
			$replace.show();
			return;
		}
		let replaceBackup;
		const enable = () => { replaceBtn.setDisabled( false ); },
			ptn = new OO.ui.TextInputWidget().on('change', enable),
			val = new OO.ui.MultilineTextInputWidget({autosize: true, maxRows: 3}).on('change', enable),
			regex = new OO.ui.CheckboxInputWidget().on('change', enable),
			modifier = new OO.ui.CheckboxInputWidget().on('change', enable),
			undoBtn = new OO.ui.ButtonWidget({label: wgULS('撤销', '撤銷'), disabled: true, flags: ['destructive']})
			.on('click', () => {
			editor.setValue( replaceBackup );
			enable();
		}),
			replaceBtn = new OO.ui.ButtonWidget({label: wgULS('替换', '替換'), flags: ['progressive']})
			.on('click', () => {
			const pattern = ptn.getValue(),
				regexp = new RegExp(regex.isSelected() ? pattern : mw.util.escapeRegExp(pattern),
				'g' + (modifier.isSelected() ? 'i' : ''));
			replaceBackup = editor.getValue();
			undoBtn.setDisabled( false );
			replaceBtn.setDisabled( true ); // 防止连续点击造成无法撤销
			editor.setValue( replaceBackup.replace(regexp, val.getValue()) );
		}),
			hideBtn = new OO.ui.ButtonWidget({label: wgULS('关闭', '關閉')})
			.on('click', () => {
			$replace.hide();
			enable();
		});
		$replace = $('<div>', {class: 'inspector-field', html: [
			$('<div>', {html: ['查找:', ptn.$element]}),
			$('<div>', {html: [wgULS('替换:', '替換:'), val.$element]}),
			$('<div>', {html: [
				new OO.ui.FieldLayout(regex, {label: wgULS('正则', '正則'), align: 'inline'}).$element,
				new OO.ui.FieldLayout(modifier, {label: $('<i>', {text: 'i'}), align: 'inline'}).$element,
				replaceBtn.$element, undoBtn.$element, hideBtn.$element
			]})
		]}).appendTo( 'body' ).draggable();
	}).add(isGadget('charinsert') ? $('<div>', {text: '快速插入'}).click(() => {
		if ($charinsert) {
			$charinsert.show();
			return;
		}
		$charinsert = $('<div>', {html: $('<div>', {text: '快速插入工具', id: 'inspector-field-title'}),
			class: 'inspector-field mw-ajax-loader'}).appendTo('body').draggable().contextmenu(e => {
			e.preventDefault();
			$charinsert.hide();
		}).on('click', '.mw-charinsert-item', function() {
			const $this = $(this),
				start = $this.data('mw-charinsert-start') || $this.data('start') || '',
				end = $this.data('mw-charinsert-end') || $this.data('end') || '';
			editor.replaceSelection( start + editor.getSelection() + end );
			editor.focus();
		});
		// 为了充分利用浏览器缓存,这里不使用API;注意url转义,这里不需要简化'/mediawiki/index.php'
		$.get({url: mw.util.getUrl(src, {variant: mw.config.get( 'wgUserLanguage' )}),
			cache: charInsert.cache !== false, dataType: 'text'}).then(doc => {
			$charinsert.append( $( doc.match(/<body[\s\S]+<\/body>/)[0] ).find( '.mw-parser-output' ) );
		}, () => {
			mw.notify([
				wgULS('无法加载', '無法加載'),
				$('<a>', {text: src, href: mw.util.getUrl(src)}),
				wgULS('!请检查该页面是否存在。', '!請檢查該頁面是否存在。')
			], {type: 'error', autoHideSeconds: 'long'});
		}).then(() => { $charinsert.removeClass( 'mw-ajax-loader' ); });
	}) : null),
		$dropdown = $('<div>', {class: "inspector-menu", html: options}),
		$hints = $('<div>', {class: "inspector-menu"}).on('click', '.inspector-hint', function() {
		const cursor = editor.getCursor();
		editor.replaceRange(this.textContent + (ns > 0 ? '}}' : ']]'),
			{line: cursor.line, ch: cursor.ch - prefix.length + 1}, cursor);
		// 随后再次执行updateHints时会自行stop
		editor.focus();
	}),
		// 判别模板时可能会误处理解析器函数
		regexps = {0: /\[\[\s*:?([^|[\]{}<>]*)$/i, 10: /{{\s*([^|[\]{}<>#]*)$/i,
		828: /{{\s*#invoke:\s*([^|[\]{}<>#:]*)$/i, 274: /{{\s*#widget:\s*([^|[\]{}<>#:]*)$/i},
		$noHint = $('<span>', {class: 'error'}),
		nsPrefixes = {0: '', 10: 'Template:', 828: '模块:', 274: 'Widget:'},
		autocomplete = isGadget('autocomplete') ? e => {
		if (![9, 27].includes( e.keyCode )) { return; } // tab和esc
		let $cursor, before, sections, title;
		const cursor = editor.getCursor(),
			line = editor.getLine( cursor.line ),
			after = line.slice( cursor.ch );
		before = line.slice(0, cursor.ch);
		const cursorActivity = () => {
			const newCursor = editor.getCursor(),
				newLine = editor.getLine( newCursor.line ),
				newAfter = newLine.slice( newCursor.ch );
			// 如果光标改变过位置
			if (newCursor.line != cursor.line || newAfter != after) {
				stop();
				return;
			}
			before = newLine.slice(0, newCursor.ch);
			updateHints();
		},
			stop = () => {
			$hints.slideUp('fast');
			editor.off('cursorActivity', cursorActivity);
		},
			updateHints = mw.util.debounce(500, () => {
			if (!regexps[ns].test( before )) {
				stop();
				return;
			}
			$cursor = $wrapper.find( '.CodeMirror-cursor' );
			prefix = before.match( regexps[ns] )[1];
			if (prefix === "" || prefix.startsWith('#')) { return; }
			if (ns == '0' && prefix.includes('#')) {
				const capture = prefix.match(/^(.+)#(.*)$/);
				prefix = capture[2];
				(title == capture[1] ? Promise.resolve() : mw.timedQuery(api, {action: 'parse', page: capture[1],
					prop: 'sections'}, wgULS('段落标题', '段落標題')).then(r => {
					title = capture[1];
					sections = r.parse.sections.map(ele => $('<div>', {class: 'inspector-hint', text: ele.line}));
				}, () => { throw null; })).then(() => {
					$hints.html( sections.filter(ele => ele.text().startsWith( prefix )) )
						.show().position({my: 'left top', of: $cursor, within: $wrapper, collision: 'fit'});
					if ($hints.children().length === 0) {
						$noHint.text(wgULS('不存在对应段落!', '不存在對應段落!')).appendTo( $hints );
					}
				}, stop);
				return;
			}
			mw.timedQuery(api, {list: 'prefixsearch', pssearch: prefix, psnamespace: ns},
				wgULS('前缀相符的页面', '前綴相符的頁面')).then(r => {
				$hints.html( r.query.prefixsearch.map(ele =>
					$('<div>', {class: 'inspector-hint', text: ele.title.slice( nsPrefixes[ns].length )})
				) ).show().position({my: 'left top', of: $cursor, within: $wrapper, collision: 'fit'});
				if ($hints.children().length === 0) {
					$noHint.text(wgULS('不存在对应页面!', '不存在對應頁面!')).appendTo( $hints );
				}
			}, stop);
		});
		if (e.keyCode == 27) {
			stop();
			return;
		}
		editor.execCommand( 'delCharBefore' ); // 删掉新增的\t
		$.each(regexps, (k, v) => { if (v.test(before)) { ns = k; } });
		if (!ns) { return; }
		mw.notify(wgULS('开启自动补全提示。', '開啟自動補全提示。'), {tag: 'autocomplete'});
		editor.on('cursorActivity', cursorActivity);
		// 实际上updateHints总是延迟500ms执行,因此下一行代码的位置影响不大
		updateHints();
	} : () => {};
	mw.loader.using(['jquery.ui', 'user.options']).then(() => {
		const actionP = new OO.ui.ActionWidget({label: '是', target: '_blank', flags: 'progressive'}),
			actionD = new OO.ui.ActionWidget({label: '否'}),
			$url = $('<a>'), // 桌面版CSS不必需href
			label = [wgULS('要在新标签页打开', '要在新標籤頁打開'), $url, wgULS('吗?', '嗎?')];
		$outer.on('contextmenu', '.cm-mw-template-name', function() {
			const template = '模板:' + $(this).text();
			// 这里增加一次确认界面是因为部分浏览器不允许JS直接打开新标签页。
			$url.text( template );
			actionP.setHref( mw.util.getUrl(template) ); // 注意url转义
			mw.dialog(dialog, [actionD, actionP], label).catch(() => {});
			return false; // 这里需要同时执行stopImmediatePropagation()和preventDefault()
		}).on('contextmenu', '.CodeMirror', e => {
			e.preventDefault();
			// 使用jquery.ui让菜单总是落在CodeMirror内,此时slideDown效果不生效
			$dropdown.show().position({my: 'left top', of: e, within: $wrapper, collision: 'fit'});
		}).on('keydown', '.CodeMirror', autocomplete).resizable( {handles: 'w', minWidth: 350} );
	});
	$('body').click(() => { $dropdown.slideUp('fast'); });
	btns[0].$element.contextmenu(e => {
		e.preventDefault();
		OO.ui.prompt( '请输入编辑摘要:' , {textInput: {value: summary}} ).then(input => {
			if (input !== null) { summary = input; }
		});
	});
	if (isBackup) {
		btns[2].$element.contextmenu(e => {
			e.preventDefault();
			loadBackup();
		});
	}
	if (isGadget( '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', () => { lang = select.getValue(); }),
			menu = select.$element.find( '.oo-ui-menuSelectWidget' )
			.click(() => { menu.addClass( 'oo-ui-element-hidden' ); });
		select.$element.prependTo( btns[1].$element );
		btns[1].$element.contextmenu(e => {
			e.preventDefault();
			menu.removeClass( 'oo-ui-element-hidden' );
		}).children('a').blur(() => { menu.addClass( 'oo-ui-element-hidden' ); });
	}
	pEmpty( $original );
	// $.when很容易出错,这里保险起见用Promise.all
	Promise.all([getJSON, mw.request, getExt]).then(data => {
		mw.config.set('extCodeMirrorConfig', data[0]);
		mw.hook( 'codemirror.config' ).fire();
		text = data[1].query.pages[0].revisions[0].content;
		editor = new CodeMirror($outer[0], {value: text, mode: 'text/mediawiki', mwConfig: data[0],
			lineWrapping: true, lineNumbers: true, resetSelectionOnContextMenu: false});
		$wrapper = $( editor.getWrapperElement() ).toggle().append( [$dropdown, $hints] );
		$('<div>', {id: 'inspector-btns', html: btns.map(ele => ele.$element)}).appendTo( $outer );
		// 处理页面上方的差异
		$('.diff').click(e => {
			const row = $( e.target ).closest('tr');
			if (row.hasClass( 'diff-title' )) { return; }
			const 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'} ); }
		});
	}, reason => { mw.apiFailure(reason, `CodeMirror${wgULS('设置或页面', '設置或頁面')}Wikitext`); })
		.then(() => { $outer.removeClass( 'mw-ajax-loader' ); });
});
//</nowiki>
// [[category:维护工具]] [[category:桌面版小工具]] [[category:需要自确用户权限的小工具]] [[category:不作为模块的小工具]]
// {{DEFAULTSORT:inspect.js}}