wikiplus-highlight 2.7.5 → 2.8.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,11 +8,12 @@
8
8
  (async () => {
9
9
  'use strict';
10
10
 
11
- const version = '2.7.5';
11
+ const version = '2.10',
12
+ newAddon = 1;
12
13
 
13
14
  /**
14
15
  * polyfill for mw.storage
15
- * @type {Object.<string, function>}
16
+ * @type {{getObject: (key: string) => ?any, setObject: (key: string, value: any) => boolean}}
16
17
  */
17
18
  const storage = typeof mw.storage === 'object' && typeof mw.storage.getObject === 'function'
18
19
  ? mw.storage
@@ -29,7 +30,7 @@
29
30
  }
30
31
  },
31
32
  setObject(key, value) {
32
- let json;
33
+ let /** @type {string} */ json;
33
34
  try {
34
35
  json = JSON.stringify(value);
35
36
  return localStorage.setItem(key, json);
@@ -40,12 +41,10 @@
40
41
  };
41
42
  /**
42
43
  * polyfill for Object.fromEntries
43
- * @type {function}
44
- * @param {Array.<[string, any]>} entries
45
- * @returns {Object}
44
+ * @type {(entries: Iterable<[string, any]>) => Object<string, any>}
46
45
  */
47
46
  const fromEntries = Object.fromEntries || (entries => {
48
- const obj = {};
47
+ const /** @type {Object<string, any>} */ obj = {};
49
48
  for (const [key, value] of entries) {
50
49
  obj[key] = value;
51
50
  }
@@ -55,14 +54,13 @@
55
54
  /**
56
55
  * 解析版本号
57
56
  * @param {string} str 版本字符串
58
- * @returns {number[]}
59
57
  */
60
58
  const getVersion = str => str.split('.').map(s => Number(s));
61
59
  /**
62
60
  * 比较版本号
63
61
  * @param {string} a
64
62
  * @param {string} b
65
- * @returns {boolean} a的版本号是否小于b的版本号
63
+ * @returns a的版本号是否小于b的版本号
66
64
  */
67
65
  const cmpVersion = (a, b) => {
68
66
  const [a0, a1] = getVersion(a),
@@ -79,11 +77,10 @@
79
77
  /**
80
78
  * 提示消息
81
79
  * @param {string[]} args
82
- * @return {function: jQuery.<HTMLParagraphElement>}
83
80
  */
84
81
  const notify = (...args) => () => {
85
- const $p = $('<p>', {html: msg(...args)});
86
- mw.notify($p, {type: 'success', autoHideSeconds: 'long'});
82
+ const /** @type {JQuery<HTMLParagraphElement>} */ $p = $('<p>', {html: msg(...args)});
83
+ mw.notify($p, {type: 'success', autoHideSeconds: 'long', tag: 'wikiplus-highlight'});
87
84
  return $p;
88
85
  };
89
86
 
@@ -93,10 +90,13 @@
93
90
  // 路径
94
91
  const CDN = '//fastly.jsdelivr.net',
95
92
  CM_CDN = 'npm/codemirror@5.65.3',
96
- MW_CDN = 'gh/bhsd-harry/codemirror-mediawiki@1.0',
93
+ MW_CDN = 'gh/bhsd-harry/codemirror-mediawiki@1.1.4',
97
94
  REPO_CDN = `npm/wikiplus-highlight@${majorVersion}`;
98
95
 
99
- // mw.config常数
96
+ /**
97
+ * mw.config常数
98
+ * @type {Object<string, string>}
99
+ */
100
100
  const {
101
101
  wgPageName: page,
102
102
  wgNamespaceNumber: ns,
@@ -107,14 +107,26 @@
107
107
  skin,
108
108
  } = mw.config.values;
109
109
 
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
+
110
121
  // 和本地缓存有关的常数
111
122
  const USING_LOCAL = mw.loader.getState('ext.CodeMirror') !== null,
112
- ALL_SETTINGS_CACHE = storage.getObject('InPageEditMwConfig') || {}, // @type {?Object.<string, Object>}
123
+ /** @type {Object<string, {time: number, config: mwConfig}>} */
124
+ ALL_SETTINGS_CACHE = storage.getObject('InPageEditMwConfig') || {},
113
125
  SITE_ID = `${server}${scriptPath}`,
114
- SITE_SETTINGS = ALL_SETTINGS_CACHE[SITE_ID] || {}, // @type {?Object}
126
+ /** @type {{time: number, config: mwConfig}} */ SITE_SETTINGS = ALL_SETTINGS_CACHE[SITE_ID] || {},
115
127
  EXPIRED = SITE_SETTINGS.time < Date.now() - 86400 * 1000 * 30;
116
128
 
117
- const CONTENTMODEL = {
129
+ const /** @type {Object<string, string>} */ CONTENTMODEL = {
118
130
  css: 'css',
119
131
  'sanitized-css': 'css',
120
132
  javascript: 'javascript',
@@ -122,7 +134,7 @@
122
134
  wikitext: 'mediawiki',
123
135
  };
124
136
 
125
- const MODE_LIST = USING_LOCAL
137
+ const /** @type {Object<string, string|[]>} */ MODE_LIST = USING_LOCAL
126
138
  ? {
127
139
  lib: 'ext.CodeMirror.lib',
128
140
  css: 'ext.CodeMirror.lib.mode.css',
@@ -150,40 +162,38 @@
150
162
  trailingspace: `${CM_CDN}/addon/edit/trailingspace.min.js`,
151
163
  matchBrackets: `${CM_CDN}/addon/edit/matchbrackets.min.js`,
152
164
  matchTags: `${REPO_CDN}/matchtags.min.js`,
165
+ fold: `${REPO_CDN}/fold.min.js`,
153
166
  };
154
167
 
155
168
  const defaultAddons = ['search'],
156
169
  defaultIndent = '4';
157
- let addons = storage.getObject('Wikiplus-highlight-addons') || defaultAddons, // @type {?string[]}
158
- indent = storage.getObject('Wikiplus-highlight-indent') || defaultIndent; // @type {string}
170
+ let /** @type {string[]} */ addons = storage.getObject('Wikiplus-highlight-addons') || defaultAddons,
171
+ /** @type {string} */ indent = storage.getObject('Wikiplus-highlight-indent') || defaultIndent;
159
172
 
160
173
  // 用于contextMenu插件
161
- const contextmenuStyle = document.getElementById('wphl-contextmenu') // @type {?HTMLStyleElement}
174
+ const /** @type {HTMLStyleElement} */ contextmenuStyle = document.getElementById('wphl-contextmenu')
162
175
  || mw.loader.addStyleTag('#Wikiplus-CodeMirror .cm-mw-template-name{cursor:pointer}');
163
176
  contextmenuStyle.id = 'wphl-contextmenu';
164
177
  contextmenuStyle.disabled = true;
165
178
 
166
- let i18n = storage.getObject('Wikiplus-highlight-i18n'), // @type {?Object.<string, string>}
167
- welcome; // @type {function: jQuery.<?HTMLParagraphElement>}
179
+ let /** @type {Object<string, string>} */ i18n = storage.getObject('Wikiplus-highlight-i18n'),
180
+ /** @type {() => JQuery<HTMLParagraphElement>} */ welcome;
168
181
  if (!i18n) { // 首次安装
169
182
  i18n = {};
170
183
  welcome = notify('welcome');
171
184
  } else if (cmpVersion(i18n['wphl-version'], version)) { // 更新版本
172
- welcome = notify('welcome-upgrade', version);
185
+ welcome = notify(`welcome-${newAddon ? 'new-addon' : 'upgrade'}`, version, newAddon);
173
186
  }
174
187
 
175
- const i18nLanguages = {
188
+ const /** @type {Object<string, string>} */ i18nLanguages = {
176
189
  zh: 'zh-hans', 'zh-hans': 'zh-hans', 'zh-cn': 'zh-hans', 'zh-my': 'zh-hans', 'zh-sg': 'zh-hans',
177
190
  'zh-hant': 'zh-hant', 'zh-tw': 'zh-hant', 'zh-hk': 'zh-hant', 'zh-mo': 'zh-hant',
178
191
  },
179
- i18nLang = i18nLanguages[userLang] || 'en', // @type {?string}
192
+ i18nLang = i18nLanguages[userLang] || 'en',
180
193
  I18N_CDN = `${CDN}/${REPO_CDN}/i18n/${i18nLang}.json`,
181
194
  isLatest = i18n['wphl-version'] === majorVersion;
182
195
 
183
- /**
184
- * 加载 I18N
185
- * @returns {promise}
186
- */
196
+ /** 加载 I18N */
187
197
  const setI18N = async () => {
188
198
  if (!isLatest || i18n['wphl-lang'] !== i18nLang) {
189
199
  i18n = await $.ajax(`${I18N_CDN}`, { // eslint-disable-line require-atomic-updates
@@ -195,7 +205,7 @@
195
205
  mw.messages.set(i18n);
196
206
  };
197
207
 
198
- const i18nPromise = Promise.all([ // 提前加载I18N
208
+ const /** @type {Promise<[void, void]>} */ i18nPromise = Promise.all([ // 提前加载I18N
199
209
  mw.loader.using('mediawiki.util'),
200
210
  setI18N(),
201
211
  ]);
@@ -203,12 +213,12 @@
203
213
  /**
204
214
  * 下载脚本
205
215
  * @param {string[]} urls 脚本路径
206
- * @param {boolean=false} local 是否从本地下载
207
- * @returns {promise}
216
+ * @param {boolean} local 是否从本地下载
217
+ * @returns {Promise<void>}
208
218
  */
209
219
  const getScript = (urls, local) => {
210
220
  if (urls.length === 0) {
211
- return;
221
+ return Promise.resolve();
212
222
  }
213
223
  return local
214
224
  ? mw.loader.using(urls)
@@ -219,22 +229,21 @@
219
229
  };
220
230
 
221
231
  // 以下进入CodeMirror相关内容
222
- let cm;
232
+ let /** @type {CodeMirror.Editor} */ cm;
223
233
 
224
234
  /**
225
235
  * 根据文本的高亮模式加载依赖项
226
236
  * @param {string} type
227
- * @returns {promise}
228
237
  */
229
238
  const initMode = async type => {
230
- let scripts = [];
231
- const externalScript = [],
232
- addonScript = [],
239
+ let /** @type {string[]} */ scripts = [];
240
+ const /** @type {string[]} */ externalScript = [],
241
+ /** @type {string[]} */ addonScript = [],
233
242
  loaded = typeof window.CodeMirror === 'function';
234
243
 
235
244
  /**
236
245
  * 代替CodeMirror的局部变量
237
- * @type {(function|Object.<string, Object>)}
246
+ * @type {CodeMirror}
238
247
  */
239
248
  const CM = loaded
240
249
  ? window.CodeMirror
@@ -278,9 +287,12 @@
278
287
  if (!CM.optionHandlers.matchBrackets && addons.includes('matchBrackets')) {
279
288
  addonScript.push(ADDON_LIST.matchBrackets);
280
289
  }
281
- if (!CM.optionHandlers.matchTags && addons.includes('matchTags')) {
290
+ if (!CM.optionHandlers.matchTags && ['matchTags', 'fold'].some(addon => addons.includes(addon))) {
282
291
  addonScript.push(ADDON_LIST.matchTags);
283
292
  }
293
+ if (!CM.optionHandlers.fold && addons.includes('fold')) {
294
+ addonScript.push(ADDON_LIST.fold);
295
+ }
284
296
  if (['widget', 'html'].includes(type)) {
285
297
  ['css', 'javascript', 'mediawiki', 'htmlmixed', 'xml'].forEach(lang => {
286
298
  if (!CM.modes[lang]) {
@@ -315,7 +327,7 @@
315
327
 
316
328
  /**
317
329
  * 更新缓存的设置数据
318
- * @param {Object} config
330
+ * @param {mwConfig} config
319
331
  */
320
332
  const updateCachedConfig = config => {
321
333
  ALL_SETTINGS_CACHE[SITE_ID] = {
@@ -328,8 +340,7 @@
328
340
  /**
329
341
  * 加载CodeMirror的mediawiki模块需要的设置数据
330
342
  * @param {string} type
331
- * @param {promise} initModePromise 使用本地CodeMirror扩展时大部分数据来自ext.CodeMirror.data模块
332
- * @returns {promise}
343
+ * @param {Promise<void>} initModePromise 使用本地CodeMirror扩展时大部分数据来自ext.CodeMirror.data模块
333
344
  */
334
345
  const getMwConfig = async (type, initModePromise) => {
335
346
  if (!['mediawiki', 'widget'].includes(type)) {
@@ -340,7 +351,7 @@
340
351
  await initModePromise;
341
352
  }
342
353
 
343
- let config = mw.config.get('extCodeMirrorConfig');
354
+ let /** @type {mwConfig} */ config = mw.config.get('extCodeMirrorConfig');
344
355
  if (!config && !EXPIRED && isLatest) {
345
356
  ({config} = SITE_SETTINGS);
346
357
  mw.config.set('extCodeMirrorConfig', config);
@@ -351,13 +362,21 @@
351
362
  return config;
352
363
  }
353
364
 
365
+ /**
366
+ * @typedef {object} siteInfoQuery
367
+ * @property {{name: string, aliases: string[], 'case-sensitive': boolean}[]} magicwords
368
+ * @property {string[]} extensiontags
369
+ * @property {string[]} functionhooks
370
+ * @property {string[]} variables
371
+ */
372
+
354
373
  /**
355
374
  * 以下情形均需要发送API请求
356
375
  * 情形2:localStorage未过期但不包含新设置
357
376
  * 情形3:新加载的 ext.CodeMirror.data
358
377
  * 情形4:config === null
359
378
  */
360
- const {
379
+ const /** @type {{query: siteInfoQuery}} */ {
361
380
  query: {magicwords, extensiontags, functionhooks, variables},
362
381
  } = await new mw.Api().get({
363
382
  meta: 'siteinfo',
@@ -367,21 +386,22 @@
367
386
  const otherMagicwords = ['msg', 'raw', 'msgnw', 'subst', 'safesubst'];
368
387
 
369
388
  /**
370
- * @param {Object[]} words
371
- * @property <string[]> aliases
372
- * @property <string> name
373
- * @returns {Object.<('alias'|'name'), string>[]}
389
+ * @param {{aliases: string[], name: string}[]} words
390
+ * @returns {{alias: string, name: string}[]}
374
391
  */
375
- const getAliases = words => words.flatMap(({aliases, name}) => aliases.map(alias => ({alias, name})));
392
+ const getAliases = words => words.flatMap(
393
+ /** @param {{aliases: string[], name: string}} */
394
+ ({aliases, name}) => aliases.map(alias => ({alias, name})),
395
+ );
376
396
  /**
377
- * @param {Object.<('alias'|'name'), string>[]} aliases
378
- * @returns {Object.<string, string>}
397
+ * @param {{alias: string, name: string}[]} aliases
398
+ * @returns {Object<string, string>}
379
399
  */
380
400
  const getConfig = aliases => fromEntries(
381
401
  aliases.map(({alias, name}) => [alias.replace(/:$/, ''), name]),
382
402
  );
383
403
 
384
- if (!config) { // 情形4:config === null
404
+ if (!config) { // 情形4:config === null\
385
405
  config = {
386
406
  tagModes: {
387
407
  pre: 'mw-tag-pre',
@@ -393,9 +413,11 @@
393
413
  ),
394
414
  urlProtocols: mw.config.get('wgUrlProtocols'),
395
415
  };
416
+ /** @type {Set<string>} */
396
417
  const realMagicwords = new Set([...functionhooks, ...variables, ...otherMagicwords]),
397
- allMagicwords = magicwords.filter(({name, aliases}) =>
398
- aliases.some(alias => /^__.+__$/.test(alias)) || realMagicwords.has(name),
418
+ allMagicwords = magicwords.filter(
419
+ /** @returns {boolean} */
420
+ ({name, aliases}) => aliases.some(alias => /^__.+__$/.test(alias)) || realMagicwords.has(name),
399
421
  ),
400
422
  sensitive = getAliases(
401
423
  allMagicwords.filter(word => word['case-sensitive']),
@@ -415,30 +437,27 @@
415
437
  const {functionSynonyms: [insensitive]} = config;
416
438
  if (!insensitive.subst) {
417
439
  getAliases(
418
- magicwords.filter(({name}) => otherMagicwords.includes(name)),
440
+ magicwords.filter(/** @return {boolean} */ ({name}) => otherMagicwords.includes(name)),
419
441
  ).forEach(({alias, name}) => {
420
442
  insensitive[alias.replace(/:$/, '')] = name;
421
443
  });
422
444
  }
423
445
  }
424
- config.redirect = magicwords.find(({name}) => name === 'redirect').aliases;
446
+ config.redirect = magicwords.find(/** @param {{name: string}} */ ({name}) => name === 'redirect').aliases;
425
447
  config.img = getConfig(
426
- getAliases(magicwords.filter(({name}) => name.startsWith('img_'))),
448
+ getAliases(magicwords.filter(/** @returns {boolean} */ ({name}) => name.startsWith('img_'))),
427
449
  );
428
450
  mw.config.set('extCodeMirrorConfig', config);
429
451
  updateCachedConfig(config);
430
452
  return config;
431
453
  };
432
454
 
433
- /**
434
- * 检查页面语言类型
435
- * @returns {promise}
436
- */
455
+ /** 检查页面语言类型 */
437
456
  const getPageMode = async () => {
438
457
  if ([274, 828].includes(ns) && !page.endsWith('/doc')) {
439
458
  const pageMode = ns === 274 ? 'Widget' : 'Lua';
440
459
  await mw.loader.using(['oojs-ui-windows', 'oojs-ui.styles.icons-content']);
441
- const bool = await OO.ui.confirm(msg('contentmodel'), {
460
+ const /** @type {boolean} */ bool = await OO.ui.confirm(msg('contentmodel'), {
442
461
  actions: [
443
462
  {label: pageMode},
444
463
  {label: 'Wikitext', action: 'accept'},
@@ -453,13 +472,13 @@
453
472
 
454
473
  /**
455
474
  * 渲染编辑器
456
- * @param {jQuery.<HTMLTextAreaElement>} $target 目标编辑框
475
+ * @param {JQuery<HTMLTextAreaElement>} $target 目标编辑框
457
476
  * @param {boolean} setting 是否是Wikiplus设置(使用json语法)
458
477
  */
459
478
  const renderEditor = async ($target, setting) => {
460
479
  const mode = setting ? 'javascript' : await getPageMode();
461
480
  const initModePromise = initMode(mode);
462
- const [mwConfig] = await Promise.all([
481
+ const /** @type {[mwConfig]} */ [mwConfig] = await Promise.all([
463
482
  getMwConfig(mode, initModePromise),
464
483
  initModePromise,
465
484
  ]);
@@ -484,7 +503,7 @@
484
503
  }
485
504
 
486
505
  const json = setting || contentmodel === 'json',
487
- {name} = $.client.profile();
506
+ /** @type {{name: string}} */ {name} = $.client.profile();
488
507
  cm = CodeMirror.fromTextArea($target[0], $.extend({
489
508
  inputStyle: name === 'safari' ? 'textarea' : 'contenteditable',
490
509
  lineNumbers: true,
@@ -499,7 +518,8 @@
499
518
  ? {bracketRegex: /[{}[\]]/}
500
519
  : true
501
520
  ),
502
- matchTags: addons.includes('matchBrackets') && ['mediawiki', 'widget'].includes(mode),
521
+ matchTags: addons.includes('matchTags') && ['mediawiki', 'widget'].includes(mode),
522
+ fold: addons.includes('fold') && ['mediawiki', 'widget'].includes(mode),
503
523
  }, mode === 'mediawiki'
504
524
  ? {}
505
525
  : {
@@ -514,20 +534,17 @@
514
534
  wrapper.id = 'Wikiplus-CodeMirror';
515
535
  if (['mediawiki', 'widget'].includes(mode) && addons.includes('contextmenu')) {
516
536
  contextmenuStyle.disabled = false;
517
- const {functionSynonyms: [synonyms]} = mw.config.get('extCodeMirrorConfig');
518
- /**
519
- * @param {string} name
520
- * @returns {string[]}
521
- */
522
- const getSysnonyms = name => Object.keys(synonyms).filter(key => synonyms[key] === name)
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)
523
540
  .map(key => key.startsWith('#') ? key : `#${key}`);
524
541
  const invoke = getSysnonyms('invoke'),
525
542
  widget = getSysnonyms('widget');
526
543
 
527
544
  await mw.loader.using('mediawiki.Title');
528
545
  $(wrapper).on('contextmenu', '.cm-mw-template-name', function() {
529
- const text = this.textContent.replace(/\u200e/g, '').trim(),
530
- title = new mw.Title(text);
546
+ const /** @type {string} */ text = this.textContent.replace(/\u200e/g, '').trim(),
547
+ /** @type {{namespace: number, getUrl: () => string}} */ title = new mw.Title(text);
531
548
  if (title.namespace !== 0 || text.startsWith(':')) {
532
549
  open(title.getUrl(), '_blank');
533
550
  } else {
@@ -538,6 +555,7 @@
538
555
  'contextmenu',
539
556
  '.cm-mw-parserfunction-name + .cm-mw-parserfunction-delimiter + .cm-mw-parserfunction',
540
557
  function() {
558
+ /** @type {string} */
541
559
  const parserFunction = this.previousSibling.previousSibling.textContent.trim().toLowerCase();
542
560
  if (invoke.includes(parserFunction)) {
543
561
  open(mw.util.getUrl(`Module:${this.textContent}`), '_blank');
@@ -581,8 +599,13 @@
581
599
 
582
600
  // 监视 Wikiplus 编辑框
583
601
  const observer = new MutationObserver(records => {
584
- const $editArea = $(records.flatMap(({addedNodes}) => [...addedNodes]))
585
- .find('#Wikiplus-Quickedit, #Wikiplus-Setting-Input');
602
+ const $editArea = $(records.flatMap(
603
+ /**
604
+ * @param {{addedNodes: NodeList}}
605
+ * @returns {Node[]}
606
+ */
607
+ ({addedNodes}) => [...addedNodes],
608
+ )).find('#Wikiplus-Quickedit, #Wikiplus-Setting-Input');
586
609
  if ($editArea.length === 0) {
587
610
  return;
588
611
  }
@@ -591,8 +614,9 @@
591
614
  observer.observe(document.body, {childList: true});
592
615
 
593
616
  // 添加样式
594
- const wphlStyle = document.getElementById('wphl-style') || mw.loader.addStyleTag(
595
- '#Wikiplus-Quickedit+.CodeMirror,#Wikiplus-Setting-Input+.CodeMirror{border:1px solid #c8ccd1;line-height:1.3;clear:both}'
617
+ const /** @type {HTMLStyleElement} */ wphlStyle = document.getElementById('wphl-style') || mw.loader.addStyleTag(
618
+ '#Wikiplus-Quickedit+.CodeMirror,#Wikiplus-Setting-Input+.CodeMirror'
619
+ + '{border:1px solid #c8ccd1;line-height:1.3;clear:both}'
596
620
  + 'div.Wikiplus-InterBox{font-size:14px;z-index:100}'
597
621
  + '.skin-minerva .Wikiplus-InterBox{font-size:16px}'
598
622
  + '.cm-trailingspace{text-decoration:underline wavy red}'
@@ -603,7 +627,10 @@
603
627
  );
604
628
  wphlStyle.id = 'wphl-style';
605
629
 
606
- // 对编辑框调用jQuery.val方法时从CodeMirror获取文本
630
+ /**
631
+ * 对编辑框调用jQuery.val方法时从CodeMirror获取文本
632
+ * @type {{get: (elem: HTMLTextAreaElement) => string, set: (elem: HTMLTextAreaElement, value: string) => void}}
633
+ */
607
634
  const {
608
635
  get = function(elem) {
609
636
  return elem.value;
@@ -611,7 +638,11 @@
611
638
  set = function(elem, value) {
612
639
  elem.value = value;
613
640
  },
614
- } = $.valHooks.textarea || {}; // @type {?Object.<string, function>}
641
+ } = $.valHooks.textarea || {};
642
+ /**
643
+ * @param {HTMLTextAreaElement} elem
644
+ * @returns {boolean}
645
+ */
615
646
  const isWikiplus = elem => ['Wikiplus-Quickedit', 'Wikiplus-Setting-Input'].includes(elem.id);
616
647
  $.valHooks.textarea = {
617
648
  get(elem) {
@@ -628,8 +659,21 @@
628
659
 
629
660
  await i18nPromise; // 以下内容依赖I18N
630
661
 
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
+
631
671
  // 设置对话框
632
- let dialog, widget, indentWidget, field, indentField;
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;
633
677
  const toggleIndent = (value = addons) => {
634
678
  indentField.toggle(value.includes('indentWithSpace'));
635
679
  };
@@ -637,7 +681,7 @@
637
681
  minerva: 'page-actions-overflow',
638
682
  citizen: 'p-actions',
639
683
  };
640
- const $portlet = $(mw.util.addPortletLink(
684
+ const /** @type {JQuery<HTMLLIElement} */ $portlet = $(mw.util.addPortletLink(
641
685
  portletContainer[skin] || 'p-cactions', '#', msg('portlet'), 'wphl-settings',
642
686
  )).click(async e => {
643
687
  e.preventDefault();
@@ -645,7 +689,7 @@
645
689
  await mw.loader.using(['oojs-ui-windows', 'oojs-ui.styles.icons-content']);
646
690
  // eslint-disable-next-line require-atomic-updates
647
691
  dialog = new OO.ui.MessageDialog({id: 'Wikiplus-highlight-dialog'});
648
- const windowManager = new OO.ui.WindowManager();
692
+ const /** @type {OOUI.widget} */ windowManager = new OO.ui.WindowManager();
649
693
  windowManager.$element.appendTo(document.body);
650
694
  windowManager.addWindows([dialog]);
651
695
  widget = new OO.ui.CheckboxMultiselectInputWidget({
@@ -655,6 +699,7 @@
655
699
  {data: 'trailingspace', label: msg('addon-trailingspace')},
656
700
  {data: 'matchBrackets', label: msg('addon-matchbrackets')},
657
701
  {data: 'matchTags', label: msg('addon-matchtags')},
702
+ {data: 'fold', label: msg('addon-fold')},
658
703
  {data: 'contextmenu', label: msg('addon-contextmenu')},
659
704
  {data: 'indentWithSpace', label: msg('addon-indentwithspace')},
660
705
  ],