@bhsd/codemirror-mediawiki 2.1.4 → 2.1.7

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.
@@ -13,6 +13,8 @@ export interface MwConfig {
13
13
  variants?: string[];
14
14
  img?: Record<string, string>;
15
15
  nsid: Record<string, number>;
16
+ permittedHtmlTags?: string[];
17
+ implicitlyClosedHtmlTags?: string[];
16
18
  }
17
19
  /**
18
20
  * Gets a LanguageSupport instance for the MediaWiki mode.
package/mw/dist/base.js CHANGED
@@ -1,7 +1,7 @@
1
- import { CodeMirror6 } from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror-mediawiki@2.1.4/dist/main.min.js';
1
+ import { CodeMirror6 } from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror-mediawiki@2.1.7/dist/main.min.js';
2
2
  (() => {
3
3
  var _a;
4
- mw.loader.load('https://testingcf.jsdelivr.net/npm/@bhsd/codemirror-mediawiki@2.1.4/mediawiki.min.css', 'text/css');
4
+ mw.loader.load('https://testingcf.jsdelivr.net/npm/@bhsd/codemirror-mediawiki@2.1.7/mediawiki.min.css', 'text/css');
5
5
  const instances = new WeakMap();
6
6
  $.valHooks['textarea'] = {
7
7
  get(elem) {
@@ -157,7 +157,7 @@ import { CodeMirror6 } from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror
157
157
  const { lang } = this;
158
158
  if (!(lang in linters)) {
159
159
  linters[lang] = await this.getLinter(opt);
160
- if (this.lang === 'mediawiki' || this.lang === 'html') {
160
+ if (this.lang === 'mediawiki') {
161
161
  const mwConfig = await getMwConfig(), config = {
162
162
  ...await wikiparse.getConfig(),
163
163
  ext: Object.keys(mwConfig.tags),
@@ -244,5 +244,5 @@ import { CodeMirror6 } from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror
244
244
  })();
245
245
  }
246
246
  });
247
- Object.assign(window, { CodeMirror });
247
+ Object.assign(window, { CodeMirror6: CodeMirror });
248
248
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bhsd/codemirror-mediawiki",
3
- "version": "2.1.4",
3
+ "version": "2.1.7",
4
4
  "description": "Modified CodeMirror mode based on wikimedia/mediawiki-extensions-CodeMirror",
5
5
  "keywords": [
6
6
  "mediawiki",
@@ -12,9 +12,13 @@
12
12
  },
13
13
  "license": "GPL-2.0",
14
14
  "files":[
15
- "/dist/*.[jt]s",
15
+ "/src/",
16
+ "!/src/gh-page.ts",
17
+ "!/src/plugins.ts",
18
+ "!/src/*.d.ts",
19
+ "/dist/",
16
20
  "/mediawiki.css",
17
- "/mw/dist/*.js"
21
+ "/mw/dist/"
18
22
  ],
19
23
  "browser": "dist/main.min.js",
20
24
  "main": "./dist/main.min.js",
@@ -24,7 +28,7 @@
24
28
  "url": "git+https://github.com/bhsd-harry/codemirror-mediawiki.git"
25
29
  },
26
30
  "scripts": {
27
- "build:core": "esbuild ./src/codemirror.ts --bundle --minify --target=es2018 --format=esm --outfile=dist/main.min.js; tsc --emitDeclarationOnly; rm dist/gh-page.d.ts",
31
+ "build:core": "esbuild ./src/codemirror.ts --bundle --minify --target=es2018 --format=esm --sourcemap --outfile=dist/main.min.js; tsc --emitDeclarationOnly; rm dist/gh-page.d.ts",
28
32
  "build:mw": "bash build.sh mw/base.ts mw/dist/base.js",
29
33
  "build:gh-page": "bash build.sh src/gh-page.ts gh-page.js",
30
34
  "build": "npm run build:core && npm run build:mw",
@@ -66,6 +70,6 @@
66
70
  "stylelint-config-recommended": "^14.0.0",
67
71
  "types-mediawiki": "^1.4.0",
68
72
  "typescript": "^5.1.6",
69
- "wikilint": "^2.3.0"
73
+ "wikilint": "^2.3.2"
70
74
  }
71
75
  }
@@ -0,0 +1,386 @@
1
+ import {Compartment, EditorState} from '@codemirror/state';
2
+ import {
3
+ EditorView,
4
+ lineNumbers,
5
+ keymap,
6
+ highlightSpecialChars,
7
+ highlightActiveLine,
8
+ highlightWhitespace,
9
+ highlightTrailingWhitespace,
10
+ } from '@codemirror/view';
11
+ import {
12
+ syntaxHighlighting,
13
+ defaultHighlightStyle,
14
+ indentOnInput,
15
+ StreamLanguage,
16
+ LanguageSupport,
17
+ bracketMatching,
18
+ indentUnit,
19
+ } from '@codemirror/language';
20
+ import {defaultKeymap, historyKeymap, history} from '@codemirror/commands';
21
+ import {searchKeymap} from '@codemirror/search';
22
+ import {linter, lintGutter, openLintPanel, closeLintPanel} from '@codemirror/lint';
23
+ import {closeBrackets} from '@codemirror/autocomplete';
24
+ import {mediawiki, html} from './mediawiki';
25
+ import * as plugins from './plugins';
26
+ import type {ViewPlugin} from '@codemirror/view';
27
+ import type {Extension, Text} from '@codemirror/state';
28
+ import type {Diagnostic} from '@codemirror/lint';
29
+ import type {Highlighter} from '@lezer/highlight';
30
+ import type {Linter} from 'eslint';
31
+
32
+ export type {MwConfig} from './mediawiki';
33
+ export type LintSource = (doc: Text) => Diagnostic[] | Promise<Diagnostic[]>;
34
+
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
+ const languages: Record<string, (config?: any) => LanguageSupport | []> = {
37
+ plain: () => [],
38
+ mediawiki,
39
+ html,
40
+ };
41
+ for (const [language, parser] of Object.entries(plugins)) {
42
+ languages[language] = (): LanguageSupport => new LanguageSupport(StreamLanguage.define(parser));
43
+ }
44
+ const linters: Record<string, Extension> = {};
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
+ const avail: Record<string, [ (config?: any) => Extension, Record<string, unknown> ]> = {
47
+ highlightSpecialChars: [highlightSpecialChars, {}],
48
+ highlightActiveLine: [highlightActiveLine, {}],
49
+ highlightWhitespace: [highlightWhitespace, {}],
50
+ highlightTrailingWhitespace: [highlightTrailingWhitespace, {}],
51
+ bracketMatching: [bracketMatching, {mediawiki: {brackets: '[]{}'}}],
52
+ closeBrackets: [closeBrackets, {}],
53
+ };
54
+
55
+ /**
56
+ * 使用传统方法加载脚本
57
+ * @param src 脚本地址
58
+ * @param target 脚本全局变量名
59
+ */
60
+ const loadScript = (src: string, target: string): Promise<void> => new Promise(resolve => {
61
+ if (target in window) {
62
+ resolve();
63
+ return;
64
+ }
65
+ const script = document.createElement('script');
66
+ script.src = `https://testingcf.jsdelivr.net/${src}`;
67
+ script.onload = (): void => {
68
+ resolve();
69
+ };
70
+ document.head.append(script);
71
+ });
72
+
73
+ export class CodeMirror6 {
74
+ readonly #textarea;
75
+ readonly #language;
76
+ readonly #linter;
77
+ readonly #extensions;
78
+ readonly #indent;
79
+ readonly #view;
80
+ #lang;
81
+ #visible = true;
82
+
83
+ get textarea(): HTMLTextAreaElement {
84
+ return this.#textarea;
85
+ }
86
+
87
+ get view(): EditorView {
88
+ return this.#view;
89
+ }
90
+
91
+ get lang(): string {
92
+ return this.#lang;
93
+ }
94
+
95
+ get visible(): boolean {
96
+ return this.#visible;
97
+ }
98
+
99
+ /**
100
+ * @param textarea 文本框
101
+ * @param lang 语言
102
+ * @param config 语言设置
103
+ */
104
+ constructor(textarea: HTMLTextAreaElement, lang = 'plain', config?: unknown) {
105
+ this.#textarea = textarea;
106
+ this.#lang = lang;
107
+ this.#language = new Compartment();
108
+ this.#linter = new Compartment();
109
+ this.#extensions = new Compartment();
110
+ this.#indent = new Compartment();
111
+ let timer: number | undefined;
112
+ const extensions = [
113
+ this.#language.of(languages[lang]!(config)),
114
+ this.#linter.of([]),
115
+ this.#extensions.of([]),
116
+ this.#indent.of(indentUnit.of('\t')),
117
+ syntaxHighlighting(defaultHighlightStyle as Highlighter),
118
+ EditorView.contentAttributes.of({
119
+ accesskey: textarea.accessKey,
120
+ dir: textarea.dir,
121
+ lang: textarea.lang,
122
+ }),
123
+ EditorState.readOnly.of(textarea.readOnly),
124
+ lineNumbers(),
125
+ EditorView.lineWrapping,
126
+ history(),
127
+ indentOnInput(),
128
+ keymap.of([
129
+ ...defaultKeymap,
130
+ ...historyKeymap,
131
+ ...searchKeymap,
132
+ ]),
133
+ EditorView.updateListener.of(({state: {doc}, docChanged}) => {
134
+ if (docChanged) {
135
+ clearTimeout(timer);
136
+ timer = window.setTimeout(() => {
137
+ textarea.value = doc.toString();
138
+ }, 400);
139
+ }
140
+ }),
141
+ ];
142
+ this.#view = new EditorView({
143
+ extensions,
144
+ doc: textarea.value,
145
+ });
146
+ const {offsetHeight, selectionStart, selectionEnd, scrollTop} = textarea,
147
+ {fontSize, lineHeight} = getComputedStyle(textarea),
148
+ hasFocus = document.activeElement === textarea;
149
+ textarea.parentNode!.insertBefore(this.#view.dom, textarea);
150
+ this.#view.dom.style.minHeight = '2em';
151
+ this.#view.dom.style.height = `${offsetHeight}px`;
152
+ this.#view.dom.style.fontSize = fontSize;
153
+ this.#view.scrollDOM.style.lineHeight = lineHeight;
154
+ this.#view.requestMeasure();
155
+ this.#view.dispatch({
156
+ selection: {anchor: selectionStart, head: selectionEnd},
157
+ });
158
+ textarea.style.display = 'none';
159
+ if (hasFocus) {
160
+ this.#view.focus();
161
+ }
162
+ requestAnimationFrame(() => {
163
+ this.#view.scrollDOM.scrollTop = scrollTop;
164
+ });
165
+ }
166
+
167
+ /**
168
+ * 设置语言
169
+ * @param lang 语言
170
+ * @param config 语言设置
171
+ */
172
+ setLanguage(lang = 'plain', config?: unknown): void {
173
+ this.#view.dispatch({
174
+ effects: [
175
+ this.#language.reconfigure(languages[lang]!(config)),
176
+ this.#linter.reconfigure(linters[lang] || []),
177
+ ],
178
+ });
179
+ this.#lang = lang;
180
+ (linters[lang] ? openLintPanel : closeLintPanel)(this.#view);
181
+ }
182
+
183
+ /**
184
+ * 开始语法检查
185
+ * @param lintSource 语法检查函数
186
+ */
187
+ lint(lintSource?: LintSource): void {
188
+ const linterExtension = lintSource
189
+ ? [
190
+ linter(view => lintSource(view.state.doc)),
191
+ lintGutter(),
192
+ ]
193
+ : [];
194
+ if (lintSource) {
195
+ linters[this.#lang] = linterExtension;
196
+ } else {
197
+ delete linters[this.#lang];
198
+ }
199
+ this.#view.dispatch({
200
+ effects: [this.#linter.reconfigure(linterExtension)],
201
+ });
202
+ (lintSource ? openLintPanel : closeLintPanel)(this.#view);
203
+ }
204
+
205
+ /** 立即更新语法检查 */
206
+ update(): void {
207
+ const extension = this.#linter.get(this.#view.state) as [[ unknown, ViewPlugin<{
208
+ set: boolean;
209
+ force(): void;
210
+ }> ]] | [];
211
+ if (extension.length > 0) {
212
+ const plugin = this.#view.plugin(extension[0]![1])!;
213
+ plugin.set = true;
214
+ plugin.force();
215
+ }
216
+ }
217
+
218
+ /**
219
+ * 添加扩展
220
+ * @param names 扩展名
221
+ */
222
+ prefer(names: readonly string[]): void {
223
+ this.#view.dispatch({
224
+ effects: [
225
+ this.#extensions.reconfigure(names.map(name => {
226
+ const [extension, configs] = avail[name]!;
227
+ return extension(configs[this.#lang]);
228
+ })),
229
+ ],
230
+ });
231
+ }
232
+
233
+ /**
234
+ * 设置缩进
235
+ * @param indent 缩进字符串
236
+ */
237
+ setIndent(indent: string): void {
238
+ this.#view.dispatch({
239
+ effects: [this.#indent.reconfigure(indentUnit.of(indent))],
240
+ });
241
+ }
242
+
243
+ /** 获取默认linter */
244
+ async getLinter(opt?: Record<string, unknown>): Promise<LintSource | undefined> {
245
+ switch (this.#lang) {
246
+ case 'mediawiki': {
247
+ const src = 'combine/npm/wikiparser-node@1.3.2-b/extensions/dist/base.min.js,'
248
+ + 'npm/wikiparser-node@1.3.2-b/extensions/dist/lint.min.js';
249
+ await loadScript(src, 'wikiparse');
250
+ const wikiLinter = new wikiparse.Linter(opt?.['include'] as boolean);
251
+ return doc => wikiLinter.codemirror(doc.toString());
252
+ }
253
+ case 'javascript': {
254
+ await loadScript('npm/eslint-linter-browserify', 'eslint');
255
+ /** @see https://npmjs.com/package/@codemirror/lang-javascript */
256
+ const esLinter = new eslint.Linter(),
257
+ conf: Linter.Config = {
258
+ env: {
259
+ browser: true,
260
+ es2018: true,
261
+ },
262
+ parserOptions: {
263
+ ecmaVersion: 9,
264
+ sourceType: 'module',
265
+ },
266
+ rules: {},
267
+ ...opt,
268
+ };
269
+ for (const [name, {meta}] of esLinter.getRules()) {
270
+ if (meta?.docs!.recommended) {
271
+ conf.rules![name] ??= 2;
272
+ }
273
+ }
274
+ return doc => esLinter.verify(doc.toString(), conf)
275
+ .map(({message, severity, line, column, endLine, endColumn}) => {
276
+ const from = doc.line(line).from + column - 1;
277
+ return {
278
+ message,
279
+ severity: severity === 1 ? 'warning' : 'error',
280
+ from,
281
+ to: endLine === undefined ? from + 1 : doc.line(endLine).from + endColumn! - 1,
282
+ };
283
+ });
284
+ }
285
+ case 'css': {
286
+ await loadScript('gh/openstyles/stylelint-bundle/dist/stylelint-bundle.min.js', 'stylelint');
287
+ /** @see https://npmjs.com/package/stylelint-config-recommended */
288
+ const conf = {
289
+ rules: {
290
+ 'annotation-no-unknown': true,
291
+ 'at-rule-no-unknown': true,
292
+ 'block-no-empty': true,
293
+ 'color-no-invalid-hex': true,
294
+ 'comment-no-empty': true,
295
+ 'custom-property-no-missing-var-function': true,
296
+ 'declaration-block-no-duplicate-custom-properties': true,
297
+ 'declaration-block-no-duplicate-properties': [
298
+ true,
299
+ {
300
+ ignore: ['consecutive-duplicates-with-different-syntaxes'],
301
+ },
302
+ ],
303
+ 'declaration-block-no-shorthand-property-overrides': true,
304
+ 'font-family-no-duplicate-names': true,
305
+ 'font-family-no-missing-generic-family-keyword': true,
306
+ 'function-calc-no-unspaced-operator': true,
307
+ 'function-linear-gradient-no-nonstandard-direction': true,
308
+ 'function-no-unknown': true,
309
+ 'keyframe-block-no-duplicate-selectors': true,
310
+ 'keyframe-declaration-no-important': true,
311
+ 'media-feature-name-no-unknown': true,
312
+ 'media-query-no-invalid': true,
313
+ 'named-grid-areas-no-invalid': true,
314
+ 'no-descending-specificity': true,
315
+ 'no-duplicate-at-import-rules': true,
316
+ 'no-duplicate-selectors': true,
317
+ 'no-empty-source': true,
318
+ 'no-invalid-double-slash-comments': true,
319
+ 'no-invalid-position-at-import-rule': true,
320
+ 'no-irregular-whitespace': true,
321
+ 'property-no-unknown': true,
322
+ 'selector-anb-no-unmatchable': true,
323
+ 'selector-pseudo-class-no-unknown': true,
324
+ 'selector-pseudo-element-no-unknown': true,
325
+ 'selector-type-no-unknown': [
326
+ true,
327
+ {
328
+ ignore: ['custom-elements'],
329
+ },
330
+ ],
331
+ 'string-no-newline': true,
332
+ 'unit-no-unknown': true,
333
+ ...opt?.['rules'] as Record<string, unknown>,
334
+ },
335
+ };
336
+ return async doc => {
337
+ const {results} = await stylelint.lint({code: doc.toString(), config: conf});
338
+ return results.flatMap(({warnings}) => warnings)
339
+ .map(({text, severity, line, column, endLine, endColumn}) => ({
340
+ message: text,
341
+ severity,
342
+ from: doc.line(line).from + column - 1,
343
+ to: endLine === undefined ? doc.line(line).to : doc.line(endLine).from + endColumn! - 1,
344
+ }));
345
+ };
346
+ }
347
+ case 'lua':
348
+ await loadScript('npm/luaparse', 'luaparse');
349
+ /** @see https://github.com/ajaxorg/ace/blob/master/lib/ace/mode/lua_worker.js */
350
+ return doc => {
351
+ try {
352
+ luaparse.parse(doc.toString());
353
+ } catch (e) {
354
+ if (e instanceof luaparse.SyntaxError) {
355
+ return [
356
+ {
357
+ message: e.message,
358
+ severity: 'error',
359
+ from: e.index,
360
+ to: e.index,
361
+ },
362
+ ];
363
+ }
364
+ }
365
+ return [];
366
+ };
367
+ default:
368
+ return undefined;
369
+ }
370
+ }
371
+
372
+ /**
373
+ * 在编辑器和文本框之间切换
374
+ * @param show 是否显示编辑器
375
+ */
376
+ toggle(show = !this.#visible): void {
377
+ if (show && !this.#visible) {
378
+ this.#view.dispatch({
379
+ changes: {from: 0, to: this.#view.state.doc.length, insert: this.#textarea.value},
380
+ });
381
+ }
382
+ this.#visible = show;
383
+ this.#view.dom.style.setProperty('display', show ? '' : 'none', 'important');
384
+ this.#textarea.style.display = show ? 'none' : '';
385
+ }
386
+ }
package/src/config.ts ADDED
@@ -0,0 +1,215 @@
1
+ /**
2
+ * @author MusikAnimal and others
3
+ * @license GPL-2.0-or-later
4
+ * @link https://gerrit.wikimedia.org/g/mediawiki/extensions/CodeMirror
5
+ */
6
+
7
+ import {Tag} from '@lezer/highlight';
8
+
9
+ /**
10
+ * Configuration for the MediaWiki highlighting mode for CodeMirror.
11
+ */
12
+ export const modeConfig = {
13
+
14
+ /**
15
+ * All HTML/XML tags permitted in MediaWiki Core.
16
+ *
17
+ * @see https://www.mediawiki.org/wiki/Extension:CodeMirror#Extension_integration
18
+ */
19
+ permittedHtmlTags: [
20
+ 'b',
21
+ 'bdi',
22
+ 'bdo',
23
+ 'del',
24
+ 'i',
25
+ 'ins',
26
+ 'u',
27
+ 'font',
28
+ 'big',
29
+ 'small',
30
+ 'sub',
31
+ 'sup',
32
+ 'h1',
33
+ 'h2',
34
+ 'h3',
35
+ 'h4',
36
+ 'h5',
37
+ 'h6',
38
+ 'cite',
39
+ 'code',
40
+ 'em',
41
+ 's',
42
+ 'strike',
43
+ 'strong',
44
+ 'tt',
45
+ 'var',
46
+ 'div',
47
+ 'center',
48
+ 'blockquote',
49
+ 'q',
50
+ 'ol',
51
+ 'ul',
52
+ 'dl',
53
+ 'table',
54
+ 'caption',
55
+ 'pre',
56
+ 'ruby',
57
+ 'rb',
58
+ 'rp',
59
+ 'rt',
60
+ 'rtc',
61
+ 'p',
62
+ 'span',
63
+ 'abbr',
64
+ 'dfn',
65
+ 'kbd',
66
+ 'samp',
67
+ 'data',
68
+ 'time',
69
+ 'mark',
70
+ 'br',
71
+ 'wbr',
72
+ 'hr',
73
+ 'li',
74
+ 'dt',
75
+ 'dd',
76
+ 'td',
77
+ 'th',
78
+ 'tr',
79
+ 'noinclude',
80
+ 'includeonly',
81
+ 'onlyinclude',
82
+ 'img',
83
+ 'meta',
84
+ 'link',
85
+ ],
86
+
87
+ /**
88
+ * HTML tags that are only self-closing.
89
+ */
90
+ implicitlyClosedHtmlTags: [
91
+ 'br',
92
+ 'hr',
93
+ 'wbr',
94
+ 'img',
95
+ 'meta',
96
+ 'link',
97
+ ],
98
+
99
+ /**
100
+ * Mapping of MediaWiki-esque token identifiers to a standardized lezer highlighting tag.
101
+ * Values are one of the default highlighting tags.
102
+ *
103
+ * Once we allow use of other themes, we may want to tweak these values for aesthetic reasons.
104
+ *
105
+ * @see https://lezer.codemirror.net/docs/ref/#highlight.tags
106
+ * @internal
107
+ */
108
+ tags: {
109
+ apostrophes: 'mw-apostrophes',
110
+ apostrophesBold: 'mw-apostrophes-bold',
111
+ apostrophesItalic: 'mw-apostrophes-italic',
112
+ comment: 'mw-comment',
113
+ doubleUnderscore: 'mw-double-underscore',
114
+ extLink: 'mw-extlink',
115
+ extLinkBracket: 'mw-extlink-bracket',
116
+ extLinkProtocol: 'mw-extlink-protocol',
117
+ extLinkText: 'mw-extlink-text',
118
+ hr: 'mw-hr',
119
+ htmlTagAttribute: 'mw-htmltag-attribute',
120
+ htmlTagBracket: 'mw-htmltag-bracket',
121
+ htmlTagName: 'mw-htmltag-name',
122
+ linkBracket: 'mw-link-bracket',
123
+ linkDelimiter: 'mw-link-delimiter',
124
+ linkText: 'mw-link-text',
125
+ linkToSection: 'mw-link-tosection',
126
+ list: 'mw-list',
127
+ parserFunction: 'mw-parserfunction',
128
+ parserFunctionBracket: 'mw-parserfunction-bracket',
129
+ parserFunctionDelimiter: 'mw-parserfunction-delimiter',
130
+ parserFunctionName: 'mw-parserfunction-name',
131
+ sectionHeader: 'mw-section-header',
132
+ sectionHeader1: 'mw-section-1',
133
+ sectionHeader2: 'mw-section-2',
134
+ sectionHeader3: 'mw-section-3',
135
+ sectionHeader4: 'mw-section-4',
136
+ sectionHeader5: 'mw-section-5',
137
+ sectionHeader6: 'mw-section-6',
138
+ signature: 'mw-signature',
139
+ tableBracket: 'mw-table-bracket',
140
+ tableDefinition: 'mw-table-definition',
141
+ tableDelimiter: 'mw-table-delimiter',
142
+ template: 'mw-template',
143
+ templateArgumentName: 'mw-template-argument-name',
144
+ templateBracket: 'mw-template-bracket',
145
+ templateDelimiter: 'mw-template-delimiter',
146
+ templateName: 'mw-template-name',
147
+ templateVariable: 'mw-templatevariable',
148
+ templateVariableBracket: 'mw-templatevariable-bracket',
149
+ templateVariableName: 'mw-templatevariable-name',
150
+ section: 'mw-section',
151
+ em: 'mw-em',
152
+ error: 'mw-error',
153
+ extGround: 'mw-ext-ground',
154
+ ext2Ground: 'mw-ext2-ground',
155
+ ext2LinkGround: 'mw-ext2-link-ground',
156
+ ext3Ground: 'mw-ext3-ground',
157
+ ext3LinkGround: 'mw-ext3-link-ground',
158
+ extLinkGround: 'mw-ext-link-ground',
159
+ extTag: 'mw-exttag',
160
+ extTagAttribute: 'mw-exttag-attribute',
161
+ extTagBracket: 'mw-exttag-bracket',
162
+ extTagName: 'mw-exttag-name',
163
+ freeExtLink: 'mw-free-extlink',
164
+ freeExtLinkProtocol: 'mw-free-extlink-protocol',
165
+ htmlEntity: 'mw-html-entity',
166
+ link: 'mw-link',
167
+ linkGround: 'mw-link-ground',
168
+ linkPageName: 'mw-link-pagename',
169
+ pageName: 'mw-pagename',
170
+ skipFormatting: 'mw-skipformatting',
171
+ strong: 'mw-strong',
172
+ tableCaption: 'mw-table-caption',
173
+ templateExtGround: 'mw-template-ext-ground',
174
+ templateExt2Ground: 'mw-template-ext2-ground',
175
+ templateExt2LinkGround: 'mw-template-ext2-link-ground',
176
+ templateExt3Ground: 'mw-template-ext3-ground',
177
+ templateExt3LinkGround: 'mw-template-ext3-link-ground',
178
+ templateExtLinkGround: 'mw-template-ext-link-ground',
179
+ templateGround: 'mw-template-ground',
180
+ templateLinkGround: 'mw-template-link-ground',
181
+ templateVariableDelimiter: 'mw-templatevariable-delimiter',
182
+ template2ExtGround: 'mw-template2-ext-ground',
183
+ template2Ext2Ground: 'mw-template2-ext2-ground',
184
+ template2Ext3Ground: 'mw-template2-ext3-ground',
185
+ templatet2Ext2LinkGround: 'mw-template2-ext2-link-ground',
186
+ template2Ext3LinkGround: 'mw-template2-ext3-link-ground',
187
+ template2ExtLinkGround: 'mw-template2-ext-link-ground',
188
+ template2Ground: 'mw-template2-ground',
189
+ template2LinkGround: 'mw-template2-link-ground',
190
+ template3ExtGround: 'mw-template3-ext-ground',
191
+ template3Ext2Ground: 'mw-template3-ext2-ground',
192
+ template3Ext3Ground: 'mw-template3-ext3-ground',
193
+ template3ExtLinkGround: 'mw-template3-ext-link-ground',
194
+ template3Ext2LinkGround: 'mw-template3-ext2-link-ground',
195
+ template3Ext3LinkGround: 'mw-template3-ext3-link-ground',
196
+ template3Ground: 'mw-template3-ground',
197
+ template3LinkGround: 'mw-template3-link-ground',
198
+ },
199
+
200
+ /**
201
+ * These are custom tokens (a.k.a. tags) that aren't mapped to any of the standardized tags.
202
+ *
203
+ * TODO: pass parent Tags in Tag.define() where appropriate for better theming.
204
+ *
205
+ * @see https://codemirror.net/docs/ref/#language.StreamParser.tokenTable
206
+ * @see https://lezer.codemirror.net/docs/ref/#highlight.Tag%5Edefine
207
+ */
208
+ get tokenTable(): Record<string, Tag> {
209
+ const table: Record<string, Tag> = {};
210
+ for (const className of Object.values(this.tags)) {
211
+ table[className] = Tag.define();
212
+ }
213
+ return table;
214
+ },
215
+ };