wikiplus-highlight 2.10.0 → 2.12.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/main.js CHANGED
@@ -8,13 +8,10 @@
8
8
  (async () => {
9
9
  'use strict';
10
10
 
11
- const version = '2.10',
11
+ const version = '2.12.1',
12
12
  newAddon = 1;
13
13
 
14
- /**
15
- * polyfill for mw.storage
16
- * @type {{getObject: (key: string) => ?any, setObject: (key: string, value: any) => boolean}}
17
- */
14
+ /** @type {mw.storage} */
18
15
  const storage = typeof mw.storage === 'object' && typeof mw.storage.getObject === 'function'
19
16
  ? mw.storage
20
17
  : {
@@ -41,15 +38,22 @@
41
38
  };
42
39
  /**
43
40
  * polyfill for Object.fromEntries
44
- * @type {(entries: Iterable<[string, any]>) => Object<string, any>}
41
+ * @type {(entries: Iterable<[string, any]>) => Record<string, any>}
45
42
  */
46
43
  const fromEntries = Object.fromEntries || (entries => {
47
- const /** @type {Object<string, any>} */ obj = {};
44
+ const /** @type {Record<string, any>} */ obj = {};
48
45
  for (const [key, value] of entries) {
49
46
  obj[key] = value;
50
47
  }
51
48
  return obj;
52
49
  });
50
+ /**
51
+ * polyfill for Array.prototype.flat
52
+ * @type {function(this: any[][]): any[]}
53
+ */
54
+ const flat = Array.prototype.flat || function() {
55
+ return this.reduce((acc, cur) => acc.concat(cur), []);
56
+ };
53
57
 
54
58
  /**
55
59
  * 解析版本号
@@ -71,7 +75,6 @@
71
75
  * 获取I18N消息
72
76
  * @param {string} key 消息键,省略'wphl-'前缀
73
77
  * @param {string[]} args
74
- * @returns {string}
75
78
  */
76
79
  const msg = (key, ...args) => mw.msg(`wphl-${key}`, ...args);
77
80
  /**
@@ -79,7 +82,7 @@
79
82
  * @param {string[]} args
80
83
  */
81
84
  const notify = (...args) => () => {
82
- const /** @type {JQuery<HTMLParagraphElement>} */ $p = $('<p>', {html: msg(...args)});
85
+ const $p = $('<p>', {html: msg(...args)});
83
86
  mw.notify($p, {type: 'success', autoHideSeconds: 'long', tag: 'wikiplus-highlight'});
84
87
  return $p;
85
88
  };
@@ -90,13 +93,9 @@
90
93
  // 路径
91
94
  const CDN = '//fastly.jsdelivr.net',
92
95
  CM_CDN = 'npm/codemirror@5.65.3',
93
- MW_CDN = 'gh/bhsd-harry/codemirror-mediawiki@1.1.4',
96
+ MW_CDN = 'gh/bhsd-harry/codemirror-mediawiki@1.1.5',
94
97
  REPO_CDN = `npm/wikiplus-highlight@${majorVersion}`;
95
98
 
96
- /**
97
- * mw.config常数
98
- * @type {Object<string, string>}
99
- */
100
99
  const {
101
100
  wgPageName: page,
102
101
  wgNamespaceNumber: ns,
@@ -107,26 +106,15 @@
107
106
  skin,
108
107
  } = mw.config.values;
109
108
 
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
109
  // 和本地缓存有关的常数
122
110
  const USING_LOCAL = mw.loader.getState('ext.CodeMirror') !== null,
123
- /** @type {Object<string, {time: number, config: mwConfig}>} */
111
+ /** @type {Record<string, {time: number, config: mwConfig}>} */
124
112
  ALL_SETTINGS_CACHE = storage.getObject('InPageEditMwConfig') || {},
125
113
  SITE_ID = `${server}${scriptPath}`,
126
114
  /** @type {{time: number, config: mwConfig}} */ SITE_SETTINGS = ALL_SETTINGS_CACHE[SITE_ID] || {},
127
115
  EXPIRED = SITE_SETTINGS.time < Date.now() - 86400 * 1000 * 30;
128
116
 
129
- const /** @type {Object<string, string>} */ CONTENTMODEL = {
117
+ const /** @type {Record<string, string>} */ CONTENTMODEL = {
130
118
  css: 'css',
131
119
  'sanitized-css': 'css',
132
120
  javascript: 'javascript',
@@ -134,7 +122,7 @@
134
122
  wikitext: 'mediawiki',
135
123
  };
136
124
 
137
- const /** @type {Object<string, string|[]>} */ MODE_LIST = USING_LOCAL
125
+ const MODE_LIST = USING_LOCAL
138
126
  ? {
139
127
  lib: 'ext.CodeMirror.lib',
140
128
  css: 'ext.CodeMirror.lib.mode.css',
@@ -170,14 +158,68 @@
170
158
  let /** @type {string[]} */ addons = storage.getObject('Wikiplus-highlight-addons') || defaultAddons,
171
159
  /** @type {string} */ indent = storage.getObject('Wikiplus-highlight-indent') || defaultIndent;
172
160
 
173
- // 用于contextMenu插件
174
- const /** @type {HTMLStyleElement} */ contextmenuStyle = document.getElementById('wphl-contextmenu')
175
- || mw.loader.addStyleTag('#Wikiplus-CodeMirror .cm-mw-template-name{cursor:pointer}');
176
- contextmenuStyle.id = 'wphl-contextmenu';
177
- contextmenuStyle.disabled = true;
161
+ /**
162
+ * contextMenu插件
163
+ * @param {CodeMirror.Editor} doc
164
+ * @param {string} mode
165
+ */
166
+ const handleContextMenu = async (doc, mode) => {
167
+ if (!['mediawiki', 'widget'].includes(mode) || !addons.includes('contextmenu')) {
168
+ return;
169
+ }
170
+ const $wrapper = $(doc.getWrapperElement()).addClass('CodeMirror-contextmenu'),
171
+ {functionSynonyms: [synonyms]} = mw.config.get('extCodeMirrorConfig') || {
172
+ functionSynonyms: [{
173
+ invoke: 'invoke',
174
+ 调用: 'invoke',
175
+ widget: 'widget',
176
+ 小工具: 'widget',
177
+ }],
178
+ };
179
+ /** @param {string} str */
180
+ const getSysnonyms = str => Object.keys(synonyms).filter(key => synonyms[key] === str)
181
+ .map(key => key.startsWith('#') ? key : `#${key}`);
182
+ const invoke = getSysnonyms('invoke'),
183
+ widget = getSysnonyms('widget');
184
+
185
+ await mw.loader.using('mediawiki.Title');
186
+ $wrapper.contextmenu(({pageX, pageY}) => {
187
+ const pos = doc.coordsChar({left: pageX, top: pageY}),
188
+ {line, ch} = pos,
189
+ type = doc.getTokenTypeAt(pos);
190
+ if (!/\bmw-(?:template-name|parserfunction)\b/.test(type)) {
191
+ return;
192
+ }
193
+ const tokens = doc.getLineTokens(line),
194
+ index = tokens.findIndex(({start, end}) => start < ch && end >= ch),
195
+ text = tokens[index].string.replace(/\u200e/g, '').replace(/_/g, ' ').trim();
196
+ if (/\bmw-template-name\b/.test(type)) {
197
+ const title = new mw.Title(text);
198
+ if (title.namespace !== 0 || text.startsWith(':')) {
199
+ open(title.getUrl(), '_blank');
200
+ } else {
201
+ open(mw.util.getUrl(`Template:${text}`), '_blank');
202
+ }
203
+ return false;
204
+ } else if (index < 2 || !/\bmw-parserfunction-delimiter\b/.test(tokens[index - 1].type)
205
+ || !/\bmw-parserfunction-name\b/.test(tokens[index - 2].type)
206
+ ) {
207
+ return;
208
+ }
209
+ const parserFunction = tokens[index - 2].string.trim().toLocaleLowerCase();
210
+ if (invoke.includes(parserFunction)) {
211
+ open(mw.util.getUrl(`Module:${text}`), '_blank');
212
+ } else if (widget.includes(parserFunction)) {
213
+ open(mw.util.getUrl(`Widget:${text}`, {action: 'edit'}), '_blank');
214
+ } else {
215
+ return;
216
+ }
217
+ return false;
218
+ });
219
+ };
178
220
 
179
- let /** @type {Object<string, string>} */ i18n = storage.getObject('Wikiplus-highlight-i18n'),
180
- /** @type {() => JQuery<HTMLParagraphElement>} */ welcome;
221
+ let /** @type {Record<string, string>} */ i18n = storage.getObject('Wikiplus-highlight-i18n'),
222
+ /** @type {() => JQuery<HTMLElement>} */ welcome;
181
223
  if (!i18n) { // 首次安装
182
224
  i18n = {};
183
225
  welcome = notify('welcome');
@@ -185,7 +227,7 @@
185
227
  welcome = notify(`welcome-${newAddon ? 'new-addon' : 'upgrade'}`, version, newAddon);
186
228
  }
187
229
 
188
- const /** @type {Object<string, string>} */ i18nLanguages = {
230
+ const /** @type {Record<string, string>} */ i18nLanguages = {
189
231
  zh: 'zh-hans', 'zh-hans': 'zh-hans', 'zh-cn': 'zh-hans', 'zh-my': 'zh-hans', 'zh-sg': 'zh-hans',
190
232
  'zh-hant': 'zh-hant', 'zh-tw': 'zh-hant', 'zh-hk': 'zh-hant', 'zh-mo': 'zh-hant',
191
233
  },
@@ -205,7 +247,7 @@
205
247
  mw.messages.set(i18n);
206
248
  };
207
249
 
208
- const /** @type {Promise<[void, void]>} */ i18nPromise = Promise.all([ // 提前加载I18N
250
+ const i18nPromise = Promise.all([ // 提前加载I18N
209
251
  mw.loader.using('mediawiki.util'),
210
252
  setI18N(),
211
253
  ]);
@@ -214,7 +256,6 @@
214
256
  * 下载脚本
215
257
  * @param {string[]} urls 脚本路径
216
258
  * @param {boolean} local 是否从本地下载
217
- * @returns {Promise<void>}
218
259
  */
219
260
  const getScript = (urls, local) => {
220
261
  if (urls.length === 0) {
@@ -229,7 +270,45 @@
229
270
  };
230
271
 
231
272
  // 以下进入CodeMirror相关内容
232
- let /** @type {CodeMirror.Editor} */ cm;
273
+ let /** @type {CodeMirror.EditorFromTextArea} */ cm;
274
+
275
+ /**
276
+ * @typedef {object} addon
277
+ * @property {string} option
278
+ * @property {string|string[]} addon
279
+ * @property {string} download
280
+ * @property {boolean} complex
281
+ * @property {string[]} modes
282
+ * @property {boolean} only
283
+ */
284
+
285
+ const /** @type {addon[]} */ options = [
286
+ {option: 'styleActiveLine', addon: 'activeLine'},
287
+ {option: 'styleSelectedText', addon: 'search', download: 'markSelection', only: true},
288
+ {option: 'showTrailingSpace', addon: 'trailingspace'},
289
+ {option: 'matchBrackets', complex: true},
290
+ {option: 'matchTags', addon: ['matchTags', 'fold'], modes: ['mediawiki', 'widget']},
291
+ {option: 'fold', modes: ['mediawiki', 'widget']},
292
+ ];
293
+
294
+ /** @param {CodeMirror} CM */
295
+ const getAddonScript = (CM, other = false) => {
296
+ const /** @type {string[]} */ addonScript = [];
297
+ for (const {option, addon = option, download = Array.isArray(addon) ? option : addon, only} of options) {
298
+ if (!(only && other) && !CM.optionHandlers[option] && intersect(addon, addons)) {
299
+ addonScript.push(ADDON_LIST[download]);
300
+ }
301
+ }
302
+ return addonScript;
303
+ };
304
+
305
+ /**
306
+ * @param {array|any} arr1
307
+ * @param {array} arr2
308
+ */
309
+ const intersect = (arr1, arr2) => Array.isArray(arr1)
310
+ ? arr1.some(ele => arr2.includes(ele))
311
+ : arr2.includes(arr1);
233
312
 
234
313
  /**
235
314
  * 根据文本的高亮模式加载依赖项
@@ -259,7 +338,7 @@
259
338
  mw.loader.load(`${CDN}/${MW_CDN}/mediawiki.min.css`, 'text/css');
260
339
  (USING_LOCAL ? externalScript : scripts).push(`${MW_CDN}/mediawiki.min.js`);
261
340
  }
262
- if (type === 'mediawiki' && typeof SITE_SETTINGS.config === 'object' && SITE_SETTINGS.config.tags.html) {
341
+ if (type === 'mediawiki' && SITE_SETTINGS.config && SITE_SETTINGS.config.tags.html) {
263
342
  // NamespaceHTML扩展自由度过高,所以这里一律当作允许<html>标签
264
343
  type = 'html'; // eslint-disable-line no-param-reassign
265
344
  }
@@ -275,24 +354,7 @@
275
354
  if (!CM.commands.findForward && addons.includes('search')) {
276
355
  addonScript.push(ADDON_LIST.search);
277
356
  }
278
- if (!CM.optionHandlers.styleActiveLine && addons.includes('activeLine')) {
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
- }
357
+ addonScript.push(...getAddonScript(CM));
296
358
  if (['widget', 'html'].includes(type)) {
297
359
  ['css', 'javascript', 'mediawiki', 'htmlmixed', 'xml'].forEach(lang => {
298
360
  if (!CM.modes[lang]) {
@@ -351,14 +413,17 @@
351
413
  await initModePromise;
352
414
  }
353
415
 
354
- let /** @type {mwConfig} */ config = mw.config.get('extCodeMirrorConfig');
416
+ let config = mw.config.get('extCodeMirrorConfig');
355
417
  if (!config && !EXPIRED && isLatest) {
356
418
  ({config} = SITE_SETTINGS);
419
+ if (config.tags.ref) { // fix a bug in InPageEdit-v2
420
+ config.tagModes.ref = 'text/mediawiki';
421
+ }
357
422
  mw.config.set('extCodeMirrorConfig', config);
358
423
  }
359
424
  if (config && config.redirect && config.img) { // 情形1:config已更新,可能来自localStorage
360
425
  return config;
361
- } else if (config) { // FIXME: 暂不需要redirect和img相关设置
426
+ } else if (config) { /** @todo 暂不需要redirect和img相关设置 */
362
427
  return config;
363
428
  }
364
429
 
@@ -370,7 +435,7 @@
370
435
  * @property {string[]} variables
371
436
  */
372
437
 
373
- /**
438
+ /*
374
439
  * 以下情形均需要发送API请求
375
440
  * 情形2:localStorage未过期但不包含新设置
376
441
  * 情形3:新加载的 ext.CodeMirror.data
@@ -389,13 +454,12 @@
389
454
  * @param {{aliases: string[], name: string}[]} words
390
455
  * @returns {{alias: string, name: string}[]}
391
456
  */
392
- const getAliases = words => words.flatMap(
393
- /** @param {{aliases: string[], name: string}} */
394
- ({aliases, name}) => aliases.map(alias => ({alias, name})),
457
+ const getAliases = words => flat.call(
458
+ words.map(({aliases, name}) => aliases.map(alias => ({alias, name}))),
395
459
  );
396
460
  /**
397
461
  * @param {{alias: string, name: string}[]} aliases
398
- * @returns {Object<string, string>}
462
+ * @returns {Record<string, string>}
399
463
  */
400
464
  const getConfig = aliases => fromEntries(
401
465
  aliases.map(({alias, name}) => [alias.replace(/:$/, ''), name]),
@@ -413,10 +477,8 @@
413
477
  ),
414
478
  urlProtocols: mw.config.get('wgUrlProtocols'),
415
479
  };
416
- /** @type {Set<string>} */
417
480
  const realMagicwords = new Set([...functionhooks, ...variables, ...otherMagicwords]),
418
481
  allMagicwords = magicwords.filter(
419
- /** @returns {boolean} */
420
482
  ({name, aliases}) => aliases.some(alias => /^__.+__$/.test(alias)) || realMagicwords.has(name),
421
483
  ),
422
484
  sensitive = getAliases(
@@ -437,15 +499,15 @@
437
499
  const {functionSynonyms: [insensitive]} = config;
438
500
  if (!insensitive.subst) {
439
501
  getAliases(
440
- magicwords.filter(/** @return {boolean} */ ({name}) => otherMagicwords.includes(name)),
502
+ magicwords.filter(({name}) => otherMagicwords.includes(name)),
441
503
  ).forEach(({alias, name}) => {
442
504
  insensitive[alias.replace(/:$/, '')] = name;
443
505
  });
444
506
  }
445
507
  }
446
- config.redirect = magicwords.find(/** @param {{name: string}} */ ({name}) => name === 'redirect').aliases;
508
+ config.redirect = magicwords.find(({name}) => name === 'redirect').aliases;
447
509
  config.img = getConfig(
448
- getAliases(magicwords.filter(/** @returns {boolean} */ ({name}) => name.startsWith('img_'))),
510
+ getAliases(magicwords.filter(({name}) => name.startsWith('img_'))),
449
511
  );
450
512
  mw.config.set('extCodeMirrorConfig', config);
451
513
  updateCachedConfig(config);
@@ -457,7 +519,7 @@
457
519
  if ([274, 828].includes(ns) && !page.endsWith('/doc')) {
458
520
  const pageMode = ns === 274 ? 'Widget' : 'Lua';
459
521
  await mw.loader.using(['oojs-ui-windows', 'oojs-ui.styles.icons-content']);
460
- const /** @type {boolean} */ bool = await OO.ui.confirm(msg('contentmodel'), {
522
+ const bool = await OO.ui.confirm(msg('contentmodel'), {
461
523
  actions: [
462
524
  {label: pageMode},
463
525
  {label: 'Wikitext', action: 'accept'},
@@ -478,7 +540,7 @@
478
540
  const renderEditor = async ($target, setting) => {
479
541
  const mode = setting ? 'javascript' : await getPageMode();
480
542
  const initModePromise = initMode(mode);
481
- const /** @type {[mwConfig]} */ [mwConfig] = await Promise.all([
543
+ const [mwConfig] = await Promise.all([
482
544
  getMwConfig(mode, initModePromise),
483
545
  initModePromise,
484
546
  ]);
@@ -503,7 +565,7 @@
503
565
  }
504
566
 
505
567
  const json = setting || contentmodel === 'json',
506
- /** @type {{name: string}} */ {name} = $.client.profile();
568
+ {name} = $.client.profile();
507
569
  cm = CodeMirror.fromTextArea($target[0], $.extend({
508
570
  inputStyle: name === 'safari' ? 'textarea' : 'contenteditable',
509
571
  lineNumbers: true,
@@ -511,15 +573,16 @@
511
573
  mode,
512
574
  mwConfig,
513
575
  json,
514
- styleActiveLine: addons.includes('activeLine'),
515
- styleSelectedText: addons.includes('search'),
516
- showTrailingSpace: addons.includes('trailingspace'),
576
+ }, fromEntries(
577
+ options.filter(({complex}) => !complex).map(({option, addon = option, modes}) => {
578
+ const mainAddon = Array.isArray(addon) ? addon[0] : addon;
579
+ return [option, addons.includes(mainAddon) && (!modes || modes.includes(mode))];
580
+ }),
581
+ ), {
517
582
  matchBrackets: addons.includes('matchBrackets') && (mode === 'mediawiki' || json
518
583
  ? {bracketRegex: /[{}[\]]/}
519
584
  : true
520
585
  ),
521
- matchTags: addons.includes('matchTags') && ['mediawiki', 'widget'].includes(mode),
522
- fold: addons.includes('fold') && ['mediawiki', 'widget'].includes(mode),
523
586
  }, mode === 'mediawiki'
524
587
  ? {}
525
588
  : {
@@ -530,44 +593,7 @@
530
593
  cm.setSize(null, height);
531
594
  cm.refresh();
532
595
 
533
- const wrapper = cm.getWrapperElement();
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
- }
596
+ handleContextMenu(cm, mode);
571
597
 
572
598
  $('#Wikiplus-Quickedit-Jump').children('a').attr('href', '#Wikiplus-CodeMirror');
573
599
 
@@ -599,12 +625,8 @@
599
625
 
600
626
  // 监视 Wikiplus 编辑框
601
627
  const observer = new MutationObserver(records => {
602
- const $editArea = $(records.flatMap(
603
- /**
604
- * @param {{addedNodes: NodeList}}
605
- * @returns {Node[]}
606
- */
607
- ({addedNodes}) => [...addedNodes],
628
+ const $editArea = $(flat.call(
629
+ records.map(({addedNodes}) => [...addedNodes]),
608
630
  )).find('#Wikiplus-Quickedit, #Wikiplus-Setting-Input');
609
631
  if ($editArea.length === 0) {
610
632
  return;
@@ -614,7 +636,7 @@
614
636
  observer.observe(document.body, {childList: true});
615
637
 
616
638
  // 添加样式
617
- const /** @type {HTMLStyleElement} */ wphlStyle = document.getElementById('wphl-style') || mw.loader.addStyleTag(
639
+ const wphlStyle = document.getElementById('wphl-style') || mw.loader.addStyleTag(
618
640
  '#Wikiplus-Quickedit+.CodeMirror,#Wikiplus-Setting-Input+.CodeMirror'
619
641
  + '{border:1px solid #c8ccd1;line-height:1.3;clear:both}'
620
642
  + 'div.Wikiplus-InterBox{font-size:14px;z-index:100}'
@@ -623,7 +645,8 @@
623
645
  + 'div.CodeMirror span.CodeMirror-matchingbracket{box-shadow:0 0 0 2px #9aef98}'
624
646
  + 'div.CodeMirror span.CodeMirror-nonmatchingbracket{box-shadow:0 0 0 2px #eace64}'
625
647
  + '#Wikiplus-highlight-dialog .oo-ui-messageDialog-title{margin-bottom:0.28571429em}'
626
- + '#Wikiplus-highlight-dialog .oo-ui-flaggedElement-notice{font-weight:normal;margin:0}',
648
+ + '#Wikiplus-highlight-dialog .oo-ui-flaggedElement-notice{font-weight:normal;margin:0}'
649
+ + '.CodeMirror-contextmenu .cm-mw-template-name{cursor:pointer}',
627
650
  );
628
651
  wphlStyle.id = 'wphl-style';
629
652
 
@@ -639,10 +662,7 @@
639
662
  elem.value = value;
640
663
  },
641
664
  } = $.valHooks.textarea || {};
642
- /**
643
- * @param {HTMLTextAreaElement} elem
644
- * @returns {boolean}
645
- */
665
+ /** @param {HTMLTextAreaElement} elem */
646
666
  const isWikiplus = elem => ['Wikiplus-Quickedit', 'Wikiplus-Setting-Input'].includes(elem.id);
647
667
  $.valHooks.textarea = {
648
668
  get(elem) {
@@ -659,21 +679,12 @@
659
679
 
660
680
  await i18nPromise; // 以下内容依赖I18N
661
681
 
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
682
  // 设置对话框
672
- let /** @type {OOUI.widget} */ dialog,
673
- /** @type {OOUI.widget} */ widget,
674
- /** @type {OOUI.widget} */ indentWidget,
675
- /** @type {OOUI.widget} */ field,
676
- /** @type {OOUI.widget} */ indentField;
683
+ let /** @type {OOUI.DialogWidget} */ dialog,
684
+ /** @type {OOUI.MultiInputWidget} */ widget,
685
+ /** @type {OOUI.InputWidget} */ indentWidget,
686
+ /** @type {OOUI.LayoutWidget} */ field,
687
+ /** @type {OOUI.LayoutWidget} */ indentField;
677
688
  const toggleIndent = (value = addons) => {
678
689
  indentField.toggle(value.includes('indentWithSpace'));
679
690
  };
@@ -681,7 +692,7 @@
681
692
  minerva: 'page-actions-overflow',
682
693
  citizen: 'p-actions',
683
694
  };
684
- const /** @type {JQuery<HTMLLIElement} */ $portlet = $(mw.util.addPortletLink(
695
+ const $portlet = $(mw.util.addPortletLink(
685
696
  portletContainer[skin] || 'p-cactions', '#', msg('portlet'), 'wphl-settings',
686
697
  )).click(async e => {
687
698
  e.preventDefault();
@@ -689,7 +700,7 @@
689
700
  await mw.loader.using(['oojs-ui-windows', 'oojs-ui.styles.icons-content']);
690
701
  // eslint-disable-next-line require-atomic-updates
691
702
  dialog = new OO.ui.MessageDialog({id: 'Wikiplus-highlight-dialog'});
692
- const /** @type {OOUI.widget} */ windowManager = new OO.ui.WindowManager();
703
+ const windowManager = new OO.ui.WindowManager();
693
704
  windowManager.$element.appendTo(document.body);
694
705
  windowManager.addWindows([dialog]);
695
706
  widget = new OO.ui.CheckboxMultiselectInputWidget({
@@ -702,6 +713,10 @@
702
713
  {data: 'fold', label: msg('addon-fold')},
703
714
  {data: 'contextmenu', label: msg('addon-contextmenu')},
704
715
  {data: 'indentWithSpace', label: msg('addon-indentwithspace')},
716
+ {
717
+ data: 'otherEditors',
718
+ label: msg(msg('version') === '2.12' ? 'addon-othereditos' : 'addon-othereditors'),
719
+ },
705
720
  ],
706
721
  value: addons,
707
722
  }).on('change', toggleIndent);
@@ -714,6 +729,9 @@
714
729
  indentField = new OO.ui.FieldLayout(indentWidget, {label: msg('addon-indent')});
715
730
  toggleIndent();
716
731
  }
732
+ const wikiplusLoaded = typeof window.Wikiplus === 'object';
733
+ widget.$element.find('.oo-ui-checkboxInputWidget').first().toggleClass('oo-ui-widget-enabled', wikiplusLoaded)
734
+ .children('input').prop('disabled', !wikiplusLoaded);
717
735
  dialog.open({
718
736
  title: msg('addon-title'),
719
737
  message: field.$element.add(indentField.$element).add(
@@ -746,4 +764,37 @@
746
764
  $('#wphl-settings').triggerHandler('click');
747
765
  });
748
766
  }
767
+
768
+ /** @param {CodeMirror.Editor} doc */
769
+ const handleOtherEditors = async doc => {
770
+ if (!addons.includes('otherEditors')) {
771
+ return;
772
+ }
773
+ let mode = doc.getOption('mode');
774
+ mode = mode === 'text/mediawiki' ? 'mediawiki' : mode;
775
+ const addonScript = getAddonScript(CodeMirror, true);
776
+ await getScript(addonScript);
777
+ for (const {option, addon = option, modes} of options.filter(({only, complex}) => !(only || complex))) {
778
+ const mainAddon = Array.isArray(addon) ? addon[0] : addon;
779
+ if (doc.getOption(option) === undefined && addons.includes(mainAddon) && (!modes || modes.includes(mode))) {
780
+ doc.setOption(option, true);
781
+ }
782
+ }
783
+ if (doc.getOption('matchBrackets') === undefined && addons.includes('matchBrackets')) {
784
+ doc.setOption('matchBrackets', mode === 'mediawiki' || doc.getOption('json')
785
+ ? {bracketRegex: /[{}[\]]/}
786
+ : true,
787
+ );
788
+ }
789
+ if (mode !== 'mediawiki' && addons.includes('indentWithSpace')) {
790
+ doc.setOption('indentUnit', indent);
791
+ doc.setOption('indentWithTabs', false);
792
+ }
793
+ handleContextMenu(doc, mode);
794
+ };
795
+
796
+ mw.hook('InPageEdit.quickEdit.codemirror').add(
797
+ /** @param {{cm: CodeMirror.Editor}} */ ({cm: doc}) => handleOtherEditors(doc),
798
+ );
799
+ mw.hook('inspector').add(/** @param {CodeMirror.Editor} doc */ doc => handleOtherEditors(doc));
749
800
  })();