@bhsd/codemirror-mediawiki 2.9.0 → 2.9.2
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/dist/main.min.js +11 -11
- package/dist/mw.min.js +4 -4
- package/dist/mw.min.js.map +4 -4
- package/i18n/en.json +1 -1
- package/i18n/zh-hans.json +1 -1
- package/i18n/zh-hant.json +1 -1
- package/package.json +1 -3
- package/mw/README.md +0 -156
- package/mw/base.ts +0 -262
- package/mw/config.ts +0 -183
- package/mw/msg.ts +0 -144
- package/mw/openLinks.ts +0 -100
- package/mw/preference.ts +0 -281
- package/mw/textSelection.ts +0 -122
- package/mw/wikiEditor.ts +0 -30
package/mw/README.md
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
<details>
|
|
2
|
-
<summary>Expand</summary>
|
|
3
|
-
|
|
4
|
-
- [Usage](#usage)
|
|
5
|
-
- [Constructor](#constructor)
|
|
6
|
-
- [Accessors](#accessors)
|
|
7
|
-
- [Methods](#methods)
|
|
8
|
-
- [defaultLint](#defaultlint)
|
|
9
|
-
- [Static properties](#static-properties)
|
|
10
|
-
- [version](#version)
|
|
11
|
-
- [Static methods](#static-methods)
|
|
12
|
-
- [fromTextArea](#fromtextarea)
|
|
13
|
-
- [Extensions](#extensions)
|
|
14
|
-
- [openLinks](#openlinks)
|
|
15
|
-
- [wikiEditor](#wikieditor)
|
|
16
|
-
- [save](#save)
|
|
17
|
-
|
|
18
|
-
</details>
|
|
19
|
-
|
|
20
|
-
# Usage
|
|
21
|
-
|
|
22
|
-
You can download the code via CDN, for example:
|
|
23
|
-
|
|
24
|
-
```js
|
|
25
|
-
// static import
|
|
26
|
-
import {CodeMirror} from 'https://cdn.jsdelivr.net/npm/@bhsd/codemirror-mediawiki/dist/mw.min.js';
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
or
|
|
30
|
-
|
|
31
|
-
```js
|
|
32
|
-
import {CodeMirror} from 'https://unpkg.com/@bhsd/codemirror-mediawiki/dist/mw.min.js';
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
or
|
|
36
|
-
|
|
37
|
-
```js
|
|
38
|
-
// dynamic import
|
|
39
|
-
const {CodeMirror} = await import('https://cdn.jsdelivr.net/npm/@bhsd/codemirror-mediawiki/dist/mw.min.js');
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
or
|
|
43
|
-
|
|
44
|
-
```js
|
|
45
|
-
const {CodeMirror} = await import('https://unpkg.com/@bhsd/codemirror-mediawiki/dist/mw.min.js');
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
The script also loads the [styles](../mediawiki.css), adds a button to configure user preferences, and watches `Shift`-clicks of any textarea.
|
|
49
|
-
|
|
50
|
-
# Constructor
|
|
51
|
-
|
|
52
|
-
<details>
|
|
53
|
-
<summary>Expand</summary>
|
|
54
|
-
|
|
55
|
-
*version added: 2.2.2*
|
|
56
|
-
|
|
57
|
-
The `CodeMirror` class extends the [`CodeMirror6`](../README.md#constructor) class with one more argument to specify the namespace.
|
|
58
|
-
|
|
59
|
-
**param**: `HTMLTextAreaElement` the textarea element to be replaced by CodeMirror
|
|
60
|
-
**param**: `string` the language mode to be used, default as plain text
|
|
61
|
-
**param**: `number` the namespace id associated with the content, default as the current namespace
|
|
62
|
-
**param**: `unknown` the optional language configuration
|
|
63
|
-
|
|
64
|
-
```js
|
|
65
|
-
const cm = new CodeMirror6(textarea); // plain text
|
|
66
|
-
const cm = new CodeMirror6(textarea, 'mediawiki', undefined, mwConfig);
|
|
67
|
-
const cm = new CodeMirror6(textarea, 'html', 274, mwConfig); // mixed MediaWiki-HTML
|
|
68
|
-
const cm = new CodeMirror6(textarea, 'css');
|
|
69
|
-
const cm = new CodeMirror6(textarea, 'javascript');
|
|
70
|
-
const cm = new CodeMirror6(textarea, 'json');
|
|
71
|
-
const cm = new CodeMirror6(textarea, 'lua');
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
</details>
|
|
75
|
-
|
|
76
|
-
# Accessors
|
|
77
|
-
|
|
78
|
-
The `CodeMirror` class inherits all the [accessors](../README.md#accessors) from the `CodeMirror6` class.
|
|
79
|
-
|
|
80
|
-
# Methods
|
|
81
|
-
|
|
82
|
-
The `CodeMirror` class inherits all the [methods](../README.md#methods) from the `CodeMirror6` class and addes more.
|
|
83
|
-
|
|
84
|
-
## defaultLint
|
|
85
|
-
|
|
86
|
-
<details>
|
|
87
|
-
<summary>Expand</summary>
|
|
88
|
-
|
|
89
|
-
*version added: 2.1.9*
|
|
90
|
-
|
|
91
|
-
**param**: `boolean` whether to start linting
|
|
92
|
-
**param**: `Record<string, unknown> | number` the optional linter configuration or the namespace id
|
|
93
|
-
Lint with a default linter.
|
|
94
|
-
|
|
95
|
-
```js
|
|
96
|
-
cm.defaultLint(true, 0);
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
</details>
|
|
100
|
-
|
|
101
|
-
# Static properties
|
|
102
|
-
|
|
103
|
-
## version
|
|
104
|
-
|
|
105
|
-
<details>
|
|
106
|
-
<summary>Expand</summary>
|
|
107
|
-
|
|
108
|
-
*version added: 2.6.3*
|
|
109
|
-
|
|
110
|
-
**type**: `string`
|
|
111
|
-
The version number.
|
|
112
|
-
</details>
|
|
113
|
-
|
|
114
|
-
# Static methods
|
|
115
|
-
|
|
116
|
-
The `CodeMirror` class inherits all the [static methods](../README.md#static-methods) from the `CodeMirror6` class and addes more.
|
|
117
|
-
|
|
118
|
-
## fromTextArea
|
|
119
|
-
|
|
120
|
-
<details>
|
|
121
|
-
<summary>Expand</summary>
|
|
122
|
-
|
|
123
|
-
*version added: 2.2.2*
|
|
124
|
-
|
|
125
|
-
**param**: `HTMLTextAreaElement` the textarea element to be replaced by CodeMirror
|
|
126
|
-
**param**: `string` the language mode to be used, default as plain text
|
|
127
|
-
**param**: `number` the namespace id associated with the content, default as the current namespace
|
|
128
|
-
Replace the textarea with CodeMirror editor.
|
|
129
|
-
|
|
130
|
-
```js
|
|
131
|
-
CodeMirror6.fromTextArea(textarea, 'mediawiki');
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
</details>
|
|
135
|
-
|
|
136
|
-
# Extensions
|
|
137
|
-
|
|
138
|
-
The `CodeMirror` class inherits all the [extensions](../README.md#extensions) from the `CodeMirror6` class and addes more.
|
|
139
|
-
|
|
140
|
-
## openLinks
|
|
141
|
-
|
|
142
|
-
*version added: 2.1.15*
|
|
143
|
-
|
|
144
|
-
CTRL/CMD-click opens a wikilink or template or external link in a new tab.
|
|
145
|
-
|
|
146
|
-
## wikiEditor
|
|
147
|
-
|
|
148
|
-
*version added: 2.4.5*
|
|
149
|
-
|
|
150
|
-
Load the WikiEditor toolbar. This extension can only be used before CodeMirror instantiation, which means it is inaccessible by the [`prefer`](../README.md#prefer) method.
|
|
151
|
-
|
|
152
|
-
## save
|
|
153
|
-
|
|
154
|
-
*version added: 2.7.0*
|
|
155
|
-
|
|
156
|
-
Save preferences as JSON on a user subpage (`Special:Mypage/codemirror-mediawiki.json`).
|
package/mw/base.ts
DELETED
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
import {CodeMirror6, CDN} from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror-mediawiki@2.9.0/dist/main.min.js';
|
|
2
|
-
import {getMwConfig, getParserConfig} from './config';
|
|
3
|
-
import {openLinks} from './openLinks';
|
|
4
|
-
import {instances, textSelection} from './textSelection';
|
|
5
|
-
import {openPreference, prefs, indentKey, wikilintConfig, codeConfigs, loadJSON} from './preference';
|
|
6
|
-
import {msg, setI18N, welcome, REPO_CDN, curVersion, localize} from './msg';
|
|
7
|
-
import {wikiEditor} from './wikiEditor';
|
|
8
|
-
import type {Diagnostic} from '@codemirror/lint';
|
|
9
|
-
import type {Config, LintError} from 'wikiparser-node';
|
|
10
|
-
import type {Linter} from 'eslint';
|
|
11
|
-
import type {LintSource, MwConfig} from '../src/codemirror';
|
|
12
|
-
|
|
13
|
-
// 每次新增插件都需要修改这里
|
|
14
|
-
const baseVersion = '2.7',
|
|
15
|
-
addons = ['save'];
|
|
16
|
-
|
|
17
|
-
mw.loader.load(`${CDN}/${REPO_CDN}/mediawiki.min.css`, 'text/css');
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* jQuery.val overrides for CodeMirror.
|
|
21
|
-
*/
|
|
22
|
-
$.valHooks['textarea'] = {
|
|
23
|
-
get(elem: HTMLTextAreaElement): string {
|
|
24
|
-
const cm = instances.get(elem);
|
|
25
|
-
return cm?.visible ? cm.view.state.doc.toString() : elem.value;
|
|
26
|
-
},
|
|
27
|
-
set(elem: HTMLTextAreaElement, value: string): void {
|
|
28
|
-
const cm = instances.get(elem);
|
|
29
|
-
if (cm?.visible) {
|
|
30
|
-
cm.setContent(value);
|
|
31
|
-
} else {
|
|
32
|
-
elem.value = value;
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const linters: Record<string, LintSource | undefined> = {};
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 判断是否为普通编辑器
|
|
41
|
-
* @param textarea 文本框
|
|
42
|
-
*/
|
|
43
|
-
const isEditor = (textarea: HTMLTextAreaElement): boolean => !textarea.closest('#cm-preference');
|
|
44
|
-
|
|
45
|
-
/** 专用于MW环境的 CodeMirror 6 编辑器 */
|
|
46
|
-
export class CodeMirror extends CodeMirror6 {
|
|
47
|
-
static version = curVersion;
|
|
48
|
-
|
|
49
|
-
ns;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* @param textarea 文本框
|
|
53
|
-
* @param lang 语言
|
|
54
|
-
* @param ns 命名空间
|
|
55
|
-
* @param config 语言设置
|
|
56
|
-
*/
|
|
57
|
-
constructor(textarea: HTMLTextAreaElement, lang?: string, ns?: number, config?: unknown) {
|
|
58
|
-
if (instances.get(textarea)?.visible) {
|
|
59
|
-
throw new RangeError('The textarea has already been replaced by CodeMirror.');
|
|
60
|
-
}
|
|
61
|
-
super(textarea, lang, config);
|
|
62
|
-
this.ns = ns;
|
|
63
|
-
instances.set(textarea, this);
|
|
64
|
-
if (mw.loader.getState('jquery.textSelection') === 'ready') {
|
|
65
|
-
$(textarea).data('jquery.textSelection', textSelection);
|
|
66
|
-
}
|
|
67
|
-
if (isEditor(textarea)) {
|
|
68
|
-
mw.hook('wiki-codemirror6').fire(this);
|
|
69
|
-
if (textarea.id === 'wpTextbox1') {
|
|
70
|
-
textarea.form?.addEventListener('submit', () => {
|
|
71
|
-
const scrollTop = document.querySelector<HTMLInputElement>('#wpScrolltop');
|
|
72
|
-
if (scrollTop) {
|
|
73
|
-
scrollTop.value = String(this.view.scrollDOM.scrollTop);
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
override toggle(show = !this.visible): void {
|
|
81
|
-
super.toggle(show);
|
|
82
|
-
$(this.textarea).data('jquery.textSelection', show && textSelection);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
override async getLinter(opt?: Record<string, unknown>): Promise<LintSource | undefined> {
|
|
86
|
-
const linter = await super.getLinter(opt);
|
|
87
|
-
linters[this.lang] = linter;
|
|
88
|
-
return linter;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* 添加或移除默认 linter
|
|
93
|
-
* @param on 是否添加
|
|
94
|
-
* @param opt linter选项
|
|
95
|
-
* @param ns 命名空间
|
|
96
|
-
*/
|
|
97
|
-
defaultLint(on: boolean, opt: Record<string, unknown>): Promise<void>;
|
|
98
|
-
defaultLint(on: boolean, ns?: number): Promise<void>;
|
|
99
|
-
async defaultLint(on: boolean, optOrNs: Record<string, unknown> | number | undefined = this.ns): Promise<void> {
|
|
100
|
-
if (!on) {
|
|
101
|
-
this.lint();
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
const {lang} = this,
|
|
105
|
-
eslint = codeConfigs.get('ESLint'),
|
|
106
|
-
stylelint = codeConfigs.get('Stylelint');
|
|
107
|
-
let opt: Record<string, unknown> | undefined;
|
|
108
|
-
if (typeof optOrNs === 'number') {
|
|
109
|
-
if (lang === 'mediawiki' && (optOrNs === 10 || optOrNs === 828 || optOrNs === 2)) {
|
|
110
|
-
opt = {include: true};
|
|
111
|
-
} else if (lang === 'javascript') {
|
|
112
|
-
opt = {
|
|
113
|
-
env: {browser: true, es2024: true, jquery: true},
|
|
114
|
-
globals: {mw: 'readonly', mediaWiki: 'readonly', OO: 'readonly'},
|
|
115
|
-
...optOrNs === 8 || optOrNs === 2300 ? {parserOptions: {ecmaVersion: 8}} : {},
|
|
116
|
-
...eslint,
|
|
117
|
-
} as Linter.Config as Record<string, unknown>;
|
|
118
|
-
} else if (lang === 'css' && stylelint) {
|
|
119
|
-
opt = stylelint;
|
|
120
|
-
}
|
|
121
|
-
} else {
|
|
122
|
-
opt = optOrNs;
|
|
123
|
-
}
|
|
124
|
-
if (!(lang in linters)) {
|
|
125
|
-
if (lang === 'mediawiki') {
|
|
126
|
-
const i18n = mw.config.get('wgUserLanguage');
|
|
127
|
-
if (['zh', 'zh-hans', 'zh-cn', 'zh-sg', 'zh-my'].includes(i18n)) {
|
|
128
|
-
opt = {...opt, i18n: 'zh-hans'};
|
|
129
|
-
} else if (['zh-hant', 'zh-tw', 'zh-hk', 'zh-mo'].includes(i18n)) {
|
|
130
|
-
opt = {...opt, i18n: 'zh-hant'};
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
await this.getLinter(opt);
|
|
134
|
-
if (lang === 'mediawiki') {
|
|
135
|
-
const [mwConfig, minConfig] = await Promise.all([getMwConfig(), wikiparse.getConfig()]);
|
|
136
|
-
wikiparse.setConfig(getParserConfig(minConfig, mwConfig));
|
|
137
|
-
}
|
|
138
|
-
} else if (opt) {
|
|
139
|
-
await this.getLinter(opt);
|
|
140
|
-
}
|
|
141
|
-
if (linters[lang]) {
|
|
142
|
-
if (lang === 'mediawiki') {
|
|
143
|
-
this.lint(
|
|
144
|
-
async doc => (await linters[lang]!(doc) as (Diagnostic & {rule: LintError.Rule})[])
|
|
145
|
-
.filter(({rule, severity}) => Number(wikilintConfig[rule]) > Number(severity === 'warning')),
|
|
146
|
-
);
|
|
147
|
-
} else {
|
|
148
|
-
this.lint(linters[lang]);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
override prefer(extensions: string[] | Record<string, boolean>): void {
|
|
154
|
-
super.prefer(extensions);
|
|
155
|
-
const hasExtension = Array.isArray(extensions)
|
|
156
|
-
? (ext: string): boolean => extensions.includes(ext)
|
|
157
|
-
: (ext: string): boolean | undefined => extensions[ext],
|
|
158
|
-
hasLint = hasExtension('lint');
|
|
159
|
-
if (hasLint !== undefined) {
|
|
160
|
-
void this.defaultLint(hasLint);
|
|
161
|
-
}
|
|
162
|
-
openLinks(this, hasExtension('openLinks'));
|
|
163
|
-
if (!Array.isArray(extensions)) {
|
|
164
|
-
for (const [k, v] of Object.entries(extensions)) {
|
|
165
|
-
prefs[v ? 'add' : 'delete'](k);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* 将 textarea 替换为 CodeMirror
|
|
172
|
-
* @param textarea textarea 元素
|
|
173
|
-
* @param lang 语言
|
|
174
|
-
* @param ns 命名空间
|
|
175
|
-
*/
|
|
176
|
-
static async fromTextArea(textarea: HTMLTextAreaElement, lang?: string, ns?: number): Promise<CodeMirror> {
|
|
177
|
-
if (prefs.has('wikiEditor') && isEditor(textarea)) {
|
|
178
|
-
await wikiEditor($(textarea));
|
|
179
|
-
}
|
|
180
|
-
if (!lang && ns === undefined) {
|
|
181
|
-
/* eslint-disable no-param-reassign */
|
|
182
|
-
const {wgAction, wgNamespaceNumber, wgPageContentModel} = mw.config.get();
|
|
183
|
-
if (wgAction === 'edit' || wgAction === 'submit') {
|
|
184
|
-
ns = wgNamespaceNumber;
|
|
185
|
-
lang = wgNamespaceNumber === 274 ? 'html' : wgPageContentModel.toLowerCase();
|
|
186
|
-
} else {
|
|
187
|
-
await mw.loader.using('oojs-ui-windows');
|
|
188
|
-
lang = (await OO.ui.prompt(msg('contentmodel')) || undefined)?.toLowerCase();
|
|
189
|
-
}
|
|
190
|
-
if (lang && lang in langMap) {
|
|
191
|
-
lang = langMap[lang];
|
|
192
|
-
}
|
|
193
|
-
/* eslint-enable no-param-reassign */
|
|
194
|
-
}
|
|
195
|
-
const isWiki = lang === 'mediawiki' || lang === 'html',
|
|
196
|
-
cm = new CodeMirror(textarea, isWiki ? undefined : lang, ns);
|
|
197
|
-
if (isWiki) {
|
|
198
|
-
let config: MwConfig;
|
|
199
|
-
if (mw.config.get('wgServerName').endsWith('.moegirl.org.cn')) {
|
|
200
|
-
if (mw.config.exists('wikilintConfig')) {
|
|
201
|
-
config = mw.config.get('extCodeMirrorConfig') as MwConfig;
|
|
202
|
-
} else {
|
|
203
|
-
const parserConfig: Config = await (await fetch(
|
|
204
|
-
`${CDN}/npm/wikiparser-node@browser/config/moegirl.json`,
|
|
205
|
-
)).json();
|
|
206
|
-
mw.config.set('wikilintConfig', parserConfig);
|
|
207
|
-
config = CodeMirror6.getMwConfig(parserConfig);
|
|
208
|
-
mw.config.set('extCodeMirrorConfig', config);
|
|
209
|
-
}
|
|
210
|
-
} else {
|
|
211
|
-
config = await getMwConfig();
|
|
212
|
-
}
|
|
213
|
-
cm.setLanguage(lang, config);
|
|
214
|
-
}
|
|
215
|
-
await loadJSON;
|
|
216
|
-
cm.prefer([...prefs]);
|
|
217
|
-
const indent = localStorage.getItem(indentKey);
|
|
218
|
-
if (indent) {
|
|
219
|
-
cm.setIndent(indent);
|
|
220
|
-
}
|
|
221
|
-
return cm;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const langMap: Record<string, string> = {
|
|
226
|
-
'sanitized-css': 'css',
|
|
227
|
-
js: 'javascript',
|
|
228
|
-
scribunto: 'lua',
|
|
229
|
-
wikitext: 'mediawiki',
|
|
230
|
-
};
|
|
231
|
-
document.body.addEventListener('click', e => {
|
|
232
|
-
if (e.target instanceof HTMLTextAreaElement && e.shiftKey && !instances.has(e.target)) {
|
|
233
|
-
e.preventDefault();
|
|
234
|
-
void CodeMirror.fromTextArea(e.target);
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
(async () => {
|
|
239
|
-
const portletContainer: Record<string, string> = {
|
|
240
|
-
minerva: 'page-actions-overflow',
|
|
241
|
-
moeskin: 'ca-more-actions',
|
|
242
|
-
citizen: 'p-tb',
|
|
243
|
-
};
|
|
244
|
-
await Promise.all([
|
|
245
|
-
mw.loader.using('mediawiki.util'),
|
|
246
|
-
setI18N(CDN),
|
|
247
|
-
]);
|
|
248
|
-
mw.hook('wiki-codemirror6').add(localize);
|
|
249
|
-
mw.util.addPortletLink(
|
|
250
|
-
portletContainer[mw.config.get('skin')] || 'p-cactions',
|
|
251
|
-
'#',
|
|
252
|
-
msg('title'),
|
|
253
|
-
'cm-settings',
|
|
254
|
-
).addEventListener('click', e => {
|
|
255
|
-
e.preventDefault();
|
|
256
|
-
const textareas = [...document.querySelectorAll<HTMLTextAreaElement>('.cm-editor + textarea')];
|
|
257
|
-
void openPreference(textareas.map(textarea => instances.get(textarea)));
|
|
258
|
-
});
|
|
259
|
-
void welcome(baseVersion, addons);
|
|
260
|
-
})();
|
|
261
|
-
|
|
262
|
-
Object.assign(window, {CodeMirror6: CodeMirror});
|
package/mw/config.ts
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import {setObject, getObject} from './msg';
|
|
2
|
-
import {CodeMirror} from './base';
|
|
3
|
-
import type {Config} from 'wikiparser-node';
|
|
4
|
-
import type {MwConfig} from '../src/mediawiki';
|
|
5
|
-
|
|
6
|
-
declare interface MagicWord {
|
|
7
|
-
name: string;
|
|
8
|
-
aliases: string[];
|
|
9
|
-
'case-sensitive': boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// 和本地缓存有关的常数
|
|
13
|
-
const USING_LOCAL = mw.loader.getState('ext.CodeMirror') !== null,
|
|
14
|
-
DATA_MODULE = mw.loader.getState('ext.CodeMirror.data') ? 'ext.CodeMirror.data' : 'ext.CodeMirror',
|
|
15
|
-
ALL_SETTINGS_CACHE: Record<string, {time: number, config: MwConfig}>
|
|
16
|
-
= getObject('InPageEditMwConfig') || {},
|
|
17
|
-
SITE_ID = `${mw.config.get('wgServerName')}${mw.config.get('wgScriptPath')}`,
|
|
18
|
-
SITE_SETTINGS = ALL_SETTINGS_CACHE[SITE_ID],
|
|
19
|
-
VALID = Number(SITE_SETTINGS?.time) > Date.now() - 86_400 * 1000 * 30;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* 将魔术字信息转换为CodeMirror接受的设置
|
|
23
|
-
* @param magicWords 完整魔术字列表
|
|
24
|
-
* @param rule 过滤函数
|
|
25
|
-
* @param flip 是否反向筛选对大小写敏感的魔术字
|
|
26
|
-
*/
|
|
27
|
-
const getConfig = (
|
|
28
|
-
magicWords: MagicWord[],
|
|
29
|
-
rule: (word: MagicWord) => boolean,
|
|
30
|
-
flip?: boolean,
|
|
31
|
-
): Record<string, string> => {
|
|
32
|
-
const words = magicWords.filter(rule).filter(({'case-sensitive': i}) => i !== flip)
|
|
33
|
-
.flatMap(({aliases, name, 'case-sensitive': i}) => aliases.map(alias => ({
|
|
34
|
-
alias: (i ? alias : alias.toLowerCase()).replace(/:$/u, ''),
|
|
35
|
-
name,
|
|
36
|
-
}))),
|
|
37
|
-
obj: Record<string, string> = {};
|
|
38
|
-
for (const {alias, name} of words) {
|
|
39
|
-
obj[alias] = name;
|
|
40
|
-
}
|
|
41
|
-
return obj;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 将魔术字信息转换为CodeMirror接受的设置
|
|
46
|
-
* @param magicWords 完整魔术字列表
|
|
47
|
-
* @param rule 过滤函数
|
|
48
|
-
*/
|
|
49
|
-
const getConfigPair = (
|
|
50
|
-
magicWords: MagicWord[],
|
|
51
|
-
rule: (word: MagicWord) => boolean,
|
|
52
|
-
): [Record<string, string>, Record<string, string>] => [true, false]
|
|
53
|
-
.map(bool => getConfig(magicWords, rule, bool)) as [Record<string, string>, Record<string, string>];
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* 将设置保存到mw.config
|
|
57
|
-
* @param config 设置
|
|
58
|
-
*/
|
|
59
|
-
const setConfig = (config: MwConfig): void => {
|
|
60
|
-
mw.config.set('extCodeMirrorConfig', config);
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
/** 加载CodeMirror的mediawiki模块需要的设置 */
|
|
64
|
-
export const getMwConfig = async (): Promise<MwConfig> => {
|
|
65
|
-
if (USING_LOCAL && !VALID) { // 只在localStorage过期时才会重新加载ext.CodeMirror.data
|
|
66
|
-
await mw.loader.using(DATA_MODULE);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
let config = mw.config.get('extCodeMirrorConfig') as MwConfig | null;
|
|
70
|
-
if (!config && VALID) {
|
|
71
|
-
({config} = SITE_SETTINGS!);
|
|
72
|
-
setConfig(config);
|
|
73
|
-
}
|
|
74
|
-
const isIPE = config && Object.values(config.functionSynonyms[0]).includes(true as unknown as string),
|
|
75
|
-
nsid = mw.config.get('wgNamespaceIds'),
|
|
76
|
-
tagModes = CodeMirror.mwTagModes;
|
|
77
|
-
// 情形1:config已更新,可能来自localStorage
|
|
78
|
-
if (config?.img && config.variants && !isIPE) {
|
|
79
|
-
config.urlProtocols = config.urlProtocols.replace(/\\:/gu, ':');
|
|
80
|
-
return {...config, nsid, tagModes};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// 以下情形均需要发送API请求
|
|
84
|
-
// 情形2:localStorage未过期但不包含新设置
|
|
85
|
-
// 情形3:新加载的 ext.CodeMirror.data
|
|
86
|
-
// 情形4:`config === null`
|
|
87
|
-
await mw.loader.using('mediawiki.api');
|
|
88
|
-
const {
|
|
89
|
-
query: {general: {variants}, magicwords, extensiontags, functionhooks, variables},
|
|
90
|
-
}: {
|
|
91
|
-
query: {
|
|
92
|
-
general: {variants?: {code: string}[]};
|
|
93
|
-
magicwords: MagicWord[];
|
|
94
|
-
extensiontags: string[];
|
|
95
|
-
functionhooks: string[];
|
|
96
|
-
variables: string[];
|
|
97
|
-
};
|
|
98
|
-
} = await new mw.Api().get({
|
|
99
|
-
meta: 'siteinfo',
|
|
100
|
-
siprop: [
|
|
101
|
-
'general',
|
|
102
|
-
'magicwords',
|
|
103
|
-
...config && !isIPE ? [] : ['extensiontags', 'functionhooks', 'variables'],
|
|
104
|
-
],
|
|
105
|
-
formatversion: '2',
|
|
106
|
-
}) as any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
107
|
-
const others = new Set(['msg', 'raw', 'msgnw', 'subst', 'safesubst']);
|
|
108
|
-
|
|
109
|
-
// 先处理魔术字和状态开关
|
|
110
|
-
if (config && !isIPE) { // 情形2或3
|
|
111
|
-
const {functionSynonyms: [insensitive]} = config;
|
|
112
|
-
if (!('subst' in insensitive)) {
|
|
113
|
-
Object.assign(insensitive, getConfig(magicwords, ({name}) => others.has(name)));
|
|
114
|
-
}
|
|
115
|
-
} else { // 情形4:`config === null`
|
|
116
|
-
// @ts-expect-error incomplete properties
|
|
117
|
-
config = {
|
|
118
|
-
tagModes: {},
|
|
119
|
-
tags: {},
|
|
120
|
-
urlProtocols: mw.config.get('wgUrlProtocols').replace(/\\:/gu, ':'),
|
|
121
|
-
};
|
|
122
|
-
for (const tag of extensiontags) {
|
|
123
|
-
config!.tags[tag.slice(1, -1)] = true;
|
|
124
|
-
}
|
|
125
|
-
const functions = new Set([
|
|
126
|
-
...functionhooks,
|
|
127
|
-
...variables,
|
|
128
|
-
...others,
|
|
129
|
-
]);
|
|
130
|
-
config!.functionSynonyms = getConfigPair(magicwords, ({name}) => functions.has(name));
|
|
131
|
-
config!.doubleUnderscore = getConfigPair(
|
|
132
|
-
magicwords,
|
|
133
|
-
({aliases}) => aliases.some(alias => /^__.+__$/u.test(alias)),
|
|
134
|
-
);
|
|
135
|
-
config!.fromApi = true;
|
|
136
|
-
}
|
|
137
|
-
config!.img = getConfig(magicwords, ({name}) => name.startsWith('img_'));
|
|
138
|
-
config!.variants = variants ? variants.map(({code}) => code) : [];
|
|
139
|
-
config!.nsid = nsid;
|
|
140
|
-
Object.assign(config!.tagModes, tagModes);
|
|
141
|
-
setConfig(config!);
|
|
142
|
-
ALL_SETTINGS_CACHE[SITE_ID] = {config: config!, time: Date.now()};
|
|
143
|
-
setObject('InPageEditMwConfig', ALL_SETTINGS_CACHE);
|
|
144
|
-
return config!;
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* 将MwConfig转换为Config
|
|
149
|
-
* @param minConfig 基础Config
|
|
150
|
-
* @param mwConfig
|
|
151
|
-
*/
|
|
152
|
-
export const getParserConfig = (minConfig: Config, mwConfig: MwConfig): Config => {
|
|
153
|
-
if (mw.config.exists('wikilintConfig')) {
|
|
154
|
-
return mw.config.get('wikilintConfig') as Config;
|
|
155
|
-
}
|
|
156
|
-
const config: Config = {
|
|
157
|
-
...minConfig,
|
|
158
|
-
ext: Object.keys(mwConfig.tags),
|
|
159
|
-
namespaces: mw.config.get('wgFormattedNamespaces'),
|
|
160
|
-
nsid: mwConfig.nsid,
|
|
161
|
-
doubleUnderscore: mwConfig.doubleUnderscore.map(
|
|
162
|
-
obj => Object.keys(obj).map(s => s.slice(2, -2)),
|
|
163
|
-
) as [string[], string[]],
|
|
164
|
-
variants: mwConfig.variants!,
|
|
165
|
-
protocol: mwConfig.urlProtocols,
|
|
166
|
-
};
|
|
167
|
-
[config.parserFunction[0]] = mwConfig.functionSynonyms;
|
|
168
|
-
if (!USING_LOCAL) {
|
|
169
|
-
for (const [key, val] of Object.entries(mwConfig.functionSynonyms[0])) {
|
|
170
|
-
if (!key.startsWith('#')) {
|
|
171
|
-
config.parserFunction[0][`#${key}`] = val;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
config.parserFunction[1] = [
|
|
176
|
-
...Object.keys(mwConfig.functionSynonyms[1]),
|
|
177
|
-
'=',
|
|
178
|
-
];
|
|
179
|
-
for (const [key, val] of Object.entries(mwConfig.img!)) {
|
|
180
|
-
config.img[key] = val.slice(4);
|
|
181
|
-
}
|
|
182
|
-
return config;
|
|
183
|
-
};
|