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