@bhsd/codemirror-mediawiki 2.1.8 → 2.1.10

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/mw/dist/base.js CHANGED
@@ -1,7 +1,8 @@
1
- import { CodeMirror6 } from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror-mediawiki@2.1.8/dist/main.min.js';
1
+ import { CodeMirror6 } from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror-mediawiki@2.1.10/dist/main.min.js';
2
2
  (() => {
3
3
  var _a;
4
- mw.loader.load('https://testingcf.jsdelivr.net/npm/@bhsd/codemirror-mediawiki@2.1.8/mediawiki.min.css', 'text/css');
4
+ mw.loader.load('https://testingcf.jsdelivr.net/npm/@bhsd/codemirror-mediawiki@2.1.10/mediawiki.min.css', 'text/css');
5
+ mw.loader.addStyleTag('.wikiEditor-ui-toolbar{z-index:7}');
5
6
  const instances = new WeakMap();
6
7
  const getInstance = ($ele) => instances.get($ele[0]);
7
8
  $.valHooks['textarea'] = {
@@ -157,12 +158,27 @@ import { CodeMirror6 } from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror
157
158
  linters[this.lang] = linter;
158
159
  return linter;
159
160
  }
160
- async defaultLint(on, opt) {
161
+ async defaultLint(on, optOrNs) {
161
162
  if (!on) {
162
163
  this.lint();
163
164
  return;
164
165
  }
165
166
  const { lang } = this;
167
+ let opt;
168
+ if (typeof optOrNs === 'number') {
169
+ if (lang === 'mediawiki' && (optOrNs === 10 || optOrNs === 828)) {
170
+ opt = { include: true };
171
+ }
172
+ else if (lang === 'javascript' && (optOrNs === 8 || optOrNs === 2300)) {
173
+ opt = {
174
+ env: { browser: true, es6: true },
175
+ parserOptions: { ecmaVersion: 6 },
176
+ };
177
+ }
178
+ }
179
+ else {
180
+ opt = optOrNs;
181
+ }
166
182
  if (!(lang in linters)) {
167
183
  await this.getLinter(opt);
168
184
  if (lang === 'mediawiki') {
@@ -241,17 +257,7 @@ import { CodeMirror6 } from 'https://testingcf.jsdelivr.net/npm/@bhsd/codemirror
241
257
  }
242
258
  }
243
259
  const cm = await CodeMirror.fromTextArea(e.target, lang);
244
- let opt;
245
- if (lang === 'mediawiki' && wgNamespaceNumber === 10) {
246
- opt = { include: true };
247
- }
248
- else if (lang === 'javascript' && (wgNamespaceNumber === 8 || wgNamespaceNumber === 2300)) {
249
- opt = {
250
- env: { browser: true, es6: true },
251
- parserOptions: { ecmaVersion: 6 },
252
- };
253
- }
254
- await cm.defaultLint(true, opt);
260
+ void cm.defaultLint(true, wgNamespaceNumber);
255
261
  })();
256
262
  }
257
263
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bhsd/codemirror-mediawiki",
3
- "version": "2.1.8",
3
+ "version": "2.1.10",
4
4
  "description": "Modified CodeMirror mode based on wikimedia/mediawiki-extensions-CodeMirror",
5
5
  "keywords": [
6
6
  "mediawiki",
@@ -28,15 +28,14 @@
28
28
  "url": "git+https://github.com/bhsd-harry/codemirror-mediawiki.git"
29
29
  },
30
30
  "scripts": {
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",
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",
32
32
  "build:mw": "bash build.sh mw/base.ts mw/dist/base.js",
33
33
  "build:gh-page": "bash build.sh src/gh-page.ts gh-page.js",
34
34
  "build": "npm run build:core && npm run build:mw",
35
35
  "lint:ts": "tsc --noEmit && tsc --project mw/tsconfig.json --noEmit && eslint --cache .",
36
36
  "lint:css": "stylelint *.css",
37
37
  "lint": "npm run lint:ts && npm run lint:css",
38
- "server": "http-server .. -c-1 --cors &",
39
- "test": "npm run server; open http://127.0.0.1:8080/codemirror-mediawiki/index.html",
38
+ "test": "npm run build:core && npm run build:gh-page && http-server .. -c-1 --cors &",
40
39
  "test:end": "pkill -x http-server"
41
40
  },
42
41
  "engines": {
package/src/codemirror.ts CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  } from '@codemirror/language';
20
20
  import {defaultKeymap, historyKeymap, history} from '@codemirror/commands';
21
21
  import {searchKeymap} from '@codemirror/search';
22
- import {linter, lintGutter, openLintPanel, closeLintPanel} from '@codemirror/lint';
22
+ import {linter, lintGutter, openLintPanel, closeLintPanel, lintKeymap} from '@codemirror/lint';
23
23
  import {closeBrackets} from '@codemirror/autocomplete';
24
24
  import {mediawiki, html} from './mediawiki';
25
25
  import * as plugins from './plugins';
@@ -32,6 +32,8 @@ import type {Linter} from 'eslint';
32
32
  export type {MwConfig} from './mediawiki';
33
33
  export type LintSource = (doc: Text) => Diagnostic[] | Promise<Diagnostic[]>;
34
34
 
35
+ declare type LintExtension = [unknown, ViewPlugin<{set: boolean, force(): void}>];
36
+
35
37
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
38
  const languages: Record<string, (config?: any) => LanguageSupport | []> = {
37
39
  plain: () => [],
@@ -43,7 +45,7 @@ for (const [language, parser] of Object.entries(plugins)) {
43
45
  }
44
46
  const linters: Record<string, Extension> = {};
45
47
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
- const avail: Record<string, [ (config?: any) => Extension, Record<string, unknown> ]> = {
48
+ const avail: Record<string, [(config?: any) => Extension, Record<string, unknown>]> = {
47
49
  highlightSpecialChars: [highlightSpecialChars, {}],
48
50
  highlightActiveLine: [highlightActiveLine, {}],
49
51
  highlightWhitespace: [highlightWhitespace, {}],
@@ -137,6 +139,7 @@ export class CodeMirror6 {
137
139
  ...defaultKeymap,
138
140
  ...historyKeymap,
139
141
  ...searchKeymap,
142
+ ...lintKeymap,
140
143
  ]),
141
144
  EditorView.updateListener.of(({state: {doc}, docChanged}) => {
142
145
  if (docChanged) {
@@ -186,6 +189,21 @@ export class CodeMirror6 {
186
189
  this.#view.dom.style.minHeight = linting ? 'calc(100px + 2em)' : '2em';
187
190
  }
188
191
 
192
+ /**
193
+ * 开关语法检查面板
194
+ * @param show 是否显示
195
+ */
196
+ #toggleLintPanel(show: boolean): void {
197
+ (show ? openLintPanel : closeLintPanel)(this.#view);
198
+ document.querySelector<HTMLUListElement>('.cm-panel-lint ul')?.blur();
199
+ this.#minHeight(show);
200
+ }
201
+
202
+ /** 获取语法检查扩展 */
203
+ #getLintExtension(): LintExtension | undefined {
204
+ return (this.#linter.get(this.#view.state) as LintExtension[])[0];
205
+ }
206
+
189
207
  /**
190
208
  * 设置语言
191
209
  * @param lang 语言
@@ -199,7 +217,7 @@ export class CodeMirror6 {
199
217
  ],
200
218
  });
201
219
  this.#lang = lang;
202
- (linters[lang] ? openLintPanel : closeLintPanel)(this.#view);
220
+ this.#toggleLintPanel(Boolean(linters[lang]));
203
221
  }
204
222
 
205
223
  /**
@@ -215,25 +233,20 @@ export class CodeMirror6 {
215
233
  : [];
216
234
  if (lintSource) {
217
235
  linters[this.#lang] = linterExtension;
218
- this.#minHeight(true);
219
236
  } else {
220
237
  delete linters[this.#lang];
221
- this.#minHeight();
222
238
  }
223
239
  this.#view.dispatch({
224
240
  effects: [this.#linter.reconfigure(linterExtension)],
225
241
  });
226
- (lintSource ? openLintPanel : closeLintPanel)(this.#view);
242
+ this.#toggleLintPanel(Boolean(lintSource));
227
243
  }
228
244
 
229
245
  /** 立即更新语法检查 */
230
246
  update(): void {
231
- const extension = this.#linter.get(this.#view.state) as [[ unknown, ViewPlugin<{
232
- set: boolean;
233
- force(): void;
234
- }> ]] | [];
235
- if (extension.length > 0) {
236
- const plugin = this.#view.plugin(extension[0]![1])!;
247
+ const extension = this.#getLintExtension();
248
+ if (extension) {
249
+ const plugin = this.#view.plugin(extension[1])!;
237
250
  plugin.set = true;
238
251
  plugin.force();
239
252
  }
@@ -281,10 +294,10 @@ export class CodeMirror6 {
281
294
  conf: Linter.Config = {
282
295
  env: {
283
296
  browser: true,
284
- es2018: true,
297
+ es2024: true,
285
298
  },
286
299
  parserOptions: {
287
- ecmaVersion: 9,
300
+ ecmaVersion: 15,
288
301
  sourceType: 'module',
289
302
  },
290
303
  rules: {},
package/src/mediawiki.ts CHANGED
@@ -48,6 +48,12 @@ export interface MwConfig {
48
48
  implicitlyClosedHtmlTags?: string[];
49
49
  }
50
50
 
51
+ const enum TableCell {
52
+ Td,
53
+ Th,
54
+ Caption,
55
+ }
56
+
51
57
  const copyState = (state: State): State => {
52
58
  const newState = {} as State;
53
59
  for (const [key, val] of Object.entries(state)) {
@@ -85,7 +91,7 @@ class MediaWiki {
85
91
  constructor(config: MwConfig) {
86
92
  this.config = config;
87
93
  // eslint-disable-next-line require-unicode-regexp
88
- this.urlProtocols = new RegExp(`^(?:${config.urlProtocols})`, 'i');
94
+ this.urlProtocols = new RegExp(`^(?:${config.urlProtocols})(?=[^\\s[\\]<>])`, 'i');
89
95
  this.isBold = false;
90
96
  this.wasBold = false;
91
97
  this.isItalic = false;
@@ -235,7 +241,7 @@ class MediaWiki {
235
241
  return (ground && `mw${ground}-ground `) + style;
236
242
  }
237
243
 
238
- inBlock(style: string, terminator: string, consumeLast = true): Tokenizer {
244
+ inBlock(style: string, terminator: string, consumeLast?: boolean): Tokenizer {
239
245
  return (stream, state) => {
240
246
  if (stream.skipTo(terminator)) {
241
247
  if (consumeLast) {
@@ -278,7 +284,7 @@ class MediaWiki {
278
284
  } else if (stream.match(/^<!--(?!.*?-->.*?=)/u, false)) {
279
285
  // T171074: handle trailing comments
280
286
  stream.backUp(count);
281
- state.tokenize = this.inBlock(modeConfig.tags.sectionHeader, '<!--', false);
287
+ state.tokenize = this.inBlock(modeConfig.tags.sectionHeader, '<!--');
282
288
  }
283
289
  return this.makeLocalStyle(modeConfig.tags.section, state);
284
290
  }
@@ -303,7 +309,7 @@ class MediaWiki {
303
309
  return this.makeLocalStyle(modeConfig.tags.templateVariableName, state);
304
310
  }
305
311
 
306
- inVariableDefault(isFirst: boolean): Tokenizer {
312
+ inVariableDefault(isFirst?: boolean): Tokenizer {
307
313
  const style = modeConfig.tags[isFirst ? 'templateVariable' : 'comment'];
308
314
  return (stream, state) => {
309
315
  if (stream.match(/^[^{}[<&~|]+/u)) {
@@ -312,7 +318,7 @@ class MediaWiki {
312
318
  state.tokenize = state.stack.pop()!;
313
319
  return this.makeLocalStyle(modeConfig.tags.templateVariableBracket, state);
314
320
  } else if (stream.eat('|')) {
315
- state.tokenize = this.inVariableDefault(false);
321
+ state.tokenize = this.inVariableDefault();
316
322
  return this.makeLocalStyle(modeConfig.tags.templateVariableDelimiter, state);
317
323
  }
318
324
  return this.eatWikiText(style)(stream, state);
@@ -345,7 +351,7 @@ class MediaWiki {
345
351
  return this.eatWikiText(modeConfig.tags.parserFunction)(stream, state);
346
352
  }
347
353
 
348
- inTemplatePageName(haveAte: boolean): Tokenizer {
354
+ inTemplatePageName(haveAte?: boolean): Tokenizer {
349
355
  return (stream, state) => {
350
356
  if (stream.match(/^\s*\|\s*/u)) {
351
357
  state.tokenize = this.inTemplateArgument(true);
@@ -372,11 +378,11 @@ class MediaWiki {
372
378
  };
373
379
  }
374
380
 
375
- inTemplateArgument(expectArgName: boolean): Tokenizer {
381
+ inTemplateArgument(expectArgName?: boolean): Tokenizer {
376
382
  return (stream, state) => {
377
383
  if (expectArgName && stream.eatWhile(/[^=|}{[<&~]/u)) {
378
384
  if (stream.eat('=')) {
379
- state.tokenize = this.inTemplateArgument(false);
385
+ state.tokenize = this.inTemplateArgument();
380
386
  return this.makeLocalStyle(modeConfig.tags.templateArgumentName, state);
381
387
  }
382
388
  return this.makeLocalStyle(modeConfig.tags.template, state);
@@ -531,7 +537,7 @@ class MediaWiki {
531
537
  };
532
538
  }
533
539
 
534
- eatTagName(chars: number, isCloseTag: boolean, isHtmlTag: boolean): Tokenizer {
540
+ eatTagName(chars: number, isCloseTag: boolean, isHtmlTag?: boolean): Tokenizer {
535
541
  return (stream, state) => {
536
542
  let name = '';
537
543
  for (let i = 0; i < chars; i++) {
@@ -556,7 +562,7 @@ class MediaWiki {
556
562
 
557
563
  inHtmlTagAttribute(name: string): Tokenizer {
558
564
  return (stream, state) => {
559
- if (stream.match(/^[^>/<{]+/u)) {
565
+ if (stream.match(/^(?:"[^<">]*"|'[^<'>]*'[^>/<{])+/u)) {
560
566
  return this.makeLocalStyle(modeConfig.tags.htmlTagAttribute, state);
561
567
  } else if (stream.match(/^\/?>/u)) {
562
568
  if (!this.implicitlyClosedHtmlTags.has(name)) {
@@ -582,7 +588,7 @@ class MediaWiki {
582
588
 
583
589
  inExtTagAttribute(name: string): Tokenizer {
584
590
  return (stream, state) => {
585
- if (stream.match(/^[^>/]+/u)) {
591
+ if (stream.match(/^(?:"[^">]*"|'[^'>]*'|[^>/])+/u)) {
586
592
  return this.makeLocalStyle(modeConfig.tags.extTagAttribute, state);
587
593
  } else if (stream.eat('>')) {
588
594
  state.extName = name;
@@ -642,7 +648,7 @@ class MediaWiki {
642
648
  return (stream, state) => {
643
649
  stream.next(); // eat <
644
650
  stream.next(); // eat /
645
- state.tokenize = this.eatTagName(name.length, true, false);
651
+ state.tokenize = this.eatTagName(name.length, true);
646
652
  return this.makeLocalStyle(modeConfig.tags.extTagBracket, state);
647
653
  };
648
654
  }
@@ -680,17 +686,6 @@ class MediaWiki {
680
686
  return this.eatWikiText(modeConfig.tags.tableDefinition)(stream, state);
681
687
  }
682
688
 
683
- inTableCaption(stream: StringStream, state: State): string {
684
- if (stream.sol() && stream.match(/^\s*(?:[|!]|\{\{\s*!\s*\}\})/u, false)) {
685
- state.tokenize = this.inTable.bind(this);
686
- return this.inTable(stream, state);
687
- } else if (stream.match(/^\s*(?:\||\{\{\s*!\s*\}\}){2}/u, false)) {
688
- state.tokenize = this.inTableRow(false, false);
689
- return '';
690
- }
691
- return this.eatWikiText(modeConfig.tags.tableCaption)(stream, state);
692
- }
693
-
694
689
  inTable(stream: StringStream, state: State): string {
695
690
  if (stream.sol()) {
696
691
  stream.eatSpace();
@@ -700,25 +695,31 @@ class MediaWiki {
700
695
  return this.makeLocalStyle(modeConfig.tags.tableDelimiter, state);
701
696
  } else if (stream.eat('+')) {
702
697
  stream.eatSpace();
703
- state.tokenize = this.inTableCaption.bind(this);
698
+ state.tokenize = this.inTableRow(true, TableCell.Caption);
704
699
  return this.makeLocalStyle(modeConfig.tags.tableDelimiter, state);
705
700
  } else if (stream.eat('}')) {
706
701
  state.tokenize = state.stack.pop()!;
707
702
  return this.makeLocalStyle(modeConfig.tags.tableBracket, state);
708
703
  }
709
704
  stream.eatSpace();
710
- state.tokenize = this.inTableRow(true, false);
705
+ state.tokenize = this.inTableRow(true, TableCell.Td);
711
706
  return this.makeLocalStyle(modeConfig.tags.tableDelimiter, state);
712
707
  } else if (stream.eat('!')) {
713
708
  stream.eatSpace();
714
- state.tokenize = this.inTableRow(true, true);
709
+ state.tokenize = this.inTableRow(true, TableCell.Th);
715
710
  return this.makeLocalStyle(modeConfig.tags.tableDelimiter, state);
716
711
  }
717
712
  }
718
713
  return this.eatWikiText('')(stream, state);
719
714
  }
720
715
 
721
- inTableRow(isStart: boolean, isHead: boolean): Tokenizer {
716
+ inTableRow(isStart: boolean, type: TableCell): Tokenizer {
717
+ let style = '';
718
+ if (type === TableCell.Caption) {
719
+ style = modeConfig.tags.tableCaption;
720
+ } else if (type === TableCell.Th) {
721
+ style = modeConfig.tags.strong;
722
+ }
722
723
  return (stream, state) => {
723
724
  if (stream.sol()) {
724
725
  if (stream.match(/^\s*(?:[|!]|\{\{\s*!\s*\}\})/u, false)) {
@@ -726,19 +727,17 @@ class MediaWiki {
726
727
  return this.inTable(stream, state);
727
728
  }
728
729
  } else if (stream.match(/^[^'|{[<&~!]+/u)) {
729
- return this.makeStyle(isHead ? modeConfig.tags.strong : '', state);
730
- } else if (
731
- stream.match(/^(?:\||\{\{\s*!\s*\}\}){2}/u) || isHead && stream.match('!!')
732
- || isStart && stream.match(/^(?:\||\{\{\s*!\s*\}\})/u)
733
- ) {
730
+ return this.makeStyle(style, state);
731
+ } else if (stream.match(/^(?:\||\{\{\s*!\s*\}\}){2}/u) || type === TableCell.Th && stream.match('!!')) {
734
732
  this.isBold = false;
735
733
  this.isItalic = false;
736
- if (isStart) {
737
- state.tokenize = this.inTableRow(false, isHead);
738
- }
734
+ state.tokenize = this.inTableRow(true, type);
735
+ return this.makeLocalStyle(modeConfig.tags.tableDelimiter, state);
736
+ } else if (isStart && stream.match(/^(?:\||\{\{\s*!\s*\}\})/u)) {
737
+ state.tokenize = this.inTableRow(false, type);
739
738
  return this.makeLocalStyle(modeConfig.tags.tableDelimiter, state);
740
739
  }
741
- return this.eatWikiText(isHead ? modeConfig.tags.strong : '')(stream, state);
740
+ return this.eatWikiText(style)(stream, state);
742
741
  };
743
742
  }
744
743
 
@@ -938,14 +937,14 @@ class MediaWiki {
938
937
  // Template
939
938
  state.nTemplate++;
940
939
  state.stack.push(state.tokenize);
941
- state.tokenize = this.inTemplatePageName(false);
940
+ state.tokenize = this.inTemplatePageName();
942
941
  return this.makeLocalStyle(modeConfig.tags.templateBracket, state);
943
942
  }
944
943
  break;
945
944
  case '<': {
946
945
  if (stream.match('!--')) { // comment
947
946
  state.stack.push(state.tokenize);
948
- state.tokenize = this.inBlock(modeConfig.tags.comment, '-->');
947
+ state.tokenize = this.inBlock(modeConfig.tags.comment, '-->', true);
949
948
  return this.makeLocalStyle(modeConfig.tags.comment, state);
950
949
  }
951
950
  const isCloseTag = Boolean(stream.eat('/')),
@@ -954,17 +953,18 @@ class MediaWiki {
954
953
  const tagname = mt[0]!.toLowerCase();
955
954
  if (tagname in this.config.tags) {
956
955
  // Parser function
956
+ state.stack.push(state.tokenize);
957
957
  if (isCloseTag) {
958
958
  state.tokenize = this.inChar('>', modeConfig.tags.error);
959
959
  return this.makeLocalStyle(modeConfig.tags.error, state);
960
960
  }
961
961
  stream.backUp(tagname.length);
962
- state.stack.push(state.tokenize);
963
- state.tokenize = this.eatTagName(tagname.length, isCloseTag, false);
962
+ state.tokenize = this.eatTagName(tagname.length, isCloseTag);
964
963
  return this.makeLocalStyle(modeConfig.tags.extTagBracket, state);
965
964
  } else if (this.permittedHtmlTags.has(tagname)) {
966
965
  // Html tag
967
966
  if (isCloseTag && tagname !== state.inHtmlTag.pop()) {
967
+ state.stack.push(state.tokenize);
968
968
  state.tokenize = this.inChar('>', modeConfig.tags.error);
969
969
  return this.makeLocalStyle(modeConfig.tags.error, state);
970
970
  } else if (isCloseTag && this.implicitlyClosedHtmlTags.has(tagname)) {