wikiplus-highlight 2.8.1 → 2.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -5
- package/dist/main.min.js +1 -1
- package/dist/main.min.js.map +1 -1
- package/fold.js +104 -38
- package/i18n/en.json +6 -3
- package/i18n/zh-hans.json +6 -3
- package/i18n/zh-hant.json +7 -4
- package/jsconfig.json +3 -0
- package/main.js +245 -177
- package/matchtags.js +45 -51
- package/package.json +2 -1
- package/search.js +14 -35
package/main.js
CHANGED
|
@@ -8,13 +8,10 @@
|
|
|
8
8
|
(async () => {
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
-
const version = '2.
|
|
12
|
-
newAddon =
|
|
11
|
+
const version = '2.13.1',
|
|
12
|
+
newAddon = 2;
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* polyfill for mw.storage
|
|
16
|
-
* @type {{getObject: (key: string) => ?any, setObject: (key: string, value: any) => boolean}}
|
|
17
|
-
*/
|
|
14
|
+
/** @type {typeof mw.storage} */
|
|
18
15
|
const storage = typeof mw.storage === 'object' && typeof mw.storage.getObject === 'function'
|
|
19
16
|
? mw.storage
|
|
20
17
|
: {
|
|
@@ -30,9 +27,8 @@
|
|
|
30
27
|
}
|
|
31
28
|
},
|
|
32
29
|
setObject(key, value) {
|
|
33
|
-
let /** @type {string} */ json;
|
|
34
30
|
try {
|
|
35
|
-
json = JSON.stringify(value);
|
|
31
|
+
const json = JSON.stringify(value);
|
|
36
32
|
return localStorage.setItem(key, json);
|
|
37
33
|
} catch (e) {
|
|
38
34
|
return false;
|
|
@@ -41,15 +37,22 @@
|
|
|
41
37
|
};
|
|
42
38
|
/**
|
|
43
39
|
* polyfill for Object.fromEntries
|
|
44
|
-
* @type {(entries: Iterable<[string, any]>) =>
|
|
40
|
+
* @type {(entries: Iterable<[string, any]>) => Record<string, any>}
|
|
45
41
|
*/
|
|
46
42
|
const fromEntries = Object.fromEntries || (entries => {
|
|
47
|
-
const /** @type {
|
|
43
|
+
const /** @type {Record<string, any>} */ obj = {};
|
|
48
44
|
for (const [key, value] of entries) {
|
|
49
45
|
obj[key] = value;
|
|
50
46
|
}
|
|
51
47
|
return obj;
|
|
52
48
|
});
|
|
49
|
+
/**
|
|
50
|
+
* polyfill for Array.prototype.flat
|
|
51
|
+
* @type {function(this: any[][]): any[]}
|
|
52
|
+
*/
|
|
53
|
+
const flat = Array.prototype.flat || function() {
|
|
54
|
+
return this.reduce((acc, cur) => acc.concat(cur), []);
|
|
55
|
+
};
|
|
53
56
|
|
|
54
57
|
/**
|
|
55
58
|
* 解析版本号
|
|
@@ -71,7 +74,6 @@
|
|
|
71
74
|
* 获取I18N消息
|
|
72
75
|
* @param {string} key 消息键,省略'wphl-'前缀
|
|
73
76
|
* @param {string[]} args
|
|
74
|
-
* @returns {string}
|
|
75
77
|
*/
|
|
76
78
|
const msg = (key, ...args) => mw.msg(`wphl-${key}`, ...args);
|
|
77
79
|
/**
|
|
@@ -79,7 +81,7 @@
|
|
|
79
81
|
* @param {string[]} args
|
|
80
82
|
*/
|
|
81
83
|
const notify = (...args) => () => {
|
|
82
|
-
const
|
|
84
|
+
const $p = $('<p>', {html: msg(...args)});
|
|
83
85
|
mw.notify($p, {type: 'success', autoHideSeconds: 'long', tag: 'wikiplus-highlight'});
|
|
84
86
|
return $p;
|
|
85
87
|
};
|
|
@@ -90,13 +92,9 @@
|
|
|
90
92
|
// 路径
|
|
91
93
|
const CDN = '//fastly.jsdelivr.net',
|
|
92
94
|
CM_CDN = 'npm/codemirror@5.65.3',
|
|
93
|
-
MW_CDN = 'gh/bhsd-harry/codemirror-mediawiki@1.1.
|
|
95
|
+
MW_CDN = 'gh/bhsd-harry/codemirror-mediawiki@1.1.5',
|
|
94
96
|
REPO_CDN = `npm/wikiplus-highlight@${majorVersion}`;
|
|
95
97
|
|
|
96
|
-
/**
|
|
97
|
-
* mw.config常数
|
|
98
|
-
* @type {Object<string, string>}
|
|
99
|
-
*/
|
|
100
98
|
const {
|
|
101
99
|
wgPageName: page,
|
|
102
100
|
wgNamespaceNumber: ns,
|
|
@@ -107,26 +105,15 @@
|
|
|
107
105
|
skin,
|
|
108
106
|
} = mw.config.values;
|
|
109
107
|
|
|
110
|
-
/**
|
|
111
|
-
* @typedef {object} mwConfig
|
|
112
|
-
* @property {Object<string, string>} tagModes
|
|
113
|
-
* @property {Object<string, boolean>} tags
|
|
114
|
-
* @property {string} urlProtocols
|
|
115
|
-
* @property {[Object<string, string>, Object<string, string>]} doubleUnderscore
|
|
116
|
-
* @property {[Object<string, string>, Object<string, string>]} functionSynonyms
|
|
117
|
-
* @property {string[]} redirect
|
|
118
|
-
* @property {Object<string, string>} img
|
|
119
|
-
*/
|
|
120
|
-
|
|
121
108
|
// 和本地缓存有关的常数
|
|
122
109
|
const USING_LOCAL = mw.loader.getState('ext.CodeMirror') !== null,
|
|
123
|
-
/** @type {
|
|
110
|
+
/** @type {Record<string, {time: number, config: mwConfig}>} */
|
|
124
111
|
ALL_SETTINGS_CACHE = storage.getObject('InPageEditMwConfig') || {},
|
|
125
112
|
SITE_ID = `${server}${scriptPath}`,
|
|
126
113
|
/** @type {{time: number, config: mwConfig}} */ SITE_SETTINGS = ALL_SETTINGS_CACHE[SITE_ID] || {},
|
|
127
114
|
EXPIRED = SITE_SETTINGS.time < Date.now() - 86400 * 1000 * 30;
|
|
128
115
|
|
|
129
|
-
const /** @type {
|
|
116
|
+
const /** @type {Record<string, string>} */ CONTENTMODEL = {
|
|
130
117
|
css: 'css',
|
|
131
118
|
'sanitized-css': 'css',
|
|
132
119
|
javascript: 'javascript',
|
|
@@ -134,7 +121,7 @@
|
|
|
134
121
|
wikitext: 'mediawiki',
|
|
135
122
|
};
|
|
136
123
|
|
|
137
|
-
const
|
|
124
|
+
const MODE_LIST = USING_LOCAL
|
|
138
125
|
? {
|
|
139
126
|
lib: 'ext.CodeMirror.lib',
|
|
140
127
|
css: 'ext.CodeMirror.lib.mode.css',
|
|
@@ -157,27 +144,129 @@
|
|
|
157
144
|
const ADDON_LIST = {
|
|
158
145
|
searchcursor: `${CM_CDN}/addon/search/searchcursor.min.js`,
|
|
159
146
|
search: `${REPO_CDN}/search.min.js`,
|
|
160
|
-
activeLine: `${CM_CDN}/addon/selection/active-line.min.js`,
|
|
161
147
|
markSelection: `${CM_CDN}/addon/selection/mark-selection.min.js`,
|
|
148
|
+
activeLine: `${CM_CDN}/addon/selection/active-line.min.js`,
|
|
162
149
|
trailingspace: `${CM_CDN}/addon/edit/trailingspace.min.js`,
|
|
163
150
|
matchBrackets: `${CM_CDN}/addon/edit/matchbrackets.min.js`,
|
|
151
|
+
closeBrackets: `${CM_CDN}/addon/edit/closebrackets.min.js`,
|
|
164
152
|
matchTags: `${REPO_CDN}/matchtags.min.js`,
|
|
165
153
|
fold: `${REPO_CDN}/fold.min.js`,
|
|
166
154
|
};
|
|
167
155
|
|
|
156
|
+
/**
|
|
157
|
+
* @typedef {object} addon
|
|
158
|
+
* @property {string} option
|
|
159
|
+
* @property {string|string[]} addon
|
|
160
|
+
* @property {string} download
|
|
161
|
+
* @property {(mode: string, json: boolean) => any} complex
|
|
162
|
+
* @property {string[]} modes
|
|
163
|
+
* @property {boolean} only
|
|
164
|
+
*/
|
|
165
|
+
|
|
166
|
+
const /** @type {addon[]} */ options = [
|
|
167
|
+
{option: 'styleSelectedText', addon: 'search', download: 'markSelection', only: true},
|
|
168
|
+
{option: 'styleActiveLine', addon: 'activeLine'},
|
|
169
|
+
{option: 'showTrailingSpace', addon: 'trailingspace'},
|
|
170
|
+
{
|
|
171
|
+
option: 'matchBrackets',
|
|
172
|
+
complex: (mode, json) => mode === 'mediawiki' || json
|
|
173
|
+
? {bracketRegex: /[{}[\]]/}
|
|
174
|
+
: true,
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
option: 'autoCloseBrackets', addon: 'closeBrackets',
|
|
178
|
+
complex: (mode, json) => mode === 'mediawiki' || json
|
|
179
|
+
? '()[]{}""'
|
|
180
|
+
: true,
|
|
181
|
+
},
|
|
182
|
+
{option: 'matchTags', addon: ['matchTags', 'fold'], modes: ['mediawiki', 'widget']},
|
|
183
|
+
{option: 'fold', modes: ['mediawiki', 'widget']},
|
|
184
|
+
];
|
|
185
|
+
|
|
168
186
|
const defaultAddons = ['search'],
|
|
169
187
|
defaultIndent = '4';
|
|
170
188
|
let /** @type {string[]} */ addons = storage.getObject('Wikiplus-highlight-addons') || defaultAddons,
|
|
171
189
|
/** @type {string} */ indent = storage.getObject('Wikiplus-highlight-indent') || defaultIndent;
|
|
172
190
|
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
191
|
+
/** @type {Record<string, string>} */
|
|
192
|
+
const entity = {'"': 'quot', "'": 'apos', '<': 'lt', '>': 'gt', '&': 'amp', ' ': 'nbsp'},
|
|
193
|
+
/** @type {(func: (str: string) => string) => (doc: CodeMirror.Editor) => void} */
|
|
194
|
+
convert = func => doc => {
|
|
195
|
+
doc.replaceSelection(doc.getSelection().split('\n').map(func).join('\n'), 'around');
|
|
196
|
+
},
|
|
197
|
+
escapeHTML = convert(str => str.split('').map(c => {
|
|
198
|
+
if (c in entity) {
|
|
199
|
+
return `&${entity[c]};`;
|
|
200
|
+
}
|
|
201
|
+
const code = c.charCodeAt();
|
|
202
|
+
return code < 256 ? `&#${code};` : `&#x${code.toString(16)};`;
|
|
203
|
+
}).join('')),
|
|
204
|
+
/** @type {function(typeof CodeMirror): boolean} */ isPc = ({keyMap}) => keyMap.default === keyMap.pcDefault,
|
|
205
|
+
extraKeysPc = {'Ctrl-/': escapeHTML, 'Ctrl-\\': convert(encodeURIComponent)},
|
|
206
|
+
extraKeysMac = {'Cmd-/': escapeHTML, 'Cmd-\\': convert(encodeURIComponent)};
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* contextMenu插件
|
|
210
|
+
* @param {CodeMirror.Editor} doc
|
|
211
|
+
* @param {string} mode
|
|
212
|
+
*/
|
|
213
|
+
const handleContextMenu = async (doc, mode) => {
|
|
214
|
+
if (!['mediawiki', 'widget'].includes(mode) || !addons.includes('contextmenu')) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const $wrapper = $(doc.getWrapperElement()).addClass('CodeMirror-contextmenu'),
|
|
218
|
+
{functionSynonyms: [synonyms]} = mw.config.get('extCodeMirrorConfig') || {
|
|
219
|
+
functionSynonyms: [{
|
|
220
|
+
invoke: 'invoke',
|
|
221
|
+
调用: 'invoke',
|
|
222
|
+
widget: 'widget',
|
|
223
|
+
小工具: 'widget',
|
|
224
|
+
}],
|
|
225
|
+
};
|
|
226
|
+
/** @param {string} str */
|
|
227
|
+
const getSysnonyms = str => Object.keys(synonyms).filter(key => synonyms[key] === str)
|
|
228
|
+
.map(key => key.startsWith('#') ? key : `#${key}`);
|
|
229
|
+
const invoke = getSysnonyms('invoke'),
|
|
230
|
+
widget = getSysnonyms('widget');
|
|
231
|
+
|
|
232
|
+
await mw.loader.using('mediawiki.Title');
|
|
233
|
+
$wrapper.contextmenu(({pageX, pageY}) => {
|
|
234
|
+
const pos = doc.coordsChar({left: pageX, top: pageY}),
|
|
235
|
+
{line, ch} = pos,
|
|
236
|
+
type = doc.getTokenTypeAt(pos);
|
|
237
|
+
if (!/\bmw-(?:template-name|parserfunction)\b/.test(type)) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const tokens = doc.getLineTokens(line),
|
|
241
|
+
index = tokens.findIndex(({start, end}) => start < ch && end >= ch),
|
|
242
|
+
text = tokens[index].string.replace(/\u200e/g, '').replace(/_/g, ' ').trim();
|
|
243
|
+
if (/\bmw-template-name\b/.test(type)) {
|
|
244
|
+
const title = new mw.Title(text);
|
|
245
|
+
if (title.namespace !== 0 || text.startsWith(':')) {
|
|
246
|
+
open(title.getUrl(), '_blank');
|
|
247
|
+
} else {
|
|
248
|
+
open(mw.util.getUrl(`Template:${text}`), '_blank');
|
|
249
|
+
}
|
|
250
|
+
return false;
|
|
251
|
+
} else if (index < 2 || !/\bmw-parserfunction-delimiter\b/.test(tokens[index - 1].type)
|
|
252
|
+
|| !/\bmw-parserfunction-name\b/.test(tokens[index - 2].type)
|
|
253
|
+
) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const parserFunction = tokens[index - 2].string.trim().toLowerCase();
|
|
257
|
+
if (invoke.includes(parserFunction)) {
|
|
258
|
+
open(mw.util.getUrl(`Module:${text}`), '_blank');
|
|
259
|
+
} else if (widget.includes(parserFunction)) {
|
|
260
|
+
open(mw.util.getUrl(`Widget:${text}`, {action: 'edit'}), '_blank');
|
|
261
|
+
} else {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
return false;
|
|
265
|
+
});
|
|
266
|
+
};
|
|
178
267
|
|
|
179
|
-
let /** @type {
|
|
180
|
-
/** @type {() => JQuery<
|
|
268
|
+
let /** @type {Record<string, string>} */ i18n = storage.getObject('Wikiplus-highlight-i18n'),
|
|
269
|
+
/** @type {() => JQuery<HTMLElement>} */ welcome;
|
|
181
270
|
if (!i18n) { // 首次安装
|
|
182
271
|
i18n = {};
|
|
183
272
|
welcome = notify('welcome');
|
|
@@ -185,7 +274,7 @@
|
|
|
185
274
|
welcome = notify(`welcome-${newAddon ? 'new-addon' : 'upgrade'}`, version, newAddon);
|
|
186
275
|
}
|
|
187
276
|
|
|
188
|
-
const /** @type {
|
|
277
|
+
const /** @type {Record<string, string>} */ i18nLanguages = {
|
|
189
278
|
zh: 'zh-hans', 'zh-hans': 'zh-hans', 'zh-cn': 'zh-hans', 'zh-my': 'zh-hans', 'zh-sg': 'zh-hans',
|
|
190
279
|
'zh-hant': 'zh-hant', 'zh-tw': 'zh-hant', 'zh-hk': 'zh-hant', 'zh-mo': 'zh-hant',
|
|
191
280
|
},
|
|
@@ -205,7 +294,7 @@
|
|
|
205
294
|
mw.messages.set(i18n);
|
|
206
295
|
};
|
|
207
296
|
|
|
208
|
-
const
|
|
297
|
+
const i18nPromise = Promise.all([ // 提前加载I18N
|
|
209
298
|
mw.loader.using('mediawiki.util'),
|
|
210
299
|
setI18N(),
|
|
211
300
|
]);
|
|
@@ -214,7 +303,6 @@
|
|
|
214
303
|
* 下载脚本
|
|
215
304
|
* @param {string[]} urls 脚本路径
|
|
216
305
|
* @param {boolean} local 是否从本地下载
|
|
217
|
-
* @returns {Promise<void>}
|
|
218
306
|
*/
|
|
219
307
|
const getScript = (urls, local) => {
|
|
220
308
|
if (urls.length === 0) {
|
|
@@ -229,7 +317,26 @@
|
|
|
229
317
|
};
|
|
230
318
|
|
|
231
319
|
// 以下进入CodeMirror相关内容
|
|
232
|
-
let /** @type {CodeMirror.
|
|
320
|
+
let /** @type {CodeMirror.EditorFromTextArea} */ cm;
|
|
321
|
+
|
|
322
|
+
/** @param {typeof CodeMirror} CM */
|
|
323
|
+
const getAddonScript = (CM, other = false) => {
|
|
324
|
+
const /** @type {string[]} */ addonScript = [];
|
|
325
|
+
for (const {option, addon = option, download = Array.isArray(addon) ? option : addon, only} of options) {
|
|
326
|
+
if (!(only && other) && !(option in CM.optionHandlers) && intersect(addon, addons)) {
|
|
327
|
+
addonScript.push(ADDON_LIST[download]);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return addonScript;
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* @param {array|any} arr1
|
|
335
|
+
* @param {array} arr2
|
|
336
|
+
*/
|
|
337
|
+
const intersect = (arr1, arr2) => Array.isArray(arr1)
|
|
338
|
+
? arr1.some(ele => arr2.includes(ele))
|
|
339
|
+
: arr2.includes(arr1);
|
|
233
340
|
|
|
234
341
|
/**
|
|
235
342
|
* 根据文本的高亮模式加载依赖项
|
|
@@ -243,7 +350,7 @@
|
|
|
243
350
|
|
|
244
351
|
/**
|
|
245
352
|
* 代替CodeMirror的局部变量
|
|
246
|
-
* @type {CodeMirror}
|
|
353
|
+
* @type {typeof CodeMirror}
|
|
247
354
|
*/
|
|
248
355
|
const CM = loaded
|
|
249
356
|
? window.CodeMirror
|
|
@@ -259,7 +366,7 @@
|
|
|
259
366
|
mw.loader.load(`${CDN}/${MW_CDN}/mediawiki.min.css`, 'text/css');
|
|
260
367
|
(USING_LOCAL ? externalScript : scripts).push(`${MW_CDN}/mediawiki.min.js`);
|
|
261
368
|
}
|
|
262
|
-
if (type === 'mediawiki' &&
|
|
369
|
+
if (type === 'mediawiki' && SITE_SETTINGS.config && SITE_SETTINGS.config.tags.html) {
|
|
263
370
|
// NamespaceHTML扩展自由度过高,所以这里一律当作允许<html>标签
|
|
264
371
|
type = 'html'; // eslint-disable-line no-param-reassign
|
|
265
372
|
}
|
|
@@ -275,24 +382,7 @@
|
|
|
275
382
|
if (!CM.commands.findForward && addons.includes('search')) {
|
|
276
383
|
addonScript.push(ADDON_LIST.search);
|
|
277
384
|
}
|
|
278
|
-
|
|
279
|
-
addonScript.push(ADDON_LIST.activeLine);
|
|
280
|
-
}
|
|
281
|
-
if (!CM.optionHandlers.styleSelectedText && addons.includes('search')) {
|
|
282
|
-
addonScript.push(ADDON_LIST.markSelection);
|
|
283
|
-
}
|
|
284
|
-
if (!CM.optionHandlers.showTrailingSpace && addons.includes('trailingspace')) {
|
|
285
|
-
addonScript.push(ADDON_LIST.trailingspace);
|
|
286
|
-
}
|
|
287
|
-
if (!CM.optionHandlers.matchBrackets && addons.includes('matchBrackets')) {
|
|
288
|
-
addonScript.push(ADDON_LIST.matchBrackets);
|
|
289
|
-
}
|
|
290
|
-
if (!CM.optionHandlers.matchTags && ['matchTags', 'fold'].some(addon => addons.includes(addon))) {
|
|
291
|
-
addonScript.push(ADDON_LIST.matchTags);
|
|
292
|
-
}
|
|
293
|
-
if (!CM.optionHandlers.fold && addons.includes('fold')) {
|
|
294
|
-
addonScript.push(ADDON_LIST.fold);
|
|
295
|
-
}
|
|
385
|
+
addonScript.push(...getAddonScript(CM));
|
|
296
386
|
if (['widget', 'html'].includes(type)) {
|
|
297
387
|
['css', 'javascript', 'mediawiki', 'htmlmixed', 'xml'].forEach(lang => {
|
|
298
388
|
if (!CM.modes[lang]) {
|
|
@@ -351,32 +441,35 @@
|
|
|
351
441
|
await initModePromise;
|
|
352
442
|
}
|
|
353
443
|
|
|
354
|
-
let
|
|
444
|
+
let config = mw.config.get('extCodeMirrorConfig');
|
|
355
445
|
if (!config && !EXPIRED && isLatest) {
|
|
356
446
|
({config} = SITE_SETTINGS);
|
|
447
|
+
if (config.tags.ref) { // fix a bug in InPageEdit-v2
|
|
448
|
+
config.tagModes.ref = 'text/mediawiki';
|
|
449
|
+
}
|
|
357
450
|
mw.config.set('extCodeMirrorConfig', config);
|
|
358
451
|
}
|
|
359
452
|
if (config && config.redirect && config.img) { // 情形1:config已更新,可能来自localStorage
|
|
360
453
|
return config;
|
|
361
|
-
} else if (config) {
|
|
454
|
+
} else if (config) { /** @todo 暂不需要redirect和img相关设置 */
|
|
362
455
|
return config;
|
|
363
456
|
}
|
|
364
457
|
|
|
365
458
|
/**
|
|
366
|
-
* @typedef {object}
|
|
459
|
+
* @typedef {object} ApiSiteInfoQuery
|
|
367
460
|
* @property {{name: string, aliases: string[], 'case-sensitive': boolean}[]} magicwords
|
|
368
461
|
* @property {string[]} extensiontags
|
|
369
462
|
* @property {string[]} functionhooks
|
|
370
463
|
* @property {string[]} variables
|
|
371
464
|
*/
|
|
372
465
|
|
|
373
|
-
|
|
466
|
+
/*
|
|
374
467
|
* 以下情形均需要发送API请求
|
|
375
468
|
* 情形2:localStorage未过期但不包含新设置
|
|
376
469
|
* 情形3:新加载的 ext.CodeMirror.data
|
|
377
470
|
* 情形4:config === null
|
|
378
471
|
*/
|
|
379
|
-
const /** @type {{query:
|
|
472
|
+
const /** @type {{query: ApiSiteInfoQuery}} */ {
|
|
380
473
|
query: {magicwords, extensiontags, functionhooks, variables},
|
|
381
474
|
} = await new mw.Api().get({
|
|
382
475
|
meta: 'siteinfo',
|
|
@@ -389,13 +482,12 @@
|
|
|
389
482
|
* @param {{aliases: string[], name: string}[]} words
|
|
390
483
|
* @returns {{alias: string, name: string}[]}
|
|
391
484
|
*/
|
|
392
|
-
const getAliases = words =>
|
|
393
|
-
|
|
394
|
-
({aliases, name}) => aliases.map(alias => ({alias, name})),
|
|
485
|
+
const getAliases = words => flat.call(
|
|
486
|
+
words.map(({aliases, name}) => aliases.map(alias => ({alias, name}))),
|
|
395
487
|
);
|
|
396
488
|
/**
|
|
397
489
|
* @param {{alias: string, name: string}[]} aliases
|
|
398
|
-
* @returns {
|
|
490
|
+
* @returns {Record<string, string>}
|
|
399
491
|
*/
|
|
400
492
|
const getConfig = aliases => fromEntries(
|
|
401
493
|
aliases.map(({alias, name}) => [alias.replace(/:$/, ''), name]),
|
|
@@ -413,10 +505,8 @@
|
|
|
413
505
|
),
|
|
414
506
|
urlProtocols: mw.config.get('wgUrlProtocols'),
|
|
415
507
|
};
|
|
416
|
-
/** @type {Set<string>} */
|
|
417
508
|
const realMagicwords = new Set([...functionhooks, ...variables, ...otherMagicwords]),
|
|
418
509
|
allMagicwords = magicwords.filter(
|
|
419
|
-
/** @returns {boolean} */
|
|
420
510
|
({name, aliases}) => aliases.some(alias => /^__.+__$/.test(alias)) || realMagicwords.has(name),
|
|
421
511
|
),
|
|
422
512
|
sensitive = getAliases(
|
|
@@ -437,15 +527,15 @@
|
|
|
437
527
|
const {functionSynonyms: [insensitive]} = config;
|
|
438
528
|
if (!insensitive.subst) {
|
|
439
529
|
getAliases(
|
|
440
|
-
magicwords.filter(
|
|
530
|
+
magicwords.filter(({name}) => otherMagicwords.includes(name)),
|
|
441
531
|
).forEach(({alias, name}) => {
|
|
442
532
|
insensitive[alias.replace(/:$/, '')] = name;
|
|
443
533
|
});
|
|
444
534
|
}
|
|
445
535
|
}
|
|
446
|
-
config.redirect = magicwords.find(
|
|
536
|
+
config.redirect = magicwords.find(({name}) => name === 'redirect').aliases;
|
|
447
537
|
config.img = getConfig(
|
|
448
|
-
getAliases(magicwords.filter(
|
|
538
|
+
getAliases(magicwords.filter(({name}) => name.startsWith('img_'))),
|
|
449
539
|
);
|
|
450
540
|
mw.config.set('extCodeMirrorConfig', config);
|
|
451
541
|
updateCachedConfig(config);
|
|
@@ -457,7 +547,7 @@
|
|
|
457
547
|
if ([274, 828].includes(ns) && !page.endsWith('/doc')) {
|
|
458
548
|
const pageMode = ns === 274 ? 'Widget' : 'Lua';
|
|
459
549
|
await mw.loader.using(['oojs-ui-windows', 'oojs-ui.styles.icons-content']);
|
|
460
|
-
const
|
|
550
|
+
const bool = await OO.ui.confirm(msg('contentmodel'), {
|
|
461
551
|
actions: [
|
|
462
552
|
{label: pageMode},
|
|
463
553
|
{label: 'Wikitext', action: 'accept'},
|
|
@@ -478,7 +568,7 @@
|
|
|
478
568
|
const renderEditor = async ($target, setting) => {
|
|
479
569
|
const mode = setting ? 'javascript' : await getPageMode();
|
|
480
570
|
const initModePromise = initMode(mode);
|
|
481
|
-
const
|
|
571
|
+
const [mwConfig] = await Promise.all([
|
|
482
572
|
getMwConfig(mode, initModePromise),
|
|
483
573
|
initModePromise,
|
|
484
574
|
]);
|
|
@@ -503,7 +593,7 @@
|
|
|
503
593
|
}
|
|
504
594
|
|
|
505
595
|
const json = setting || contentmodel === 'json',
|
|
506
|
-
|
|
596
|
+
{name} = $.client.profile();
|
|
507
597
|
cm = CodeMirror.fromTextArea($target[0], $.extend({
|
|
508
598
|
inputStyle: name === 'safari' ? 'textarea' : 'contenteditable',
|
|
509
599
|
lineNumbers: true,
|
|
@@ -511,17 +601,15 @@
|
|
|
511
601
|
mode,
|
|
512
602
|
mwConfig,
|
|
513
603
|
json,
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
}, mode === 'mediawiki'
|
|
524
|
-
? {}
|
|
604
|
+
}, fromEntries(
|
|
605
|
+
options.map(({option, addon = option, modes, complex = mod => !modes || modes.includes(mod)}) => {
|
|
606
|
+
const mainAddon = Array.isArray(addon) ? addon[0] : addon;
|
|
607
|
+
return [option, addons.includes(mainAddon) && complex(mode, json)];
|
|
608
|
+
}),
|
|
609
|
+
), mode === 'mediawiki'
|
|
610
|
+
? {
|
|
611
|
+
extraKeys: addons.includes('escape') && (isPc(CodeMirror) ? extraKeysPc : extraKeysMac),
|
|
612
|
+
}
|
|
525
613
|
: {
|
|
526
614
|
indentUnit: addons.includes('indentWithSpace') ? indent : defaultIndent,
|
|
527
615
|
indentWithTabs: !addons.includes('indentWithSpace'),
|
|
@@ -530,44 +618,7 @@
|
|
|
530
618
|
cm.setSize(null, height);
|
|
531
619
|
cm.refresh();
|
|
532
620
|
|
|
533
|
-
|
|
534
|
-
wrapper.id = 'Wikiplus-CodeMirror';
|
|
535
|
-
if (['mediawiki', 'widget'].includes(mode) && addons.includes('contextmenu')) {
|
|
536
|
-
contextmenuStyle.disabled = false;
|
|
537
|
-
const /** @type {mwConfig} */ {functionSynonyms: [synonyms]} = mw.config.get('extCodeMirrorConfig');
|
|
538
|
-
/** @param {string} str */
|
|
539
|
-
const getSysnonyms = str => Object.keys(synonyms).filter(key => synonyms[key] === str)
|
|
540
|
-
.map(key => key.startsWith('#') ? key : `#${key}`);
|
|
541
|
-
const invoke = getSysnonyms('invoke'),
|
|
542
|
-
widget = getSysnonyms('widget');
|
|
543
|
-
|
|
544
|
-
await mw.loader.using('mediawiki.Title');
|
|
545
|
-
$(wrapper).on('contextmenu', '.cm-mw-template-name', function() {
|
|
546
|
-
const /** @type {string} */ text = this.textContent.replace(/\u200e/g, '').trim(),
|
|
547
|
-
/** @type {{namespace: number, getUrl: () => string}} */ title = new mw.Title(text);
|
|
548
|
-
if (title.namespace !== 0 || text.startsWith(':')) {
|
|
549
|
-
open(title.getUrl(), '_blank');
|
|
550
|
-
} else {
|
|
551
|
-
open(mw.util.getUrl(`Template:${text}`), '_blank');
|
|
552
|
-
}
|
|
553
|
-
return false;
|
|
554
|
-
}).on(
|
|
555
|
-
'contextmenu',
|
|
556
|
-
'.cm-mw-parserfunction-name + .cm-mw-parserfunction-delimiter + .cm-mw-parserfunction',
|
|
557
|
-
function() {
|
|
558
|
-
/** @type {string} */
|
|
559
|
-
const parserFunction = this.previousSibling.previousSibling.textContent.trim().toLowerCase();
|
|
560
|
-
if (invoke.includes(parserFunction)) {
|
|
561
|
-
open(mw.util.getUrl(`Module:${this.textContent}`), '_blank');
|
|
562
|
-
} else if (widget.includes(parserFunction)) {
|
|
563
|
-
open(mw.util.getUrl(`Widget:${this.textContent}`, {action: 'edit'}), '_blank');
|
|
564
|
-
}
|
|
565
|
-
return false;
|
|
566
|
-
},
|
|
567
|
-
);
|
|
568
|
-
} else {
|
|
569
|
-
contextmenuStyle.disabled = true;
|
|
570
|
-
}
|
|
621
|
+
handleContextMenu(cm, mode);
|
|
571
622
|
|
|
572
623
|
$('#Wikiplus-Quickedit-Jump').children('a').attr('href', '#Wikiplus-CodeMirror');
|
|
573
624
|
|
|
@@ -579,18 +630,17 @@
|
|
|
579
630
|
$('#Wikiplus-Quickedit-MinorEdit').click();
|
|
580
631
|
$('#Wikiplus-Quickedit-Submit').triggerHandler('click');
|
|
581
632
|
};
|
|
582
|
-
cm.addKeyMap($.extend(
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
'
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
: {},
|
|
633
|
+
cm.addKeyMap($.extend(
|
|
634
|
+
isPc(CodeMirror)
|
|
635
|
+
? {'Ctrl-S': submit, 'Shift-Ctrl-S': submitMinor}
|
|
636
|
+
: {'Cmd-S': submit, 'Shift-Cmd-S': submitMinor},
|
|
637
|
+
Wikiplus.getSetting('esc_to_exit_quickedit')
|
|
638
|
+
? {
|
|
639
|
+
Esc() {
|
|
640
|
+
$('#Wikiplus-Quickedit-Back').triggerHandler('click');
|
|
641
|
+
},
|
|
642
|
+
}
|
|
643
|
+
: {},
|
|
594
644
|
));
|
|
595
645
|
}
|
|
596
646
|
|
|
@@ -599,12 +649,8 @@
|
|
|
599
649
|
|
|
600
650
|
// 监视 Wikiplus 编辑框
|
|
601
651
|
const observer = new MutationObserver(records => {
|
|
602
|
-
const $editArea = $(
|
|
603
|
-
|
|
604
|
-
* @param {{addedNodes: NodeList}}
|
|
605
|
-
* @returns {Node[]}
|
|
606
|
-
*/
|
|
607
|
-
({addedNodes}) => [...addedNodes],
|
|
652
|
+
const $editArea = $(flat.call(
|
|
653
|
+
records.map(({addedNodes}) => [...addedNodes]),
|
|
608
654
|
)).find('#Wikiplus-Quickedit, #Wikiplus-Setting-Input');
|
|
609
655
|
if ($editArea.length === 0) {
|
|
610
656
|
return;
|
|
@@ -614,7 +660,7 @@
|
|
|
614
660
|
observer.observe(document.body, {childList: true});
|
|
615
661
|
|
|
616
662
|
// 添加样式
|
|
617
|
-
const
|
|
663
|
+
const wphlStyle = document.getElementById('wphl-style') || mw.loader.addStyleTag(
|
|
618
664
|
'#Wikiplus-Quickedit+.CodeMirror,#Wikiplus-Setting-Input+.CodeMirror'
|
|
619
665
|
+ '{border:1px solid #c8ccd1;line-height:1.3;clear:both}'
|
|
620
666
|
+ 'div.Wikiplus-InterBox{font-size:14px;z-index:100}'
|
|
@@ -623,7 +669,8 @@
|
|
|
623
669
|
+ 'div.CodeMirror span.CodeMirror-matchingbracket{box-shadow:0 0 0 2px #9aef98}'
|
|
624
670
|
+ 'div.CodeMirror span.CodeMirror-nonmatchingbracket{box-shadow:0 0 0 2px #eace64}'
|
|
625
671
|
+ '#Wikiplus-highlight-dialog .oo-ui-messageDialog-title{margin-bottom:0.28571429em}'
|
|
626
|
-
+ '#Wikiplus-highlight-dialog .oo-ui-flaggedElement-notice{font-weight:normal;margin:0}'
|
|
672
|
+
+ '#Wikiplus-highlight-dialog .oo-ui-flaggedElement-notice{font-weight:normal;margin:0}'
|
|
673
|
+
+ '.CodeMirror-contextmenu .cm-mw-template-name{cursor:pointer}',
|
|
627
674
|
);
|
|
628
675
|
wphlStyle.id = 'wphl-style';
|
|
629
676
|
|
|
@@ -639,10 +686,7 @@
|
|
|
639
686
|
elem.value = value;
|
|
640
687
|
},
|
|
641
688
|
} = $.valHooks.textarea || {};
|
|
642
|
-
/**
|
|
643
|
-
* @param {HTMLTextAreaElement} elem
|
|
644
|
-
* @returns {boolean}
|
|
645
|
-
*/
|
|
689
|
+
/** @param {HTMLTextAreaElement} elem */
|
|
646
690
|
const isWikiplus = elem => ['Wikiplus-Quickedit', 'Wikiplus-Setting-Input'].includes(elem.id);
|
|
647
691
|
$.valHooks.textarea = {
|
|
648
692
|
get(elem) {
|
|
@@ -659,21 +703,12 @@
|
|
|
659
703
|
|
|
660
704
|
await i18nPromise; // 以下内容依赖I18N
|
|
661
705
|
|
|
662
|
-
/**
|
|
663
|
-
* @typedef {object} OOUI.widget
|
|
664
|
-
* @property {(data: any) => {closing: Promise<{action: string}>}} open
|
|
665
|
-
* @property {() => string|string[]} getValue
|
|
666
|
-
* @property {JQuery<HTMLDivElement>} $element
|
|
667
|
-
* @property {(show: boolean) => OOUI.widget} toggle
|
|
668
|
-
* @property {(windows: OOUI.widget[]) => void} addWindows
|
|
669
|
-
*/
|
|
670
|
-
|
|
671
706
|
// 设置对话框
|
|
672
|
-
let /** @type {OOUI.
|
|
673
|
-
/** @type {OOUI.
|
|
674
|
-
/** @type {OOUI.
|
|
675
|
-
/** @type {OOUI.
|
|
676
|
-
/** @type {OOUI.
|
|
707
|
+
let /** @type {OOUI.MessageDialog} */ dialog,
|
|
708
|
+
/** @type {OOUI.CheckboxMultiselectInputWidget} */ widget,
|
|
709
|
+
/** @type {OOUI.NumberInputWidget} */ indentWidget,
|
|
710
|
+
/** @type {OOUI.FieldLayout} */ field,
|
|
711
|
+
/** @type {OOUI.FieldLayout} */ indentField;
|
|
677
712
|
const toggleIndent = (value = addons) => {
|
|
678
713
|
indentField.toggle(value.includes('indentWithSpace'));
|
|
679
714
|
};
|
|
@@ -681,7 +716,7 @@
|
|
|
681
716
|
minerva: 'page-actions-overflow',
|
|
682
717
|
citizen: 'p-actions',
|
|
683
718
|
};
|
|
684
|
-
const
|
|
719
|
+
const $portlet = $(mw.util.addPortletLink(
|
|
685
720
|
portletContainer[skin] || 'p-cactions', '#', msg('portlet'), 'wphl-settings',
|
|
686
721
|
)).click(async e => {
|
|
687
722
|
e.preventDefault();
|
|
@@ -689,19 +724,17 @@
|
|
|
689
724
|
await mw.loader.using(['oojs-ui-windows', 'oojs-ui.styles.icons-content']);
|
|
690
725
|
// eslint-disable-next-line require-atomic-updates
|
|
691
726
|
dialog = new OO.ui.MessageDialog({id: 'Wikiplus-highlight-dialog'});
|
|
692
|
-
const
|
|
727
|
+
const windowManager = new OO.ui.WindowManager();
|
|
693
728
|
windowManager.$element.appendTo(document.body);
|
|
694
729
|
windowManager.addWindows([dialog]);
|
|
695
730
|
widget = new OO.ui.CheckboxMultiselectInputWidget({
|
|
696
731
|
options: [
|
|
697
|
-
{
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
{data: 'contextmenu', label: msg('addon-contextmenu')},
|
|
704
|
-
{data: 'indentWithSpace', label: msg('addon-indentwithspace')},
|
|
732
|
+
...options.map(({option, addon = option}) => {
|
|
733
|
+
const mainAddon = Array.isArray(addon) ? addon[0] : addon;
|
|
734
|
+
return {data: mainAddon, label: msg(`addon-${mainAddon.toLowerCase()}`)};
|
|
735
|
+
}),
|
|
736
|
+
...['escape', 'contextmenu', 'indentWithSpace', 'otherEditors']
|
|
737
|
+
.map(addon => ({data: addon, label: msg(`addon-${addon.toLowerCase()}`)})),
|
|
705
738
|
],
|
|
706
739
|
value: addons,
|
|
707
740
|
}).on('change', toggleIndent);
|
|
@@ -714,6 +747,9 @@
|
|
|
714
747
|
indentField = new OO.ui.FieldLayout(indentWidget, {label: msg('addon-indent')});
|
|
715
748
|
toggleIndent();
|
|
716
749
|
}
|
|
750
|
+
const wikiplusLoaded = typeof window.Wikiplus === 'object';
|
|
751
|
+
widget.$element.find('.oo-ui-checkboxInputWidget').first().toggleClass('oo-ui-widget-enabled', wikiplusLoaded)
|
|
752
|
+
.children('input').prop('disabled', !wikiplusLoaded);
|
|
717
753
|
dialog.open({
|
|
718
754
|
title: msg('addon-title'),
|
|
719
755
|
message: field.$element.add(indentField.$element).add(
|
|
@@ -746,4 +782,36 @@
|
|
|
746
782
|
$('#wphl-settings').triggerHandler('click');
|
|
747
783
|
});
|
|
748
784
|
}
|
|
785
|
+
|
|
786
|
+
/** @param {CodeMirror.Editor} doc */
|
|
787
|
+
const handleOtherEditors = async doc => {
|
|
788
|
+
if (!addons.includes('otherEditors')) {
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
let mode = doc.getOption('mode');
|
|
792
|
+
mode = mode === 'text/mediawiki' ? 'mediawiki' : mode;
|
|
793
|
+
const addonScript = getAddonScript(CodeMirror, true),
|
|
794
|
+
json = doc.getOption('json');
|
|
795
|
+
await getScript(addonScript);
|
|
796
|
+
for (const {
|
|
797
|
+
option, addon = option, modes, complex = (/** @type {string} */ mod) => !modes || modes.includes(mod),
|
|
798
|
+
} of options.filter(({only}) => !only)) {
|
|
799
|
+
const mainAddon = Array.isArray(addon) ? addon[0] : addon;
|
|
800
|
+
if (doc.getOption(option) === undefined && addons.includes(mainAddon)) {
|
|
801
|
+
doc.setOption(option, complex(mode, json));
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
if (mode !== 'mediawiki' && addons.includes('indentWithSpace')) {
|
|
805
|
+
doc.setOption('indentUnit', indent);
|
|
806
|
+
doc.setOption('indentWithTabs', false);
|
|
807
|
+
} else if (mode === 'mediawiki' && addons.includes('escape')) {
|
|
808
|
+
doc.addKeyMap(isPc(CodeMirror) ? extraKeysPc : extraKeysMac, true);
|
|
809
|
+
}
|
|
810
|
+
handleContextMenu(doc, mode);
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
mw.hook('InPageEdit.quickEdit.codemirror').add(
|
|
814
|
+
/** @param {{cm: CodeMirror.Editor}} */ ({cm: doc}) => handleOtherEditors(doc),
|
|
815
|
+
);
|
|
816
|
+
mw.hook('inspector').add(/** @param {CodeMirror.Editor} doc */ doc => handleOtherEditors(doc));
|
|
749
817
|
})();
|