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/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.21',
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
- const getVersion = (str = version) => str.split('.').map(s => Number(s));
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.5',
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.config.values;
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', addon: 'search', download: 'markSelection', only: true,
183
- complex: () => !addons.has('wikiEditor'),
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', addon: 'closeBrackets',
195
- complex: (mode, json) => mode === 'mediawiki' || json
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.split('').map(c => {
229
+ escapeHTML = convert(str => [...str].map(c => {
215
230
  if (c in entity) {
216
231
  return `&${entity[c]};`;
217
232
  }
218
- const code = c.charCodeAt();
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 (!['mediawiki', 'widget'].includes(mode) || !addons.has('contextmenu')) {
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
- /** @param {string} str */
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 (const [i, {type, end, string}] of [...tokens.entries()].reverse()) {
253
- if (i > 0 && tokens[i - 1].type === type) {
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(/\u200e/g, '').replace(/_/g, ' ').trim();
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', 'zh-hans': 'zh-hans', 'zh-cn': 'zh-hans', 'zh-my': 'zh-hans', 'zh-sg': 'zh-hans',
297
- 'zh-hant': 'zh-hant', 'zh-tw': 'zh-hant', 'zh-hk': 'zh-hant', 'zh-mo': 'zh-hant', ka: 'ka',
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
- /** @param {typeof CodeMirror} CM */
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
- * @param {T[]|T} arr
367
- * @param {Set<T>} set
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 (['mediawiki', 'widget'].includes(type) && !CM.modes.mediawiki) {
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 (['widget', 'html'].includes(type)) {
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 (!['mediawiki', 'widget'].includes(type)) {
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
- * @param {{aliases: string[], name: string}[]} words
491
- * @returns {{alias: string, name: string}[]}
492
- */
493
- const getAliases = words => flatten(
494
- words.map(({aliases, name}) => aliases.map(alias => ({alias, name}))),
495
- );
496
- /**
497
- * @param {{alias: string, name: string}[]} aliases
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 ([274, 828].includes(ns) && !page.endsWith('/doc')) {
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} = $.wikiEditor.modules.dialogs;
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
- inputStyle: 'contenteditable',
658
- lineNumbers: !/Android\b/.test(navigator.userAgent),
659
- lineWrapping: true,
660
- mode,
661
- mwConfig,
662
- json,
663
- }, fromEntries(
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
- submit = () => {
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
- [true, 'true'].includes(Wikiplus.getSetting('esc_to_exit_quickedit'))
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('keydown.wphl', '.ui-dialog', function(e) {
746
- if (e.key === 'Escape') {
747
- /** @type {{$textarea: JQuery<HTMLTextAreaElement>}} */
748
- const context = $(this).children('.ui-dialog-content').data('context');
749
- if (context && context.$textarea && context.$textarea.attr('id') === 'Wikiplus-Quickedit') {
750
- e.stopPropagation();
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.getElementById('wphl-style') || mw.loader.addStyleTag(
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 = function(elem) {
779
- return elem.value;
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
- /** @param {HTMLTextAreaElement} elem */
787
- const isWikiplus = elem => ['Wikiplus-Quickedit', 'Wikiplus-Setting-Input'].includes(elem.id);
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 (!dialog) {
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.then(data => {
871
- field.$element.detach();
872
- indentField.$element.detach();
873
- if (typeof data === 'object' && data.action === 'accept') {
874
- const value = widget.getValue();
875
- addons = new Set(value);
876
- indent = Number(indentWidget.getValue());
877
- storage.setObject('Wikiplus-highlight-addons', value);
878
- storage.setObject('Wikiplus-highlight-indent', indent);
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
- /** @param {CodeMirror.Editor} doc */
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, options, addons, i18n, i18nLang, wphlStyle, $portlet, USING_LOCAL, MODE_LIST, ADDON_LIST,
928
- msg, htmlMsg, escapeHTML, handleContextMenu, setI18N, getAddonScript,
929
- updateCachedConfig, getMwConfig, renderEditor, handleOtherEditors,
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
  })();