wikiplus-highlight 2.21.0 → 2.22.2
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/.eslintrc.json +466 -22
- package/README.md +2 -2
- package/dist/main.min.js +1 -1
- package/dist/main.min.js.map +1 -1
- package/fold.js +91 -64
- package/i18n/en.json +1 -1
- package/i18n/ka.json +1 -1
- package/i18n/zh-hans.json +1 -1
- package/i18n/zh-hant.json +1 -1
- package/main.js +229 -159
- package/matchtags.js +66 -38
- package/package.json +5 -1
- package/search.js +25 -15
package/main.js
CHANGED
|
@@ -13,13 +13,14 @@
|
|
|
13
13
|
}
|
|
14
14
|
mw.libs.wphl = {}; // 开始加载
|
|
15
15
|
|
|
16
|
-
const version = '2.
|
|
16
|
+
const version = '2.22.2',
|
|
17
17
|
newAddon = 0;
|
|
18
18
|
|
|
19
19
|
/** @type {typeof mw.storage} */
|
|
20
20
|
const storage = typeof mw.storage === 'object' && typeof mw.storage.getObject === 'function'
|
|
21
21
|
? mw.storage
|
|
22
22
|
: {
|
|
23
|
+
/** @override */
|
|
23
24
|
getObject(key) {
|
|
24
25
|
const json = localStorage.getItem(key);
|
|
25
26
|
if (json === false) {
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
return null;
|
|
32
33
|
}
|
|
33
34
|
},
|
|
35
|
+
/** @override */
|
|
34
36
|
setObject(key, value) {
|
|
35
37
|
try {
|
|
36
38
|
return localStorage.setItem(key, JSON.stringify(value));
|
|
@@ -39,18 +41,20 @@
|
|
|
39
41
|
}
|
|
40
42
|
},
|
|
41
43
|
};
|
|
44
|
+
|
|
42
45
|
/**
|
|
43
46
|
* polyfill for `Object.fromEntries`
|
|
44
47
|
* @type {(entries: Iterable<[string, T]>) => Record<string, T>}
|
|
45
48
|
* @template T
|
|
46
49
|
*/
|
|
47
|
-
const fromEntries = Object.fromEntries || (entries => {
|
|
50
|
+
const fromEntries = Object.fromEntries || (entries => { // eslint-disable-line es-x/no-object-fromentries
|
|
48
51
|
const /** @type {Record<string, T>} */ obj = {};
|
|
49
52
|
for (const [key, value] of entries) {
|
|
50
53
|
obj[key] = value;
|
|
51
54
|
}
|
|
52
55
|
return obj;
|
|
53
56
|
});
|
|
57
|
+
|
|
54
58
|
/**
|
|
55
59
|
* polyfill for `Array.prototype.flat`
|
|
56
60
|
* @type {(arr: HTMLElement[][]) => HTMLElement[]}
|
|
@@ -59,16 +63,21 @@
|
|
|
59
63
|
if (typeof arr.flat === 'function') {
|
|
60
64
|
return arr.flat();
|
|
61
65
|
}
|
|
66
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
62
67
|
return arr.reduce((acc, cur) => acc.concat(cur), []);
|
|
63
68
|
};
|
|
64
69
|
|
|
65
|
-
/**
|
|
66
|
-
|
|
70
|
+
/**
|
|
71
|
+
* 解析版本号
|
|
72
|
+
* @param {string} str 版本号
|
|
73
|
+
*/
|
|
74
|
+
const getVersion = (str = version) => str.split('.').map(Number);
|
|
75
|
+
|
|
67
76
|
/**
|
|
68
77
|
* 比较版本号
|
|
69
|
-
* @param {string} a
|
|
70
|
-
* @param {string} b
|
|
71
|
-
* @returns `a`的版本号是否小于`b`的版本号
|
|
78
|
+
* @param {string} a 版本号1
|
|
79
|
+
* @param {string} b 版本号2
|
|
80
|
+
* @returns {boolean} `a`的版本号是否小于`b`的版本号
|
|
72
81
|
*/
|
|
73
82
|
const cmpVersion = (a, b) => {
|
|
74
83
|
const [a0, a1] = getVersion(a),
|
|
@@ -79,18 +88,19 @@
|
|
|
79
88
|
/**
|
|
80
89
|
* 获取I18N消息
|
|
81
90
|
* @param {string} key 消息键,省略`wphl-`前缀
|
|
82
|
-
* @param {string[]} args
|
|
91
|
+
* @param {string[]} args 替换`$1`等的参数
|
|
83
92
|
*/
|
|
84
93
|
const msg = (key, ...args) => mw.msg(`wphl-${key}`, ...args);
|
|
94
|
+
|
|
85
95
|
/**
|
|
86
96
|
* 生成JQuery的I18N消息
|
|
87
|
-
* @param {string[]} args
|
|
97
|
+
* @param {string[]} args 替换`$1`等的参数
|
|
88
98
|
*/
|
|
89
99
|
const htmlMsg = (...args) => $($.parseHTML(msg(...args)));
|
|
90
100
|
|
|
91
101
|
/**
|
|
92
102
|
* 提示消息
|
|
93
|
-
* @param {string[]} args
|
|
103
|
+
* @param {string[]} args 替换`$1`等的参数
|
|
94
104
|
*/
|
|
95
105
|
const notify = (...args) => () => {
|
|
96
106
|
const $p = $('<p>', {html: msg(...args)});
|
|
@@ -104,10 +114,10 @@
|
|
|
104
114
|
// 路径
|
|
105
115
|
const CDN = '//fastly.jsdelivr.net',
|
|
106
116
|
CM_CDN = 'npm/codemirror@5.65.3',
|
|
107
|
-
MW_CDN = 'gh/bhsd-harry/codemirror-mediawiki@1.1.
|
|
117
|
+
MW_CDN = 'gh/bhsd-harry/codemirror-mediawiki@1.1.6',
|
|
108
118
|
REPO_CDN = `npm/wikiplus-highlight@${majorVersion}`;
|
|
109
119
|
|
|
110
|
-
const {
|
|
120
|
+
const {config: {values: {
|
|
111
121
|
wgPageName: page,
|
|
112
122
|
wgNamespaceNumber: ns,
|
|
113
123
|
wgPageContentModel: contentmodel,
|
|
@@ -115,7 +125,7 @@
|
|
|
115
125
|
wgScriptPath: scriptPath,
|
|
116
126
|
wgUserLanguage: userLang,
|
|
117
127
|
skin,
|
|
118
|
-
} = mw
|
|
128
|
+
}}} = mw;
|
|
119
129
|
|
|
120
130
|
// 和本地缓存有关的常数
|
|
121
131
|
const USING_LOCAL = mw.loader.getState('ext.CodeMirror') !== null,
|
|
@@ -169,30 +179,34 @@
|
|
|
169
179
|
|
|
170
180
|
/**
|
|
171
181
|
* @typedef {object} addon
|
|
172
|
-
* @property {string} option
|
|
173
|
-
* @property {string|string[]} addon
|
|
174
|
-
* @property {string} download
|
|
175
|
-
* @property {(mode: string, json: boolean) => any} complex
|
|
176
|
-
* @property {string[]} modes
|
|
177
|
-
* @property {boolean} only
|
|
182
|
+
* @property {string} option 对应的CodeMirror选项
|
|
183
|
+
* @property {string|string[]} addon 对应的Wikiplus-highlight插件
|
|
184
|
+
* @property {string} download 需要下载的CodeMirror扩展
|
|
185
|
+
* @property {(mode: string, json: boolean) => any} complex 插件加载条件
|
|
186
|
+
* @property {string[]} modes 使用的高亮模式
|
|
187
|
+
* @property {boolean} only 是否仅用于Wikiplus
|
|
178
188
|
*/
|
|
179
189
|
|
|
180
190
|
const /** @type {addon[]} */ options = [
|
|
181
191
|
{
|
|
182
|
-
option: 'styleSelectedText',
|
|
183
|
-
|
|
192
|
+
option: 'styleSelectedText',
|
|
193
|
+
addon: 'search',
|
|
194
|
+
download: 'markSelection',
|
|
195
|
+
only: true,
|
|
196
|
+
/** @implements */ complex: () => !addons.has('wikiEditor'),
|
|
184
197
|
},
|
|
185
198
|
{option: 'styleActiveLine', addon: 'activeLine'},
|
|
186
199
|
{option: 'showTrailingSpace', addon: 'trailingspace'},
|
|
187
200
|
{
|
|
188
201
|
option: 'matchBrackets',
|
|
189
|
-
complex: (mode, json) => mode === 'mediawiki' || json
|
|
190
|
-
? {bracketRegex: /[{}[\]]/}
|
|
202
|
+
/** @implements */ complex: (mode, json) => mode === 'mediawiki' || json
|
|
203
|
+
? {bracketRegex: /[{}[\]]/u}
|
|
191
204
|
: true,
|
|
192
205
|
},
|
|
193
206
|
{
|
|
194
|
-
option: 'autoCloseBrackets',
|
|
195
|
-
|
|
207
|
+
option: 'autoCloseBrackets',
|
|
208
|
+
addon: 'closeBrackets',
|
|
209
|
+
/** @implements */ complex: (mode, json) => mode === 'mediawiki' || json
|
|
196
210
|
? '()[]{}""'
|
|
197
211
|
: true,
|
|
198
212
|
},
|
|
@@ -211,11 +225,11 @@
|
|
|
211
225
|
convert = func => doc => {
|
|
212
226
|
doc.replaceSelection(doc.getSelection().split('\n').map(func).join('\n'), 'around');
|
|
213
227
|
},
|
|
214
|
-
escapeHTML = convert(str => str.
|
|
228
|
+
escapeHTML = convert(str => [...str].map(c => {
|
|
215
229
|
if (c in entity) {
|
|
216
230
|
return `&${entity[c]};`;
|
|
217
231
|
}
|
|
218
|
-
const code = c.
|
|
232
|
+
const code = c.codePointAt();
|
|
219
233
|
return code < 256 ? `&#${code};` : `&#x${code.toString(16)};`;
|
|
220
234
|
}).join('')),
|
|
221
235
|
/** @type {function(typeof CodeMirror): boolean} */ isPc = ({keyMap}) => keyMap.default === keyMap.pcDefault,
|
|
@@ -224,18 +238,22 @@
|
|
|
224
238
|
|
|
225
239
|
/**
|
|
226
240
|
* contextMenu插件
|
|
227
|
-
* @param {CodeMirror.Editor} doc
|
|
228
|
-
* @param {string} mode
|
|
241
|
+
* @param {CodeMirror.Editor} doc CodeMirror编辑区
|
|
242
|
+
* @param {string} mode 高亮模式
|
|
229
243
|
*/
|
|
230
244
|
const handleContextMenu = (doc, mode) => {
|
|
231
|
-
if (
|
|
245
|
+
if (mode !== 'mediawiki' && mode !== 'widget' || !addons.has('contextmenu')) {
|
|
232
246
|
return;
|
|
233
247
|
}
|
|
234
248
|
const $wrapper = $(doc.getWrapperElement()).addClass('CodeMirror-contextmenu'),
|
|
235
249
|
{functionSynonyms: [synonyms]} = mw.config.get('extCodeMirrorConfig') || {
|
|
236
250
|
functionSynonyms: [{invoke: 'invoke', 调用: 'invoke', widget: 'widget', 小工具: 'widget'}],
|
|
237
251
|
};
|
|
238
|
-
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 生成别名映射表
|
|
255
|
+
* @param {string} str 别名
|
|
256
|
+
*/
|
|
239
257
|
const getSysnonyms = str => Object.keys(synonyms).filter(key => synonyms[key] === str)
|
|
240
258
|
.map(key => key.startsWith('#') ? key : `#${key}`);
|
|
241
259
|
const invoke = getSysnonyms('invoke'),
|
|
@@ -245,20 +263,21 @@
|
|
|
245
263
|
const pos = doc.coordsChar({left: pageX, top: pageY}),
|
|
246
264
|
{line, ch} = pos,
|
|
247
265
|
curType = doc.getTokenTypeAt(pos);
|
|
248
|
-
if (!/\bmw-(?:template-name|parserfunction)\b
|
|
249
|
-
return;
|
|
266
|
+
if (!/\bmw-(?:template-name|parserfunction)\b/u.test(curType)) {
|
|
267
|
+
return undefined;
|
|
250
268
|
}
|
|
251
269
|
const tokens = doc.getLineTokens(line);
|
|
252
|
-
for (
|
|
253
|
-
|
|
270
|
+
for (let i = tokens.length - 1; i > 0; i--) {
|
|
271
|
+
const {type, end, string} = tokens[i];
|
|
272
|
+
if (tokens[i - 1].type === type) {
|
|
254
273
|
tokens[i - 1].end = end;
|
|
255
274
|
tokens[i - 1].string += string;
|
|
256
275
|
tokens.splice(i, 1);
|
|
257
276
|
}
|
|
258
277
|
}
|
|
259
278
|
const index = tokens.findIndex(({start, end}) => start < ch && end >= ch),
|
|
260
|
-
text = tokens[index].string.replace(/\
|
|
261
|
-
if (/\bmw-template-name\b
|
|
279
|
+
text = tokens[index].string.replace(/\u200E/gu, '').replace(/_/gu, ' ').trim();
|
|
280
|
+
if (/\bmw-template-name\b/u.test(curType)) {
|
|
262
281
|
const title = new mw.Title(text);
|
|
263
282
|
if (title.namespace !== 0 || text.startsWith(':')) {
|
|
264
283
|
open(title.getUrl(), '_blank');
|
|
@@ -266,10 +285,10 @@
|
|
|
266
285
|
open(mw.util.getUrl(`Template:${text}`), '_blank');
|
|
267
286
|
}
|
|
268
287
|
return false;
|
|
269
|
-
} else if (index < 2 || !/\bmw-parserfunction-delimiter\b
|
|
270
|
-
|| !/\bmw-parserfunction-name\b
|
|
288
|
+
} else if (index < 2 || !/\bmw-parserfunction-delimiter\b/u.test(tokens[index - 1].type)
|
|
289
|
+
|| !/\bmw-parserfunction-name\b/u.test(tokens[index - 2].type)
|
|
271
290
|
) {
|
|
272
|
-
return;
|
|
291
|
+
return undefined;
|
|
273
292
|
}
|
|
274
293
|
const parserFunction = tokens[index - 2].string.trim().toLowerCase();
|
|
275
294
|
if (invoke.includes(parserFunction)) {
|
|
@@ -277,7 +296,7 @@
|
|
|
277
296
|
} else if (widget.includes(parserFunction)) {
|
|
278
297
|
open(mw.util.getUrl(`Widget:${text}`, {action: 'edit'}), '_blank');
|
|
279
298
|
} else {
|
|
280
|
-
return;
|
|
299
|
+
return undefined;
|
|
281
300
|
}
|
|
282
301
|
return false;
|
|
283
302
|
});
|
|
@@ -293,8 +312,16 @@
|
|
|
293
312
|
}
|
|
294
313
|
|
|
295
314
|
const /** @type {Record<string, string>} */ i18nLanguages = {
|
|
296
|
-
zh: 'zh-hans',
|
|
297
|
-
'zh-
|
|
315
|
+
zh: 'zh-hans',
|
|
316
|
+
'zh-hans': 'zh-hans',
|
|
317
|
+
'zh-cn': 'zh-hans',
|
|
318
|
+
'zh-my': 'zh-hans',
|
|
319
|
+
'zh-sg': 'zh-hans',
|
|
320
|
+
'zh-hant': 'zh-hant',
|
|
321
|
+
'zh-tw': 'zh-hant',
|
|
322
|
+
'zh-hk': 'zh-hant',
|
|
323
|
+
'zh-mo': 'zh-hant',
|
|
324
|
+
ka: 'ka',
|
|
298
325
|
},
|
|
299
326
|
i18nLang = i18nLanguages[userLang] || 'en',
|
|
300
327
|
I18N_CDN = `${CDN}/${REPO_CDN}/i18n/${i18nLang}.json`,
|
|
@@ -319,14 +346,15 @@
|
|
|
319
346
|
|
|
320
347
|
/**
|
|
321
348
|
* 下载MW扩展脚本
|
|
322
|
-
* @param {string[]} exts
|
|
349
|
+
* @param {string[]} exts CodeMirror扩展模块
|
|
323
350
|
*/
|
|
324
|
-
const getInternalScript = exts => exts.length ? mw.loader.using(exts) : Promise.resolve();
|
|
351
|
+
const getInternalScript = exts => exts.length > 0 ? mw.loader.using(exts) : Promise.resolve();
|
|
352
|
+
|
|
325
353
|
/**
|
|
326
354
|
* 下载外部脚本
|
|
327
|
-
* @param {string[]} urls
|
|
355
|
+
* @param {string[]} urls CodeMirror脚本网址
|
|
328
356
|
*/
|
|
329
|
-
const getExternalScript = urls => urls.length
|
|
357
|
+
const getExternalScript = urls => urls.length > 0
|
|
330
358
|
? $.ajax(`${CDN}/${urls.length > 1 ? 'combine/' : ''}${urls.join()}`, {dataType: 'script', cache: true})
|
|
331
359
|
: Promise.resolve();
|
|
332
360
|
|
|
@@ -351,7 +379,11 @@
|
|
|
351
379
|
// 以下进入CodeMirror相关内容
|
|
352
380
|
let /** @type {CodeMirror.EditorFromTextArea} */ cm;
|
|
353
381
|
|
|
354
|
-
/**
|
|
382
|
+
/**
|
|
383
|
+
* 生成需要的插件列表
|
|
384
|
+
* @param {typeof CodeMirror} CM CodeMirror
|
|
385
|
+
* @param {boolean} other 是否用于Wikiplus以外的textarea
|
|
386
|
+
*/
|
|
355
387
|
const getAddonScript = (CM, other = false) => {
|
|
356
388
|
const /** @type {string[]} */ addonScript = [];
|
|
357
389
|
for (const {option, addon = option, download = Array.isArray(addon) ? option : addon, only} of options) {
|
|
@@ -363,8 +395,9 @@
|
|
|
363
395
|
};
|
|
364
396
|
|
|
365
397
|
/**
|
|
366
|
-
*
|
|
367
|
-
* @param {
|
|
398
|
+
* 交集
|
|
399
|
+
* @param {T[]|T} arr 集合1(可重)
|
|
400
|
+
* @param {Set<T>} set 集合2
|
|
368
401
|
* @template T
|
|
369
402
|
*/
|
|
370
403
|
const intersect = (arr, set) => Array.isArray(arr)
|
|
@@ -373,7 +406,7 @@
|
|
|
373
406
|
|
|
374
407
|
/**
|
|
375
408
|
* 根据文本的高亮模式加载依赖项
|
|
376
|
-
* @param {string} type
|
|
409
|
+
* @param {string} type 高亮模式
|
|
377
410
|
*/
|
|
378
411
|
const initMode = type => {
|
|
379
412
|
let /** @type {string[]} */ scripts = [];
|
|
@@ -398,12 +431,12 @@
|
|
|
398
431
|
// NamespaceHTML扩展自由度过高,所以这里一律当作允许<html>标签
|
|
399
432
|
type = 'html'; // eslint-disable-line no-param-reassign
|
|
400
433
|
}
|
|
401
|
-
if (
|
|
434
|
+
if ((type === 'mediawiki' || type === 'widget') && !CM.modes.mediawiki) {
|
|
402
435
|
// 总是外部样式表和外部脚本
|
|
403
436
|
mw.loader.load(`${CDN}/${MW_CDN}/mediawiki.min.css`, 'text/css');
|
|
404
437
|
scripts.push(`${MW_CDN}/mediawiki.min.js`);
|
|
405
438
|
}
|
|
406
|
-
if (
|
|
439
|
+
if (type === 'widget' || type === 'html') {
|
|
407
440
|
for (const lang of ['css', 'javascript', 'mediawiki', 'htmlmixed', 'xml']) {
|
|
408
441
|
if (!CM.modes[lang]) {
|
|
409
442
|
scripts = scripts.concat(MODE_LIST[lang]);
|
|
@@ -438,21 +471,39 @@
|
|
|
438
471
|
|
|
439
472
|
/**
|
|
440
473
|
* 更新缓存的设置数据
|
|
441
|
-
* @param {mwConfig} config
|
|
474
|
+
* @param {mwConfig} config wikitext设置
|
|
442
475
|
*/
|
|
443
476
|
const updateCachedConfig = config => {
|
|
444
477
|
ALL_SETTINGS_CACHE[SITE_ID] = {config, time: Date.now()};
|
|
445
478
|
storage.setObject('InPageEditMwConfig', ALL_SETTINGS_CACHE);
|
|
446
479
|
};
|
|
447
480
|
|
|
481
|
+
/**
|
|
482
|
+
* 展开别名列表
|
|
483
|
+
* @param {{aliases: string[], name: string}[]} words 原名
|
|
484
|
+
* @returns {{alias: string, name: string}[]}
|
|
485
|
+
*/
|
|
486
|
+
const getAliases = words => flatten(
|
|
487
|
+
words.map(({aliases, name}) => aliases.map(alias => ({alias, name}))),
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* 将别名信息转换为CodeMirror接受的设置
|
|
492
|
+
* @param {{alias: string, name: string}[]} aliases 别名
|
|
493
|
+
* @returns {Record<string, string>}
|
|
494
|
+
*/
|
|
495
|
+
const getConfig = aliases => fromEntries(
|
|
496
|
+
aliases.map(({alias, name}) => [alias.replace(/:$/u, ''), name]),
|
|
497
|
+
);
|
|
498
|
+
|
|
448
499
|
/**
|
|
449
500
|
* 加载CodeMirror的mediawiki模块需要的设置数据
|
|
450
|
-
* @param {string} type
|
|
501
|
+
* @param {string} type 高亮模式
|
|
451
502
|
* @param {Promise<void>} initModePromise 使用本地CodeMirror扩展时大部分数据来自ext.CodeMirror.data模块
|
|
452
503
|
*/
|
|
453
504
|
const getMwConfig = async (type, initModePromise) => {
|
|
454
|
-
if (
|
|
455
|
-
return;
|
|
505
|
+
if (type !== 'mediawiki' && type !== 'widget') {
|
|
506
|
+
return undefined;
|
|
456
507
|
}
|
|
457
508
|
|
|
458
509
|
if (USING_LOCAL && EXPIRED) { // 只在localStorage过期时才会重新加载ext.CodeMirror.data
|
|
@@ -486,22 +537,15 @@
|
|
|
486
537
|
});
|
|
487
538
|
const otherMagicwords = ['msg', 'raw', 'msgnw', 'subst', 'safesubst'];
|
|
488
539
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
* @returns {Record<string, string>}
|
|
499
|
-
*/
|
|
500
|
-
const getConfig = aliases => fromEntries(
|
|
501
|
-
aliases.map(({alias, name}) => [alias.replace(/:$/, ''), name]),
|
|
502
|
-
);
|
|
503
|
-
|
|
504
|
-
if (!config) { // 情形4:`config === null`
|
|
540
|
+
if (config) { // 情形2或3
|
|
541
|
+
const {functionSynonyms: [insensitive]} = config;
|
|
542
|
+
if (!insensitive.subst) {
|
|
543
|
+
const aliases = getAliases(magicwords.filter(({name}) => otherMagicwords.includes(name)));
|
|
544
|
+
for (const {alias, name} of aliases) {
|
|
545
|
+
insensitive[alias.replace(/:$/u, '')] = name;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
} else { // 情形4:`config === null`
|
|
505
549
|
config = {
|
|
506
550
|
tagModes: {
|
|
507
551
|
pre: 'mw-tag-pre',
|
|
@@ -515,7 +559,7 @@
|
|
|
515
559
|
};
|
|
516
560
|
const realMagicwords = new Set([...functionhooks, ...variables, ...otherMagicwords]),
|
|
517
561
|
allMagicwords = magicwords.filter(
|
|
518
|
-
({name, aliases}) => aliases.some(alias => /^__.+__
|
|
562
|
+
({name, aliases}) => aliases.some(alias => /^__.+__$/u.test(alias)) || realMagicwords.has(name),
|
|
519
563
|
),
|
|
520
564
|
sensitive = getAliases(
|
|
521
565
|
allMagicwords.filter(word => word['case-sensitive']),
|
|
@@ -524,22 +568,13 @@
|
|
|
524
568
|
allMagicwords.filter(word => !word['case-sensitive']),
|
|
525
569
|
).map(({alias, name}) => ({alias: alias.toLowerCase(), name}));
|
|
526
570
|
config.doubleUnderscore = [
|
|
527
|
-
getConfig(insensitive.filter(({alias}) => /^__.+__
|
|
528
|
-
getConfig(sensitive.filter(({alias}) => /^__.+__
|
|
571
|
+
getConfig(insensitive.filter(({alias}) => /^__.+__$/u.test(alias))),
|
|
572
|
+
getConfig(sensitive.filter(({alias}) => /^__.+__$/u.test(alias))),
|
|
529
573
|
];
|
|
530
574
|
config.functionSynonyms = [
|
|
531
|
-
getConfig(insensitive.filter(({alias}) => !/^__.+__
|
|
532
|
-
getConfig(sensitive.filter(({alias}) => !/^__.+__
|
|
575
|
+
getConfig(insensitive.filter(({alias}) => !/^__.+__|^#$/u.test(alias))),
|
|
576
|
+
getConfig(sensitive.filter(({alias}) => !/^__.+__|^#$/u.test(alias))),
|
|
533
577
|
];
|
|
534
|
-
} else { // 情形2或3
|
|
535
|
-
const {functionSynonyms: [insensitive]} = config;
|
|
536
|
-
if (!insensitive.subst) {
|
|
537
|
-
getAliases(
|
|
538
|
-
magicwords.filter(({name}) => otherMagicwords.includes(name)),
|
|
539
|
-
).forEach(({alias, name}) => {
|
|
540
|
-
insensitive[alias.replace(/:$/, '')] = name;
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
578
|
}
|
|
544
579
|
config.redirect = magicwords.find(({name}) => name === 'redirect').aliases;
|
|
545
580
|
config.img = getConfig(
|
|
@@ -552,7 +587,7 @@
|
|
|
552
587
|
|
|
553
588
|
/** 检查页面语言类型 */
|
|
554
589
|
const getPageMode = async () => {
|
|
555
|
-
if (
|
|
590
|
+
if ((ns === 274 || ns === 828) && !page.endsWith('/doc')) {
|
|
556
591
|
const pageMode = ns === 274 ? 'Widget' : 'Lua';
|
|
557
592
|
await mw.loader.using(['oojs-ui-windows', 'oojs-ui.styles.icons-content']);
|
|
558
593
|
const bool = await OO.ui.confirm(msg('contentmodel'), {
|
|
@@ -570,17 +605,17 @@
|
|
|
570
605
|
* See jQuery.textSelection.js for method documentation
|
|
571
606
|
*/
|
|
572
607
|
const cmTextSelection = {
|
|
573
|
-
getContents() {
|
|
608
|
+
/** @override */ getContents() {
|
|
574
609
|
return cm.getValue();
|
|
575
610
|
},
|
|
576
|
-
setContents(content) {
|
|
611
|
+
/** @override */ setContents(content) {
|
|
577
612
|
cm.setValue(content);
|
|
578
613
|
return this;
|
|
579
614
|
},
|
|
580
|
-
getSelection() {
|
|
615
|
+
/** @override */ getSelection() {
|
|
581
616
|
return cm.getSelection();
|
|
582
617
|
},
|
|
583
|
-
setSelection(option) {
|
|
618
|
+
/** @override */ setSelection(option) {
|
|
584
619
|
cm.setSelection(
|
|
585
620
|
cm.posFromIndex(option.start),
|
|
586
621
|
'end' in option ? cm.posFromIndex(option.end) : undefined,
|
|
@@ -588,11 +623,11 @@
|
|
|
588
623
|
cm.focus();
|
|
589
624
|
return this;
|
|
590
625
|
},
|
|
591
|
-
replaceSelection(value) {
|
|
626
|
+
/** @override */ replaceSelection(value) {
|
|
592
627
|
cm.replaceSelection(value);
|
|
593
628
|
return this;
|
|
594
629
|
},
|
|
595
|
-
getCaretPosition(option) {
|
|
630
|
+
/** @override */ getCaretPosition(option) {
|
|
596
631
|
const caretPos = cm.indexFromPos(cm.getCursor('from')),
|
|
597
632
|
endPos = cm.indexFromPos(cm.getCursor('to'));
|
|
598
633
|
if (option.startAndEnd) {
|
|
@@ -600,7 +635,7 @@
|
|
|
600
635
|
}
|
|
601
636
|
return caretPos;
|
|
602
637
|
},
|
|
603
|
-
scrollToCaretPosition() {
|
|
638
|
+
/** @override */ scrollToCaretPosition() {
|
|
604
639
|
cm.scrollIntoView();
|
|
605
640
|
return this;
|
|
606
641
|
},
|
|
@@ -624,7 +659,7 @@
|
|
|
624
659
|
if (typeof mw.addWikiEditor === 'function') {
|
|
625
660
|
mw.addWikiEditor($target);
|
|
626
661
|
} else {
|
|
627
|
-
const {config} =
|
|
662
|
+
const {wikiEditor: {modules: {dialogs: {config}}}} = $;
|
|
628
663
|
$target.wikiEditor('addModule', {
|
|
629
664
|
...$.wikiEditor.modules.toolbar.config.getDefaultConfig(),
|
|
630
665
|
...config.getDefaultConfig(),
|
|
@@ -653,29 +688,31 @@
|
|
|
653
688
|
}
|
|
654
689
|
|
|
655
690
|
const json = setting || contentmodel === 'json';
|
|
656
|
-
cm = CodeMirror.fromTextArea($target[0], $.extend(
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
options.map(({option, addon = option, modes, complex = mod => !modes || modes.includes(mod)}) => {
|
|
665
|
-
const mainAddon = Array.isArray(addon) ? addon[0] : addon;
|
|
666
|
-
return [option, addons.has(mainAddon) && complex(mode, json)];
|
|
667
|
-
}),
|
|
668
|
-
), mode === 'mediawiki'
|
|
669
|
-
? {
|
|
670
|
-
extraKeys: addons.has('escape') && (isPc(CodeMirror) ? extraKeysPc : extraKeysMac),
|
|
671
|
-
}
|
|
672
|
-
: {
|
|
673
|
-
indentUnit: addons.has('indentWithSpace') ? indent : defaultIndent,
|
|
674
|
-
indentWithTabs: !addons.has('indentWithSpace'),
|
|
691
|
+
cm = CodeMirror.fromTextArea($target[0], $.extend(
|
|
692
|
+
{
|
|
693
|
+
inputStyle: 'contenteditable',
|
|
694
|
+
lineNumbers: !/Android\b/u.test(navigator.userAgent),
|
|
695
|
+
lineWrapping: true,
|
|
696
|
+
mode,
|
|
697
|
+
mwConfig,
|
|
698
|
+
json,
|
|
675
699
|
},
|
|
700
|
+
fromEntries(
|
|
701
|
+
options.map(({option, addon = option, modes, complex = mod => !modes || modes.includes(mod)}) => {
|
|
702
|
+
const mainAddon = Array.isArray(addon) ? addon[0] : addon;
|
|
703
|
+
return [option, addons.has(mainAddon) && complex(mode, json)];
|
|
704
|
+
}),
|
|
705
|
+
),
|
|
706
|
+
mode === 'mediawiki'
|
|
707
|
+
? {
|
|
708
|
+
extraKeys: addons.has('escape') && (isPc(CodeMirror) ? extraKeysPc : extraKeysMac),
|
|
709
|
+
}
|
|
710
|
+
: {
|
|
711
|
+
indentUnit: addons.has('indentWithSpace') ? indent : defaultIndent,
|
|
712
|
+
indentWithTabs: !addons.has('indentWithSpace'),
|
|
713
|
+
},
|
|
676
714
|
));
|
|
677
715
|
cm.setSize(null, height);
|
|
678
|
-
cm.refresh();
|
|
679
716
|
cm.getWrapperElement().id = 'Wikiplus-CodeMirror';
|
|
680
717
|
|
|
681
718
|
if ($.fn.textSelection) {
|
|
@@ -685,7 +722,7 @@
|
|
|
685
722
|
if (addons.has('wikiEditor')) {
|
|
686
723
|
const context = $target.data('wikiEditorContext'),
|
|
687
724
|
{keyMap} = CodeMirror,
|
|
688
|
-
callback = () => {
|
|
725
|
+
callback = /** 替代CodeMirror的Ctrl/Cmd-F快捷键 */ () => {
|
|
689
726
|
$.wikiEditor.modules.dialogs.api.openDialog(context, 'search-and-replace');
|
|
690
727
|
};
|
|
691
728
|
cm.addKeyMap(keyMap.default === keyMap.pcDefault ? {'Ctrl-F': callback} : {'Cmd-F': callback});
|
|
@@ -699,15 +736,16 @@
|
|
|
699
736
|
const /** @type {Wikiplus} */ Wikiplus = typeof window.Wikiplus === 'object'
|
|
700
737
|
? window.Wikiplus
|
|
701
738
|
: {
|
|
702
|
-
getSetting(key) {
|
|
739
|
+
/** @override */ getSetting(key) {
|
|
703
740
|
const settings = storage.getObject('Wikiplus_Settings');
|
|
704
741
|
return settings && settings[key];
|
|
705
742
|
},
|
|
706
743
|
},
|
|
707
|
-
|
|
744
|
+
escToExitQuickEdit = Wikiplus.getSetting('esc_to_exit_quickedit'),
|
|
745
|
+
submit = /** 提交编辑 */ () => {
|
|
708
746
|
$('#Wikiplus-Quickedit-Submit').triggerHandler('click');
|
|
709
747
|
},
|
|
710
|
-
submitMinor = () => {
|
|
748
|
+
submitMinor = /** 提交小编辑 */ () => {
|
|
711
749
|
$('#Wikiplus-Quickedit-MinorEdit').click();
|
|
712
750
|
$('#Wikiplus-Quickedit-Submit').triggerHandler('click');
|
|
713
751
|
};
|
|
@@ -715,9 +753,9 @@
|
|
|
715
753
|
isPc(CodeMirror)
|
|
716
754
|
? {'Ctrl-S': submit, 'Shift-Ctrl-S': submitMinor}
|
|
717
755
|
: {'Cmd-S': submit, 'Shift-Cmd-S': submitMinor},
|
|
718
|
-
|
|
756
|
+
escToExitQuickEdit === true || escToExitQuickEdit === 'true'
|
|
719
757
|
? {
|
|
720
|
-
Esc() {
|
|
758
|
+
/** 按下Esc键退出编辑 */ Esc() {
|
|
721
759
|
$('#Wikiplus-Quickedit-Back').triggerHandler('click');
|
|
722
760
|
},
|
|
723
761
|
}
|
|
@@ -725,6 +763,7 @@
|
|
|
725
763
|
));
|
|
726
764
|
}
|
|
727
765
|
|
|
766
|
+
cm.refresh();
|
|
728
767
|
mw.hook('wiki-codemirror').fire(cm);
|
|
729
768
|
};
|
|
730
769
|
|
|
@@ -742,18 +781,23 @@
|
|
|
742
781
|
});
|
|
743
782
|
observer.observe(body, {childList: true});
|
|
744
783
|
|
|
745
|
-
$(body).on(
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
784
|
+
$(body).on(
|
|
785
|
+
'keydown.wphl',
|
|
786
|
+
'.ui-dialog',
|
|
787
|
+
/** @this {HTMLBodyElement} */
|
|
788
|
+
function(e) {
|
|
789
|
+
if (e.key === 'Escape') {
|
|
790
|
+
/** @type {{$textarea: JQuery<HTMLTextAreaElement>}} */
|
|
791
|
+
const context = $(this).children('.ui-dialog-content').data('context');
|
|
792
|
+
if (context && context.$textarea && context.$textarea.attr('id') === 'Wikiplus-Quickedit') {
|
|
793
|
+
e.stopPropagation();
|
|
794
|
+
}
|
|
751
795
|
}
|
|
752
|
-
}
|
|
753
|
-
|
|
796
|
+
},
|
|
797
|
+
);
|
|
754
798
|
|
|
755
799
|
// 添加样式
|
|
756
|
-
const wphlStyle = document.
|
|
800
|
+
const wphlStyle = document.querySelector('#wphl-style') || mw.loader.addStyleTag(
|
|
757
801
|
'#Wikiplus-CodeMirror{border:1px solid #c8ccd1;line-height:1.3;clear:both;'
|
|
758
802
|
+ '-moz-user-select:auto;-webkit-user-select:auto;user-select:auto}' // fix mobile select
|
|
759
803
|
+ '#Wikiplus-CodeMirror .CodeMirror-gutter-wrapper{'
|
|
@@ -775,21 +819,22 @@
|
|
|
775
819
|
* @type {{get: (elem: HTMLTextAreaElement) => string, set: (elem: HTMLTextAreaElement, value: string) => void}}
|
|
776
820
|
*/
|
|
777
821
|
const {
|
|
778
|
-
get =
|
|
779
|
-
|
|
780
|
-
},
|
|
781
|
-
set = function(elem, value) {
|
|
822
|
+
get = elem => elem.value,
|
|
823
|
+
set = (elem, value) => {
|
|
782
824
|
elem.value = value;
|
|
783
825
|
},
|
|
784
826
|
} = $.valHooks.textarea || {};
|
|
785
827
|
|
|
786
|
-
/**
|
|
787
|
-
|
|
828
|
+
/**
|
|
829
|
+
* 是否是Wikiplus编辑区
|
|
830
|
+
* @param {HTMLTextAreaElement} elem textarea元素
|
|
831
|
+
*/
|
|
832
|
+
const isWikiplus = elem => elem.id === 'Wikiplus-Quickedit' || elem.id === 'Wikiplus-Setting-Input';
|
|
788
833
|
$.valHooks.textarea = {
|
|
789
|
-
get(elem) {
|
|
834
|
+
/** @override */ get(elem) {
|
|
790
835
|
return isWikiplus(elem) && cm ? cm.getValue() : get(elem);
|
|
791
836
|
},
|
|
792
|
-
set(elem, value) {
|
|
837
|
+
/** @override */ set(elem, value) {
|
|
793
838
|
if (isWikiplus(elem) && cm) {
|
|
794
839
|
cm.setValue(value);
|
|
795
840
|
} else {
|
|
@@ -808,6 +853,10 @@
|
|
|
808
853
|
/** @type {OOUI.NumberInputWidget} */ indentWidget,
|
|
809
854
|
/** @type {OOUI.FieldLayout} */ field,
|
|
810
855
|
/** @type {OOUI.FieldLayout} */ indentField;
|
|
856
|
+
/**
|
|
857
|
+
* 显示/隐藏缩进大小选项
|
|
858
|
+
* @param {string[]} value 加载的插件
|
|
859
|
+
*/
|
|
811
860
|
const toggleIndent = (value = [...addons]) => {
|
|
812
861
|
indentField.toggle(value.includes('indentWithSpace'));
|
|
813
862
|
};
|
|
@@ -820,7 +869,10 @@
|
|
|
820
869
|
portletContainer[skin] || 'p-cactions', '#', msg('portlet'), 'wphl-settings',
|
|
821
870
|
)).click(async e => {
|
|
822
871
|
e.preventDefault();
|
|
823
|
-
if (
|
|
872
|
+
if (dialog) {
|
|
873
|
+
widget.setValue([...addons]);
|
|
874
|
+
indentWidget.setValue(indent);
|
|
875
|
+
} else {
|
|
824
876
|
await mw.loader.using(['oojs-ui-windows', 'oojs-ui.styles.icons-content']);
|
|
825
877
|
// eslint-disable-next-line require-atomic-updates
|
|
826
878
|
dialog = new OO.ui.MessageDialog({id: 'Wikiplus-highlight-dialog'});
|
|
@@ -850,14 +902,11 @@
|
|
|
850
902
|
indentField = new OO.ui.FieldLayout(indentWidget, {label: msg('addon-indent')});
|
|
851
903
|
toggleIndent();
|
|
852
904
|
Object.assign(mw.libs.wphl, {widget, indentWidget});
|
|
853
|
-
} else {
|
|
854
|
-
widget.setValue([...addons]);
|
|
855
|
-
indentWidget.setValue(indent);
|
|
856
905
|
}
|
|
857
906
|
const wikiplusLoaded = typeof window.Wikiplus === 'object' || typeof window.Pages === 'object';
|
|
858
907
|
searchWidget.setDisabled(!wikiplusLoaded);
|
|
859
908
|
wikiEditorWidget.setDisabled(!wikiplusLoaded || !mw.loader.getState('ext.wikiEditor'));
|
|
860
|
-
dialog.open({
|
|
909
|
+
const data = await dialog.open({
|
|
861
910
|
title: msg('addon-title'),
|
|
862
911
|
message: field.$element.add(indentField.$element).add(
|
|
863
912
|
$('<p>', {html: msg('feedback')}),
|
|
@@ -867,17 +916,18 @@
|
|
|
867
916
|
{action: 'accept', label: mw.msg('ooui-dialog-message-accept'), flags: 'progressive'},
|
|
868
917
|
],
|
|
869
918
|
size: i18nLang === 'en' || skin === 'minerva' ? 'medium' : 'small',
|
|
870
|
-
}).closing
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
919
|
+
}).closing;
|
|
920
|
+
field.$element.detach();
|
|
921
|
+
indentField.$element.detach();
|
|
922
|
+
if (typeof data === 'object' && data.action === 'accept') {
|
|
923
|
+
/* eslint-disable require-atomic-updates */
|
|
924
|
+
const value = widget.getValue();
|
|
925
|
+
addons = new Set(value);
|
|
926
|
+
indent = Number(indentWidget.getValue());
|
|
927
|
+
storage.setObject('Wikiplus-highlight-addons', value);
|
|
928
|
+
storage.setObject('Wikiplus-highlight-indent', indent);
|
|
929
|
+
/* eslint-enable require-atomic-updates */
|
|
930
|
+
}
|
|
881
931
|
});
|
|
882
932
|
if (skin === 'minerva') {
|
|
883
933
|
$portlet.find('.mw-ui-icon').addClass('mw-ui-icon-minerva-settings');
|
|
@@ -891,7 +941,10 @@
|
|
|
891
941
|
});
|
|
892
942
|
}
|
|
893
943
|
|
|
894
|
-
/**
|
|
944
|
+
/**
|
|
945
|
+
* 处理非Wikiplus编辑器
|
|
946
|
+
* @param {CodeMirror.Editor} doc CodeMirror编辑区
|
|
947
|
+
*/
|
|
895
948
|
const handleOtherEditors = async doc => {
|
|
896
949
|
if (!addons.has('otherEditors')) {
|
|
897
950
|
return;
|
|
@@ -924,8 +977,25 @@
|
|
|
924
977
|
mw.hook('inspector').add(/** @param {CodeMirror.Editor} doc */ doc => handleOtherEditors(doc));
|
|
925
978
|
|
|
926
979
|
mw.libs.wphl = {
|
|
927
|
-
version,
|
|
928
|
-
|
|
929
|
-
|
|
980
|
+
version,
|
|
981
|
+
options,
|
|
982
|
+
addons,
|
|
983
|
+
i18n,
|
|
984
|
+
i18nLang,
|
|
985
|
+
wphlStyle,
|
|
986
|
+
$portlet,
|
|
987
|
+
USING_LOCAL,
|
|
988
|
+
MODE_LIST,
|
|
989
|
+
ADDON_LIST,
|
|
990
|
+
msg,
|
|
991
|
+
htmlMsg,
|
|
992
|
+
escapeHTML,
|
|
993
|
+
handleContextMenu,
|
|
994
|
+
setI18N,
|
|
995
|
+
getAddonScript,
|
|
996
|
+
updateCachedConfig,
|
|
997
|
+
getMwConfig,
|
|
998
|
+
renderEditor,
|
|
999
|
+
handleOtherEditors,
|
|
930
1000
|
}; // 加载完毕
|
|
931
1001
|
})();
|