@bhsd/codemirror-mediawiki 3.9.2 → 3.10.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.
Files changed (67) hide show
  1. package/README.md +147 -87
  2. package/dist/bidi.d.ts +9 -8
  3. package/dist/bidi.js +38 -23
  4. package/dist/codemirror.d.ts +7 -0
  5. package/dist/codemirror.js +22 -9
  6. package/dist/color.d.ts +8 -5
  7. package/dist/color.js +5 -1
  8. package/dist/config.d.ts +1 -0
  9. package/dist/config.js +1 -0
  10. package/dist/constants.d.ts +3 -2
  11. package/dist/constants.js +3 -2
  12. package/dist/css.d.ts +5 -0
  13. package/dist/css.js +14 -6
  14. package/dist/escape.d.ts +22 -2
  15. package/dist/escape.js +44 -25
  16. package/dist/fold.d.ts +57 -5
  17. package/dist/fold.js +149 -58
  18. package/dist/hover.d.ts +16 -3
  19. package/dist/hover.js +84 -67
  20. package/dist/html.js +17 -12
  21. package/dist/indent.d.ts +7 -0
  22. package/dist/indent.js +7 -1
  23. package/dist/index.d.ts +54 -16
  24. package/dist/index.js +91 -38
  25. package/dist/inlay.d.ts +1 -1
  26. package/dist/inlay.js +26 -25
  27. package/dist/javascript.d.ts +10 -1
  28. package/dist/javascript.js +50 -2
  29. package/dist/keybindings.d.ts +1 -0
  30. package/dist/keybindings.js +1 -0
  31. package/dist/keymap.d.ts +11 -0
  32. package/dist/keymap.js +3 -4
  33. package/dist/linter.d.ts +31 -2
  34. package/dist/linter.js +10 -3
  35. package/dist/lintsource.d.ts +47 -3
  36. package/dist/lintsource.js +50 -11
  37. package/dist/lua.d.ts +0 -2
  38. package/dist/lua.js +27 -10
  39. package/dist/main.min.js +31 -29
  40. package/dist/matchBrackets.d.ts +16 -0
  41. package/dist/matchBrackets.js +16 -0
  42. package/dist/matchTag.d.ts +5 -2
  43. package/dist/matchTag.js +11 -7
  44. package/dist/mediawiki.d.ts +15 -2
  45. package/dist/mediawiki.js +59 -45
  46. package/dist/mw.min.js +33 -37
  47. package/dist/mwConfig.js +2 -2
  48. package/dist/openLinks.d.ts +12 -2
  49. package/dist/openLinks.js +64 -54
  50. package/dist/ref.d.ts +16 -2
  51. package/dist/ref.js +110 -95
  52. package/dist/signature.d.ts +7 -1
  53. package/dist/signature.js +53 -49
  54. package/dist/static.d.ts +4 -0
  55. package/dist/static.js +4 -0
  56. package/dist/statusBar.js +9 -8
  57. package/dist/theme.d.ts +1 -0
  58. package/dist/theme.js +8 -0
  59. package/dist/token.d.ts +29 -7
  60. package/dist/token.js +33 -18
  61. package/dist/util.d.ts +25 -2
  62. package/dist/util.js +47 -1
  63. package/dist/wiki.min.js +32 -36
  64. package/i18n/en.json +2 -2
  65. package/i18n/zh-hans.json +2 -2
  66. package/i18n/zh-hant.json +2 -2
  67. package/package.json +15 -13
package/dist/bidi.d.ts CHANGED
@@ -3,13 +3,14 @@
3
3
  * @license GPL-2.0-or-later
4
4
  * @see https://gerrit.wikimedia.org/g/mediawiki/extensions/CodeMirror
5
5
  */
6
- import { EditorView, Direction, ViewPlugin } from '@codemirror/view';
7
- import type { ViewUpdate, DecorationSet } from '@codemirror/view';
6
+ import { EditorView } from '@codemirror/view';
7
+ import type { DecorationSet } from '@codemirror/view';
8
+ import type { Extension } from '@codemirror/state';
9
+ /**
10
+ * 计算需要`unicode-bidi:isolate`的范围
11
+ * @ignore
12
+ * @test
13
+ */
8
14
  export declare const computeIsolates: ({ visibleRanges, state, textDirection }: EditorView) => DecorationSet;
9
- declare const _default: ViewPlugin<{
10
- isolates: DecorationSet;
11
- tree: import("@lezer/common").Tree;
12
- dir: Direction;
13
- update({ docChanged, viewportChanged, state, view }: ViewUpdate): void;
14
- }, undefined>;
15
+ declare const _default: Extension[];
15
16
  export default _default;
package/dist/bidi.js CHANGED
@@ -7,12 +7,16 @@ import { EditorView, Direction, ViewPlugin, Decoration } from '@codemirror/view'
7
7
  import { Prec, RangeSetBuilder } from '@codemirror/state';
8
8
  import { syntaxTree } from '@codemirror/language';
9
9
  import { tokens } from './config.js';
10
- import { isolateSelector, ltrSelector } from './constants.js';
11
10
  import { getTag } from './matchTag.js';
12
- const cls = isolateSelector.slice(1), isolateLTR = Decoration.mark({
11
+ const isolateSelector = '.cm-bidi-isolate', ltrSelector = '.cm-bidi-ltr', cls = isolateSelector.slice(1), isolateLTR = Decoration.mark({
13
12
  class: `${cls} ${ltrSelector.slice(1)}`,
14
13
  bidiIsolate: Direction.LTR,
15
14
  }), isolate = Decoration.mark({ class: cls });
15
+ /**
16
+ * 计算需要`unicode-bidi:isolate`的范围
17
+ * @ignore
18
+ * @test
19
+ */
16
20
  export const computeIsolates = ({ visibleRanges, state, textDirection }) => {
17
21
  const set = new RangeSetBuilder();
18
22
  if (textDirection === Direction.RTL) {
@@ -60,26 +64,37 @@ export const computeIsolates = ({ visibleRanges, state, textDirection }) => {
60
64
  }
61
65
  return set.finish();
62
66
  };
63
- export default ViewPlugin.fromClass(class {
64
- constructor(view) {
65
- this.isolates = computeIsolates(view);
66
- this.tree = syntaxTree(view.state);
67
- this.dir = view.textDirection;
68
- }
69
- update({ docChanged, viewportChanged, state, view }) {
70
- const tree = syntaxTree(state), { textDirection } = view;
71
- if (docChanged || viewportChanged || tree !== this.tree || textDirection !== this.dir) {
67
+ export default [
68
+ ViewPlugin.fromClass(class {
69
+ constructor(view) {
72
70
  this.isolates = computeIsolates(view);
73
- this.tree = tree;
74
- this.dir = textDirection;
71
+ this.tree = syntaxTree(view.state);
72
+ this.dir = view.textDirection;
75
73
  }
76
- }
77
- }, {
78
- provide(plugin) {
79
- const access = (view) => view.plugin(plugin)?.isolates ?? Decoration.none;
80
- return Prec.lowest([
81
- EditorView.decorations.of(access),
82
- EditorView.bidiIsolatedRanges.of(access),
83
- ]);
84
- },
85
- });
74
+ update({ docChanged, viewportChanged, state, view }) {
75
+ const tree = syntaxTree(state), { textDirection } = view;
76
+ if (docChanged || viewportChanged || tree !== this.tree || textDirection !== this.dir) {
77
+ this.isolates = computeIsolates(view);
78
+ this.tree = tree;
79
+ this.dir = textDirection;
80
+ }
81
+ }
82
+ }, {
83
+ provide(plugin) {
84
+ const access = (view) => view.plugin(plugin)?.isolates ?? Decoration.none;
85
+ return Prec.lowest([
86
+ EditorView.decorations.of(access),
87
+ EditorView.bidiIsolatedRanges.of(access),
88
+ ]);
89
+ },
90
+ }),
91
+ EditorView.theme({
92
+ [`${isolateSelector}, &[dir="rtl"] .cm-mw-template-name`]: {
93
+ unicodeBidi: 'isolate',
94
+ },
95
+ [ltrSelector]: {
96
+ direction: 'ltr',
97
+ display: 'inline-block',
98
+ },
99
+ }),
100
+ ];
@@ -33,6 +33,12 @@ export declare const menuRegistry: MenuItem[];
33
33
  export declare const destroyListeners: ((view: EditorView) => void)[];
34
34
  export declare const themes: Record<string, Extension>;
35
35
  export declare const optionalFunctions: OptionalFunctions;
36
+ /**
37
+ * 替换选中内容
38
+ * @param view
39
+ * @param func 用于生成替换文本和光标位置的函数
40
+ * @test
41
+ */
36
42
  export declare const replaceSelections: (view: EditorView, func: ReplaceFunction) => void;
37
43
  /** CodeMirror 6 editor */
38
44
  export declare class CodeMirror6 {
@@ -151,6 +157,7 @@ export declare class CodeMirror6 {
151
157
  * Replace the current selection with the result of a function
152
158
  * @param view EditorView instance
153
159
  * @param func function to produce the replacement text
160
+ * @test
154
161
  */
155
162
  static replaceSelections: (view: EditorView, func: ReplaceFunction) => void;
156
163
  /**
@@ -1,6 +1,6 @@
1
1
  import { EditorView, lineNumbers, keymap, highlightActiveLineGutter } from '@codemirror/view';
2
2
  import { EditorSelection, Compartment, EditorState, SelectionRange, } from '@codemirror/state';
3
- import { syntaxHighlighting, defaultHighlightStyle, indentOnInput, indentUnit, ensureSyntaxTree, } from '@codemirror/language';
3
+ import { syntaxHighlighting, defaultHighlightStyle, indentOnInput, indentUnit, ensureSyntaxTree, syntaxTree, } from '@codemirror/language';
4
4
  import { defaultKeymap, historyKeymap, history, redo, indentWithTab, insertNewlineKeepIndent, deleteCharBackwardStrict, } from '@codemirror/commands';
5
5
  import { search, searchKeymap } from '@codemirror/search';
6
6
  import { linter, lintGutter, lintKeymap } from '@codemirror/lint';
@@ -16,8 +16,7 @@ export const plain = () => [
16
16
  ];
17
17
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
18
  export const languages = { plain };
19
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
- export const avail = {};
19
+ export const avail = {}; // eslint-disable-line @typescript-eslint/no-explicit-any
21
20
  export const linterRegistry = {};
22
21
  export const menuRegistry = [];
23
22
  export const destroyListeners = [];
@@ -33,13 +32,19 @@ export const optionalFunctions = {
33
32
  return () => { };
34
33
  },
35
34
  };
36
- const editExtensions = new Set(['closeBrackets', 'autocompletion', 'signatureHelp']);
35
+ const editExtensions = new Set(['closeBrackets', 'autocompletion', 'signatureHelp', 'escape']);
37
36
  const linters = {};
38
37
  const phrases = {};
38
+ /**
39
+ * 替换选中内容
40
+ * @param view
41
+ * @param func 用于生成替换文本和光标位置的函数
42
+ * @test
43
+ */
39
44
  export const replaceSelections = (view, func) => {
40
45
  const { state } = view;
41
- view.dispatch(state.changeByRange(({ from, to }) => {
42
- const result = func(state.sliceDoc(from, to), { from, to });
46
+ view.dispatch(state.changeByRange(range => {
47
+ const { from, to } = range, result = func(state.sliceDoc(from, to), range);
43
48
  if (typeof result === 'string') {
44
49
  return {
45
50
  range: EditorSelection.range(from, from + result.length),
@@ -172,18 +177,18 @@ export class CodeMirror6 {
172
177
  '& .cm-lineNumbers .cm-gutterElement': {
173
178
  textAlign: 'end',
174
179
  },
175
- [`.cm-textfield,.cm-button,${panelSelector}.cm-search label,${panelSelector}.cm-gotoLine label`]: {
180
+ [`.cm-textfield, .cm-button,${panelSelector}.cm-search label,${panelSelector}.cm-gotoLine label`]: {
176
181
  fontSize: 'inherit',
177
182
  },
178
183
  [`${panelSelector} [name="close"]`]: {
179
184
  color: 'inherit',
180
185
  },
181
186
  }),
182
- EditorView.updateListener.of(({ state: { doc }, startState: { doc: startDoc }, docChanged, focusChanged, }) => {
187
+ EditorView.updateListener.of(({ state, startState: { doc: startDoc }, docChanged, focusChanged, selectionSet, }) => {
183
188
  if (docChanged) {
184
189
  clearTimeout(timer);
185
190
  timer = setTimeout(() => {
186
- textarea.value = doc.toString();
191
+ textarea.value = state.doc.toString();
187
192
  textarea.dispatchEvent(new InputEvent('input'));
188
193
  }, 400);
189
194
  if (!noDetectionLangs.has(this.#lang) && !startDoc.toString().trim()) {
@@ -193,6 +198,13 @@ export class CodeMirror6 {
193
198
  if (focusChanged) {
194
199
  textarea.dispatchEvent(new FocusEvent(this.#view.hasFocus ? 'focus' : 'blur'));
195
200
  }
201
+ if (selectionSet && this.lang === 'mediawiki'
202
+ && ['localhost:8080', 'bhsd-harry.github.io'].includes(location.host)) {
203
+ const tree = syntaxTree(state), { head } = state.selection.main, { name } = tree.resolve(head), innerName = tree.resolveInner(head).name;
204
+ if (name !== innerName) {
205
+ console.error(`Cursor at ${head}: ${name} (inner: ${innerName})`);
206
+ }
207
+ }
196
208
  }),
197
209
  ...readOnly
198
210
  ? [
@@ -511,6 +523,7 @@ export class CodeMirror6 {
511
523
  * Replace the current selection with the result of a function
512
524
  * @param view EditorView instance
513
525
  * @param func function to produce the replacement text
526
+ * @test
514
527
  */
515
528
  static replaceSelections = replaceSelections;
516
529
  }
package/dist/color.d.ts CHANGED
@@ -1,6 +1,9 @@
1
- import type { Text, Extension } from '@codemirror/state';
2
- import type { Tree } from '@lezer/common';
3
- import type { WidgetOptions } from '@bhsd/codemirror-css-color-picker';
4
- export declare const discoverColors: (_: Tree, from: number, to: number, type: string, doc: Text) => WidgetOptions[] | null;
5
- declare const _default: () => Extension;
1
+ import { makeColorPicker } from '@bhsd/codemirror-css-color-picker';
2
+ import type { Extension } from '@codemirror/state';
3
+ /**
4
+ * @implements
5
+ * @test
6
+ */
7
+ export declare const discoverColors: Parameters<typeof makeColorPicker>[0]['discoverColors'];
8
+ declare const _default: Extension[];
6
9
  export default _default;
package/dist/color.js CHANGED
@@ -1,5 +1,9 @@
1
1
  import { splitColors, numToHex } from '@bhsd/common';
2
2
  import { parseCallExpression, parseColorLiteral, ColorType, colorPickerTheme, makeColorPicker, } from '@bhsd/codemirror-css-color-picker';
3
+ /**
4
+ * @implements
5
+ * @test
6
+ */
3
7
  export const discoverColors = (_, from, to, type, doc) => {
4
8
  if (!/mw-(?:(?:ext|html)tag-attribute-value|table-definition)/u.test(type)
5
9
  && (!/mw-(?:template|parserfunction)(?:$|_)/u.test(type)
@@ -26,7 +30,7 @@ export const discoverColors = (_, from, to, type, doc) => {
26
30
  };
27
31
  }).filter(options => options !== null);
28
32
  };
29
- export default () => [
33
+ export default [
30
34
  makeColorPicker({ discoverColors }),
31
35
  colorPickerTheme,
32
36
  ];
package/dist/config.d.ts CHANGED
@@ -64,6 +64,7 @@ tokens: {
64
64
  magicLink: string;
65
65
  pageName: string;
66
66
  parserFunction: string;
67
+ parserFunctionArgumentName: string;
67
68
  parserFunctionBracket: string;
68
69
  parserFunctionDelimiter: string;
69
70
  parserFunctionName: string;
package/dist/config.js CHANGED
@@ -124,6 +124,7 @@ var tokens = {
124
124
  magicLink: "mw-magic-link",
125
125
  pageName: "mw-pagename",
126
126
  parserFunction: "mw-parserfunction",
127
+ parserFunctionArgumentName: "mw-parserfunction-argument-name",
127
128
  parserFunctionBracket: "mw-parserfunction-bracket",
128
129
  parserFunctionDelimiter: "mw-parserfunction-delimiter",
129
130
  parserFunctionName: "mw-parserfunction-name",
@@ -1,2 +1,3 @@
1
- export declare const base: Record<'CDN', string | undefined>, hoverSelector = ".cm-tooltip-hover-mw", diagnosticSelector = ".cm-diagnosticText-clickable", panelSelector = ".cm-panel", panelsSelector = ".cm-panels", isolateSelector = ".cm-bidi-isolate", ltrSelector = ".cm-bidi-ltr", actionSelector = ".cm-diagnosticAction", noDetectionLangs: Set<string>, bgDark = "#4c566a", matchingCls = "cm-matchingTag", nonmatchingCls = "cm-nonmatchingTag";
2
- export declare const isWMF: boolean, isMac: boolean;
1
+ export declare const base: Record<'CDN', string | undefined>, hoverSelector = ".cm-tooltip-hover-mw", diagnosticSelector = ".cm-diagnosticText-clickable", panelSelector = ".cm-panel", panelsSelector = ".cm-panels", actionSelector = ".cm-diagnosticAction", noDetectionLangs: Set<string>, bgDark = "#4c566a", matchingCls = "cm-matchingTag", nonmatchingCls = "cm-nonmatchingTag";
2
+ export declare const isWMF: boolean;
3
+ export declare const isMac: boolean;
package/dist/constants.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { wmf } from '@bhsd/common';
2
- export const base = { CDN: undefined }, hoverSelector = '.cm-tooltip-hover-mw', diagnosticSelector = '.cm-diagnosticText-clickable', panelSelector = '.cm-panel', panelsSelector = '.cm-panels', isolateSelector = '.cm-bidi-isolate', ltrSelector = '.cm-bidi-ltr', actionSelector = '.cm-diagnosticAction', noDetectionLangs = new Set(['plain', 'mediawiki']), bgDark = '#4c566a', matchingCls = 'cm-matchingTag', nonmatchingCls = 'cm-nonmatchingTag';
2
+ export const base = { CDN: undefined }, hoverSelector = '.cm-tooltip-hover-mw', diagnosticSelector = '.cm-diagnosticText-clickable', panelSelector = '.cm-panel', panelsSelector = '.cm-panels', actionSelector = '.cm-diagnosticAction', noDetectionLangs = new Set(['plain', 'mediawiki']), bgDark = '#4c566a', matchingCls = 'cm-matchingTag', nonmatchingCls = 'cm-nonmatchingTag';
3
3
  export const isWMF = /* @__PURE__ */ (() => typeof location === 'object'
4
- && new RegExp(String.raw `\.(?:${wmf})\.org$`, 'u').test(location.hostname))(), isMac = /* @__PURE__ */ (() => {
4
+ && new RegExp(String.raw `\.(?:${wmf})\.org$`, 'u').test(location.hostname))();
5
+ export const isMac = /* @__PURE__ */ (() => {
5
6
  const { vendor, userAgent, maxTouchPoints, platform } = navigator;
6
7
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
7
8
  return vendor?.includes('Apple Computer')
package/dist/css.d.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  import { LanguageSupport } from '@codemirror/language';
2
2
  import type { Extension } from '@codemirror/state';
3
3
  import type { Dialect } from './codemirror';
4
+ /**
5
+ * CSS completion source with dialect-specific adjustments.
6
+ * @param dialect 是否是sanitized-css
7
+ * @test
8
+ */
4
9
  export declare const cssCompletion: (dialect?: Dialect) => Extension;
5
10
  declare const _default: (dialect: Dialect) => LanguageSupport;
6
11
  export default _default;
package/dist/css.js CHANGED
@@ -1,11 +1,18 @@
1
1
  import { cssLanguage, cssCompletionSource } from '@codemirror/lang-css';
2
2
  import { LanguageSupport, syntaxTree } from '@codemirror/language';
3
- export const cssCompletion = (dialect) => cssLanguage.data.of({
4
- autocomplete(context) {
3
+ import { sliceDoc } from './util.js';
4
+ const cssWideKeywords = /* @__PURE__ */ (() => ['revert', 'revert-layer'].map((label) => ({ label, type: 'keyword' })))();
5
+ /**
6
+ * CSS completion source with dialect-specific adjustments.
7
+ * @param dialect 是否是sanitized-css
8
+ * @test
9
+ */
10
+ export const cssCompletion = (dialect) => {
11
+ const source = context => {
5
12
  const { state, pos } = context, node = syntaxTree(state).resolveInner(pos, -1), result = cssCompletionSource(context);
6
13
  if (result) {
7
14
  if (node.name === 'ValueName') {
8
- const options = [{ label: 'revert', type: 'keyword' }, ...result.options];
15
+ const options = [...cssWideKeywords, ...result.options];
9
16
  let { prevSibling } = node;
10
17
  while (prevSibling && prevSibling.name !== 'PropertyName') {
11
18
  ({ prevSibling } = prevSibling);
@@ -13,7 +20,7 @@ export const cssCompletion = (dialect) => cssLanguage.data.of({
13
20
  if (prevSibling) {
14
21
  for (let i = 0; i < options.length; i++) {
15
22
  const option = options[i];
16
- if (CSS.supports(state.sliceDoc(prevSibling.from, node.from) + option.label)) {
23
+ if (CSS.supports(sliceDoc(state, prevSibling), option.label)) {
17
24
  options.splice(i, 1, { ...option, boost: 50 });
18
25
  }
19
26
  }
@@ -26,6 +33,7 @@ export const cssCompletion = (dialect) => cssLanguage.data.of({
26
33
  }
27
34
  }
28
35
  return result;
29
- },
30
- });
36
+ };
37
+ return cssLanguage.data.of({ autocomplete: source });
38
+ };
31
39
  export default (dialect) => new LanguageSupport(cssLanguage, cssCompletion(dialect));
package/dist/escape.d.ts CHANGED
@@ -1,5 +1,25 @@
1
+ import { getLSP } from '@bhsd/browser';
2
+ import type { EditorView } from '@codemirror/view';
1
3
  import type { Extension } from '@codemirror/state';
2
4
  import type { CodeMirror6 } from './codemirror';
3
- export declare const escapeHTML: (str: string) => string, escapeURI: (str: string) => string;
4
- declare const _default: (cm: CodeMirror6) => Extension;
5
+ /**
6
+ * 转义HTML
7
+ * @param str 输入字符串
8
+ * @test
9
+ */
10
+ export declare const escapeHTML: (str: string) => string,
11
+ /**
12
+ * 转义URI
13
+ * @param str 输入字符串
14
+ * @test
15
+ */
16
+ escapeURI: (str: string) => string,
17
+ /**
18
+ * 使用魔术字转义选中文本
19
+ * @param view
20
+ * @param lsp LSP实例
21
+ * @test
22
+ */
23
+ escapeWiki: (view: EditorView, lsp: Exclude<ReturnType<typeof getLSP>, undefined>) => Promise<void>;
24
+ declare const _default: (articlePath?: string) => (cm: CodeMirror6) => Extension;
5
25
  export default _default;
package/dist/escape.js CHANGED
@@ -5,6 +5,7 @@ import { getLSP } from '@bhsd/browser';
5
5
  import elt from 'crelt';
6
6
  import { base } from './constants.js';
7
7
  import { replaceSelections, menuRegistry, } from './codemirror.js';
8
+ import { sliceDoc, toConfigGetter, } from './util.js';
8
9
  const entity = { '"': 'quot', "'": 'apos', '<': 'lt', '>': 'gt', '&': 'amp', ' ': 'nbsp' };
9
10
  /**
10
11
  * 根据函数转换选中文本
@@ -18,13 +19,24 @@ const convert = (func, cmd) => (view) => {
18
19
  }
19
20
  return cmd(view);
20
21
  };
22
+ /**
23
+ * 转义HTML
24
+ * @param str 输入字符串
25
+ * @test
26
+ */
21
27
  export const escapeHTML = (str) => [...str].map(c => {
22
28
  if (c in entity) {
23
29
  return `&${entity[c]};`;
24
30
  }
25
31
  const code = c.codePointAt(0);
26
32
  return code < 256 ? `&#${code};` : `&#x${code.toString(16)};`;
27
- }).join(''), escapeURI = (str) => {
33
+ }).join(''),
34
+ /**
35
+ * 转义URI
36
+ * @param str 输入字符串
37
+ * @test
38
+ */
39
+ escapeURI = (str) => {
28
40
  if (str.includes('%')) {
29
41
  try {
30
42
  return decodeURIComponent(str);
@@ -32,28 +44,35 @@ export const escapeHTML = (str) => [...str].map(c => {
32
44
  catch { }
33
45
  }
34
46
  return encodeURIComponent(str);
47
+ },
48
+ /**
49
+ * 使用魔术字转义选中文本
50
+ * @param view
51
+ * @param lsp LSP实例
52
+ * @test
53
+ */
54
+ escapeWiki = async (view, lsp) => {
55
+ const { state } = view, { ranges } = state.selection, replacements = new WeakMap();
56
+ for (const range of ranges) {
57
+ // eslint-disable-next-line no-await-in-loop
58
+ const [action] = await lsp.provideRefactoringAction(sliceDoc(state, range));
59
+ replacements.set(range, action?.edit.changes[''][0].newText);
60
+ }
61
+ view.dispatch(state.changeByRange(range => {
62
+ const insert = replacements.get(range);
63
+ if (insert === undefined) {
64
+ return { range };
65
+ }
66
+ return {
67
+ range: EditorSelection.range(range.from, range.from + insert.length),
68
+ changes: { from: range.from, to: range.to, insert },
69
+ };
70
+ }));
35
71
  };
36
- const escapeWiki = (view, getConfig) => {
37
- const { state } = view, { ranges } = state.selection, lsp = getLSP(view, false, getConfig, base.CDN);
38
- if (lsp && 'provideRefactoringAction' in lsp && ranges.some(({ empty }) => !empty)) {
39
- (async () => {
40
- const replacements = new WeakMap();
41
- for (const range of ranges) {
42
- // eslint-disable-next-line no-await-in-loop
43
- const [action] = await lsp.provideRefactoringAction(state.sliceDoc(range.from, range.to));
44
- replacements.set(range, action?.edit.changes[''][0].newText);
45
- }
46
- view.dispatch(state.changeByRange(range => {
47
- const insert = replacements.get(range);
48
- if (insert === undefined) {
49
- return { range };
50
- }
51
- return {
52
- range: EditorSelection.range(range.from, range.from + insert.length),
53
- changes: { from: range.from, to: range.to, insert },
54
- };
55
- }));
56
- })();
72
+ const escapeWikiCommand = (view, getConfig) => {
73
+ const lsp = getLSP(view, false, getConfig, base.CDN);
74
+ if (lsp && 'provideRefactoringAction' in lsp && view.state.selection.ranges.some(({ empty }) => !empty)) {
75
+ void escapeWiki(view, lsp);
57
76
  return true;
58
77
  }
59
78
  return false;
@@ -84,7 +103,7 @@ menuRegistry.push({
84
103
  if (lsp && 'provideRefactoringAction' in lsp) {
85
104
  const btnWiki = elt('div', 'Escape with magic words');
86
105
  btnWiki.addEventListener('click', e => {
87
- escapeWiki(view, cm.getWikiConfig);
106
+ escapeWikiCommand(view, cm.getWikiConfig);
88
107
  handlerBase(view, e);
89
108
  });
90
109
  items.unshift(btnWiki);
@@ -93,13 +112,13 @@ menuRegistry.push({
93
112
  return items;
94
113
  },
95
114
  });
96
- export default (cm) => keymap.of([
115
+ export default (articlePath) => (cm) => keymap.of([
97
116
  { key: 'Mod-[', run: convert(escapeHTML, indentLess) },
98
117
  { key: 'Mod-]', run: convert(escapeURI, indentMore) },
99
118
  {
100
119
  key: 'Mod-\\',
101
120
  run(view) {
102
- return escapeWiki(view, cm.getWikiConfig);
121
+ return escapeWikiCommand(view, toConfigGetter(cm.getWikiConfig, articlePath));
103
122
  },
104
123
  },
105
124
  ]);
package/dist/fold.d.ts CHANGED
@@ -1,25 +1,77 @@
1
- import { EditorView } from '@codemirror/view';
2
- import type { Command } from '@codemirror/view';
3
- import type { EditorState, Extension } from '@codemirror/state';
1
+ import { GutterMarker, EditorView } from '@codemirror/view';
2
+ import { RangeSet } from '@codemirror/state';
3
+ import type { BlockInfo, Command } from '@codemirror/view';
4
+ import type { EditorState, StateEffect, Extension } from '@codemirror/state';
4
5
  import type { SyntaxNode, Tree } from '@lezer/common';
5
6
  export interface DocRange {
6
7
  from: number;
7
8
  to: number;
8
9
  }
10
+ declare type AnchorUpdate = (pos: number, range: DocRange) => number;
11
+ export declare const updateSelection: AnchorUpdate, updateAll: AnchorUpdate;
9
12
  /**
10
13
  * 寻找可折叠的范围
11
14
  * @param state
12
15
  * @param posOrNode 字符位置或语法树节点
13
16
  * @param tree 语法树
14
17
  * @param refOnly 是否仅检查`<ref>`标签
18
+ * @test
15
19
  */
16
20
  export declare const foldable: (state: EditorState, posOrNode: number | SyntaxNode, tree?: Tree | null, refOnly?: boolean) => DocRange | false;
17
- export declare const foldableLine: ({ state, viewport: { to: end }, viewportLineBlocks }: EditorView, { from: f, to: t }: DocRange) => DocRange | false;
21
+ /**
22
+ * 折叠所有模板
23
+ * @param state
24
+ * @param tree 语法树
25
+ * @param effects 折叠
26
+ * @param node 语法树节点
27
+ * @param end 终止位置
28
+ * @param anchor 光标位置
29
+ * @param update 更新光标位置
30
+ * @param refOnly 是否仅检查`<ref>`标签
31
+ * @test
32
+ */
33
+ export declare const traverse: (state: EditorState, tree: Tree, effects: StateEffect<DocRange>[], node: SyntaxNode | null, end: number, anchor: number, update: AnchorUpdate, refOnly?: boolean) => number;
34
+ declare class FoldMarker extends GutterMarker {
35
+ readonly open: boolean;
36
+ constructor(open: boolean);
37
+ eq(other: this): boolean;
38
+ toDOM({ state }: EditorView): HTMLElement;
39
+ }
40
+ /**
41
+ * 寻找可折叠的行范围
42
+ * @ignore
43
+ * @test
44
+ */
45
+ export declare const foldableLine: ({ state, viewportLineBlocks }: EditorView, { from: f, to: t }: DocRange) => DocRange | false;
46
+ /**
47
+ * 生成行号旁的折叠标记
48
+ * @param view
49
+ * @test
50
+ */
51
+ export declare const buildMarkers: (view: EditorView) => RangeSet<FoldMarker>;
52
+ /**
53
+ * 生成折叠命令
54
+ * @param refOnly 是否仅检查`<ref>`标签
55
+ * @test
56
+ */
57
+ export declare const foldCommand: (refOnly?: boolean) => Command;
18
58
  export declare const foldRef: Command;
19
59
  export declare const unfoldRef: Command;
60
+ /**
61
+ * 获取所有光标所在的行
62
+ * @param view
63
+ * @test
64
+ */
65
+ export declare const selectedLines: (view: EditorView) => BlockInfo[];
66
+ /**
67
+ * Fold the template at the selection/cursor
68
+ * @param view
69
+ * @test
70
+ */
71
+ export declare const foldAt: Command;
20
72
  declare const _default: (e?: Extension | undefined) => Extension;
21
73
  export default _default;
22
- export declare const mediaWikiFold: Extension;
74
+ export declare const mediawikiFold: Extension;
23
75
  /**
24
76
  * 点击提示折叠模板参数
25
77
  * @param view