wikiplus-highlight 2.8.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,12 +8,10 @@
8
8
  (async () => {
9
9
  'use strict';
10
10
 
11
- const version = '2.8';
11
+ const version = '2.12.1',
12
+ newAddon = 1;
12
13
 
13
- /**
14
- * polyfill for mw.storage
15
- * @type {{getObject: (key: string) => ?any, setObject: (key: string, value: any) => boolean}}
16
- */
14
+ /** @type {mw.storage} */
17
15
  const storage = typeof mw.storage === 'object' && typeof mw.storage.getObject === 'function'
18
16
  ? mw.storage
19
17
  : {
@@ -40,15 +38,22 @@
40
38
  };
41
39
  /**
42
40
  * polyfill for Object.fromEntries
43
- * @type {(entries: Iterable<[string, any]>) => Object<string, any>}
41
+ * @type {(entries: Iterable<[string, any]>) => Record<string, any>}
44
42
  */
45
43
  const fromEntries = Object.fromEntries || (entries => {
46
- const /** @type {Object<string, any>} */ obj = {};
44
+ const /** @type {Record<string, any>} */ obj = {};
47
45
  for (const [key, value] of entries) {
48
46
  obj[key] = value;
49
47
  }
50
48
  return obj;
51
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
+ };
52
57
 
53
58
  /**
54
59
  * 解析版本号
@@ -70,7 +75,6 @@
70
75
  * 获取I18N消息
71
76
  * @param {string} key 消息键,省略'wphl-'前缀
72
77
  * @param {string[]} args
73
- * @returns {string}
74
78
  */
75
79
  const msg = (key, ...args) => mw.msg(`wphl-${key}`, ...args);
76
80
  /**
@@ -78,8 +82,8 @@
78
82
  * @param {string[]} args
79
83
  */
80
84
  const notify = (...args) => () => {
81
- const /** @type {JQuery<HTMLParagraphElement>} */ $p = $('<p>', {html: msg(...args)});
82
- mw.notify($p, {type: 'success', autoHideSeconds: 'long'});
85
+ const $p = $('<p>', {html: msg(...args)});
86
+ mw.notify($p, {type: 'success', autoHideSeconds: 'long', tag: 'wikiplus-highlight'});
83
87
  return $p;
84
88
  };
85
89
 
@@ -89,13 +93,9 @@
89
93
  // 路径
90
94
  const CDN = '//fastly.jsdelivr.net',
91
95
  CM_CDN = 'npm/codemirror@5.65.3',
92
- MW_CDN = 'gh/bhsd-harry/codemirror-mediawiki@1.1.3',
96
+ MW_CDN = 'gh/bhsd-harry/codemirror-mediawiki@1.1.5',
93
97
  REPO_CDN = `npm/wikiplus-highlight@${majorVersion}`;
94
98
 
95
- /**
96
- * mw.config常数
97
- * @type {Object<string, string>}
98
- */
99
99
  const {
100
100
  wgPageName: page,
101
101
  wgNamespaceNumber: ns,
@@ -106,26 +106,15 @@
106
106
  skin,
107
107
  } = mw.config.values;
108
108
 
109
- /**
110
- * @typedef {object} mwConfig
111
- * @property {Object<string, string>} tagModes
112
- * @property {Object<string, boolean>} tags
113
- * @property {string} urlProtocols
114
- * @property {[Object<string, string>, Object<string, string>]} doubleUnderscore
115
- * @property {[Object<string, string>, Object<string, string>]} functionSynonyms
116
- * @property {string[]} redirect
117
- * @property {Object<string, string>} img
118
- */
119
-
120
109
  // 和本地缓存有关的常数
121
110
  const USING_LOCAL = mw.loader.getState('ext.CodeMirror') !== null,
122
- /** @type {Object<string, {time: number, config: mwConfig}>} */
111
+ /** @type {Record<string, {time: number, config: mwConfig}>} */
123
112
  ALL_SETTINGS_CACHE = storage.getObject('InPageEditMwConfig') || {},
124
113
  SITE_ID = `${server}${scriptPath}`,
125
114
  /** @type {{time: number, config: mwConfig}} */ SITE_SETTINGS = ALL_SETTINGS_CACHE[SITE_ID] || {},
126
115
  EXPIRED = SITE_SETTINGS.time < Date.now() - 86400 * 1000 * 30;
127
116
 
128
- const /** @type {Object<string, string>} */ CONTENTMODEL = {
117
+ const /** @type {Record<string, string>} */ CONTENTMODEL = {
129
118
  css: 'css',
130
119
  'sanitized-css': 'css',
131
120
  javascript: 'javascript',
@@ -133,7 +122,7 @@
133
122
  wikitext: 'mediawiki',
134
123
  };
135
124
 
136
- const /** @type {Object<string, string|[]>} */ MODE_LIST = USING_LOCAL
125
+ const MODE_LIST = USING_LOCAL
137
126
  ? {
138
127
  lib: 'ext.CodeMirror.lib',
139
128
  css: 'ext.CodeMirror.lib.mode.css',
@@ -161,7 +150,6 @@
161
150
  trailingspace: `${CM_CDN}/addon/edit/trailingspace.min.js`,
162
151
  matchBrackets: `${CM_CDN}/addon/edit/matchbrackets.min.js`,
163
152
  matchTags: `${REPO_CDN}/matchtags.min.js`,
164
- foldCode: `${CM_CDN}/addon/fold/foldcode.min.js`,
165
153
  fold: `${REPO_CDN}/fold.min.js`,
166
154
  };
167
155
 
@@ -170,22 +158,76 @@
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');
184
226
  } else if (cmpVersion(i18n['wphl-version'], version)) { // 更新版本
185
- welcome = notify('welcome-upgrade', version);
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,27 +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 && addons.includes('matchTags')) {
291
- addonScript.push(ADDON_LIST.matchTags);
292
- }
293
- if (!CM.prototype.foldCode && addons.includes('fold')) {
294
- addonScript.push(ADDON_LIST.foldCode);
295
- }
296
- if (!CM.optionHandlers.fold && addons.includes('fold')) {
297
- addonScript.push(ADDON_LIST.fold);
298
- }
357
+ addonScript.push(...getAddonScript(CM));
299
358
  if (['widget', 'html'].includes(type)) {
300
359
  ['css', 'javascript', 'mediawiki', 'htmlmixed', 'xml'].forEach(lang => {
301
360
  if (!CM.modes[lang]) {
@@ -354,14 +413,17 @@
354
413
  await initModePromise;
355
414
  }
356
415
 
357
- let /** @type {mwConfig} */ config = mw.config.get('extCodeMirrorConfig');
416
+ let config = mw.config.get('extCodeMirrorConfig');
358
417
  if (!config && !EXPIRED && isLatest) {
359
418
  ({config} = SITE_SETTINGS);
419
+ if (config.tags.ref) { // fix a bug in InPageEdit-v2
420
+ config.tagModes.ref = 'text/mediawiki';
421
+ }
360
422
  mw.config.set('extCodeMirrorConfig', config);
361
423
  }
362
424
  if (config && config.redirect && config.img) { // 情形1:config已更新,可能来自localStorage
363
425
  return config;
364
- } else if (config) { // FIXME: 暂不需要redirect和img相关设置
426
+ } else if (config) { /** @todo 暂不需要redirect和img相关设置 */
365
427
  return config;
366
428
  }
367
429
 
@@ -373,7 +435,7 @@
373
435
  * @property {string[]} variables
374
436
  */
375
437
 
376
- /**
438
+ /*
377
439
  * 以下情形均需要发送API请求
378
440
  * 情形2:localStorage未过期但不包含新设置
379
441
  * 情形3:新加载的 ext.CodeMirror.data
@@ -392,13 +454,12 @@
392
454
  * @param {{aliases: string[], name: string}[]} words
393
455
  * @returns {{alias: string, name: string}[]}
394
456
  */
395
- const getAliases = words => words.flatMap(
396
- /** @param {{aliases: string[], name: string}} */
397
- ({aliases, name}) => aliases.map(alias => ({alias, name})),
457
+ const getAliases = words => flat.call(
458
+ words.map(({aliases, name}) => aliases.map(alias => ({alias, name}))),
398
459
  );
399
460
  /**
400
461
  * @param {{alias: string, name: string}[]} aliases
401
- * @returns {Object<string, string>}
462
+ * @returns {Record<string, string>}
402
463
  */
403
464
  const getConfig = aliases => fromEntries(
404
465
  aliases.map(({alias, name}) => [alias.replace(/:$/, ''), name]),
@@ -416,10 +477,8 @@
416
477
  ),
417
478
  urlProtocols: mw.config.get('wgUrlProtocols'),
418
479
  };
419
- /** @type {Set<string>} */
420
480
  const realMagicwords = new Set([...functionhooks, ...variables, ...otherMagicwords]),
421
481
  allMagicwords = magicwords.filter(
422
- /** @returns {boolean} */
423
482
  ({name, aliases}) => aliases.some(alias => /^__.+__$/.test(alias)) || realMagicwords.has(name),
424
483
  ),
425
484
  sensitive = getAliases(
@@ -440,15 +499,15 @@
440
499
  const {functionSynonyms: [insensitive]} = config;
441
500
  if (!insensitive.subst) {
442
501
  getAliases(
443
- magicwords.filter(/** @return {boolean} */ ({name}) => otherMagicwords.includes(name)),
502
+ magicwords.filter(({name}) => otherMagicwords.includes(name)),
444
503
  ).forEach(({alias, name}) => {
445
504
  insensitive[alias.replace(/:$/, '')] = name;
446
505
  });
447
506
  }
448
507
  }
449
- config.redirect = magicwords.find(/** @param {{name: string}} */ ({name}) => name === 'redirect').aliases;
508
+ config.redirect = magicwords.find(({name}) => name === 'redirect').aliases;
450
509
  config.img = getConfig(
451
- getAliases(magicwords.filter(/** @returns {boolean} */ ({name}) => name.startsWith('img_'))),
510
+ getAliases(magicwords.filter(({name}) => name.startsWith('img_'))),
452
511
  );
453
512
  mw.config.set('extCodeMirrorConfig', config);
454
513
  updateCachedConfig(config);
@@ -460,7 +519,7 @@
460
519
  if ([274, 828].includes(ns) && !page.endsWith('/doc')) {
461
520
  const pageMode = ns === 274 ? 'Widget' : 'Lua';
462
521
  await mw.loader.using(['oojs-ui-windows', 'oojs-ui.styles.icons-content']);
463
- const /** @type {boolean} */ bool = await OO.ui.confirm(msg('contentmodel'), {
522
+ const bool = await OO.ui.confirm(msg('contentmodel'), {
464
523
  actions: [
465
524
  {label: pageMode},
466
525
  {label: 'Wikitext', action: 'accept'},
@@ -481,7 +540,7 @@
481
540
  const renderEditor = async ($target, setting) => {
482
541
  const mode = setting ? 'javascript' : await getPageMode();
483
542
  const initModePromise = initMode(mode);
484
- const /** @type {[mwConfig]} */ [mwConfig] = await Promise.all([
543
+ const [mwConfig] = await Promise.all([
485
544
  getMwConfig(mode, initModePromise),
486
545
  initModePromise,
487
546
  ]);
@@ -506,7 +565,7 @@
506
565
  }
507
566
 
508
567
  const json = setting || contentmodel === 'json',
509
- /** @type {{name: string}} */ {name} = $.client.profile();
568
+ {name} = $.client.profile();
510
569
  cm = CodeMirror.fromTextArea($target[0], $.extend({
511
570
  inputStyle: name === 'safari' ? 'textarea' : 'contenteditable',
512
571
  lineNumbers: true,
@@ -514,14 +573,16 @@
514
573
  mode,
515
574
  mwConfig,
516
575
  json,
517
- styleActiveLine: addons.includes('activeLine'),
518
- styleSelectedText: addons.includes('search'),
519
- 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
+ ), {
520
582
  matchBrackets: addons.includes('matchBrackets') && (mode === 'mediawiki' || json
521
583
  ? {bracketRegex: /[{}[\]]/}
522
584
  : true
523
585
  ),
524
- matchTags: addons.includes('matchBrackets') && ['mediawiki', 'widget'].includes(mode),
525
586
  }, mode === 'mediawiki'
526
587
  ? {}
527
588
  : {
@@ -532,44 +593,7 @@
532
593
  cm.setSize(null, height);
533
594
  cm.refresh();
534
595
 
535
- const wrapper = cm.getWrapperElement();
536
- wrapper.id = 'Wikiplus-CodeMirror';
537
- if (['mediawiki', 'widget'].includes(mode) && addons.includes('contextmenu')) {
538
- contextmenuStyle.disabled = false;
539
- const /** @type {mwConfig} */ {functionSynonyms: [synonyms]} = mw.config.get('extCodeMirrorConfig');
540
- /** @param {string} str */
541
- const getSysnonyms = str => Object.keys(synonyms).filter(key => synonyms[key] === str)
542
- .map(key => key.startsWith('#') ? key : `#${key}`);
543
- const invoke = getSysnonyms('invoke'),
544
- widget = getSysnonyms('widget');
545
-
546
- await mw.loader.using('mediawiki.Title');
547
- $(wrapper).on('contextmenu', '.cm-mw-template-name', function() {
548
- const /** @type {string} */ text = this.textContent.replace(/\u200e/g, '').trim(),
549
- /** @type {{namespace: number, getUrl: () => string}} */ title = new mw.Title(text);
550
- if (title.namespace !== 0 || text.startsWith(':')) {
551
- open(title.getUrl(), '_blank');
552
- } else {
553
- open(mw.util.getUrl(`Template:${text}`), '_blank');
554
- }
555
- return false;
556
- }).on(
557
- 'contextmenu',
558
- '.cm-mw-parserfunction-name + .cm-mw-parserfunction-delimiter + .cm-mw-parserfunction',
559
- function() {
560
- /** @type {string} */
561
- const parserFunction = this.previousSibling.previousSibling.textContent.trim().toLowerCase();
562
- if (invoke.includes(parserFunction)) {
563
- open(mw.util.getUrl(`Module:${this.textContent}`), '_blank');
564
- } else if (widget.includes(parserFunction)) {
565
- open(mw.util.getUrl(`Widget:${this.textContent}`, {action: 'edit'}), '_blank');
566
- }
567
- return false;
568
- },
569
- );
570
- } else {
571
- contextmenuStyle.disabled = true;
572
- }
596
+ handleContextMenu(cm, mode);
573
597
 
574
598
  $('#Wikiplus-Quickedit-Jump').children('a').attr('href', '#Wikiplus-CodeMirror');
575
599
 
@@ -601,12 +625,8 @@
601
625
 
602
626
  // 监视 Wikiplus 编辑框
603
627
  const observer = new MutationObserver(records => {
604
- const $editArea = $(records.flatMap(
605
- /**
606
- * @param {{addedNodes: NodeList}}
607
- * @returns {Node[]}
608
- */
609
- ({addedNodes}) => [...addedNodes],
628
+ const $editArea = $(flat.call(
629
+ records.map(({addedNodes}) => [...addedNodes]),
610
630
  )).find('#Wikiplus-Quickedit, #Wikiplus-Setting-Input');
611
631
  if ($editArea.length === 0) {
612
632
  return;
@@ -616,7 +636,7 @@
616
636
  observer.observe(document.body, {childList: true});
617
637
 
618
638
  // 添加样式
619
- const /** @type {HTMLStyleElement} */ wphlStyle = document.getElementById('wphl-style') || mw.loader.addStyleTag(
639
+ const wphlStyle = document.getElementById('wphl-style') || mw.loader.addStyleTag(
620
640
  '#Wikiplus-Quickedit+.CodeMirror,#Wikiplus-Setting-Input+.CodeMirror'
621
641
  + '{border:1px solid #c8ccd1;line-height:1.3;clear:both}'
622
642
  + 'div.Wikiplus-InterBox{font-size:14px;z-index:100}'
@@ -625,7 +645,8 @@
625
645
  + 'div.CodeMirror span.CodeMirror-matchingbracket{box-shadow:0 0 0 2px #9aef98}'
626
646
  + 'div.CodeMirror span.CodeMirror-nonmatchingbracket{box-shadow:0 0 0 2px #eace64}'
627
647
  + '#Wikiplus-highlight-dialog .oo-ui-messageDialog-title{margin-bottom:0.28571429em}'
628
- + '#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}',
629
650
  );
630
651
  wphlStyle.id = 'wphl-style';
631
652
 
@@ -641,10 +662,7 @@
641
662
  elem.value = value;
642
663
  },
643
664
  } = $.valHooks.textarea || {};
644
- /**
645
- * @param {HTMLTextAreaElement} elem
646
- * @returns {boolean}
647
- */
665
+ /** @param {HTMLTextAreaElement} elem */
648
666
  const isWikiplus = elem => ['Wikiplus-Quickedit', 'Wikiplus-Setting-Input'].includes(elem.id);
649
667
  $.valHooks.textarea = {
650
668
  get(elem) {
@@ -661,21 +679,12 @@
661
679
 
662
680
  await i18nPromise; // 以下内容依赖I18N
663
681
 
664
- /**
665
- * @typedef {object} OOUI.widget
666
- * @property {(data: any) => {closing: Promise<{action: string}>}} open
667
- * @property {() => string|string[]} getValue
668
- * @property {JQuery<HTMLDivElement>} $element
669
- * @property {(show: boolean) => OOUI.widget} toggle
670
- * @property {(windows: OOUI.widget[]) => void} addWindows
671
- */
672
-
673
682
  // 设置对话框
674
- let /** @type {OOUI.widget} */ dialog,
675
- /** @type {OOUI.widget} */ widget,
676
- /** @type {OOUI.widget} */ indentWidget,
677
- /** @type {OOUI.widget} */ field,
678
- /** @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;
679
688
  const toggleIndent = (value = addons) => {
680
689
  indentField.toggle(value.includes('indentWithSpace'));
681
690
  };
@@ -683,7 +692,7 @@
683
692
  minerva: 'page-actions-overflow',
684
693
  citizen: 'p-actions',
685
694
  };
686
- const /** @type {JQuery<HTMLLIElement} */ $portlet = $(mw.util.addPortletLink(
695
+ const $portlet = $(mw.util.addPortletLink(
687
696
  portletContainer[skin] || 'p-cactions', '#', msg('portlet'), 'wphl-settings',
688
697
  )).click(async e => {
689
698
  e.preventDefault();
@@ -691,7 +700,7 @@
691
700
  await mw.loader.using(['oojs-ui-windows', 'oojs-ui.styles.icons-content']);
692
701
  // eslint-disable-next-line require-atomic-updates
693
702
  dialog = new OO.ui.MessageDialog({id: 'Wikiplus-highlight-dialog'});
694
- const /** @type {OOUI.widget} */ windowManager = new OO.ui.WindowManager();
703
+ const windowManager = new OO.ui.WindowManager();
695
704
  windowManager.$element.appendTo(document.body);
696
705
  windowManager.addWindows([dialog]);
697
706
  widget = new OO.ui.CheckboxMultiselectInputWidget({
@@ -701,8 +710,13 @@
701
710
  {data: 'trailingspace', label: msg('addon-trailingspace')},
702
711
  {data: 'matchBrackets', label: msg('addon-matchbrackets')},
703
712
  {data: 'matchTags', label: msg('addon-matchtags')},
713
+ {data: 'fold', label: msg('addon-fold')},
704
714
  {data: 'contextmenu', label: msg('addon-contextmenu')},
705
715
  {data: 'indentWithSpace', label: msg('addon-indentwithspace')},
716
+ {
717
+ data: 'otherEditors',
718
+ label: msg(msg('version') === '2.12' ? 'addon-othereditos' : 'addon-othereditors'),
719
+ },
706
720
  ],
707
721
  value: addons,
708
722
  }).on('change', toggleIndent);
@@ -715,6 +729,9 @@
715
729
  indentField = new OO.ui.FieldLayout(indentWidget, {label: msg('addon-indent')});
716
730
  toggleIndent();
717
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);
718
735
  dialog.open({
719
736
  title: msg('addon-title'),
720
737
  message: field.$element.add(indentField.$element).add(
@@ -747,4 +764,37 @@
747
764
  $('#wphl-settings').triggerHandler('click');
748
765
  });
749
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));
750
800
  })();