LLWiki正在建设中,欢迎加入我们!
“MediaWiki:Gadget-inspect.js”的版本间差异
跳转到导航
跳转到搜索
小 |
小 |
||
(未显示2个用户的80个中间版本) | |||
第1行: | 第1行: | ||
//<nowiki> |
//<nowiki> |
||
// 由 |
// 由[[mediawiki:gadget-inspect-loader.js]]调用,可以使用ES6语法 |
||
/** |
/** |
||
* @Function: 在页面内快速编辑和预览 |
* @Function: 在页面内快速编辑和预览 |
||
* @Author: [[User:Bhsd]] |
* @Author: [[User:Bhsd]] |
||
* @Translation: [[User:Lakejason0]] |
|||
*/ |
*/ |
||
"use strict"; |
"use strict"; |
||
/*global |
/*global OO, CodeMirror, wgULS*/ |
||
$(() => { // 避免全局变量 |
|||
const id = mw.config.get( 'wgArticleId' ), |
|||
let nextid, list = mw.storage.getObject( 'inspect-category' ), flag; |
|||
const id = mw.config.get( 'wgArticleId' ), |
|||
page = mw.config.get( 'wgPageName' ), // 这里未转义,后面应用时需要注意 |
|||
isCode = mw.config.get( 'wgPageContentModel' ) != 'wikitext', // javascript/css/Scribunto/json |
|||
gadgets = mw.gadgets || {}, |
|||
gadgets = mw.gadgets || {}, |
|||
charInsert = gadgets.charinsert || {}, |
|||
rule = inspect.rule, |
|||
src = charInsert.src || 'special:mypage/edittools', |
|||
// 由于resizable,left, top, height必须加!important,width不可加!important |
|||
src = charInsert.src || 'special:我的用户页/edittools'; |
|||
cssHide = mw.util.addCSS( `#inspector { position:fixed; bottom:0; right:24px; left:auto !important; |
|||
$(function() { |
|||
top:auto !important; height:auto !important; width:calc(50% - 7rem - 0.5px); } |
|||
// 特殊页面或页面未创建、重定向、不是阅读模式、历史版本 |
|||
div.start-screen { bottom:40px; }` ), |
|||
if (id === 0 || mw.config.get('wgIsRedirect') || mw.config.get('wgAction') != 'view' || |
|||
$content = $('#mw-content-text, #mw-imagepage-content').last(), |
|||
mw.config.get( 'wgRevisionId' ) < curRevid || !(rule === undefined ? true : rule)) { return; } |
|||
// wikitext/Scribunto对应.mw-parser-output,javascript/css对应.mw-code,json对应.mw-json |
|||
// 由于resizable,left, top, height必须加!important,width不可加!important |
|||
$original = $content.children('.mw-json, .mw-code, .mw-parser-output'), |
|||
const cssHide = mw.util.addCSS( '#inspector { position:fixed; bottom:0; right:24px; left:unset !important;' + |
|||
$outer = $('<div>', {id: 'inspector', class: 'mw-ajax-loader'}).insertBefore( $original ), |
|||
'top:unset !important; height:unset !important; width:calc(50% - 7rem - 0.5px); }' ), |
|||
api = new mw.Api(); |
|||
$content = $('#mw-content-text, #mw-imagepage-content').last(), |
|||
if (!list) { nextid = id + 1; } |
|||
// wikitext/Scribunto对应.mw-parser-output,javascript/css对应.mw-code,json对应.mw-json |
|||
else { |
|||
$original = $content.children('.mw-json, .mw-code, .mw-parser-output'), |
|||
flag = list[0][ list[1] ] == id; |
|||
$outer = $('<div>', {id: 'inspector', class: 'mw-ajax-loader'}).insertBefore( $original ), |
|||
if (flag) { list[1]++; } |
|||
api = new mw.Api(), |
|||
nextid = list[0][ list[1] ]; |
|||
initList = function(pages) { |
|||
} |
|||
flag = pages[0] == id ? 1 : 0; |
|||
const btnGo = new OO.ui.ButtonWidget({flags: 'progressive', icon: 'next', href: `/zh?redirect=no&curid=${nextid}`, |
|||
list = [pages, flag]; |
|||
disabled: !nextid}), |
|||
mw.storage.setObject( 'inspect-category', [pages, 0] ); |
|||
initList = (pages, ctn) => { |
|||
btnGo.setHref( '/zh?redirect=no&curid=' + pages[ flag ] ).setDisabled( false ); |
|||
console.info({pages: pages, ctn: ctn, id: id}); |
|||
}; |
|||
flag = pages[0] == id || ctn && pages.includes(id); // &&的优先级高于|| |
|||
var nextid, list = mw.storage.getObject( 'inspect-category' ), flag; |
|||
list = [pages, flag ? pages.indexOf(id) + 1 : 0]; |
|||
if (!list) { nextid = id + 1; } |
|||
if (ctn && !flag) { mw.notify(wgULS('当前页面并不属于该分类或命名空间!', '目前的頁面並不屬於此分類或命名空間!'), |
|||
else { |
|||
{type: 'warn'}); } |
|||
flag = (list[0][ list[1] ] == id); |
|||
mw.storage.setObject( 'inspect-category', [pages, list[1] - flag] ); |
|||
if (flag) { list[1]++; } |
|||
btnGo.setHref( `/zh?redirect=no&curid=${pages[ list[1] ]}` ).setDisabled( false ); |
|||
nextid = list[0][ list[1] ]; |
|||
}, |
|||
} |
|||
dialog = new OO.ui.MessageDialog(), |
|||
const btnGo = new OO.ui.ButtonWidget({flags: 'progressive', icon: 'next', href: '/zh?redirect=no&curid=' + nextid, |
|||
actionC = new OO.ui.ActionWidget({action: 'cancel', label: '取消'}), |
|||
disabled: !nextid}); |
|||
actionG = new OO.ui.ActionWidget({action: 'go', label: wgULS('确定', '確認'), flags: 'progressive'}), |
|||
// 这个click事件不能绑到btnGo上,否则会禁用href跳转 |
|||
textInput = new OO.ui.TextInputWidget(), |
|||
btnGo.$element.click(function() { if (list && flag) { mw.storage.setObject( 'inspect-category', list ); } }) |
|||
fromHere = new OO.ui.CheckboxInputWidget(), |
|||
.contextmenu(function(e) { |
|||
message = [ $('<p>', {text: wgULS('请输入分类名或命名空间编号:', '請輸入分類名或命名空間編號:')}), |
|||
e.preventDefault(); |
|||
textInput.$element, |
|||
OO.ui.prompt( wgULS('请输入分类名或名字空间编号:', '請輸入分類名或名字空間編號:') ).then(function(cat) { |
|||
new OO.ui.FieldLayout(fromHere, {label: wgULS('从当前页面继续', '從目前的頁面繼續'), align: 'inline'}).$element |
|||
if (cat === null) { return; } |
|||
], |
|||
// 输入空白则清除分类 |
|||
updateCat = function(cat, ctn) { |
|||
// 输入空白则清除分类 |
|||
mw.storage.remove( 'inspect-category' ); |
|||
if (cat === '') { |
|||
btnGo.setHref( '/zh?redirect=no&curid=' + (id + 1) ).setDisabled( false ); |
|||
mw.storage.remove( 'inspect-category' ); |
|||
return; |
|||
btnGo.setHref( `/zh?redirect=no&curid=${id + 1}` ).setDisabled( false ); |
|||
} |
|||
return; |
|||
// 输入数字对应名字空间 |
|||
} |
|||
if (!isNaN(cat)) { |
|||
// 输入数字对应命名空间 |
|||
if (!Object.keys( mw.config.get( 'wgFormattedNamespaces' ) ).includes(cat)) { |
|||
if (!isNaN(cat)) { |
|||
mw.notify(wgULS('错误的名字空间编号!', '錯誤的名字空間編號!'), {type: 'error'}); |
|||
if (!Object.keys( mw.config.get( 'wgFormattedNamespaces' ) ).includes(cat)) { |
|||
return; |
|||
mw.notify(wgULS('错误的命名空间编号!', '錯誤的命名空間編號!'), {type: 'error'}); |
|||
} |
|||
return; |
|||
mw.timedQuery(api, {list: 'allpages', apnamespace: cat, apfilterredir: 'nonredirects', aplimit: 'max'}, |
|||
} |
|||
wgULS('该名字空间的页面列表', '該名字空間的頁面列表')).then(function(ap) { |
|||
mw.timedQuery(api, {list: 'allpages', apnamespace: cat, apfilterredir: 'nonredirects', aplimit: 'max'}, |
|||
const pages = ap.query.allpages.map(function(ele) { return ele.pageid; }) |
|||
wgULS('该命名空间的页面列表', '此命名空間的頁面清單')).then(ap => { |
|||
.sort(function(a, b) { return 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); |
|||
initList(pages, ctn); |
|||
}, function() {}); // 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(function(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(function(cm) { |
|||
cmsort: 'timestamp', cmdir: 'older'}, wgULS('分类下的页面列表', '分類下的頁面清單')).then(cm => { |
|||
const pages = cm.query.categorymembers.map(function(ele) { return 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); |
|||
initList(pages, ctn); |
|||
}, function() {}); |
|||
}, () => {}); |
|||
}, () => {}); |
|||
}; |
|||
// 这个click事件不能绑到btnGo上,否则会禁用href跳转 |
|||
// 模板、JavaScript、CSS、Lua、JSON只需要箭头按钮 |
|||
btnGo.$element.click(() => { if (list && flag) { mw.storage.setObject( 'inspect-category', list ); } }) |
|||
if ((mw.config.get( 'wgNamespaceNumber' ) == 10 && !page.endsWith( '/doc' )) || isCode) { |
|||
.contextmenu(e => { |
|||
$outer.removeClass( 'mw-ajax-loader' ).append( $('<div>', {id: 'inspector-btns', html: btnGo.$element}) ); |
|||
e.preventDefault(); |
|||
mw.loader.addStyleTag( '#inspector-btns { width:auto; }' ); |
|||
mw.dialog(dialog, [actionC, actionG], message).then(action => { |
|||
return; |
|||
if (action == 'go') { updateCat( textInput.getValue(), fromHere.isSelected() ); } |
|||
} |
|||
}); |
|||
// 先提交Ajax请求,这里手动设置cache: true |
|||
}); |
|||
const getJSON = $.get({ dataType: 'json', cache: true, |
|||
// 模板、JavaScript、CSS、Lua、JSON只需要箭头按钮 |
|||
url: '/zh?title=mediawiki:gadget-CodeMirror.json&action=raw&ctype=application/json' }); |
|||
if (mw.config.get( 'wgNamespaceNumber' ) == 10 && !page.endsWith( '/doc' ) || isCode) { |
|||
// 生成通用的API请求 |
|||
$outer.removeClass( 'mw-ajax-loader' ).append( $('<div>', {id: 'inspector-btns', html: btnGo.$element}) ); |
|||
mw.request = mw.request || mw.standardQuery(api); |
|||
mw.loader.addStyleTag( '#inspector-btns { width:auto; }' ); |
|||
// 标注<p>标签 |
|||
return; |
|||
const css = mw.util.addCSS( '.empty { border:1px solid rgba(253,220,154,0.5); box-shadow:0 0 0.5em #fddc9a; }' + |
|||
} |
|||
// 解决<ul>等元素文字不换行的问题 |
|||
// 先提交Ajax请求,这里手动设置cache: true |
|||
'#mw-content-text .mw-parser-output { display:flow-root; overflow:hidden; word-wrap:break-word; }' ); |
|||
const getJSON = mw.loader.using('ext.CodeMirror.data'), |
|||
css.disabled = true; |
|||
getExt = mw.loader.using(['ext.CodeMirror.lib', 'ext.CodeMirror.mode.mediawiki']); |
|||
var dialog, actionP, actionD, text, editor, $wrapper, $replace, $charinsert, |
|||
// 生成通用的API请求 |
|||
lang = mw.config.get( 'wgUserVariant' ); |
|||
mw.standardQuery(api); |
|||
// 也可以使用user.options,这里使用其他方法绕过 |
|||
let text, editor, $wrapper, $replace, $charinsert, prefix, ns, section, secTitle, summary, |
|||
const isGadget = function(name) { |
|||
lang = mw.config.get( 'wgUserVariant' ); |
|||
return ['loaded', 'loading', 'ready'].includes( mw.loader.getState('ext.gadget.' + name) ); |
|||
$original.find( ':header:has(.mw-editsection)' ).dblclick(function() { |
|||
}, |
|||
section = mw.util.getParamValue( 'section', $(this).find( '.mw-editsection > a' ).attr('href') ); |
|||
isBackup = isGadget('contentBackup'), |
|||
mw.sectionQuery(api, section).then(r => { |
|||
backupObj = isBackup ? Object.fromEntries( mw.storage.getObject( 'LLWiki-contentBackup' ) || [] ) : {}, |
|||
editor.setValue( r.parse.wikitext ); |
|||
backup = isBackup ? function() { |
|||
secTitle = r.parse.sections[0].anchor.replaceAll(' ', '_'); |
|||
backupObj[id] = [mw.now(), editor.getValue()]; |
|||
}, () => {}); |
|||
mw.storage.setObject( Object.entries(backupObj) ); |
|||
}); |
|||
} : function() {}, |
|||
// 标注<p>标签 |
|||
loadBackup = function() { |
|||
const css = mw.util.addCSS( `.empty { border:1px solid rgba(253,220,154,0.5); box-shadow:0 0 0.5em #fddc9a; } |
|||
const backupContent = (backupObj[id] || [])[1]; |
|||
#mw-content-text .mw-parser-output { display:flow-root; max-height:100vh; overflow:auto; |
|||
if (!backupContent) { return; } |
|||
word-wrap:break-word; }` ); |
|||
mw.confirm( wgULS('要加载备份吗?', '要加載備份嗎?'), 'progressive' ).then(function(confirm) { |
|||
css.disabled = true; |
|||
if (confirm) { editor.setValue( backupContent ); } |
|||
// 也可以使用user.options,这里使用其他方法绕过 |
|||
}); |
|||
const isGadget = (name) => ['loaded', 'loading', 'ready'].includes( mw.loader.getState(`ext.gadget.${name}`) ), |
|||
}, |
|||
isBackup = isGadget('contentBackup'), |
|||
pEmpty = function($div) { |
|||
backupObj = isBackup ? Object.fromEntries( mw.storage.getObject( 'LLWiki-contentBackup' ) || [] ) : {}, |
|||
$div.find( 'p:not(:has(script))' ).filter(function() { return !/\S/.test( this.textContent ); }) |
|||
backup = isBackup ? () => { |
|||
.addClass('empty'); |
|||
backupObj[id] = [mw.now(), editor.getValue()]; |
|||
}, |
|||
mw.storage.setObject( 'LLWiki-contentBackup', Object.entries(backupObj) ); |
|||
$placeholder = $('<div>', {class: "mw-parser-output"}).css('display', 'none'), // 用于替换时保留原始数据 |
|||
} : () => {}, |
|||
$url = $('<a>'), // 桌面版CSS不必需href |
|||
loadBackup = () => { |
|||
$warning = $('<div>', {html: [wgULS("确定要还原为未编辑的状态吗?", "確認要復原為未編輯的狀態嗎?"), '<br>', |
|||
const backupContent = (backupObj[id] || [])[1]; |
|||
wgULS("建议您做好编辑内容备份。", "建議您做好編輯內容備份。")]}), |
|||
if (!backupContent) { |
|||
btns = [new OO.ui.ButtonWidget({label: '提交', flags: ['primary', 'progressive']}).on('click', function() { |
|||
mw.notify(wgULS('当前页面尚未备份!', '目前的頁面尚未備份!'), {type: 'warn'}); |
|||
backup(); |
|||
return; |
|||
// 改变CSS样式表示提交中 |
|||
} |
|||
btns[0].setDisabled( true ); |
|||
mw.confirm( wgULS('要加载备份吗?', '要載入備份嗎?'), 'progressive' ).then(confirm => { |
|||
mw.safeEdit(api, curRevid, { pageid: id, text: editor.getValue(), |
|||
if (!confirm) { return; } |
|||
summary: wgULS( '使用[[help:小工具/页面文本对比查看器|页面/文本对比查看器]]快速编辑', |
|||
editor.setValue( backupContent ); |
|||
'使用[[help:小工具/页面文本对比查看器|頁面/文本對比察看器]]快速編輯' |
|||
mw.notify(wgULS('已还原备份!', '已還原備份!'), {type: 'success'}); |
|||
) }, isBackup).then(function() { location.reload(); }, |
|||
}); |
|||
function(reason) { btns[0].setDisabled( reason == 'editConflict' ); }); // 编辑冲突必须刷新页面,不应重复提交 |
|||
}, |
|||
}), new OO.ui.ButtonWidget({label: wgULS('预览', '預覽')}).on('click', function() { |
|||
pEmpty = ($div) => { |
|||
backup(); |
|||
$div.find( 'p:not(:has(script))' ).filter(function() { return !/\S/.test( this.textContent ); }) |
|||
// 改变CSS样式表示预览中 |
|||
.addClass('empty'); |
|||
btns[1].setDisabled( true ); |
|||
}, |
|||
console.log('API request: 请求预览'); |
|||
placeholder = [ |
|||
const now = mw.now(); |
|||
$('<div>', {class: "mw-parser-output"}).css('display', 'none'), |
|||
api.parse( editor.getValue(), {title: page, uselang: lang, disablelimitreport: 1, disableeditsection: 1} ) |
|||
$('<h1>', {id: 'firstHeading', class: 'firstHeading'}), |
|||
.then(function(html) { |
|||
$('<div>', {id: 'catlinks'}).css('display', 'none'), |
|||
console.log('End API request: 已生成预览,用时 ' + (mw.now() - now) + ' ms'); |
|||
$('.mw-indicators') |
|||
if ($.contains( $content[0], $original[0] )) { $original.after( $placeholder ).detach(); } |
|||
], // 用于替换时保留原始数据 |
|||
$content.children( '.mw-parser-output' ).replaceWith( html ); |
|||
$warning = $('<div>', {html: [wgULS("确定要还原为未编辑的状态吗?", "確認要還原為未編輯的狀態嗎?"), '<br>', |
|||
pEmpty( $content.children( '.mw-parser-output' ) ); |
|||
wgULS("建议您做好编辑内容备份。", "建議您做好編輯內容備份。")]}), |
|||
if (mw.resizeLyrics) { mw.resizeLyrics(); } |
|||
$heading = $('#firstHeading'), |
|||
mw.hook( 'wikipage.content' ).fire($content); |
|||
$cat = $('#catlinks'), |
|||
}, function(reason) { mw.apiFailure(reason, wgULS('预览', '預覽')); }) // mw.apiFailure不会抛出错误 |
|||
$indicator = $('.mw-indicator'), |
|||
.then(function() { btns[1].setDisabled( false ); }); |
|||
btns = [new OO.ui.ButtonWidget({label: '提交', flags: ['primary', 'progressive']}).on('click', () => { |
|||
backup(); |
|||
mw.confirm($warning, ['primary', 'destructive']).then(function(confirm) { |
|||
// 改变CSS样式表示提交中 |
|||
if (!confirm) { return; } |
|||
btns[0].setDisabled( true ); |
|||
editor.setValue( text ); |
|||
mw.safeEdit(api, {pageid: id, text: editor.getValue(), section: section, |
|||
$content.children( '.mw-parser-output' ).replaceWith( $original ); |
|||
summary: (section === undefined ? '' : `/* ${secTitle} */ `) + (summary ? `${summary} //` : '//') + |
|||
if (mw.resizeLyrics) { mw.resizeLyrics(); } |
|||
wgULS( '使用[[help:小工具/页面文本对比查看器|页面/文本对比查看器]]快速编辑', |
|||
}); |
|||
'使用[[help:小工具/页面文本对比查看器|頁面/文字對比檢視器]]快速編輯')}, isBackup) |
|||
}), new OO.ui.ButtonWidget({label: wgULS('显示', '顯示')}).on('click', function() { |
|||
.then(() => { location.href = mw.util.getUrl( page ); }, |
|||
css.disabled = !css.disabled; |
|||
reason => { btns[0].setDisabled( reason == 'editConflict' ); }); // 编辑冲突必须刷新页面,不应重复提交 |
|||
cssHide.disabled = !cssHide.disabled; |
|||
}), new OO.ui.ButtonWidget({label: wgULS('预览', '預覽')}).on('click', () => { |
|||
$wrapper.toggle(); |
|||
backup(); |
|||
btns[3].setLabel( css.disabled ? wgULS('显示', '顯示') : wgULS('隐藏', '隱藏')); |
|||
// 改变CSS样式表示预览中 |
|||
btns[2].setDisabled( css.disabled ); // 折叠时无法更新textarea |
|||
btns[1].setDisabled( true ); |
|||
if (mw.resizeLyrics) { mw.resizeLyrics(); } |
|||
mw.timedParse(api, {text: editor.getValue(), uselang: lang, |
|||
}), btnGo], |
|||
prop: 'text|categorieshtml|displaytitle|indicators|modules|headitems' |
|||
options = $('<div>', {text: wgULS('查找替换', '查找替換')}).click(function() { |
|||
}, wgULS('预览', '預覽')).then(r => { |
|||
if ($replace) { |
|||
mw.loader.load( r.parse.modulestyles ); |
|||
$replace.show(); |
|||
const tabsStyles = r.parse.headitems.find(function(ele) { return ele.tag === 'TabsStyles'; }); |
|||
return; |
|||
if (tabsStyles) { |
|||
} |
|||
$('head').append(tabsStyles.content); // 可能重复,但重复也没关系 |
|||
var replaceBackup; |
|||
} |
|||
const ptn = new OO.ui.TextInputWidget(), |
|||
if ($.contains( $content[0], $original[0] )) { |
|||
val = new OO.ui.MultilineTextInputWidget({autosize: true, maxRows: 3}), |
|||
$original.after( placeholder[0] ).detach(); |
|||
regex = new OO.ui.CheckboxInputWidget(), |
|||
$heading.after( placeholder[1] ).detach(); |
|||
modifier = new OO.ui.CheckboxInputWidget(), |
|||
$cat.after( placeholder[2] ).detach(); |
|||
undoBtn = new OO.ui.ButtonWidget({label: wgULS('撤销', '撤銷'), disabled: true, flags: ['destructive']}) |
|||
$indicator.detach(); |
|||
.on('click', function() { editor.setValue( replaceBackup ); }), |
|||
} |
|||
replaceBtn = new OO.ui.ButtonWidget({label: wgULS('替换', '替換'), flags: ['progressive']}) |
|||
$content.children( '.mw-parser-output' ).replaceWith( r.parse.text ); |
|||
.on('click', function() { |
|||
pEmpty( $content.children( '.mw-parser-output' ) ); |
|||
const pattern = ptn.getValue(), |
|||
if (mw.resizeLyrics) { mw.resizeLyrics(); } |
|||
regexp = new RegExp(regex.isSelected() ? pattern : mw.util.escapeRegExp(pattern), |
|||
mw.hook( 'wikipage.content' ).fire($content); |
|||
'g' + modifier.isSelected() ? 'i' : ''); |
|||
$('#catlinks').replaceWith( r.parse.categorieshtml ); |
|||
replaceBackup = editor.getValue(); |
|||
placeholder[1].html( r.parse.displaytitle ); |
|||
undoBtn.setDisabled(false); |
|||
placeholder[3].html( Object.entries( r.parse.indicators ).map(ele => |
|||
editor.setValue( replaceBackup.replace(regexp, val.getValue()) ); |
|||
$('<div>', {class: 'mw-indicator', id: `mw-indicator-${ele[0]}`, html: ele[1]})) ); |
|||
}), |
|||
}, () => {}).then(() => { btns[1].setDisabled( false ); }); |
|||
hideBtn = new OO.ui.ButtonWidget({label: wgULS('关闭', '關閉')}) |
|||
}), new OO.ui.ButtonWidget({label: wgULS('还原', '還原'), flags: 'destructive', disabled: true}).on('click', () => { |
|||
.on('click', function() { $replace.hide(); }); |
|||
mw.confirm($warning, ['primary', 'destructive']).then(confirm => { |
|||
$replace = $('<div>', {class: 'inspector-field', html: [ |
|||
if (!confirm) { return; } |
|||
$('<div>', {html: ['查找:', ptn.$element]}), |
|||
section = undefined; |
|||
$('<div>', {html: [wgULS('替换:', '替換:'), val.$element]}), |
|||
editor.setValue( text ); |
|||
$('<div>', {html: [ |
|||
if ($.contains( $content[0], $original[0] )) { return; } // 无事发生 |
|||
wgULS('正则', '正則'), regex.$element, |
|||
$content.children( '.mw-parser-output' ).replaceWith( $original ); |
|||
$('<i>', {text: 'i'}), modifier.$element, |
|||
placeholder[1].replaceWith( $heading ); |
|||
replaceBtn.$element, undoBtn.$element, hideBtn.$element |
|||
$('#catlinks').replaceWith( $cat ); |
|||
]}) |
|||
placeholder[3].html( $indicator ); |
|||
]}).appendTo( 'body' ).draggable(); |
|||
if (mw.resizeLyrics) { mw.resizeLyrics(); } |
|||
}).add(isGadget('charinsert') ? $('<div>', {text: '快速插入'}).click(function() { |
|||
}); |
|||
if ($charinsert) { |
|||
}), new OO.ui.ButtonWidget({label: wgULS('显示', '顯示')}).on('click', () => { |
|||
$charinsert.show(); |
|||
css.disabled = !css.disabled; |
|||
return; |
|||
cssHide.disabled = !cssHide.disabled; |
|||
} |
|||
$wrapper.toggle(); |
|||
$charinsert = $('<div>', {html: $('<div>', {text: '快速插入工具', id: 'inspector-field-title'}), |
|||
editor.setSize(); |
|||
class: 'inspector-field mw-ajax-loader'}).appendTo('body').draggable().contextmenu(function(e) { |
|||
btns[3].setLabel( css.disabled ? wgULS('显示', '顯示') : wgULS('隐藏', '隱藏')); |
|||
e.preventDefault(); |
|||
btns[2].setDisabled( css.disabled ); // 折叠时无法更新textarea |
|||
$charinsert.hide(); |
|||
if (mw.resizeLyrics) { mw.resizeLyrics(); } |
|||
}).on('click', '.mw-charinsert-item', function() { |
|||
}), btnGo], |
|||
const $this = $(this), |
|||
options = $('<div>', {text: wgULS('查找替换', '尋找取代')}).click(() => { |
|||
start = $this.data('mw-charinsert-start') || $this.data('start') || '', |
|||
if ($replace) { |
|||
end = $this.data('mw-charinsert-end') || $this.data('end') || ''; |
|||
$replace.show(); |
|||
editor.replaceSelection( start + editor.getSelection() + end ); |
|||
return; |
|||
}); |
|||
} |
|||
// 为了充分利用浏览器缓存,这里不使用API;注意url转义,这里不需要简化'/mediawiki/index.php' |
|||
let replaceBackup; |
|||
$.get({url: mw.util.getUrl(src, {variant: mw.config.get( 'wgUserLanguage' )}), |
|||
const enable = () => { replaceBtn.setDisabled( false ); }, |
|||
cache: charInsert.cache !== false, dataType: 'text'}).then(function(doc) { |
|||
ptn = new OO.ui.TextInputWidget().on('change', enable), |
|||
$charinsert.append( $( doc.match(/<body[\s\S]+<\/body>/)[0] ).find( '.mw-parser-output' ) ); |
|||
val = new OO.ui.MultilineTextInputWidget({autosize: true, maxRows: 3}).on('change', enable), |
|||
}, function() { |
|||
regex = new OO.ui.CheckboxInputWidget().on('change', enable), |
|||
mw.notify([ |
|||
modifier = new OO.ui.CheckboxInputWidget().on('change', enable), |
|||
wgULS('无法加载', '無法加載'), |
|||
undoBtn = new OO.ui.ButtonWidget({label: wgULS('撤销', '復原'), disabled: true, flags: ['destructive']}) |
|||
$('<a>', {text: src, href: mw.util.getUrl(src)}), |
|||
.on('click', () => { |
|||
wgULS('!请检查该页面是否存在。', '!請檢查該頁面是否存在。') |
|||
editor.setValue( replaceBackup ); |
|||
], {type: 'error', autoHideSeconds: 'long'}); |
|||
enable(); |
|||
}).then(function() { $charinsert.removeClass( 'mw-ajax-loader' ); }); |
|||
}), |
|||
}) : null), |
|||
replaceBtn = new OO.ui.ButtonWidget({label: wgULS('替换', '取代'), flags: ['progressive']}) |
|||
$dropdown = $('<div>', {class: "inspector-menu", html: options}); |
|||
.on('click', () => { |
|||
$outer.on('contextmenu', '.cm-mw-template-name', function() { |
|||
const pattern = ptn.getValue(), |
|||
regexp = new RegExp(regex.isSelected() ? pattern : mw.util.escapeRegExp(pattern), |
|||
var label; |
|||
'g' + (modifier.isSelected() ? 'i' : '')); |
|||
// 这里增加一次确认界面是因为部分浏览器不允许JS直接打开新标签页。 |
|||
replaceBackup = editor.getValue(); |
|||
if (!dialog) { |
|||
undoBtn.setDisabled( false ); |
|||
actionP = new OO.ui.ActionWidget({label: '是', target: '_blank', flags: 'progressive'}); |
|||
replaceBtn.setDisabled( true ); // 防止连续点击造成无法撤销 |
|||
actionD = new OO.ui.ActionWidget({label: '否', flags: 'destructive'}); |
|||
editor.setValue( replaceBackup.replace(regexp, val.getValue()) ); |
|||
label = [wgULS('要在新标签页打开', '要在新標籤頁打開'), $url, wgULS('吗?', '嗎?')]; |
|||
}), |
|||
} |
|||
hideBtn = new OO.ui.ButtonWidget({label: wgULS('关闭', '關閉')}) |
|||
$url.text( template ); |
|||
.on('click', () => { |
|||
actionP.setHref( mw.util.getUrl(template) ); // 注意url转义 |
|||
$replace.hide(); |
|||
dialog = mw.dialog(dialog, [actionP, actionD], label); |
|||
enable(); |
|||
return false; // 这里需要同时执行stopImmediatePropagation()和preventDefault() |
|||
}); |
|||
}).on('contextmenu', '.CodeMirror-code', function(e) { |
|||
$replace = $('<div>', {class: 'inspector-field', html: [ |
|||
e.preventDefault(); |
|||
$('<div>', {html: [wgULS('查找:', '尋找:' ), ptn.$element]}), |
|||
const position = editor.cursorCoords(), |
|||
$('<div>', {html: [wgULS('替换:', '取代:'), val.$element]}), |
|||
offset = $wrapper.offset(); |
|||
$('<div>', {html: [ |
|||
$dropdown.slideDown( 'fast' ).css({left: position.left - offset.left, top: position.top - offset.top}); |
|||
new OO.ui.FieldLayout(regex, {label: wgULS('正则', '正規表達式'), align: 'inline'}).$element, |
|||
}).resizable( {handles: 'w', minWidth: 350} ); |
|||
new OO.ui.FieldLayout(modifier, {label: $('<i>', {text: 'i'}), align: 'inline'}).$element, |
|||
$('body').click(function() { $dropdown.slideUp('fast'); }); |
|||
replaceBtn.$element, undoBtn.$element, hideBtn.$element |
|||
if (isBackup) { |
|||
]}) |
|||
btns[2].$element.contextmenu(function(e) { |
|||
]}).appendTo( 'body' ).draggable(); |
|||
e.preventDefault(); |
|||
}).add(isGadget('charinsert') ? $('<div>', {text: '快速插入'}).click(() => { |
|||
loadBackup(); |
|||
if ($charinsert) { |
|||
}); |
|||
$charinsert.show(); |
|||
} |
|||
return; |
|||
if (isGadget( 'PreviewWithVariant' ) ) { |
|||
} |
|||
const options = [{label: "大陆简体", data: "zh-cn"}, {label: "香港繁體", data: "zh-hk"}, |
|||
$charinsert = $('<div>', {html: $('<div>', {text: '快速插入工具', id: 'inspector-field-title'}), |
|||
{label: "澳門繁體", data: "zh-mo"}, {label: "马来西亚简体", data: "zh-my"}, |
|||
class: 'inspector-field mw-ajax-loader'}).appendTo('body').draggable().contextmenu(e => { |
|||
{label: "新加坡简体", data: "zh-sg"}, {label: "臺灣繁體", data: "zh-tw"}], |
|||
e.preventDefault(); |
|||
select = new OO.ui.DropdownInputWidget({classes: ['inspector-variant'], options: options, value: lang}) |
|||
$charinsert.hide(); |
|||
.on('change', function() { lang = select.getValue(); }), |
|||
}).on('click', '.mw-charinsert-item', function() { |
|||
const $this = $(this), |
|||
menu.addClass( 'oo-ui-element-hidden' ); |
|||
start = $this.data('mw-charinsert-start') || $this.data('start') || '', |
|||
}); |
|||
end = $this.data('mw-charinsert-end') || $this.data('end') || ''; |
|||
select.$element.prependTo( btns[1].$element ); |
|||
editor.replaceSelection( start + editor.getSelection() + end ); |
|||
btns[1].$element.contextmenu(function(e) { |
|||
editor.focus(); |
|||
e.preventDefault(); |
|||
}); |
|||
menu.removeClass( 'oo-ui-element-hidden' ); |
|||
// 为了充分利用浏览器缓存,这里不使用API;注意url转义,这里不需要简化'/mediawiki/index.php' |
|||
}).children('a').blur(function() { menu.addClass( 'oo-ui-element-hidden' ); }); |
|||
$.get({url: mw.util.getUrl(src, {variant: mw.config.get( 'wgUserLanguage' )}), |
|||
} |
|||
cache: charInsert.cache !== false, dataType: 'text'}).then(doc => { |
|||
pEmpty( $original ); |
|||
$charinsert.append( $( doc.match(/<body[\s\S]+<\/body>/)[0] ).find( '.mw-parser-output' ) ); |
|||
// $.when很容易出错,这里保险起见用Promise.all |
|||
}, () => { |
|||
Promise.all([getJSON, mw.request]).then(function(data) { |
|||
mw.notify([ |
|||
mw.config.set('extCodeMirrorConfig', data[0]); |
|||
wgULS('无法加载', '無法載入'), |
|||
mw.hook( 'codemirror.config' ).fire(); |
|||
$('<a>', {text: src, href: mw.util.getUrl(src)}), |
|||
text = data[1].query.pages[0].revisions[0].content; |
|||
wgULS('!请检查该页面是否存在。', '!請檢查此頁面是否存在。') |
|||
editor = new CodeMirror($outer[0], {value: text, mode: 'text/mediawiki', mwConfig: data[0], |
|||
], {type: 'error', autoHideSeconds: 'long'}); |
|||
lineWrapping: true, lineNumbers: true}); |
|||
}).then(() => { $charinsert.removeClass( 'mw-ajax-loader' ); }); |
|||
$wrapper = $( editor.getWrapperElement() ).toggle().append( $dropdown ); |
|||
}) : null), |
|||
$('<div>', {id: 'inspector-btns', html: btns.map(function(ele) { return ele.$element; })}).appendTo( $outer ); |
|||
$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 ? '}}' : ']]'), |
|||
isLineno = row.children( '.diff-lineno' ).length ? 1 : 0, |
|||
{line: cursor.line, ch: cursor.ch - prefix.length + 1}, cursor); |
|||
rowLineno = isLineno ? row : row.prevAll( ':has(.diff-lineno)' ).first(), |
|||
// 随后再次执行updateHints时会自行stop |
|||
n = parseInt( rowLineno.children().last().text().match(/\d+/) ) + row.index() - rowLineno.index() |
|||
editor.focus(); |
|||
- 2 + isLineno, // 使用parseInt规避可能的程序错误,注意CodeMirror的行号从0开始 |
|||
}), |
|||
nLine = editor.lastLine(); |
|||
// 判别模板时可能会误处理解析器函数 |
|||
if (nLine >= n) { |
|||
regexps = {0: /\[\[\s*:?([^|[\]{}<>]*)$/i, 10: /{{\s*([^|[\]{}<>#]*)$/i, |
|||
editor.scrollIntoView(nLine); |
|||
828: /{{\s*#invoke:\s*([^|[\]{}<>#:]*)$/i, 274: /{{\s*#widget:\s*([^|[\]{}<>#:]*)$/i}, |
|||
editor.scrollIntoView(n); |
|||
$noHint = $('<span>', {class: 'error'}), |
|||
} |
|||
nsPrefixes = {0: '', 10: 'Template:', 828: '模块:', 274: 'Widget:'}, |
|||
else { mw.notify( wgULS('当前不存在该行!', '當前不存在該行!'), {type: 'warn'} ); } |
|||
autocomplete = isGadget('autocomplete') ? e => { |
|||
}); |
|||
if (![9, 27].includes( e.keyCode )) { return; } // tab和esc |
|||
}, function(reason) { mw.apiFailure(reason, 'CodeMirror' + wgULS('设置或页面', '設置或頁面') + 'Wikitext'); }) |
|||
let $cursor, before, sections, title; |
|||
.then(function() { $outer.removeClass( 'mw-ajax-loader' ); }); |
|||
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( wgULS('请输入编辑摘要:', '請輸入編輯摘要:') , {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.hook( 'codemirror.config' ).fire(); |
|||
text = data[1].query.pages[0].revisions[0].content; |
|||
editor = new CodeMirror($outer[0], {value: text, mode: 'text/mediawiki', |
|||
mwConfig: mw.config.get('extCodeMirrorConfig'), lineWrapping: true, lineNumbers: true |
|||
}); |
|||
$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> |
//</nowiki> |
||
// [[category:维护工具]] [[category:桌面版小工具]] [[category:需要自确用户权限的小工具]] [[category:作为模块的小工具]] |
// [[category:维护工具]] [[category:桌面版小工具]] [[category:需要自确用户权限的小工具]] [[category:不作为模块的小工具]] |
||
// {{DEFAULTSORT:inspect.js}} |
// {{DEFAULTSORT:inspect.js}} |
2023年3月27日 (一) 13:11的最新版本
//<nowiki> // 由[[mediawiki:gadget-inspect-loader.js]]调用,可以使用ES6语法 /** * @Function: 在页面内快速编辑和预览 * @Author: [[User:Bhsd]] * @Translation: [[User:Lakejason0]] */ "use strict"; /*global OO, CodeMirror, wgULS*/ $(() => { // 避免全局变量 let nextid, list = mw.storage.getObject( 'inspect-category' ), flag; const id = mw.config.get( 'wgArticleId' ), 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:mypage/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 = mw.loader.using('ext.CodeMirror.data'), 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].anchor.replaceAll(' ', '_'); }, () => {}); }); // 标注<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, {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|categorieshtml|displaytitle|indicators|modules|headitems' }, wgULS('预览', '預覽')).then(r => { mw.loader.load( r.parse.modulestyles ); const tabsStyles = r.parse.headitems.find(function(ele) { return ele.tag === 'TabsStyles'; }); if (tabsStyles) { $('head').append(tabsStyles.content); // 可能重复,但重复也没关系 } 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); $('#catlinks').replaceWith( r.parse.categorieshtml ); 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]})) ); }, () => {}).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: [wgULS('查找:', '尋找:' ), 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( wgULS('请输入编辑摘要:', '請輸入編輯摘要:') , {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.hook( 'codemirror.config' ).fire(); text = data[1].query.pages[0].revisions[0].content; editor = new CodeMirror($outer[0], {value: text, mode: 'text/mediawiki', mwConfig: mw.config.get('extCodeMirrorConfig'), lineWrapping: true, lineNumbers: true }); $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}}