wikiplus-highlight 2.7.5 → 2.10.0
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/README.md +35 -5
- package/bump.sh +23 -13
- package/dist/main.min.js +1 -1
- package/dist/main.min.js.map +1 -1
- package/fold.js +179 -0
- package/i18n/en.json +8 -2
- package/i18n/zh-hans.json +8 -2
- package/i18n/zh-hant.json +8 -2
- package/jsconfig.json +8 -0
- package/main.js +130 -85
- package/matchtags.js +51 -26
- package/package.json +7 -3
- package/search.js +34 -11
package/main.js
CHANGED
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
(async () => {
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
-
const version = '2.
|
|
11
|
+
const version = '2.10',
|
|
12
|
+
newAddon = 1;
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* polyfill for mw.storage
|
|
15
|
-
* @type {
|
|
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 {
|
|
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
|
|
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.
|
|
93
|
+
MW_CDN = 'gh/bhsd-harry/codemirror-mediawiki@1.1.4',
|
|
97
94
|
REPO_CDN = `npm/wikiplus-highlight@${majorVersion}`;
|
|
98
95
|
|
|
99
|
-
|
|
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
|
-
|
|
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] || {},
|
|
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,
|
|
158
|
-
indent = storage.getObject('Wikiplus-highlight-indent') || defaultIndent;
|
|
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')
|
|
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'),
|
|
167
|
-
|
|
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('
|
|
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',
|
|
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
|
|
207
|
-
* @returns {
|
|
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 {
|
|
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(
|
|
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 {
|
|
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 {
|
|
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 {
|
|
371
|
-
* @
|
|
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(
|
|
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 {
|
|
378
|
-
* @returns {Object
|
|
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(
|
|
398
|
-
|
|
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 {
|
|
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('
|
|
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
|
-
|
|
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(
|
|
585
|
-
|
|
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
|
|
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
|
-
|
|
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 || {};
|
|
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
|
|
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
|
],
|