User:Addonian1123/QuickEdit-core.js
外观
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google Chrome、Firefox、Microsoft Edge及Safari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
| 這是QuickEdit+小工具的描述頁。 |
| 使用QuickEdit+進行編輯時,必須確保您的操作不危害社群及不違反共識,且您對您的操作完全負責。 請務必了解維基百科的方針與指引,並且在方針與指引許可的範圍內使用本工具。 如果您無法做到以上要求,請停止使用本工具。如有疑問,請到互助客棧發起討論。 |
| 文檔請見:User:Addonian1123/QuickEdit+ |
| 【QuickEdit+】組件 主JavaScript:User:Addonian1123/QuickEdit.js Core核心:User:Addonian1123/QuickEdit-core.js CSS樣式表:User:Addonian1123/QuickEdit-core.css GUI設定:User:Addonian1123/QuickEdit-settings.js |
// Quick Edit
// Edit sections of a page without leaving the article
// Based on [[mw:User:QuickEdit]] by [[meta:Iniquity]]
// Documentation: [[User:Addonian1123/QuickEdit+]]
// Main: [[User:Addonian1123/QuickEdit.js]]
var _addText="{{工具警告|QuickEdit+}}";
var _addText="{{Notice|<big>'''文檔請見:[[User:Addonian1123/QuickEdit+]]'''</big>}}";
var _addText="{{Notice|<big>'''【QuickEdit+】組件'''</big><br>主JavaScript:[[User:Addonian1123/QuickEdit.js]]<br>Core核心:[[User:Addonian1123/QuickEdit-core.js]]<br>CSS樣式表:[[User:Addonian1123/QuickEdit-core.css]]<br><!--Config-未啟用:[[User:Addonian1123/QuickEdit-config.js]]<br>-->GUI設定:[[User:Addonian1123/QuickEdit-settings.js]]}}";
;(function () {
const isMultiEdit = localStorage.getItem('qe_multi_edit') === 'true';
const isEditNoticeEnabled = localStorage.getItem('qe_edit_notice') !== 'false';
const isDebugEnabled = localStorage.getItem('qeDebug') === 'true';
// 繁簡
const userLang = mw.config.get('wgUserLanguage') || mw.config.get('wgUserVariant') || 'zh-hans';
const isHans = ['zh-hans', 'zh-cn', 'zh-my', 'zh-sg'].some(l => userLang.toLowerCase().startsWith(l));
const msg = {
errorInitCodeMirror: isHans ? '[QuickEdit+] 初始化WikiEditor或CodeMirror失败:' : '[QuickEdit+] 初始化WikiEditor或CodeMirror失敗:',
errorLoadModules: isHans ? '[QuickEdit+] 加载WikiEditor、CodeMirror或Wikificator失败:' : '[QuickEdit+] 載入WikiEditor、CodeMirror或Wikificator失敗:',
warnNoIntegrations: isHans ? '[QuickEdit+] 未找到qeIntegrations或applyExternalIntegrations。' : '[QuickEdit+] 未找到qeIntegrations或applyExternalIntegrations。',
errorWhenClose: isHans ? '关闭WikiEditor或CodeMirror时发生错误:' : '關閉WikiEditor或CodeMirror時發生錯誤:',
errorCloseAfterSave: isHans ? '在保存更改后关闭WikiEditor或CodeMirror时发生错误:' : '在儲存變更後關閉WikiEditor或CodeMirror時發生錯誤:',
editAllSection: isHans ? '编辑整个段落(包含子段落)' : '編輯整個段落(包含子段落)',
cancelButtonTitle: isHans ? '关闭QuickEdit且不保存更改' : '關閉QuickEdit並捨棄所有變更',
pageEditNotice: isHans ? '正在编辑页面:' : '正在編輯頁面:',
saving: isHans ? '正在保存更改...' : '正在發布變更...',
save: isHans ? '保存更改' : '發布變更',
cancelNotice: isHans ? '已取消更改。' : '已捨棄變更。',
saveNotice: isHans ? '已保存更改。' :'已發布變更。',
editSummary: isHans ? '透过[[User:Addonian1123/QuickEdit|QuickEdit+]]快速编辑' : '透過[[User:Addonian1123/QuickEdit|QuickEdit+]]快速編輯',
};
var strings = {
cancelNotice: msg.cancelNotice,
saveNotice: msg.saveNotice,
};
const titleRegexp = new RegExp(
mw.config
.get('wgArticlePath')
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
.replace(/\\\$1/, '([^?]+)') + '|[?&]title=([^&#]*)'
)
let apiSingleton
function api(func, params) {
if (!apiSingleton) {
apiSingleton = new mw.Api()
}
$.extend(params, {
errorformat: 'html',
errorlang: mw.config.get('wgUserLanguage'),
errorsuselocal: true
})
return apiSingleton[func](params).fail(function (_, data) {
mw.notify(apiSingleton.getErrorMessage(data), {
type: 'error',
tag: 'qe'
})
})
}
function getPageInfo ( title , sectionID ) {
qeLog ( 'getPageInfo:request' , { title : title , sectionID : sectionID });
const params = {
action : 'query' ,
curtimestamp : 1 ,
prop : 'revisions' ,
indexpageids : 1 ,
titles : title ,
rvprop : [ 'timestamp' , 'content' ],
rvslots : 'main'
};
if ( sectionID !== null && sectionID !== undefined && sectionID !== '' ) {
params.rvsection = sectionID;
}
return api ( 'get' , params ).then( function ( res ) {
const pageId = res && res.query && res.query.pageids && res.query.pageids[0]
const page = pageId && res.query.pages && res.query.pages[pageId]
const rev = page && page.revisions && page.revisions[0]
const exists = !!(page && !page.missing && page.revisions && page.revisions.length)
const full = rev && rev.slots && rev.slots.main ? rev.slots.main['*'] : ''
const base = (rev && rev.timestamp) || res.curtimestamp
const result = {
start: res.curtimestamp,
base: base,
full: full,
exists: exists
}
qeLog('getPageInfo:response', {
exists: exists,
hasRev: !!rev,
fullLength: full ? full.length : 0
})
return result
})
}
function getPreviewCallback(editor) {
editor.children('.preview').remove()
new OO.ui.ProgressBarWidget().$element.addClass('qe-progress-bar').appendTo(editor)
return function (html) {
editor.children('.preview').remove()
editor.children('.qe-progress-bar').remove()
const $preview = $('<div>')
.html(html)
.addClass('qe-border-color-base qe-preview-block')
.addClass('preview')
.appendTo(editor)
mw.hook('wikipage.content').fire($preview)
}
}
function qeLog() {
if (!isDebugEnabled) return;
try {
console.log.apply(console, ['[QuickEdit+]'].concat([].slice.call(arguments)));
} catch (_e) {}
}
function smoothScrollToElement(el, duration) {
const $el = el && el.jquery ? el : $(el)
if (!$el || !$el.length) return
const viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0
const elementHeight = $el.outerHeight()
const offsetTop = $el.offset().top
const scrollTop = window.pageYOffset || document.documentElement.scrollTop || 0
const visibleTop = scrollTop
const visibleBottom = scrollTop + viewportHeight
const elementBottom = offsetTop + elementHeight
const fits = elementHeight <= viewportHeight
const fullyVisible = offsetTop >= visibleTop && elementBottom <= visibleBottom
if (fits && fullyVisible) {
return Promise.resolve(false)
}
const targetTop = Math.max(
0,
Math.round(offsetTop - (viewportHeight - Math.min(elementHeight, viewportHeight)) / 2)
)
const animDuration = typeof duration === 'number' ? duration : 400
try {
const $doc = $('html, body')
if (typeof $doc.stop === 'function' && typeof $doc.animate === 'function') {
return new Promise(function (resolve) {
$doc.stop(true).animate({ scrollTop: targetTop }, animDuration, 'swing', function () {
resolve(true)
})
})
}
} catch (_e) {}
try {
window.scrollTo({ top: targetTop, behavior: 'smooth' })
return new Promise(function (resolve) {
setTimeout(function () {
resolve(true)
}, animDuration)
})
} catch (_e) {}
return Promise.resolve(true)
}
function focusWithoutScroll(element) {
if (!element) return
try {
const x = window.pageXOffset || document.documentElement.scrollLeft || 0
const y = window.pageYOffset || document.documentElement.scrollTop || 0
try {
element.focus({ preventScroll: true })
} catch (_e) {
element.focus()
}
window.scrollTo(x, y)
} catch (_e) {}
}
function showCompare(editor, title, from, to) {
mw.loader.load('mediawiki.diff.styles')
api('post', {
action: 'compare',
fromslots: 'main',
'fromtext-main': from,
fromtitle: title,
frompst: 'true',
toslots: 'main',
'totext-main': to,
totitle: title,
topst: 'true'
})
.then(function (r) {
return r.compare['*']
? $('<table>')
.addClass('diff')
.append(
$('<colgroup>').append(
$('<col>').addClass('diff-marker'),
$('<col>').addClass('diff-content'),
$('<col>').addClass('diff-marker'),
$('<col>').addClass('diff-content')
)
)
.append(r.compare['*'])
: mw.msg('diff-empty')
})
.then(getPreviewCallback(editor))
}
function getSectionSummary(text) {
const match = text.match(/^(=+)(.+)\1\s*(\n|$)/)
return !match
? ''
: '/* ' +
match[2]
.trim()
.replace(/\[\[:?([^[|]+)\|([^[]+)\]\]/g, '$2')
.replace(/\[\[:?([^[]+)\|?\]\]/g, '$1')
.replace(new RegExp('\\[(?:' + mw.config.get('wgUrlProtocols') + ')([^ ]+?) ([^\\[]+)\\]', 'ig'), '$2')
.replace(/(''|'''|''''')(?!')/g, '')
.replace(/<[^>]+?>/g, '') +
' */ '
}
async function showEditor($element, customTitle, customSectionID) {
const progress = new OO.ui.ProgressBarWidget()
let title = customTitle
let sectionID = customSectionID
const heading = ($element && $element.length) ? $element.parents(':header, .mw-heading').last() : $()
let matcher
let inserter
if (heading.length) {
matcher = heading.nextUntil.bind(heading)
inserter = heading.after.bind(heading)
const targetEl = $element.siblings('.qe-target').last()
const href = (targetEl && targetEl.attr && targetEl.attr('href')) || ''
const titleMatch = (href && href.match && href.match(titleRegexp)) || []
const fallbackTitle = mw.config.get('wgPageName')
if (!title) title = decodeURIComponent(titleMatch[1] || titleMatch[2] || fallbackTitle)
if (sectionID === undefined) {
const sectionMatch = /[?&]v?e?section=T?-?(\d*)/.exec(href) || []
sectionID = sectionMatch[1] !== undefined && sectionMatch[1] !== '' ? sectionMatch[1] : '0'
}
} else {
const $articleContent = ($element && $element.length && $element.is('#mw-content-text')) ? $element : $('#mw-content-text')
if (!title) title = mw.config.get('wgPageName')
if (sectionID === undefined) sectionID = null
matcher = function (selector) {
return $articleContent.find(selector)
}
inserter = function ($el) {
$articleContent.prepend($el)
}
}
qeLog('showEditor:target', { title: title, sectionID: sectionID })
if (heading.length && !heading.closest('.mw-parser-output').length) {
const $articleContent = $('#mw-content-text')
matcher = function (selector) {
const $child = $articleContent.children(selector).first()
return $child.length ? $child.prevAll() : $articleContent.children()
}
inserter = $articleContent.prepend.bind($articleContent)
}
inserter(progress.$element.addClass('qe-progress-bar'))
if ($element && $element.length) $element.addClass('qe-loading')
if (!isMultiEdit) {
$('.qe-hide').removeClass('qe-hide')
$('.qe-heading').removeClass('qe-heading')
$('#qe-editor').remove()
}
getPageInfo(title, sectionID).then(
async function (r) {
await new mw.Api().loadMessages([
'publishchanges',
'tooltip-publish',
'accesskey-publish',
'showpreview',
'tooltip-preview',
'accesskey-preview',
'showdiff',
'tooltip-diff',
'accesskey-diff',
'cancel',
'word-separator',
'brackets',
'diff-empty',
'summary',
'minoredit',
'summary-preview'
])
const start = r.start
const base = r.base
const full = r.full
const isNewPage = !r.exists
let saving = false
let expanded = (sectionID === null)
const remainderStart = (sectionID !== null && sectionID !== undefined && sectionID !== '') ? full.match(/\n=+.+=+(?:\n|$)/) : null
const part = isNewPage ? '' : remainderStart ? full.substring(0, remainderStart.index) : full
const remainder = isNewPage ? '' : remainderStart ? full.substring(remainderStart.index) : ''
let level = 0
let editor
full.replace(/^(=+).+?(=+)(?:\n|$)/, function (m, a, b) {
level = Math.min(a.length, b.length)
return m
})
let levelMatch = 'h1'
for (let i = 2; i <= level; i++) levelMatch += ', h' + i + ':has(*), .mw-heading' + i
const partSection = heading.length ? matcher(':header:has(*), .mw-heading') : $()
const fullSection = heading.length ? matcher(levelMatch) : $()
;((textarea = new OO.ui.MultilineTextInputWidget({
rows: 1,
maxRows: 20,
autosize: true,
value: part
})),
(summary = new OO.ui.TextInputWidget({
value: (isNewPage || sectionID === null) ? '' : getSectionSummary(part)
})),
(minor = new OO.ui.CheckboxInputWidget({
accessKey: 'i',
selected: mw.user.options.get('minoredit') === 1 || mw.user.options.get('minoredit') === '1'
})),
(save = new OO.ui.ButtonInputWidget({
label: mw.msg('publishchanges'),
flags: ['primary', 'progressive'],
accessKey: 's'
})),
(preview = new OO.ui.ButtonInputWidget({
label: mw.msg('showpreview')
})),
(compare = new OO.ui.ButtonInputWidget({
label: mw.msg('showdiff')
})),
(cancel = new OO.ui.ButtonInputWidget({
useInputTag: true,
label: mw.msg('cancel'),
title: msg.cancelButtonTitle,
flags: ['secondary', 'destructive']
})),
(more = new OO.ui.ButtonInputWidget({
label: '+',
title: msg.editAllSection
})),
(buttons = new OO.ui.HorizontalLayout({
items: [save, preview, compare, cancel]
})))
if (!isNewPage && part != full && sectionID !== null) {
buttons.addItems([more], 3)
}
if (partSection.length) partSection.addClass('qe-hide')
if (heading.length) heading.addClass('qe-heading')
if ($element && $element.length) $element.removeClass('qe-loading')
progress.$element.remove()
textarea.$input.addClass('qe-textarea')
summary.on('enter', function () {
save.emit('click')
})
save.on('click', function () {
if (saving) return
const fullText = textarea.getValue() + (expanded ? '' : remainder)
saving = true
save.setLabel(msg.saving)
compare.setDisabled(true)
preview.setDisabled(true)
cancel.setDisabled(true)
more.setDisabled(true)
const editParams = {
action: 'edit',
title: title,
summary: (function () {
var userSummary = summary.getValue().trim();
return userSummary ? userSummary + ' // ' + msg.editSummary : msg.editSummary;
})(),
text: fullText
};
if (minor.isSelected()) {
editParams.minor = true;
} else {
editParams.notminor = true;
}
if ( sectionID !== null && sectionID !== undefined && sectionID !== '' ) {
editParams.section = sectionID;
}
api('postWithEditToken', editParams).then(
function () {
api('get', {
action: 'parse',
page: mw.config.get('wgPageName'),
prop: ['text', 'categorieshtml']
}).then(function (r) {
const contentText = $('#mw-content-text'),
catLinks = $('#catlinks')
const $parserOutput = contentText.find('.mw-parser-output')
if ($parserOutput.length) {
$parserOutput.replaceWith(r.parse.text['*'])
} else {
contentText.html(r.parse.text['*'])
}
mw.hook('wikipage.content').fire(contentText)
if (catLinks.length) {
catLinks.replaceWith(r.parse.categorieshtml['*'])
mw.hook('wikipage.categories').fire($('#catlinks'))
}
if (typeof textarea.$input.wikiEditor === 'function') {
try {
const $cmWrapper = textarea.$input.closest('.ext-codemirror-wrapper')
if ($cmWrapper.length) {
const cmInstance = $cmWrapper.data('cmInstance')
if (cmInstance && typeof cmInstance.destroy === 'function') {
cmInstance.destroy()
}
}
textarea.$input.wikiEditor('destroy')
} catch (e) {
console.error(msg.errorCloseAfterSave, e)
}
}
editor.remove()
if (heading.length) heading.removeClass('qe-heading')
if (fullSection.length) fullSection.removeClass('qe-hide')
if (window.qeIntegrations && typeof window.qeIntegrations.onEditorClose === 'function') {
window.qeIntegrations.onEditorClose()
}
saving = false
if (isEditNoticeEnabled) {
mw.notify( strings.saveNotice, {
type: 'success',
tag: 'saved'
} );
}
})
},
function (code) {
if (code == 'editconflict') {
showEditConflict(editor, title, sectionID, fullText).then(function (r) {
start = r.start
base = r.base
textarea = r.textarea
expanded = true
})
}
compare.setDisabled(false)
preview.setDisabled(false)
cancel.setDisabled(false)
more.setDisabled(expanded)
saving = false
save.setLabel(msg.save)
}
)
})
preview.on('click', function () {
const summaryValue = summary.getValue()
const sectionMatch = summaryValue.match(/^\/\*\s*([^*]+?)\s*\*\/(.*)$/)
let summaryHtml = ''
if (sectionMatch) {
const section = sectionMatch[1].trim()
const rest = sectionMatch[2] ? sectionMatch[2].trimStart() : ''
const anchor = '#' + section.replace(/ /g, '_')
summaryHtml =
'<span class="comment"><span class="autocomment">→<a href="' +
anchor +
'"><bdi dir="ltr">' +
section +
'</bdi></a>: </span>'
const restParsePromise = api('post', {
action: 'parse',
title,
prop: 'text',
pst: 'true',
disablelimitreport: 'true',
disableeditsection: 'true',
sectionpreview: 'true',
disabletoc: 'true',
text: rest
})
const mainPreviewPromise = api('post', {
action: 'parse',
title,
prop: 'text',
pst: 'true',
disablelimitreport: 'true',
disableeditsection: 'true',
sectionpreview: 'true',
disabletoc: 'true',
text: textarea.getValue()
})
Promise.all([restParsePromise, mainPreviewPromise])
.then(function ([restRes, mainRes]) {
let restHtml = restRes.parse.text['*']
if (restHtml.startsWith('<div')) {
try {
restHtml = $(restHtml).html()
} catch (_e) {
restHtml = restHtml
.replace(/^<div[^>]*>[\s\n\r]*<p>/, '<div>')
.replace(/<\/p>[\s\n\r]*<\/div>$/, '</div>')
}
}
restHtml = restHtml.replace(/^<p>([\s\S]*)<\/p>$/i, '$1')
const summaryPreviewHtml =
'<div class="qe-summary-preview qe-border-color-base">' +
'<b>' +
mw.msg('summary-preview') +
'</b><br>' +
summaryHtml +
restHtml +
'</span>' +
'</div>'
const mainPreviewHtml = mainRes.parse.text['*'] + '<div style="clear:both;"></div>'
return summaryPreviewHtml + mainPreviewHtml
})
.then(getPreviewCallback(editor))
} else {
const summaryParsePromise = api('post', {
action: 'parse',
title,
prop: 'text',
pst: 'true',
disablelimitreport: 'true',
disableeditsection: 'true',
sectionpreview: 'true',
disabletoc: 'true',
text: summaryValue
})
const mainPreviewPromise = api('post', {
action: 'parse',
title,
prop: 'text',
pst: 'true',
disablelimitreport: 'true',
disableeditsection: 'true',
sectionpreview: 'true',
disabletoc: 'true',
text: textarea.getValue()
})
Promise.all([summaryParsePromise, mainPreviewPromise])
.then(function ([summaryRes, mainRes]) {
const summaryPreviewHtml =
'<div class="qe-summary-preview qe-border-color-base">' +
'<b>' +
mw.msg('summary-preview') +
'</b><br>' +
'<span class="comment">' +
summaryRes.parse.text['*'] +
'</span>' +
'</div>'
const mainPreviewHtml = mainRes.parse.text['*'] + '<div style="clear:both;"></div>'
return summaryPreviewHtml + mainPreviewHtml
})
.then(getPreviewCallback(editor))
}
})
compare.on('click', function () {
showCompare(editor, title, part + (expanded ? remainder : ''), textarea.getValue())
})
cancel.on('click', function () {
if (typeof textarea.$input.wikiEditor === 'function') {
try {
const $cmWrapper = textarea.$input.closest('.ext-codemirror-wrapper')
if ($cmWrapper.length) {
const cmInstance = $cmWrapper.data('cmInstance')
if (cmInstance && typeof cmInstance.destroy === 'function') {
cmInstance.destroy()
}
}
textarea.$input.wikiEditor('destroy')
} catch (e) {
console.error(msg.errorWhenClose, e)
}
}
editor.remove()
if (heading.length) heading.removeClass('qe-heading')
if (fullSection.length) fullSection.removeClass('qe-hide')
if (window.qeIntegrations && typeof window.qeIntegrations.onEditorClose === 'function') {
window.qeIntegrations.onEditorClose()
}
if (isEditNoticeEnabled) {
mw.notify( strings.cancelNotice, {
type: 'warn',
tag: 'canceled'
} );
}
})
more.on('click', function () {
expanded = true
textarea.setValue(textarea.getValue() + remainder)
if (typeof textarea.adjustSize === 'function') {
textarea.adjustSize(true)
}
const $wrapper = $('.ext-codemirror-wrapper').first()
setTimeout(function () {
const $textarea = $wrapper.find('textarea')
if ($textarea.length) {
const height = $textarea.outerHeight()
const $nextDiv = $textarea.next('div')
if ($nextDiv.length) {
$nextDiv.height(height)
$nextDiv.css('height', height + 'px')
$nextDiv[0].style.setProperty('height', height + 'px', 'important')
}
}
}, 5)
if (fullSection.length) fullSection.addClass('qe-hide')
more.setDisabled(true)
})
editor = $('<div id="qe-editor">').append(
$('<div>')
.addClass('qe-editor-block qe-background-color-interactive qe-color-base qe-border-color-base')
.append(
textarea.$element.addClass('qe-textarea'),
$('<div>')
.addClass('qe-edit-options')
.append(
$('<div id="wpSummaryWidget">')
.addClass('qe-border-color-base')
.append(
$('<div>')
.html(
mw.message('summary').parse()
.replace(/^(<|<)span[^>]*(>|>)/i, '')
.replace(/(<|<)\/span(>|>)$/i, '')
)
.on('click', 'a', function (e) {
e.preventDefault();
var text = $(this).text().trim();
if (!text) return;
var cur = summary.getValue().trim();
summary.setValue(cur ? cur + ' ' + text : text);
}),
summary.$element.addClass('qe-summary-input')
),
new OO.ui.FieldLayout(minor, {
label: new OO.ui.HtmlSnippet(mw.message('minoredit').parse()),
align: 'inline'
}).$element.addClass('qe-minoredit-block'),
buttons.$element.addClass('qe-buttons qe-border-color-base'),
title !== mw.config.get('wgPageName')
? $('<div>')
.addClass('qe-not-current-page qe-border-color-base')
.append(
msg.pageEditNotice,
$('<a>')
.attr('href', mw.config.get('wgArticlePath').replace('$1', title))
.addClass('qe-not-current-page-link')
.text(title.replace(/_/g, ' '))
)
: undefined
)
)
)
inserter(editor)
let didSmoothScroll = false
function maybeSmoothScroll() {
try {
const viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0
const elementHeight = editor && editor.outerHeight ? editor.outerHeight() : 0
const offsetTop = editor && editor.offset ? editor.offset().top : 0
const scrollTop = window.pageYOffset || document.documentElement.scrollTop || 0
const visibleTop = scrollTop
const visibleBottom = scrollTop + viewportHeight
const elementBottom = offsetTop + elementHeight
const fits = elementHeight <= viewportHeight
const fullyVisible = offsetTop >= visibleTop && elementBottom <= visibleBottom
const needScroll = !(fits && fullyVisible)
if (!didSmoothScroll && needScroll) {
didSmoothScroll = true
smoothScrollToElement(editor, 450).then(function () {
if (textarea && textarea.$input && textarea.$input[0]) {
focusWithoutScroll(textarea.$input[0])
}
})
}
} catch (_e) {}
}
requestAnimationFrame(function () {
setTimeout(maybeSmoothScroll, 0)
})
setTimeout(function () {
if (textarea && textarea.$input && textarea.$input[0]) {
focusWithoutScroll(textarea.$input[0])
}
}, 0)
mw.loader
.using(['ext.wikiEditor', 'ext.CodeMirror.WikiEditor', 'ext.CodeMirror.mode.mediawiki'])
.then(function (require) {
if (typeof mw.addWikiEditor === 'function') {
try {
mw.addWikiEditor(textarea.$input)
textarea.$input.wikiEditor({
toolbar: {
format: {},
insert: {},
advanced: {}
}
})
const CodeMirrorWikiEditor = require('ext.CodeMirror.WikiEditor')
const mediawikiLang = require('ext.CodeMirror.mode.mediawiki')
const cmWe = new CodeMirrorWikiEditor(textarea.$input[0], mediawikiLang())
cmWe.initialize()
try {
if (cmWe && cmWe.view && typeof cmWe.view.focus === 'function') {
const x = window.pageXOffset || document.documentElement.scrollLeft || 0
const y = window.pageYOffset || document.documentElement.scrollTop || 0
cmWe.view.focus()
window.scrollTo(x, y)
} else if (textarea && textarea.$input && textarea.$input[0]) {
focusWithoutScroll(textarea.$input[0])
}
} catch (_e) {}
maybeSmoothScroll()
} catch (e) {
console.error(msg.errorInitCodeMirror, e);
}
}
setTimeout(function () {
if (window.qeIntegrations && typeof window.qeIntegrations.applyExternalIntegrations === 'function') {
window.qeIntegrations.applyExternalIntegrations(textarea, summary)
} else {
console.warn(msg.warnNoIntegrations);
}
}, 50)
})
.catch(function (error) {
console.error(msg.errorLoadModules, error);
})
textarea.$input.on('wikiEditor-toolbar-done', function () {
if (window.qeIntegrations && typeof window.qeIntegrations.applyExternalIntegrations === 'function') {
window.qeIntegrations.applyExternalIntegrations(textarea, summary)
} else {
console.warn(msg.warnNoIntegrations);
}
})
textarea.updatePosition()
},
function () {
if ($element && $element.length) $element.removeClass('qe-loading')
progress.$element.remove()
}
)
}
function showEditConflict(editor, title, sectionID, text) {
return getPageInfo(title, sectionID).then(function (r) {
const textarea = new OO.ui.MultilineTextInputWidget({
rows: 1,
maxRows: 20,
autosize: true,
value: r.full
}),
textarea2 = new OO.ui.MultilineTextInputWidget({
rows: 1,
maxRows: 20,
autosize: true,
value: text
})
function syncSize() {
textarea.styleHeight = -1
textarea.adjustSize(true)
textarea2.styleHeight = -1
textarea2.adjustSize(true)
const height = Math.max(textarea.$input.height(), textarea2.$input.height())
textarea.$input.height(height)
textarea2.$input.height(height)
}
textarea.$input.addClass('qe-textarea')
editor.find('> :first-child > :first-child').remove()
$('<table>')
.addClass('qe-border-color-base')
.css({
width: '100%',
borderWidth: '1px',
borderStyle: 'solid',
borderBottom: 'none',
borderSpacing: '0',
margin: '0 !important'
})
.append(
$('<tr>').append(
$('<th>').addClass('qe-table-th').text('Their version (to be saved)'),
$('<th>')
.css({
width: '50%',
paddingTop: '4px'
})
.text('Your version')
),
$('<tr>').append(
$('<td>')
.css({
width: '50%',
padding: '4px 4px 0 8px'
})
.append(
textarea.$element.css({
width: '100%',
maxWidth: '100%',
fontFamily: 'monospace, monospace'
})
),
$('<td>')
.css({
width: '50%',
padding: '4px 8px 0 4px'
})
.append(
textarea2.$element.css({
width: '100%',
maxWidth: '100%',
fontFamily: 'monospace, monospace'
})
)
)
)
.prependTo(editor.find('> :first-child'))
textarea.on('change', syncSize)
textarea2.on('change', syncSize)
syncSize()
showCompare(editor, title, text, r.full)
r.textarea = textarea
return r
})
}
function clickHandler(event) {
const $element = $(event.target).closest('.qe-editlink')
if (!$element.length || $element.hasClass('qe-loading')) return
event.preventDefault()
showEditor($element)
}
// 匯出全域 API
function qeEditSection(title, sectionID, $mountTarget) {
showEditor($mountTarget || $('#mw-content-text'), title, sectionID)
}
window.qeEditSection = qeEditSection
$.when(mw.loader.using(['oojs-ui-core']), $.ready).done(function () {
const $body = $(document.body)
$body.on('click', clickHandler)
})
})()