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/README.md +10 -2
- package/bump.sh +3 -1
- package/dist/main.min.js +1 -1
- package/dist/main.min.js.map +1 -1
- package/fold.js +244 -0
- package/i18n/en.json +9 -2
- package/i18n/zh-hans.json +9 -2
- package/i18n/zh-hant.json +9 -2
- package/jsconfig.json +3 -0
- package/main.js +199 -149
- package/matchtags.js +45 -46
- package/package.json +2 -1
- package/search.js +9 -19
package/main.js
CHANGED
|
@@ -8,12 +8,10 @@
|
|
|
8
8
|
(async () => {
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
-
const version = '2.
|
|
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]>) =>
|
|
41
|
+
* @type {(entries: Iterable<[string, any]>) => Record<string, any>}
|
|
44
42
|
*/
|
|
45
43
|
const fromEntries = Object.fromEntries || (entries => {
|
|
46
|
-
const /** @type {
|
|
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
|
|
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.
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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 {
|
|
180
|
-
/** @type {() => JQuery<
|
|
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('
|
|
227
|
+
welcome = notify(`welcome-${newAddon ? 'new-addon' : 'upgrade'}`, version, newAddon);
|
|
186
228
|
}
|
|
187
229
|
|
|
188
|
-
const /** @type {
|
|
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
|
|
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.
|
|
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' &&
|
|
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
|
-
|
|
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
|
|
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) {
|
|
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 =>
|
|
396
|
-
|
|
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 {
|
|
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(
|
|
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(
|
|
508
|
+
config.redirect = magicwords.find(({name}) => name === 'redirect').aliases;
|
|
450
509
|
config.img = getConfig(
|
|
451
|
-
getAliases(magicwords.filter(
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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
|
-
|
|
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 = $(
|
|
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
|
|
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.
|
|
675
|
-
/** @type {OOUI.
|
|
676
|
-
/** @type {OOUI.
|
|
677
|
-
/** @type {OOUI.
|
|
678
|
-
/** @type {OOUI.
|
|
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
|
|
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
|
|
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
|
})();
|