@bhsd/codemirror-mediawiki 3.9.2 → 3.10.0

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 (51) hide show
  1. package/README.md +71 -11
  2. package/dist/bidi.d.ts +4 -8
  3. package/dist/bidi.js +33 -23
  4. package/dist/codemirror.js +15 -9
  5. package/dist/color.d.ts +1 -1
  6. package/dist/color.js +1 -1
  7. package/dist/config.d.ts +1 -0
  8. package/dist/config.js +1 -0
  9. package/dist/constants.d.ts +3 -2
  10. package/dist/constants.js +3 -2
  11. package/dist/css.js +9 -6
  12. package/dist/escape.d.ts +1 -1
  13. package/dist/escape.js +4 -3
  14. package/dist/fold.d.ts +2 -2
  15. package/dist/fold.js +106 -39
  16. package/dist/hover.d.ts +2 -2
  17. package/dist/hover.js +72 -69
  18. package/dist/index.d.ts +54 -16
  19. package/dist/index.js +91 -38
  20. package/dist/inlay.d.ts +1 -1
  21. package/dist/inlay.js +26 -25
  22. package/dist/javascript.js +45 -2
  23. package/dist/linter.d.ts +15 -2
  24. package/dist/linter.js +2 -2
  25. package/dist/lintsource.d.ts +15 -3
  26. package/dist/lintsource.js +13 -7
  27. package/dist/lua.d.ts +0 -2
  28. package/dist/lua.js +32 -26
  29. package/dist/main.min.js +31 -29
  30. package/dist/matchTag.js +4 -3
  31. package/dist/mediawiki.d.ts +4 -2
  32. package/dist/mediawiki.js +56 -44
  33. package/dist/mw.min.js +33 -37
  34. package/dist/mwConfig.js +2 -2
  35. package/dist/openLinks.d.ts +4 -2
  36. package/dist/openLinks.js +56 -54
  37. package/dist/ref.d.ts +1 -1
  38. package/dist/ref.js +92 -93
  39. package/dist/signature.d.ts +1 -1
  40. package/dist/signature.js +50 -49
  41. package/dist/statusBar.js +9 -8
  42. package/dist/theme.js +6 -0
  43. package/dist/token.d.ts +20 -6
  44. package/dist/token.js +26 -17
  45. package/dist/util.d.ts +17 -2
  46. package/dist/util.js +39 -1
  47. package/dist/wiki.min.js +32 -36
  48. package/i18n/en.json +2 -2
  49. package/i18n/zh-hans.json +2 -2
  50. package/i18n/zh-hant.json +2 -2
  51. package/package.json +14 -12
package/dist/matchTag.js CHANGED
@@ -3,6 +3,7 @@ import { StateField } from '@codemirror/state';
3
3
  import { ensureSyntaxTree } from '@codemirror/language';
4
4
  import { voidHtmlTags, selfClosingTags } from './config.js';
5
5
  import { matchingCls, nonmatchingCls } from './constants.js';
6
+ import { sliceDoc } from './util.js';
6
7
  class Tag {
7
8
  get closing() {
8
9
  return isClosing(this.first, this.type, this.state, true);
@@ -32,7 +33,7 @@ const isTag = ({ name }) => /-(?:ext|html)tag-(?!bracket)/u.test(name), isTagCom
32
33
  const reHtml = new RegExp(`-htmltag-${s}`, 'u'), reExt = new RegExp(`-exttag-${s}`, 'u');
33
34
  return ({ name }, type) => (type === 'ext' ? reExt : reHtml).test(name);
34
35
  }, isBracket = isTagComponent('bracket'), isName = isTagComponent('name'), isClosing = (node, type, state, first) => isBracket(node, type)
35
- && state.sliceDoc(node.from, node.to)[first ? 'endsWith' : 'startsWith']('/'), getName = (state, { from, to }) => state.sliceDoc(from, to).trim().toLowerCase();
36
+ && sliceDoc(state, node)[first ? 'endsWith' : 'startsWith']('/'), getName = (state, node) => sliceDoc(state, node).trim().toLowerCase();
36
37
  /**
37
38
  * 获取标签信息,破损的HTML标签会返回`null`
38
39
  * @param state
@@ -93,9 +94,9 @@ export const matchTag = (state, pos) => {
93
94
  if (!tree) {
94
95
  return null;
95
96
  }
96
- let node = tree.resolve(pos, -1);
97
+ let node = tree.resolveInner(pos, -1);
97
98
  if (!isTag(node)) {
98
- node = tree.resolve(pos, 1);
99
+ node = tree.resolveInner(pos, 1);
99
100
  if (!isTag(node)) {
100
101
  return null;
101
102
  }
@@ -15,6 +15,7 @@ import type { MwConfig } from './token';
15
15
  export declare const isWikiLink: (name: string) => boolean;
16
16
  export declare class FullMediaWiki extends MediaWiki {
17
17
  #private;
18
+ readonly templatedata: boolean;
18
19
  readonly nsRegex: RegExp;
19
20
  readonly functionSynonyms: Completion[];
20
21
  readonly doubleUnderscore: Completion[];
@@ -25,7 +26,7 @@ export declare class FullMediaWiki extends MediaWiki {
25
26
  readonly htmlAttrs: Completion[];
26
27
  readonly elementAttrs: Map<string | undefined, Completion[]>;
27
28
  readonly extAttrs: Map<string, Completion[]>;
28
- constructor(config: MwConfig);
29
+ constructor(config: MwConfig, templatedata?: boolean);
29
30
  /**
30
31
  * This defines the actual CSS class assigned to each tag/token.
31
32
  *
@@ -39,5 +40,6 @@ export declare class FullMediaWiki extends MediaWiki {
39
40
  /**
40
41
  * Get a LanguageSupport instance for the MediaWiki mode.
41
42
  * @param config Configuration for the MediaWiki mode
43
+ * @param templatedata Whether to enable template parameter autocompletion
42
44
  */
43
- export declare const mediawikiBase: (config: MwConfig) => LanguageSupport;
45
+ export declare const mediawikiBase: (config: MwConfig, templatedata?: boolean) => LanguageSupport;
package/dist/mediawiki.js CHANGED
@@ -9,9 +9,10 @@ import { insertCompletionText, pickedCompletion } from '@codemirror/autocomplete
9
9
  import { isUnderscore } from '@bhsd/cm-util';
10
10
  import { commonHtmlAttrs, htmlAttrs, extAttrs } from 'wikiparser-node/dist/util/sharable.mjs';
11
11
  import { htmlTags, tokens } from './config.js';
12
- import { isWMF, isolateSelector, ltrSelector } from './constants.js';
12
+ import { hoverSelector, isWMF, } from './constants.js';
13
13
  import { MediaWiki } from './token.js';
14
- import { hasTag, braceStackUpdate, leadingSpaces, } from './util.js';
14
+ import { hasTag, leadingSpaces, findTemplateName, } from './util.js';
15
+ const ranks = { Required: 1, Suggested: 2, Optional: 3, Deprecated: 4 };
15
16
  /**
16
17
  * 是否是普通维基链接
17
18
  * @param name 节点名称
@@ -41,9 +42,10 @@ const apply = (view, completion, from, to) => {
41
42
  });
42
43
  };
43
44
  export class FullMediaWiki extends MediaWiki {
44
- constructor(config) {
45
+ constructor(config, templatedata = false) {
45
46
  super(config);
46
47
  const { urlProtocols, nsid, functionSynonyms, doubleUnderscore, } = config;
48
+ this.templatedata = templatedata;
47
49
  this.nsRegex = new RegExp(String.raw `^(${Object.keys(nsid).filter(ns => ns !== '').join('|')
48
50
  .replace(/_/gu, ' ')})\s*:\s*`, 'iu');
49
51
  this.functionSynonyms = functionSynonyms.flatMap((obj, i) => Object.keys(obj).map((label) => ({
@@ -95,7 +97,6 @@ export class FullMediaWiki extends MediaWiki {
95
97
  const parser = super.mediawiki(tags);
96
98
  parser.languageData = {
97
99
  closeBrackets: { brackets: ['(', '[', '{', '"'], before: ')]}>' },
98
- autocomplete: this.completionSource,
99
100
  };
100
101
  return parser;
101
102
  }
@@ -155,22 +156,29 @@ export class FullMediaWiki extends MediaWiki {
155
156
  return result?.length
156
157
  ? {
157
158
  offset: leadingSpaces(search).length,
158
- options: result.map(([key, detail]) => ({ type: 'variable', label: key + equal, detail })),
159
+ options: result.flatMap(([keys, detail, info, name]) => keys.map((key) => ({
160
+ type: 'variable',
161
+ label: key + equal,
162
+ section: { name: name, rank: ranks[name] },
163
+ ...detail && { detail },
164
+ ...info && { info },
165
+ }))),
159
166
  }
160
167
  : undefined;
161
168
  }
162
169
  /** 自动补全魔术字和标签名 */
163
170
  get completionSource() {
164
171
  return async (context) => {
165
- const { state, pos, explicit } = context, node = syntaxTree(state).resolve(pos, -1), { name: n, from: f, to: t, } = node, types = new Set(n.split('_')), isParserFunction = hasTag(types, 'parserFunctionName'),
172
+ const { state, pos, explicit } = context, node = syntaxTree(state).resolveInner(pos, -1), { name: n, prevSibling, from: f, to: t, } = node, types = new Set(n.split('_')), isParserFunction = hasTag(types, 'parserFunctionName'),
166
173
  /** 开头不包含` `,但可能包含`_` */ search = state.sliceDoc(f, pos).trimStart(), start = pos - search.length;
167
- let { prevSibling } = node;
168
- if (explicit || isParserFunction && search.includes('#') || isWMF) {
174
+ // 需要opensearch API的建议,只在显式触发时或WMF网站上提供
175
+ if (explicit || isWMF || isParserFunction && search.includes('#')) {
169
176
  const obj = isWMF
170
177
  ? null
171
178
  : {
172
179
  validFor: /^[^|{}<>[\]#]*$/u,
173
180
  };
181
+ // 模板名
174
182
  if (isParserFunction || hasTag(types, 'templateName')) {
175
183
  const options = search.includes(':') ? [] : [...this.functionSynonyms], suggestions = await this.#linkSuggest(search, 10) ?? { offset: 0, options: [] };
176
184
  options.push(...suggestions.options);
@@ -189,8 +197,12 @@ export class FullMediaWiki extends MediaWiki {
189
197
  ...obj,
190
198
  };
191
199
  }
200
+ // 页面名
192
201
  const isPage = hasTag(types, 'pageName') && hasTag(types, 'parserFunction') || 0;
193
202
  if (isPage && search.trim() || hasTag(types, 'linkPageName')) {
203
+ if (!this.config.linkSuggest) {
204
+ return null;
205
+ }
194
206
  const isLink = isWikiLink(n);
195
207
  let prefix = '', ns = 0;
196
208
  if (isPage) {
@@ -218,34 +230,18 @@ export class FullMediaWiki extends MediaWiki {
218
230
  ...obj,
219
231
  };
220
232
  }
233
+ }
234
+ // 需要TemplateData API的建议,只在显式触发时提供
235
+ if (this.config.paramSuggest
236
+ && (explicit || this.templatedata)
237
+ && this.tags.includes('templatedata')) {
221
238
  const isArgument = hasTag(types, 'templateArgumentName'), prevIsDelimiter = prevSibling?.name.includes(tokens.templateDelimiter), isDelimiter = hasTag(types, 'templateDelimiter')
222
239
  || hasTag(types, 'templateBracket') && prevIsDelimiter;
223
- if (this.tags.includes('templatedata')
224
- && (isDelimiter
225
- || isArgument && !search.includes('=')
226
- || hasTag(types, 'template') && prevIsDelimiter)) {
227
- let stack = -1,
228
- /** 可包含`_`、`:`等 */ page = '';
229
- while (prevSibling) {
230
- const { name, from, to } = prevSibling;
231
- if (name.includes(tokens.templateBracket)) {
232
- const [lbrace, rbrace] = braceStackUpdate(state, prevSibling);
233
- stack += lbrace;
234
- if (stack >= 0) {
235
- break;
236
- }
237
- stack += rbrace;
238
- }
239
- else if (stack === -1 && name.includes(tokens.templateName)) {
240
- page = state.sliceDoc(from, to) + page;
241
- }
242
- else if (page && !name.includes(tokens.comment)) {
243
- prevSibling = null;
244
- break;
245
- }
246
- ({ prevSibling } = prevSibling);
247
- }
248
- if (prevSibling && page) {
240
+ if (isDelimiter
241
+ || isArgument && !search.includes('=')
242
+ || hasTag(types, 'template') && prevIsDelimiter) {
243
+ const page = findTemplateName(state, node);
244
+ if (page) {
249
245
  const equal = isArgument && state.sliceDoc(pos, t).trim() === '=' ? '' : '=', suggestions = await this.#paramSuggest(isDelimiter ? '' : search, page, equal);
250
246
  if (suggestions && suggestions.options.length > 0) {
251
247
  return {
@@ -293,10 +289,12 @@ export class FullMediaWiki extends MediaWiki {
293
289
  'comment',
294
290
  'templateVariableName',
295
291
  'templateName',
292
+ 'parserFunctionName',
296
293
  'linkPageName',
297
294
  'linkToSection',
298
295
  'extLink',
299
296
  ])) {
297
+ // 不可能是状态开关、标签、协议或图片参数名
300
298
  return null;
301
299
  }
302
300
  let mt = context.matchBefore(/__(?:(?!__)[\p{L}\p{N}_])*$/u);
@@ -374,7 +372,7 @@ const getSelector = (cls, prefix = '') => typeof prefix === 'string'
374
372
  : prefix.map(p => getSelector(cls, p)).join();
375
373
  const getGround = (type, ground) => ground ? `${type}${ground === 1 ? '' : ground}-` : '';
376
374
  const getGrounds = (grounds, r, g, b, a) => ({
377
- [grounds.map(([template, ext, link]) => `.cm-mw-${getGround('template', template)}${getGround('exttag', ext)}${getGround('link', link)}ground`).join()]: {
375
+ [grounds.map(([template, ext, link]) => `.cm-mw-${getGround('template', template)}${getGround('ext', ext)}${getGround('link', link)}ground`).join()]: {
378
376
  backgroundColor: `rgb(${r},${g},${b},${a})`,
379
377
  },
380
378
  });
@@ -429,7 +427,7 @@ const theme = /* @__PURE__ */ EditorView.theme({
429
427
  color: 'var(--cm-tpl)',
430
428
  fontWeight: 'bold',
431
429
  },
432
- '.cm-mw-template-argument-name': {
430
+ [getSelector(['-argument-name'], ['template', 'parserfunction'])]: {
433
431
  color: 'var(--cm-arg)',
434
432
  fontWeight: 'normal',
435
433
  },
@@ -507,19 +505,33 @@ const theme = /* @__PURE__ */ EditorView.theme({
507
505
  '.cm-mw-tag-ref': {
508
506
  backgroundColor: 'var(--cm-ref)',
509
507
  },
510
- [`${isolateSelector}, &[dir="rtl"] .cm-mw-template-name`]: {
511
- unicodeBidi: 'isolate',
508
+ // hover tooltip and signature tooltip
509
+ [hoverSelector]: {
510
+ padding: '2px 5px',
511
+ width: 'max-content',
512
+ maxWidth: '60vw',
513
+ maxHeight: '60vh',
514
+ overflowY: 'auto',
515
+ },
516
+ [`${hoverSelector} *`]: {
517
+ marginTop: '0!important',
518
+ marginBottom: '0!important',
512
519
  },
513
- [ltrSelector]: {
514
- direction: 'ltr',
515
- display: 'inline-block',
520
+ [`${hoverSelector}>div`]: {
521
+ fontSize: '90%',
522
+ lineHeight: 1.4,
516
523
  },
517
524
  });
518
525
  /**
519
526
  * Get a LanguageSupport instance for the MediaWiki mode.
520
527
  * @param config Configuration for the MediaWiki mode
528
+ * @param templatedata Whether to enable template parameter autocompletion
521
529
  */
522
- export const mediawikiBase = (config) => {
523
- const mode = new FullMediaWiki(config), lang = StreamLanguage.define(mode.mediawiki());
524
- return new LanguageSupport(lang, [syntaxHighlighting(HighlightStyle.define(mode.getTagStyles())), theme]);
530
+ export const mediawikiBase = (config, templatedata) => {
531
+ const mode = new FullMediaWiki(config, templatedata), lang = StreamLanguage.define(mode.mediawiki());
532
+ return new LanguageSupport(lang, [
533
+ syntaxHighlighting(HighlightStyle.define(mode.getTagStyles())),
534
+ theme,
535
+ lang.data.of({ autocomplete: mode.completionSource }),
536
+ ]);
525
537
  };