wikiparser-node 1.40.0 → 1.42.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 (66) hide show
  1. package/README.md +55 -7
  2. package/bundle/bundle-es8.min.js +27 -27
  3. package/bundle/bundle-lsp.min.js +29 -29
  4. package/bundle/bundle.min.js +19 -19
  5. package/config/.schema.json +5 -0
  6. package/config/default.json +1 -1
  7. package/config/enwiki.json +3 -2
  8. package/config/jawiki.json +6 -5
  9. package/config/llwiki.json +2 -1
  10. package/config/minimum.json +1 -1
  11. package/config/moegirl.json +2 -1
  12. package/config/zhwiki.json +3 -2
  13. package/dist/addon/attribute.js +1 -1
  14. package/dist/addon/transclude.js +1 -3
  15. package/dist/base.d.mts +5 -3
  16. package/dist/base.d.ts +5 -3
  17. package/dist/bin/config.js +11 -10
  18. package/dist/index.js +1 -2
  19. package/dist/lib/element.d.ts +3 -8
  20. package/dist/lib/element.js +2 -26
  21. package/dist/lib/lintConfig.js +30 -23
  22. package/dist/lib/lsp.d.ts +2 -0
  23. package/dist/lib/lsp.js +8 -20
  24. package/dist/lib/node.d.ts +2 -0
  25. package/dist/lib/node.js +0 -7
  26. package/dist/lib/range.d.ts +0 -7
  27. package/dist/lib/range.js +5 -14
  28. package/dist/lib/text.js +4 -6
  29. package/dist/map.d.ts +66 -0
  30. package/dist/map.js +2 -0
  31. package/dist/mixin/attributesParent.d.ts +4 -3
  32. package/dist/mixin/elementLike.d.ts +13 -0
  33. package/dist/mixin/elementLike.js +66 -53
  34. package/dist/parser/selector.js +3 -3
  35. package/dist/render/extension.js +139 -8
  36. package/dist/render/html.js +72 -3
  37. package/dist/render/magicWords.js +6 -3
  38. package/dist/src/attribute.d.ts +2 -2
  39. package/dist/src/attribute.js +18 -14
  40. package/dist/src/attributes.d.ts +3 -3
  41. package/dist/src/attributes.js +35 -19
  42. package/dist/src/extLink.js +5 -4
  43. package/dist/src/heading.js +11 -3
  44. package/dist/src/index.js +3 -4
  45. package/dist/src/link/base.js +2 -4
  46. package/dist/src/link/file.js +5 -5
  47. package/dist/src/link/galleryImage.js +1 -3
  48. package/dist/src/magicLink.js +6 -5
  49. package/dist/src/multiLine/gallery.js +2 -3
  50. package/dist/src/nowiki/doubleUnderscore.d.ts +1 -0
  51. package/dist/src/nowiki/doubleUnderscore.js +4 -1
  52. package/dist/src/nowiki/quote.js +1 -3
  53. package/dist/src/parameter.js +5 -2
  54. package/dist/src/table/td.d.ts +2 -2
  55. package/dist/src/table/td.js +1 -1
  56. package/dist/src/tag/html.js +29 -12
  57. package/dist/src/tagPair/ext.js +4 -1
  58. package/dist/src/tagPair/translate.d.ts +1 -1
  59. package/dist/src/tagPair/translate.js +1 -1
  60. package/dist/util/debug.js +5 -7
  61. package/dist/util/html.js +3 -11
  62. package/dist/util/sharable.js +2 -0
  63. package/dist/util/sharable.mjs +3 -1
  64. package/extensions/dist/base.js +1 -1
  65. package/extensions/dist/env.js +34 -0
  66. package/package.json +19 -20
package/dist/lib/text.js CHANGED
@@ -220,7 +220,7 @@ let AstText = (() => {
220
220
  return [];
221
221
  }
222
222
  errorRegex.lastIndex = 0;
223
- const errors = [], nextType = nextSibling?.type, nextName = nextSibling?.name, previousType = previousSibling?.type, root = this.getRootNode(), rootStr = root.toString(), { ext, html, variants } = root.getAttribute('config'), { top, left } = root.posFromIndex(start), { lintConfig } = index_1.default, tagLike = lintConfig.rules['tag-like'], specified = typeof tagLike === 'object' && tagLike[1]
223
+ const errors = [], nextType = nextSibling?.type, nextName = nextSibling?.name, previousType = previousSibling?.type, root = this.getRootNode(), rootStr = root.toString(), { ext, html, variants } = root.getAttribute('config'), { top, left } = root.posFromIndex(start), { lintConfig } = index_1.default, tagLike = lintConfig.rules['tag-like'], specified = tagLike[1]
224
224
  ? new Set(Object.keys(tagLike[1]).filter(tag => tag !== 'invalid' && tag !== 'disallowed'))
225
225
  : new Set(), tags = new Set([
226
226
  'onlyinclude',
@@ -260,8 +260,7 @@ let AstText = (() => {
260
260
  // Rule & Severity
261
261
  let startIndex = start + index, endIndex = startIndex + length, rule, severity, endLine, endCol;
262
262
  const nextChar = rootStr[endIndex], previousChar = rootStr[startIndex - 1], leftBracket = lbrace || lbrack, lConverter = error === '{' && previousChar === '-' && variants.length > 0, rConverter = error === '}' && nextChar === '-' && variants.length > 0, brokenExtLink = lbrack && nextType === 'free-ext-link' && !data.slice(index + 1).trim()
263
- || rbrack && previousType === 'free-ext-link'
264
- && !data.slice(0, index).includes(']');
263
+ || rbrack && previousType === 'free-ext-link' && index === 0;
265
264
  if (magicLink) {
266
265
  rule = 'lonely-http';
267
266
  error = error.toUpperCase();
@@ -571,9 +570,8 @@ let AstText = (() => {
571
570
  if (/\s$/u.test(this.data)) {
572
571
  const spaces = [], mt = /\n[^\S\n]*$/u.exec(this.data);
573
572
  let { nextSibling } = this, mt2 = null;
574
- while (nextSibling && (nextSibling.is('comment')
575
- || nextSibling.is('category')
576
- || nextSibling.type === 'text')) {
573
+ while (nextSibling
574
+ && (nextSibling.is('comment') || nextSibling.is('category') || nextSibling.type === 'text')) {
577
575
  if (nextSibling.type === 'text') {
578
576
  mt2 = mt && /^[^\S\n]*(?=\n)/u.exec(nextSibling.data);
579
577
  if (mt2 || nextSibling.data.trim()) {
package/dist/map.d.ts ADDED
@@ -0,0 +1,66 @@
1
+ import type { RedirectToken, RedirectTargetToken, OnlyincludeToken, IncludeToken, CommentToken, ExtToken, AttributesToken, AttributeToken, AtomToken, ArgToken, TranscludeToken, SyntaxToken, ParameterToken, HeadingToken, HtmlToken, TableToken, TrToken, TdToken, DoubleUnderscoreToken, HrToken, LinkToken, CategoryToken, FileToken, ImageParameterToken, QuoteToken, ExtLinkToken, MagicLinkToken, ListToken, DdToken, ConverterToken, ConverterFlagsToken, ConverterRuleToken, TranslateToken, TvarToken, HiddenToken, GalleryImageToken, ParamLineToken, ImagemapLinkToken, ListRangeToken } from './internal';
2
+ export interface TokenTypeMap {
3
+ redirect: RedirectToken;
4
+ 'redirect-syntax': SyntaxToken;
5
+ 'redirect-target': RedirectTargetToken;
6
+ translate: TranslateToken;
7
+ 'translate-attr': SyntaxToken;
8
+ tvar: TvarToken;
9
+ 'tvar-name': SyntaxToken;
10
+ onlyinclude: OnlyincludeToken;
11
+ include: IncludeToken;
12
+ comment: CommentToken;
13
+ ext: ExtToken;
14
+ 'ext-attrs': AttributesToken;
15
+ 'ext-attr-dirty': AtomToken;
16
+ 'ext-attr': AttributeToken;
17
+ 'attr-key': AtomToken;
18
+ arg: ArgToken;
19
+ 'arg-name': AtomToken;
20
+ hidden: HiddenToken;
21
+ 'magic-word': TranscludeToken;
22
+ 'magic-word-name': SyntaxToken;
23
+ 'invoke-function': AtomToken;
24
+ 'invoke-module': AtomToken;
25
+ template: TranscludeToken;
26
+ 'template-name': AtomToken;
27
+ parameter: ParameterToken;
28
+ heading: HeadingToken;
29
+ 'heading-trail': SyntaxToken;
30
+ html: HtmlToken;
31
+ 'html-attrs': AttributesToken;
32
+ 'html-attr-dirty': AtomToken;
33
+ 'html-attr': AttributeToken;
34
+ table: TableToken;
35
+ tr: TrToken;
36
+ td: TdToken;
37
+ 'table-syntax': SyntaxToken;
38
+ 'table-attrs': AttributesToken;
39
+ 'table-attr-dirty': AtomToken;
40
+ 'table-attr': AttributeToken;
41
+ hr: HrToken;
42
+ 'double-underscore': DoubleUnderscoreToken;
43
+ link: LinkToken;
44
+ 'link-target': AtomToken;
45
+ category: CategoryToken;
46
+ file: FileToken;
47
+ 'gallery-image': GalleryImageToken;
48
+ 'imagemap-image': GalleryImageToken;
49
+ 'image-parameter': ImageParameterToken;
50
+ quote: QuoteToken;
51
+ 'ext-link': ExtLinkToken;
52
+ 'ext-link-url': MagicLinkToken;
53
+ 'free-ext-link': MagicLinkToken;
54
+ 'magic-link': MagicLinkToken;
55
+ list: ListToken;
56
+ dd: DdToken;
57
+ converter: ConverterToken;
58
+ 'converter-flags': ConverterFlagsToken;
59
+ 'converter-flag': AtomToken;
60
+ 'converter-rule': ConverterRuleToken;
61
+ 'converter-rule-variant': AtomToken;
62
+ 'param-line': ParamLineToken;
63
+ 'imagemap-link': ImagemapLinkToken;
64
+ 'list-range': ListRangeToken;
65
+ }
66
+ export type SelectedTokenTypes = keyof TokenTypeMap;
package/dist/map.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,6 +1,7 @@
1
1
  export interface AttributesParentBase {
2
2
  /** all attributes / 全部属性 */
3
- attributes: Record<string, string | true>;
3
+ get attributes(): Record<string, string>;
4
+ set attributes(attrs: Record<string, string | true>);
4
5
  /** class attribute in string / 以字符串表示的class属性 */
5
6
  className: string;
6
7
  /** class attribute in Set / 以Set表示的class属性 */
@@ -20,7 +21,7 @@ export interface AttributesParentBase {
20
21
  * 获取指定属性
21
22
  * @param key attribute name / 属性键
22
23
  */
23
- getAttr(key: string): string | true | undefined;
24
+ getAttr(key: string): string | undefined;
24
25
  /**
25
26
  * Get all attribute names
26
27
  *
@@ -32,7 +33,7 @@ export interface AttributesParentBase {
32
33
  *
33
34
  * 获取全部属性
34
35
  */
35
- getAttrs(): Record<string, string | true>;
36
+ getAttrs(): Record<string, string>;
36
37
  /**
37
38
  * Set the attribute
38
39
  *
@@ -1,5 +1,7 @@
1
1
  import type { AstNodes, Token, HtmlToken, ExtToken } from '../internal';
2
+ import type { TokenTypeMap, SelectedTokenTypes } from '../map';
2
3
  declare type ElementConstructor = abstract new (...args: any[]) => {
4
+ readonly parentNode: Token | undefined;
3
5
  readonly childNodes: readonly AstNodes[];
4
6
  detach?: () => void;
5
7
  };
@@ -12,12 +14,21 @@ export interface ElementLike {
12
14
  readonly lastElementChild: Token | undefined;
13
15
  /** number of child elements / 非文本子节点总数 */
14
16
  readonly childElementCount: number;
17
+ /**
18
+ * Get the closest ancestor node that matches the selector
19
+ *
20
+ * 最近的符合选择器的祖先节点
21
+ * @param selector selector / 选择器
22
+ */
23
+ closest<K extends SelectedTokenTypes>(selector: K): TokenTypeMap[K] | undefined;
24
+ closest<T = Token>(selector: string): T | undefined;
15
25
  /**
16
26
  * Get the first descendant that matches the selector
17
27
  *
18
28
  * 符合选择器的第一个后代节点
19
29
  * @param selector selector / 选择器
20
30
  */
31
+ querySelector<K extends SelectedTokenTypes>(selector: K): TokenTypeMap[K] | undefined;
21
32
  querySelector<T = Token>(selector: string): T | undefined;
22
33
  /**
23
34
  * Get all descendants that match the selector
@@ -25,6 +36,7 @@ export interface ElementLike {
25
36
  * 符合选择器的所有后代节点
26
37
  * @param selector selector / 选择器
27
38
  */
39
+ querySelectorAll<K extends SelectedTokenTypes>(selector: K): TokenTypeMap[K][];
28
40
  querySelectorAll<T = Token>(selector: string): T[];
29
41
  /**
30
42
  * Escape `=` and `|`
@@ -39,6 +51,7 @@ export interface ElementLike {
39
51
  * 类型选择器
40
52
  * @param types token types / 节点类型
41
53
  */
54
+ getElementByTypes<K extends SelectedTokenTypes>(types: K): TokenTypeMap[K] | undefined;
42
55
  getElementByTypes<T = Token>(types: string): T | undefined;
43
56
  /**
44
57
  * Get the first descendant with the id
@@ -7,30 +7,41 @@ const selector_1 = require("../util/selector");
7
7
  const constants_1 = require("../util/constants");
8
8
  /** @ignore */
9
9
  const elementLike = (constructor) => {
10
- LINT: {
11
- class ElementLike extends constructor {
12
- /* NOT FOR BROWSER */
13
- get children() {
14
- return this.childNodes.filter((child) => child.type !== 'text');
15
- }
16
- get firstElementChild() {
17
- return this.childNodes.find((child) => child.type !== 'text');
18
- }
19
- get lastElementChild() {
20
- return this.childNodes.findLast((child) => child.type !== 'text');
21
- }
22
- get childElementCount() {
23
- return this.children.length;
24
- }
25
- /* NOT FOR BROWSER END */
26
- #getCondition(selector) {
27
- return (0, selector_1.getCondition)(selector,
28
- // eslint-disable-next-line unicorn/no-negated-condition
29
- !('type' in this) ?
30
- undefined :
31
- this);
10
+ class ElementLike extends constructor {
11
+ /* NOT FOR BROWSER */
12
+ get children() {
13
+ return this.childNodes.filter((child) => child.type !== 'text');
14
+ }
15
+ get firstElementChild() {
16
+ return this.childNodes.find((child) => child.type !== 'text');
17
+ }
18
+ get lastElementChild() {
19
+ return this.childNodes.findLast((child) => child.type !== 'text');
20
+ }
21
+ get childElementCount() {
22
+ return this.children.length;
23
+ }
24
+ /* NOT FOR BROWSER END */
25
+ #getCondition(selector) {
26
+ return (0, selector_1.getCondition)(selector,
27
+ // eslint-disable-next-line unicorn/no-negated-condition
28
+ !('type' in this) ?
29
+ undefined :
30
+ this);
31
+ }
32
+ closest(selector) {
33
+ const condition = this.#getCondition(selector);
34
+ let { parentNode } = this;
35
+ while (parentNode) {
36
+ if (condition(parentNode)) {
37
+ return parentNode;
38
+ }
39
+ ({ parentNode } = parentNode);
32
40
  }
33
- getElementBy(condition) {
41
+ return undefined;
42
+ }
43
+ getElementBy(condition) {
44
+ LINT: {
34
45
  const stack = [...this.childNodes].reverse();
35
46
  while (stack.length > 0) {
36
47
  const child = stack.pop(), { type, childNodes } = child;
@@ -46,10 +57,12 @@ const elementLike = (constructor) => {
46
57
  }
47
58
  return undefined;
48
59
  }
49
- querySelector(selector) {
50
- return this.getElementBy(this.#getCondition(selector));
51
- }
52
- getElementsBy(condition) {
60
+ }
61
+ querySelector(selector) {
62
+ LINT: return this.getElementBy(this.#getCondition(selector));
63
+ }
64
+ getElementsBy(condition) {
65
+ LINT: {
53
66
  const stack = [...this.childNodes].reverse(), descendants = [];
54
67
  while (stack.length > 0) {
55
68
  const child = stack.pop(), { type, childNodes } = child;
@@ -65,36 +78,36 @@ const elementLike = (constructor) => {
65
78
  }
66
79
  return descendants;
67
80
  }
68
- querySelectorAll(selector) {
69
- return this.getElementsBy(this.#getCondition(selector));
70
- }
71
- escape() {
72
- LSP: {
73
- for (const child of this.childNodes) {
74
- child.escape();
75
- }
76
- /* NOT FOR BROWSER */
77
- this.detach?.();
81
+ }
82
+ querySelectorAll(selector) {
83
+ LINT: return this.getElementsBy(this.#getCondition(selector));
84
+ }
85
+ escape() {
86
+ LSP: {
87
+ for (const child of this.childNodes) {
88
+ child.escape();
78
89
  }
90
+ /* NOT FOR BROWSER */
91
+ this.detach?.();
79
92
  }
80
- /* NOT FOR BROWSER */
81
- getElementByTypes(types) {
82
- const typeSet = new Set(types.split(',').map(str => str.trim()));
83
- return this.getElementBy((({ type }) => typeSet.has(type)));
84
- }
85
- getElementById(id) {
86
- return this.getElementBy((token => 'id' in token && token.id === id));
87
- }
88
- getElementsByClassName(className) {
89
- return this.getElementsBy((token => 'classList' in token && token.classList.has(className)));
90
- }
91
- getElementsByTagName(tag) {
92
- return this.getElementsBy((({ type, name }) => name === tag && (type === 'html' || type === 'ext')));
93
- }
94
93
  }
95
- (0, debug_1.mixin)(ElementLike, constructor);
96
- return ElementLike;
94
+ /* NOT FOR BROWSER */
95
+ getElementByTypes(types) {
96
+ const typeSet = new Set(types.split(',').map(str => str.trim()));
97
+ return this.getElementBy((({ type }) => typeSet.has(type)));
98
+ }
99
+ getElementById(id) {
100
+ return this.getElementBy((token => 'id' in token && token.id === id));
101
+ }
102
+ getElementsByClassName(className) {
103
+ return this.getElementsBy((token => 'classList' in token && token.classList.has(className)));
104
+ }
105
+ getElementsByTagName(tag) {
106
+ return this.getElementsBy((({ type, name }) => name === tag && (type === 'html' || type === 'ext')));
107
+ }
97
108
  }
109
+ (0, debug_1.mixin)(ElementLike, constructor);
110
+ return ElementLike;
98
111
  };
99
112
  exports.elementLike = elementLike;
100
113
  constants_1.mixins['elementLike'] = __filename;
@@ -163,8 +163,8 @@ const matches = (token, step, scope, has) => {
163
163
  }
164
164
  }
165
165
  else if (selector.length === 4) { // 情形2:属性选择器
166
- const [key, equal, val = '', i] = selector, isAttr = typeof token.hasAttr === 'function' && typeof token.getAttr === 'function';
167
- if (!(key in token || isAttr && token.hasAttr(key))) {
166
+ const [key, equal, val = '', i] = selector;
167
+ if (!(key in token || typeof token.hasAttr === 'function' && token.hasAttr(key))) {
168
168
  return equal === '!=';
169
169
  }
170
170
  const v = toCase(val, i), thisVal = getAttr(token, key);
@@ -234,7 +234,7 @@ const matches = (token, step, scope, has) => {
234
234
  for (; node; node = node.parentNode) {
235
235
  const lang = node.attributes?.['lang'];
236
236
  if (lang !== undefined) {
237
- return typeof lang === 'string' && regex.test(lang);
237
+ return regex.test(lang);
238
238
  }
239
239
  }
240
240
  return false;
@@ -3,6 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.renderExt = void 0;
4
4
  const constants_1 = require("../util/constants");
5
5
  const string_1 = require("../util/string");
6
+ const sharable_1 = require("../util/sharable");
7
+ /** @ignore */
8
+ const getCiteNoteId = (i, refName) => `cite_note${refName ? `-${(0, string_1.sanitizeAttr)(refName, true)}` : ''}-${i}`, getCiteRefId = (i, count, refName) => `cite_ref-${refName ? `${(0, string_1.sanitizeAttr)(refName, true)}_${i}-${count - 1}` : i}`, updateRef = (ref, content, dir) => {
9
+ if (!ref.content) {
10
+ ref.content = content;
11
+ ref.dir = dir;
12
+ }
13
+ };
6
14
  /**
7
15
  * 将扩展标签渲染为HTML
8
16
  * @param token 扩展标签节点
@@ -33,14 +41,16 @@ const renderExt = (token, opt) => {
33
41
  .trim()}${padding}</div>`;
34
42
  }
35
43
  case 'gallery': {
36
- const caption = firstChild.getAttrToken('caption'), perrow = parseInt(String(firstChild.getAttr('perrow'))), mode = firstChild.getAttr('mode'), { classList } = firstChild, nolines = typeof mode === 'string' && mode.toLowerCase() === 'nolines', padding = nolines ? 9 : 43;
44
+ const caption = firstChild.getAttrToken('caption'), perrow = parseInt(firstChild.getAttr('perrow') || ''), { classList } = firstChild, mode = firstChild.getAttr('mode')?.toLowerCase(), nolines = mode === 'nolines', padding = nolines ? 9 : 43;
37
45
  classList.add('gallery');
38
46
  if (nolines) {
39
47
  classList.add('mw-gallery-nolines');
40
48
  }
49
+ else if (!mode || mode === 'traditional') {
50
+ classList.add('mw-gallery-traditional');
51
+ }
41
52
  if (perrow > 0) {
42
- const style = firstChild.getAttr('style');
43
- firstChild.setAttr('style', `max-width: ${(lastChild.widths + padding) * perrow}px;${typeof style === 'string' ? style : ''}`);
53
+ firstChild.setAttr('style', `max-width: ${(lastChild.widths + padding) * perrow}px;${firstChild.getAttr('style') || ''}`);
44
54
  }
45
55
  return `<ul${firstChild.toHtmlInternal()}>\n${caption
46
56
  ? `\t<li class="gallerycaption">${caption.lastChild.toHtmlInternal({ nowrap: true })}</li>\n`
@@ -51,7 +61,7 @@ const renderExt = (token, opt) => {
51
61
  let html = lastChild.toHtmlInternal().trimEnd().replace(/^\n+/u, ''), lexer = firstChild.getAttr('lang');
52
62
  const dir = firstChild.getAttr('dir') === 'rtl' ? ' rtl' : 'ltr', isInline = firstChild.getAttr('enclose') === 'none' || firstChild.hasAttr('inline'), showLines = firstChild.hasAttr('line'), { classList } = firstChild;
53
63
  classList.add('mw-highlight');
54
- if (lexer && lexer !== true) {
64
+ if (lexer) {
55
65
  const { Prism, loadLanguage } = require('./syntaxhighlight');
56
66
  lexer = lexer.toLowerCase();
57
67
  if (Prism) {
@@ -64,7 +74,7 @@ const renderExt = (token, opt) => {
64
74
  }
65
75
  catch { }
66
76
  }
67
- const highlight = firstChild.getAttr('highlight'), lines = typeof highlight === 'string' && new Set(highlight.split(',').flatMap((str) => {
77
+ const highlight = firstChild.getAttr('highlight'), lines = highlight && new Set(highlight.split(',').flatMap((str) => {
68
78
  const num = Number(str);
69
79
  if (Number.isInteger(num) && num > 0) {
70
80
  return num;
@@ -83,14 +93,14 @@ const renderExt = (token, opt) => {
83
93
  if (linenos) {
84
94
  const linelinks = firstChild.getAttr('linelinks'), startAttr = firstChild.getAttr('start');
85
95
  lineReplace = '<span class="linenos" data-line="$1"></span>';
86
- if (startAttr && startAttr !== true) {
96
+ if (startAttr) {
87
97
  start = Number(startAttr);
88
98
  if (!Number.isInteger(start) || start < 0) {
89
99
  start = 1;
90
100
  }
91
101
  }
92
- if (linelinks && linelinks !== true) {
93
- lineReplace = `<a href="#${linelinks}-$1">${lineReplace}</a>`;
102
+ if (linelinks) {
103
+ lineReplace = `<a href="#${(0, string_1.sanitizeId)(linelinks)}-$1">${lineReplace}</a>`;
94
104
  begin = `${linelinks}-`;
95
105
  end = '</span>';
96
106
  }
@@ -145,6 +155,127 @@ const renderExt = (token, opt) => {
145
155
  ? `<code${firstChild.toHtmlInternal()}>${html.trim().replaceAll('\n', ' ')}</code>`
146
156
  : `<div${firstChild.toHtmlInternal()}>${html && `<pre>${(0, string_1.newline)(html)}</pre>`}</div>`;
147
157
  }
158
+ case 'ref': {
159
+ const refs = constants_1.states.get(token.getRootNode())?.refs;
160
+ if (!refs) {
161
+ return '';
162
+ }
163
+ const follow = firstChild.getAttr('follow') || '';
164
+ if (/^\d+$/u.test(follow)) {
165
+ return '';
166
+ }
167
+ let refName = firstChild.getAttr('name') || '';
168
+ if (!/\D/u.test(refName)) {
169
+ refName = '';
170
+ }
171
+ else if (refName && follow) {
172
+ return '';
173
+ }
174
+ let dir = firstChild.getAttr('dir')?.toLowerCase();
175
+ if (dir !== 'ltr' && dir !== 'rtl') {
176
+ dir = undefined;
177
+ }
178
+ const text = token.innerText?.trim(), references = token.closest('ext#references');
179
+ if (references) {
180
+ const { referencesGroup } = refs.get(references.getAttr('group') || '');
181
+ if (refName && text) {
182
+ const ref = referencesGroup.find(({ name: n }) => n === refName);
183
+ if (ref) {
184
+ updateRef(ref, lastChild, dir);
185
+ }
186
+ }
187
+ return '';
188
+ }
189
+ else if (!refName && !text || text && /<references\b[^>]*>/iu.test(text)) {
190
+ return '';
191
+ }
192
+ else if (text && /<ref\b[^>]*>/iu.test(text)) {
193
+ const inner = lastChild.cloneNode();
194
+ for (const ref of inner.querySelectorAll('ext#ref')) {
195
+ ref.remove();
196
+ }
197
+ if (/<ref\b[^>]*>/iu.test(inner.toString())) {
198
+ return '';
199
+ }
200
+ }
201
+ const group = firstChild.getAttr('group') || '';
202
+ if (!refs.has(group)) {
203
+ refs.set(group, { referencesGroup: [], follows: [] });
204
+ }
205
+ const { referencesGroup, follows } = refs.get(group);
206
+ if (follow) {
207
+ const ref = referencesGroup.find(({ name: n }) => n === follow);
208
+ if (ref) {
209
+ if (ref.content) {
210
+ ref.content.safeAppend([' ', ...lastChild.childNodes]);
211
+ }
212
+ else {
213
+ ref.content = lastChild;
214
+ }
215
+ }
216
+ else {
217
+ refs.id++;
218
+ follows.push({ content: lastChild });
219
+ }
220
+ return '';
221
+ }
222
+ let i = refName ? referencesGroup.findIndex(({ name: n }) => n === refName) : -1, count = 1, ref;
223
+ if (i === -1) {
224
+ i = referencesGroup.length;
225
+ ref = {
226
+ ...refName && { name: refName },
227
+ ...text && { content: lastChild },
228
+ dir,
229
+ count,
230
+ id: ++refs.id,
231
+ };
232
+ referencesGroup.push(ref);
233
+ }
234
+ else {
235
+ ref = referencesGroup[i];
236
+ ref.count++;
237
+ ({ count } = ref);
238
+ if (text) {
239
+ updateRef(ref, lastChild, dir);
240
+ }
241
+ }
242
+ return `<sup id="${getCiteRefId(ref.id, count, refName)}" class="reference"><a href="#${getCiteNoteId(ref.id, refName)}"><span class="cite-bracket">[</span>${group}${group && ' '}${i + 1}<span class="cite-bracket">]</span></a></sup>`;
243
+ }
244
+ case 'references': {
245
+ const refs = constants_1.states.get(token.getRootNode())?.refs;
246
+ if (!refs
247
+ || firstChild.childNodes.filter(node => node.is('ext-attr'))
248
+ .some(({ name: key }) => !sharable_1.extAttrs['references'].has(key))) {
249
+ return '';
250
+ }
251
+ const group = firstChild.getAttr('group') || '';
252
+ if (!refs.has(group)) {
253
+ return '';
254
+ }
255
+ const html = lastChild.toHtmlInternal();
256
+ if (!refs.has(group)) { // 嵌套的`<ref>`
257
+ return html;
258
+ }
259
+ const { referencesGroup, follows } = refs.get(group);
260
+ if (referencesGroup.length === 0 && follows.length === 0) {
261
+ return '';
262
+ }
263
+ let ol = `<ol class="references"${group && ` data-mw-group="${group}"`}>`;
264
+ for (const { content } of follows) {
265
+ ol += `\n<p><span class="reference-text">${content.toHtmlInternal()}</span>\n</p>`;
266
+ }
267
+ for (let i = 0; i < referencesGroup.length; i++) {
268
+ const { content, count, dir, name: refName, id } = referencesGroup[i];
269
+ ol += `\n<li id="${getCiteNoteId(id, refName)}"${dir ? ` class="mw-cite-dir-${dir}"` : ''}><span class="mw-cite-backlink">${count === 1
270
+ ? `<a href="#${getCiteRefId(id, 1, refName)}">↑</a>`
271
+ : `↑${Array.from({ length: count }, (_, j) => ` <sup><a href="#${getCiteRefId(id, j + 1, refName)}">${i + 1}.${j}</a></sup>`).join('')}`}</span> <span class="reference-text">${content?.toHtmlInternal() ?? ''}</span>\n</li>`;
272
+ }
273
+ ol += '\n</ol>';
274
+ refs.delete(group);
275
+ return firstChild.getAttr('responsive') === '0'
276
+ ? ol
277
+ : `<div class="mw-references-wrap${referencesGroup.length > 10 ? ' mw-references-columns' : ''}">${ol}</div>`;
278
+ }
148
279
  default:
149
280
  return '';
150
281
  }
@@ -2,7 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.toHtml = void 0;
4
4
  const constants_1 = require("../util/constants");
5
- const blockElems = 'table|h[1-6]|pre|p|[uod]l', antiBlockElems = 't[dh]';
5
+ const blockElems = 'table|h[1-6]|pre|p|[uod]l', antiBlockElems = 't[dh]', allowed = new Set(['sup', 'sub', 'bdi', 'i', 'b', 's', 'strike', 'q']);
6
+ const tocContainer = '<div id="toc" class="toc" role="navigation" aria-labelledby="mw-toc-heading">'
7
+ + '<input type="checkbox" role="button" id="toctogglecheckbox" class="toctogglecheckbox" style="display:none" />'
8
+ + '<div class="toctitle">'
9
+ + '<h2 id="mw-toc-heading">Contents</h2>'
10
+ + '<span class="toctogglespan"><label class="toctogglelabel" for="toctogglecheckbox"></label></span>'
11
+ + '</div>';
6
12
  // eslint-disable-next-line @typescript-eslint/no-unused-expressions
7
13
  /<(?:table|\/t[dh]|\/?(?:tr|caption|d[td]|li))\b/iu;
8
14
  const openRegex = new RegExp(String.raw `<(?:${blockElems}|\/${antiBlockElems}|\/?(?:tr|caption|d[td]|li))\b`, 'iu');
@@ -14,8 +20,70 @@ const closeRegex = new RegExp(String.raw `<(?:\/(?:${blockElems})|${antiBlockEle
14
20
  * @param token 展开后的节点
15
21
  */
16
22
  const toHtml = (token) => {
17
- constants_1.states.set(token, { headings: new Set(), categories: new Set() });
23
+ constants_1.states.set(token, {
24
+ headings: new Set(),
25
+ categories: new Set(),
26
+ refs: Object.assign(new Map(), { id: 0 }),
27
+ });
28
+ // 前处理引用
29
+ const hasCite = token.getAttribute('config').ext.includes('references');
30
+ if (hasCite) {
31
+ token.append('\n', token.createElement('references', { selfClosing: true }));
32
+ }
33
+ // 处理目录
34
+ const tocSwitch = token.querySelector('double-underscore#toc'), forcetoc = tocSwitch || token.querySelector('double-underscore#forcetoc'), tocData = [];
35
+ if (forcetoc || !token.querySelector('double-underscore#notoc')) {
36
+ const headings = token.querySelectorAll('heading,html#h1,html#h2,html#h3,html#h4,html#h5,html#h6');
37
+ let firstHeading;
38
+ for (const heading of headings) {
39
+ if (heading.type === 'heading') {
40
+ tocData.push([heading.level, heading.getRenderedId(), heading.firstChild.toHtmlInternal().trim()]);
41
+ firstHeading ??= heading;
42
+ }
43
+ else {
44
+ const tocLine = heading.getTocLine();
45
+ if (tocLine) {
46
+ tocData.push([Number(heading.name.slice(1)), ...tocLine]);
47
+ firstHeading ??= heading;
48
+ }
49
+ }
50
+ }
51
+ const { length } = tocData;
52
+ if (forcetoc && length || length > 3) {
53
+ const levels = [], tocNumbers = [];
54
+ let toc = tocContainer, i = 0;
55
+ for (const [level, id, text] of tocData) {
56
+ const n = levels.length;
57
+ let j = levels.findIndex(l => l >= level), prefix;
58
+ if (j === -1) {
59
+ j = n;
60
+ prefix = '\n<ul>';
61
+ levels.push(level);
62
+ tocNumbers.push(1);
63
+ }
64
+ else {
65
+ prefix = `${'</li>\n</ul>\n'.repeat(n - j - 1)}</li>`;
66
+ levels.splice(j, Infinity, level);
67
+ tocNumbers.length = j + 1;
68
+ tocNumbers[j]++;
69
+ }
70
+ toc += `${prefix}\n<li class="toclevel-${j + 1} tocsection-${++i}"><a href="#${id}"><span class="tocnumber">${tocNumbers.join('.')}</span> <span class="toctext">${text.replaceAll(/<(\/?)([a-z]\w*)\b.*?>/gu, (_, slash, tag) => allowed.has(tag) ? `<${slash}${tag}>` : '').trim()}</span></a>`;
71
+ }
72
+ toc = i === 0 ? '' : `${toc}${'</li>\n</ul>\n'.repeat(levels.length)}</div>\n`;
73
+ if (tocSwitch) {
74
+ tocSwitch.tocData = toc;
75
+ }
76
+ else {
77
+ firstHeading.tocData = toc;
78
+ }
79
+ }
80
+ }
81
+ // 后处理引用
18
82
  const lines = token.toHtmlInternal().split('\n');
83
+ if (hasCite && lines.at(-1) === '') {
84
+ lines.pop();
85
+ }
86
+ // 处理正文
19
87
  let output = '', inBlockElem = false, pendingPTag = false, inBlockquote = false, lastParagraph = '';
20
88
  const /** @ignore */ closeParagraph = () => {
21
89
  if (lastParagraph) {
@@ -78,9 +146,10 @@ const toHtml = (token) => {
78
146
  }
79
147
  }
80
148
  output += closeParagraph();
149
+ let html = output.trimEnd();
150
+ // 处理分类
81
151
  const { categories } = constants_1.states.get(token);
82
152
  constants_1.states.delete(token);
83
- let html = output.trimEnd();
84
153
  if (categories.size > 0) {
85
154
  html += `
86
155
  <div id="catlinks" class="catlinks"><div><a href="${token.normalizeTitle('Special:Categories', -1, { temporary: true }).getUrl()}" title="Special:Categories">Categories</a>: <ul>${[...categories].map(catlink => `<li>${catlink}</li>`).join('')}</div></div>`;