wikiparser-node 1.41.0 → 1.43.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 (64) hide show
  1. package/README.md +43 -18
  2. package/bundle/bundle-es8.min.js +27 -27
  3. package/bundle/bundle-lsp.min.js +28 -28
  4. package/bundle/bundle.min.js +19 -19
  5. package/dist/addon/attribute.js +5 -7
  6. package/dist/addon/link.js +3 -2
  7. package/dist/addon/transclude.js +17 -29
  8. package/dist/bin/config.js +1 -1
  9. package/dist/extensions/typings.d.ts +1 -1
  10. package/dist/index.js +1 -2
  11. package/dist/lib/document.js +5 -5
  12. package/dist/lib/element.d.ts +3 -8
  13. package/dist/lib/element.js +2 -26
  14. package/dist/lib/lsp.d.ts +2 -0
  15. package/dist/lib/lsp.js +8 -20
  16. package/dist/lib/node.d.ts +2 -0
  17. package/dist/lib/node.js +0 -7
  18. package/dist/lib/range.d.ts +0 -7
  19. package/dist/lib/range.js +5 -14
  20. package/dist/lib/ranges.js +2 -2
  21. package/dist/lib/text.js +3 -5
  22. package/dist/map.d.ts +66 -0
  23. package/dist/map.js +2 -0
  24. package/dist/mixin/attributesParent.d.ts +4 -3
  25. package/dist/mixin/elementLike.d.ts +13 -0
  26. package/dist/mixin/elementLike.js +66 -53
  27. package/dist/parser/selector.js +3 -3
  28. package/dist/render/expand.js +4 -2
  29. package/dist/render/extension.js +139 -8
  30. package/dist/render/html.js +72 -3
  31. package/dist/render/magicWords.js +24 -131
  32. package/dist/src/arg.js +1 -2
  33. package/dist/src/atom.js +1 -1
  34. package/dist/src/attribute.d.ts +2 -2
  35. package/dist/src/attribute.js +17 -13
  36. package/dist/src/attributes.d.ts +3 -3
  37. package/dist/src/attributes.js +30 -14
  38. package/dist/src/converterRule.js +3 -5
  39. package/dist/src/extLink.js +5 -4
  40. package/dist/src/heading.js +12 -5
  41. package/dist/src/imageParameter.js +1 -2
  42. package/dist/src/index.js +4 -5
  43. package/dist/src/link/base.js +3 -5
  44. package/dist/src/link/file.js +7 -8
  45. package/dist/src/link/galleryImage.js +1 -3
  46. package/dist/src/magicLink.js +7 -7
  47. package/dist/src/multiLine/gallery.js +2 -3
  48. package/dist/src/nowiki/doubleUnderscore.d.ts +1 -0
  49. package/dist/src/nowiki/doubleUnderscore.js +4 -1
  50. package/dist/src/nowiki/quote.js +1 -3
  51. package/dist/src/onlyinclude.js +1 -2
  52. package/dist/src/parameter.js +6 -4
  53. package/dist/src/table/index.js +1 -2
  54. package/dist/src/table/td.d.ts +2 -2
  55. package/dist/src/table/td.js +5 -7
  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/src/transclude.js +4 -2
  61. package/dist/util/debug.js +5 -7
  62. package/dist/util/html.js +3 -11
  63. package/extensions/dist/base.js +1 -1
  64. package/package.json +57 -21
@@ -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;
@@ -186,7 +186,7 @@ const expand = (wikitext, page, callPage, config, include, context, now = index_
186
186
  clean(accum, args);
187
187
  return implicitNewLine(constants_1.functionHooks.get(name)(target, context || undefined), prev);
188
188
  }
189
- else if (magicWords_1.expandedMagicWords.has(name)) {
189
+ try {
190
190
  const result = (0, magicWords_1.expandMagicWord)(name, args.map(({ anon, name: key, value }) => anon ? value : `${key}=${value}`), callPage, config, now, accum);
191
191
  if (result === false) {
192
192
  return m;
@@ -194,7 +194,9 @@ const expand = (wikitext, page, callPage, config, include, context, now = index_
194
194
  clean(accum, args);
195
195
  return implicitNewLine(result, prev);
196
196
  }
197
- return m;
197
+ catch {
198
+ return m;
199
+ }
198
200
  });
199
201
  plain.setText(expanded);
200
202
  if (plain.type === 'parameter-key') {
@@ -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>`;