MediaWiki:Gadget-EditorAPIs.js
外观
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google Chrome、Firefox、Microsoft Edge及Safari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
/**
* 取自 oldid=45922557
* hook 維基百科所有編輯器!
*
* 用法:
* mw.hook('editorapi.ready').add(function (editor) {
* // 指令...使用editor或window.wikitextEditor
* });
* mw.loader.load('https://zh-two.iwiki.icu/w/index.php?title=User:逆襲的天邪鬼/js/EditorAPIs.js&action=raw&ctype=text/javascript');
*
* 注意事項:
* 1. 這是個構件,直接拿去importScript沒有任何意義。
* 2. 建議不要直接copy內容。請注意原始碼中的TODO,並且根據實際需要來fork。
* 3. 不支援IE<10,不支援Wikiplus,CodeEditor (AceEditor)不支援selection功能。
* 4. 不支援VisualEditor的視覺化模式。請注意檢查 wikitextEditor.mode
* 5. 透過 wikitextEditor.mode 可以檢查目前的編輯器模式。mode值包括:none(不是編輯器,各介面不可用)、wikitext(經典)、wikEd(wikEd編輯器)、wikitext2017(2017原始碼編輯)、codemirror(CodeMirror编辑器)、view(處於「閱讀」模式,實際上沒有編輯器,需要手動開啟)、visual(可識別但不支援,注意!)。
* 6. 注意VisualEditor的問題:
* (1) 因為VisualEditor不會立刻載入,因此使用者使用VE的話 editorapi.ready 會觸發兩遍。
* (2) 在使用者切換視覺化編輯與原始碼2017編輯模式時,VE的ready會被再次觸發,因此editorapi.ready也會跟著觸發。
* 7. window.editorapi = { useViewMode: true }; // 開啟view模式
*/
$(function () {
'use strict';
if (window.wikitextEditor) {
return;
}
window.wikitextEditor = { mode: 'none' };
if (['submit', 'edit'].indexOf(mw.config.get('wgAction')) !== -1) {
// CodeMirror
if ($('#wpTextbox1 + .CodeMirror').length > 0) {
window.wikitextEditor = {
get mode() {
return 'codemirror';
},
get text() {
return $('#wpTextbox1').val();
},
set text(t) {
$('#wpTextbox1').val(t);
},
get summary() {
return $('#wpSummary').val();
},
set summary(text) {
$('#wpSummary').val(text);
},
get minorEdit() {
return $('#wpMinoredit').prop('checked');
},
set minorEdit(v) {
$('#wpMinoredit').prop('checked', v);
},
submit: function () {
$('#wpSave').click();
},
showPreview: function () {
$('#wpPreview').click();
},
showDiff: function () {
$('#wpDiff').click();
},
get selectionStart() {
return $('#wpTextbox1').textSelection('getCaretPosition');
},
set selectionStart(pos) {
var end = $('#wpTextbox1').textSelection('getCaretPosition', {startAndEnd:true})[1];
$('#wpTextbox1').textSelection('setSelection', {
start: pos,
end: end
});
},
get selectionEnd() {
return $('#wpTextbox1').textSelection('getCaretPosition', {startAndEnd:true})[1];
},
set selectionEnd(pos) {
var start = $('#wpTextbox1').textSelection('getCaretPosition');
$('#wpTextbox1').textSelection('setSelection', {
start: start,
end: pos
});
},
get selectionText() {
return $('#wpTextbox1').textSelection('getSelection');
},
set selectionText(text) {
$('#wpTextbox1').textSelection('encapsulateSelection', {
replace: true,
peri: text,
});
},
};
mw.hook('editorapi.ready').fire(window.wikitextEditor);
} else if ($('.ace_editor').length > 0) {
// CodeEditor or AceEditor
mw.hook('codeEditor.configure').add(function(editor) {
window.wikitextEditor = {
get mode() {
return 'codeeditor';
},
get text() {
return editor.getValue();
},
set text(t) {
editor.setValue(t)
},
get summary() {
return $('#wpSummary').val();
},
set summary(text) {
$('#wpSummary').val(text);
},
get minorEdit() {
return $('#wpMinoredit').prop('checked');
},
set minorEdit(v) {
$('#wpMinoredit').prop('checked', v);
},
submit: function () {
$('#wpSave').click();
},
showPreview: function () {
$('#wpPreview').click();
},
showDiff: function () {
$('#wpDiff').click();
},
get selectionStart() {
throw new Error('NotImplementedError')
},
set selectionStart(pos) {
throw new Error('NotImplementedError')
},
get selectionEnd() {
throw new Error('NotImplementedError')
},
set selectionEnd(pos) {
throw new Error('NotImplementedError')
},
get selectionText() {
return editor.getTextRange();
},
set selectionText(text) {
throw new Error('NotImplementedError')
},
};
mw.hook('editorapi.ready').fire(window.wikitextEditor);
})
} else {
// 傳統原始碼編輯器
var textbox = document.getElementById('wpTextbox1');
window.wikitextEditor = {
get mode() {
if (window.wikEd && wikEd.useWikEd) {
return 'wikEd';
} else {
return 'wikitext';
}
},
get text() {
if (window.wikEd && wikEd.useWikEd) {
wikEd.UpdateTextarea();
}
return $('#wpTextbox1').val();
},
set text(t) {
$('#wpTextbox1').val(t);
if (window.wikEd && wikEd.useWikEd) {
wikEd.UpdateFrame();
}
},
get summary() {
return $('#wpSummary').val();
},
set summary(text) {
$('#wpSummary').val(text);
},
get minorEdit() {
return $('#wpMinoredit').prop('checked');
},
set minorEdit(v) {
$('#wpMinoredit').prop('checked', v);
},
submit: function () {
$('#wpSave').click();
},
showPreview: function () {
$('#wpPreview').click();
},
showDiff: function () {
$('#wpDiff').click();
},
get selectionStart() {
// TODO: WikEd支援。後面selectionEnd同。
return textbox.selectionStart;
},
set selectionStart(pos) {
textbox.selectionStart = pos;
},
get selectionEnd() {
return textbox.selectionEnd;
},
set selectionEnd(pos) {
textbox.selectionEnd = pos;
},
get selectionText() {
if (window.wikEd && wikEd.useWikEd) {
var obj = {};
wikEd.GetText(obj, 'selection, cursor');
return obj.selection.plain || '';
} else {
return textbox.value.substring(textbox.selectionStart, textbox.selectionEnd);
}
},
set selectionText(text) {
if (window.wikEd && wikEd.useWikEd) {
wikEd.EditButton(null, null, null, function (obj) {
wikEd.GetText(obj, 'selection, cursor');
if (obj.selection.plain !== '') {
obj.changed = obj.selection;
} else {
obj.changed = obj.cursor;
}
obj.changed.plain = text;
obj.changed.keepSel = true;
return;
});
} else {
textbox.setRangeText(text);
}
},
};
mw.hook('editorapi.ready').fire(window.wikitextEditor);
}
} else if (window.editorapi && window.editorapi.useViewMode) {
// 直接在頁面上提交。頁面名稱為目前頁面。
var submitForm = function (options) {
$('<form method="post" style="display:none;">')
.attr('action', mw.util.getUrl(mw.config.get('wgPageName'), {action: 'submit'}))
.attr('enctype', 'multipart/form-data')
.append($('<textarea name="wpTextbox1">').val(options.text || ''),
$('<input name="wpSummary">').val(options.summary || ''),
'<input name="wpMinoredit" type="checkbox" value="1"' + (options.minorEdit ? ' checked="checked"' : '') + '>',
'<input name="format" value="text/x-wiki">',
'<input name="wpAutoSummary" value="hashhashhashhash">', // 防止「未填寫摘要」的提示
'<input name="model" value="wikitext">',
'<input name="wpEditToken" value="' + mw.user.tokens.get('csrfToken') + '">',
'<input name="wpUltimateParam" value="1">',
'<input name="' + (options.button || 'wpSave') + '" type="submit" value="1">',
'<input name="mode" value="text">')
.appendTo('body').submit();
};
var e = {
mode: 'view',
text: null, // 稍後會嘗試取得目前頁面的原始碼
summary: '',
minorEdit: false,
submit: function () {
submitForm({
text: this.text,
summary: this.summary,
minorEdit: this.minorEdit,
button: 'wpSave',
});
},
showPreview: function () {
submitForm({
text: this.text,
summary: this.summary,
minorEdit: this.minorEdit,
button: 'wpPreview',
});
},
showDiff: function () {
submitForm({
text: this.text,
summary: this.summary,
minorEdit: this.minorEdit,
button: 'wpDiff',
});
},
selectionStart: 0,
selectionEnd: 0,
get selectionText() {
return this.text.substring(this.selectionStart, this.selectionEnd);
},
set selectionText(text) {
this.text = this.text.substring(0, this.selectionStart) + text + this.text.substring(this.selectionEnd);
},
};
// 取得頁面內容
$.ajax({
url: mw.util.wikiScript('api'),
data: {
action: 'query',
prop: 'revisions',
rvprop: 'content',
titles: mw.config.get('wgPageName'),
format: 'json',
},
dataType: 'json',
type: 'POST',
success: function (data) {
if (data.query) {
var pages = data.query.pages;
for (var pageid in pages) {
var page = pages[pageid];
if (e.text === null && !('missing' in page)) {
e.text = page.revisions[0]['*'];
}
}
}
}
});
window.wikitextEditor = e;
mw.hook('editorapi.ready').fire(window.wikitextEditor);
}
mw.hook('mobileFrontend.editorOpened').add(function () {
var textbox = document.getElementById('wikitext-editor');
window.wikitextEditor = {
get mode() {
return 'mobileFrontend';
},
get text() {
return $('#wikitext-editor').val();
},
set text(t) {
$('#wikitext-editor').val(t);
},
get summary() {
return $('textarea.summary').val();
},
set summary(text) {
$('textarea.summary').val(text);
},
get minorEdit() {
return;
},
set minorEdit(v) {
return;
},
submit: function () {
$('button.continue').click();
$('button.submit').click();
},
showPreview: function() {
$('button.continue').click();
},
showDiff: function() {
return;
},
get selectionStart() {
return textbox.selectionStart;
},
set selectionStart(pos) {
textbox.selectionStart = pos;
},
get selectionEnd() {
return textbox.selectionEnd;
},
set selectionEnd(pos) {
textbox.selectionEnd = pos;
},
get selectionText() {
return textbox.value.substring(textbox.selectionStart, textbox.selectionEnd);
},
set selectionText(text) {
textbox.setRangeText(text);
}
};
mw.hook('editorapi.ready').fire(window.wikitextEditor);
});
(function () {
var data2text = function (data) {
var r = [];
if (data) {
for (var i=0; i<data.length; i++) {
if (typeof data[i] === 'string') {
r.push(data[i]);
} else if (data[i] && data[i].type === '/paragraph') {
r.push('\n');
}
}
return r.join('');
} else {
return '';
}
};
var pendingSummary = '';
var pendingMinoredit = false;
var pendingAction = '';
var addedHook = false;
mw.hook('ve.activationComplete').add(function () {
// 視覺化編輯器。注意,切換視覺化與原始碼2017模式也會執行
var surface = ve.init.target.getSurface();
var surfaceModel = surface.getModel();
var processed = false;
var addHook = function () {
if (!addedHook) {
mw.hook('ve.saveDialog.stateChanged').add(function() {
if (ve.init.target.saveDialog && !processed) {
var dialog = ve.init.target.saveDialog;
dialog.setEditSummary(pendingSummary || '');
dialog.checkboxesByName.wpMinoredit.setSelected(pendingMinoredit && true);
// 另一個叫做wpWatchthis
setTimeout(function () {
if (!ve.init.target.saveDialog) { return; }
switch (pendingAction) {
case 'submit':
dialog.executeAction('save');
break;
case 'diff':
dialog.executeAction('review');
break;
case 'preview':
dialog.executeAction('preview');
break;
}
}, 500);
processed = true;
}
});
addedHook = true;
}
};
// FIXME: HACK
var keepSel = false;
var lastSel;
window.wikitextEditor = {
get mode() {
if (surface.getMode() === 'visual') {
return 'visual';
} else {
return 'wikitext2017';
}
},
get text() {
// TODO: 視覺化模式支援
if (surface.getMode() === 'visual') {
return null;
}
var data = surfaceModel.documentModel.data;
if (data && data.data) {
return data2text(data.data);
} else {
return '';
}
},
set text(text) {
if (surface.getMode() === 'visual') {
return;
}
var rangeToRemove = new ve.Range(0, surfaceModel.documentModel.getLength());
var fragment = surfaceModel.getLinearFragment(rangeToRemove);
fragment.insertContent(text);
},
get summary() {
if (ve.init.target.saveDialog) {
return ve.init.target.saveDialog.editSummaryInput.$input.val();
} else {
return pendingSummary || '';
}
},
set summary(text) {
if (ve.init.target.saveDialog) {
ve.init.target.saveDialog.setEditSummary(text);
} else {
pendingSummary = text;
addHook();
}
},
get minorEdit() {
if (ve.init.target.saveDialog) {
return ve.init.target.saveDialog.checkboxesByName.wpMinoredit.selected;
} else {
return pendingMinoredit && true;
}
},
set minorEdit(v) {
if (ve.init.target.saveDialog) {
ve.init.target.saveDialog.checkboxesByName.wpMinoredit.setSelected(v && true);
} else {
pendingMinoredit = v && true;
addHook();
}
},
submit: function () {
// TODO:改成聰明的做法
// TODO:如果沒對文字做過修改,對話方塊是叫不出來的
if (ve.init.target.saveDialog) {
if (!ve.init.target.saveDialog.visible) {
ve.init.target.saveDialog.open().then(function() {
ve.init.target.saveDialog.executeAction('save');
});
} else {
ve.init.target.saveDialog.executeAction('save');
}
} else {
pendingAction = 'submit';
addHook();
ve.init.target.toolbarSaveButton.emit('click');
}
},
showPreview: function () {
if (ve.init.target.saveDialog) {
if (!ve.init.target.saveDialog.visible) {
ve.init.target.saveDialog.open().then(function() {
ve.init.target.saveDialog.executeAction('preview');
});
} else {
ve.init.target.saveDialog.executeAction('preview');
}
} else {
pendingAction = 'preview';
addHook();
ve.init.target.toolbarSaveButton.emit('click');
}
},
showDiff: function () {
if (ve.init.target.saveDialog) {
if (!ve.init.target.saveDialog.visible) {
ve.init.target.saveDialog.open().then(function() {
ve.init.target.saveDialog.executeAction('review');
});
} else {
ve.init.target.saveDialog.executeAction('review');
}
} else {
pendingAction = 'diff';
addHook();
ve.init.target.toolbarSaveButton.emit('click');
}
},
get selectionStart() {
// 注意:
// 1. 此位置是編輯框的位置,而非文字位置。後面selectionEnd同。
// 2. 編輯器失去焦點時,選中區域會變為NullSelection!
var selection = surfaceModel.getSelection();
if (selection instanceof ve.dm.LinearSelection) {
return selection.getRange().start;
} else {
return null;
}
},
set selectionStart(pos) {
var selection = surfaceModel.getSelection();
if (selection instanceof ve.dm.LinearSelection) {
surfaceModel.setLinearSelection(new ve.Range(pos, selection.getRange().end));
}
},
get selectionEnd() {
var selection = surfaceModel.getSelection();
if (selection instanceof ve.dm.LinearSelection) {
return selection.getRange().end;
} else {
return null;
}
},
set selectionEnd(pos) {
var selection = keepSel ? lastSel : surfaceModel.getSelection();
if (selection instanceof ve.dm.LinearSelection) {
surfaceModel.setLinearSelection(new ve.Range(selection.getRange().start, pos));
}
},
get selectionText() {
// 注意:編輯器失去焦點時,選中區域會變為NullSelection!
var selection = surfaceModel.getSelection();
if (selection instanceof ve.dm.LinearSelection) {
var fragment = surfaceModel.getLinearFragment(selection.getRange());
return data2text(fragment.getData());
} else {
return '';
}
},
set selectionText(text) {
var selection = keepSel ? lastSel : surfaceModel.getSelection();
if (selection instanceof ve.dm.LinearSelection) {
var fragment = surfaceModel.getLinearFragment(selection.getRange());
fragment.insertContent(text);
}
},
set keepSelection(v) {
// FIXME: 這裡是Hack,因為編輯器失去焦點時會同時失去選區,
// 不處理的話在$.dialog()之後會無法取得選區內容
if (v) {
keepSel = true;
lastSel = surfaceModel.getSelection();
} else {
keepSel = false;
}
}
};
mw.hook('editorapi.ready').fire(window.wikitextEditor);
});
})();
});