@wdprlib/parser 3.1.2 → 3.2.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 (124) hide show
  1. package/dist/index.cjs +295 -118
  2. package/dist/index.js +272 -95
  3. package/package.json +5 -3
  4. package/src/index.ts +163 -0
  5. package/src/lexer/index.ts +20 -0
  6. package/src/lexer/lexer.ts +687 -0
  7. package/src/lexer/tokens.ts +141 -0
  8. package/src/parser/constants.ts +173 -0
  9. package/src/parser/depth.ts +251 -0
  10. package/src/parser/index.ts +18 -0
  11. package/src/parser/parse.ts +315 -0
  12. package/src/parser/postprocess/divAdjacentParagraph.ts +76 -0
  13. package/src/parser/postprocess/index.ts +15 -0
  14. package/src/parser/postprocess/spanStrip.ts +697 -0
  15. package/src/parser/preprocess/expr.ts +265 -0
  16. package/src/parser/preprocess/index.ts +38 -0
  17. package/src/parser/preprocess/typography.ts +67 -0
  18. package/src/parser/preprocess/utils.ts +250 -0
  19. package/src/parser/preprocess/whitespace.ts +111 -0
  20. package/src/parser/rules/block/align.ts +282 -0
  21. package/src/parser/rules/block/bibliography.ts +359 -0
  22. package/src/parser/rules/block/block-list.ts +689 -0
  23. package/src/parser/rules/block/blockquote.ts +238 -0
  24. package/src/parser/rules/block/center.ts +87 -0
  25. package/src/parser/rules/block/clear-float.ts +75 -0
  26. package/src/parser/rules/block/code.ts +187 -0
  27. package/src/parser/rules/block/collapsible.ts +337 -0
  28. package/src/parser/rules/block/comment.ts +73 -0
  29. package/src/parser/rules/block/content-separator.ts +79 -0
  30. package/src/parser/rules/block/definition-list.ts +270 -0
  31. package/src/parser/rules/block/div.ts +400 -0
  32. package/src/parser/rules/block/embed-block.ts +153 -0
  33. package/src/parser/rules/block/footnoteblock.ts +200 -0
  34. package/src/parser/rules/block/heading.ts +142 -0
  35. package/src/parser/rules/block/horizontal-rule.ts +61 -0
  36. package/src/parser/rules/block/html.ts +222 -0
  37. package/src/parser/rules/block/iframe.ts +239 -0
  38. package/src/parser/rules/block/iftags.ts +150 -0
  39. package/src/parser/rules/block/include.ts +179 -0
  40. package/src/parser/rules/block/index.ts +127 -0
  41. package/src/parser/rules/block/list.ts +244 -0
  42. package/src/parser/rules/block/math.ts +183 -0
  43. package/src/parser/rules/block/module/backlinks/index.ts +31 -0
  44. package/src/parser/rules/block/module/backlinks/types.ts +21 -0
  45. package/src/parser/rules/block/module/categories/index.ts +34 -0
  46. package/src/parser/rules/block/module/categories/types.ts +21 -0
  47. package/src/parser/rules/block/module/css/index.ts +37 -0
  48. package/src/parser/rules/block/module/iftags/condition.ts +109 -0
  49. package/src/parser/rules/block/module/iftags/index.ts +26 -0
  50. package/src/parser/rules/block/module/iftags/preprocess.ts +140 -0
  51. package/src/parser/rules/block/module/iftags/resolve.ts +73 -0
  52. package/src/parser/rules/block/module/iftags/types.ts +63 -0
  53. package/src/parser/rules/block/module/include/index.ts +20 -0
  54. package/src/parser/rules/block/module/include/resolve.ts +556 -0
  55. package/src/parser/rules/block/module/index.ts +122 -0
  56. package/src/parser/rules/block/module/join/index.ts +34 -0
  57. package/src/parser/rules/block/module/join/types.ts +23 -0
  58. package/src/parser/rules/block/module/listpages/compiler.ts +453 -0
  59. package/src/parser/rules/block/module/listpages/extract.ts +410 -0
  60. package/src/parser/rules/block/module/listpages/index.ts +83 -0
  61. package/src/parser/rules/block/module/listpages/normalize.ts +390 -0
  62. package/src/parser/rules/block/module/listpages/parser.ts +106 -0
  63. package/src/parser/rules/block/module/listpages/resolve.ts +130 -0
  64. package/src/parser/rules/block/module/listpages/types.ts +513 -0
  65. package/src/parser/rules/block/module/listpages/url-resolver.ts +186 -0
  66. package/src/parser/rules/block/module/listusers/compiler.ts +77 -0
  67. package/src/parser/rules/block/module/listusers/extract.ts +45 -0
  68. package/src/parser/rules/block/module/listusers/index.ts +36 -0
  69. package/src/parser/rules/block/module/listusers/parser.ts +54 -0
  70. package/src/parser/rules/block/module/listusers/resolve.ts +58 -0
  71. package/src/parser/rules/block/module/listusers/types.ts +93 -0
  72. package/src/parser/rules/block/module/mapping.ts +61 -0
  73. package/src/parser/rules/block/module/page-tree/index.ts +38 -0
  74. package/src/parser/rules/block/module/page-tree/types.ts +29 -0
  75. package/src/parser/rules/block/module/rate/index.ts +28 -0
  76. package/src/parser/rules/block/module/rate/types.ts +19 -0
  77. package/src/parser/rules/block/module/resolve.ts +411 -0
  78. package/src/parser/rules/block/module/types-common.ts +59 -0
  79. package/src/parser/rules/block/module/types.ts +61 -0
  80. package/src/parser/rules/block/module/utils.ts +43 -0
  81. package/src/parser/rules/block/module/walk.ts +380 -0
  82. package/src/parser/rules/block/module.ts +164 -0
  83. package/src/parser/rules/block/orphan-li.ts +177 -0
  84. package/src/parser/rules/block/paragraph.ts +157 -0
  85. package/src/parser/rules/block/table-block.ts +726 -0
  86. package/src/parser/rules/block/table.ts +441 -0
  87. package/src/parser/rules/block/tabview.ts +331 -0
  88. package/src/parser/rules/block/toc.ts +129 -0
  89. package/src/parser/rules/block/utils.ts +615 -0
  90. package/src/parser/rules/index.ts +49 -0
  91. package/src/parser/rules/inline/anchor-name.ts +154 -0
  92. package/src/parser/rules/inline/anchor.ts +327 -0
  93. package/src/parser/rules/inline/bibcite.ts +153 -0
  94. package/src/parser/rules/inline/bold.ts +86 -0
  95. package/src/parser/rules/inline/color.ts +140 -0
  96. package/src/parser/rules/inline/comment.ts +90 -0
  97. package/src/parser/rules/inline/equation-ref.ts +115 -0
  98. package/src/parser/rules/inline/expr.ts +526 -0
  99. package/src/parser/rules/inline/footnote.ts +223 -0
  100. package/src/parser/rules/inline/guillemet.ts +64 -0
  101. package/src/parser/rules/inline/html.ts +132 -0
  102. package/src/parser/rules/inline/image.ts +328 -0
  103. package/src/parser/rules/inline/index.ts +150 -0
  104. package/src/parser/rules/inline/italic.ts +74 -0
  105. package/src/parser/rules/inline/line-break.ts +326 -0
  106. package/src/parser/rules/inline/link-anchor.ts +147 -0
  107. package/src/parser/rules/inline/link-single.ts +164 -0
  108. package/src/parser/rules/inline/link-star.ts +134 -0
  109. package/src/parser/rules/inline/link-triple.ts +267 -0
  110. package/src/parser/rules/inline/math-inline.ts +126 -0
  111. package/src/parser/rules/inline/monospace.ts +78 -0
  112. package/src/parser/rules/inline/raw.ts +262 -0
  113. package/src/parser/rules/inline/size.ts +244 -0
  114. package/src/parser/rules/inline/span.ts +424 -0
  115. package/src/parser/rules/inline/strikethrough.ts +115 -0
  116. package/src/parser/rules/inline/subscript.ts +84 -0
  117. package/src/parser/rules/inline/superscript.ts +84 -0
  118. package/src/parser/rules/inline/text.ts +84 -0
  119. package/src/parser/rules/inline/underline.ts +127 -0
  120. package/src/parser/rules/inline/user.ts +147 -0
  121. package/src/parser/rules/inline/utils.ts +344 -0
  122. package/src/parser/rules/types.ts +252 -0
  123. package/src/parser/rules/utils.ts +155 -0
  124. package/src/parser/toc.ts +130 -0
@@ -0,0 +1,331 @@
1
+ /**
2
+ *
3
+ * Block rule for Wikidot tabbed content: `[[tabview]]` (or `[[tabs]]`).
4
+ *
5
+ * A tabview contains one or more `[[tab Label]]...[[/tab]]` blocks:
6
+ *
7
+ * ```
8
+ * [[tabview]]
9
+ * [[tab First Tab]]
10
+ * Content of the first tab.
11
+ * [[/tab]]
12
+ * [[tab Second Tab]]
13
+ * Content of the second tab.
14
+ * [[/tab]]
15
+ * [[/tabview]]
16
+ * ```
17
+ *
18
+ * Key behaviours:
19
+ * - Both `[[tabview]]` and `[[tabs]]` are accepted as the outer wrapper.
20
+ * - Any attributes or text after the block name on the opening tag are
21
+ * silently ignored (Wikidot behaviour: `[[tabview Foo]]` is valid).
22
+ * - If a tab has no label, it defaults to `"untitled"`.
23
+ * - Tab body content is parsed as block-level markup.
24
+ * - An empty tabview (no tabs) fails the rule, falling back to text.
25
+ * - Non-tab content between tabs (other than whitespace/newlines) causes
26
+ * the rule to fail.
27
+ *
28
+ * @module
29
+ */
30
+ import type { Element, TabData } from "@wdprlib/ast";
31
+ import type { BlockRule, ParseContext, RuleResult } from "../types";
32
+ import { currentToken } from "../types";
33
+ import { parseBlockName, parseBlocksUntil } from "./utils";
34
+
35
+ /**
36
+ * Parses a single `[[tab Label]]...[[/tab]]` block within a tabview.
37
+ *
38
+ * The label is everything between the block name and `]]` (leading
39
+ * whitespace is trimmed). An empty label defaults to `"untitled"`.
40
+ * Newlines are not allowed in the label -- if one is encountered, the
41
+ * parse fails.
42
+ *
43
+ * Body content is parsed as block-level markup via {@link parseBlocksUntil}.
44
+ *
45
+ * @param ctx - Parse context, positioned before the expected `[[tab ...]]`.
46
+ * @returns The tab data and consumed count, or `null` on failure.
47
+ */
48
+ function parseTab(ctx: ParseContext): { tab: TabData; consumed: number } | null {
49
+ let pos = ctx.pos;
50
+ let consumed = 0;
51
+
52
+ // Skip whitespace/newlines before tab
53
+ while (ctx.tokens[pos]?.type === "WHITESPACE" || ctx.tokens[pos]?.type === "NEWLINE") {
54
+ pos++;
55
+ consumed++;
56
+ }
57
+
58
+ // Expect [[
59
+ if (ctx.tokens[pos]?.type !== "BLOCK_OPEN") {
60
+ return null;
61
+ }
62
+ pos++;
63
+ consumed++;
64
+
65
+ // Parse block name
66
+ const nameResult = parseBlockName(ctx, pos);
67
+ if (!nameResult || nameResult.name.toLowerCase() !== "tab") {
68
+ return null;
69
+ }
70
+ pos += nameResult.consumed;
71
+ consumed += nameResult.consumed;
72
+
73
+ // Parse label (everything until ]])
74
+ let label = "";
75
+ while (pos < ctx.tokens.length) {
76
+ const token = ctx.tokens[pos];
77
+ if (!token) break;
78
+ if (token.type === "BLOCK_CLOSE") {
79
+ break;
80
+ }
81
+ if (token.type === "NEWLINE") {
82
+ // No newline allowed in label
83
+ return null;
84
+ }
85
+ // Skip leading whitespace in label
86
+ if (label === "" && token.type === "WHITESPACE") {
87
+ pos++;
88
+ consumed++;
89
+ continue;
90
+ }
91
+ label += token.value;
92
+ pos++;
93
+ consumed++;
94
+ }
95
+
96
+ // Expect ]]
97
+ if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
98
+ return null;
99
+ }
100
+ pos++;
101
+ consumed++;
102
+
103
+ // Skip newline after opening tag
104
+ if (ctx.tokens[pos]?.type === "NEWLINE") {
105
+ pos++;
106
+ consumed++;
107
+ }
108
+
109
+ // Close condition for [[/tab]]
110
+ const closeCondition = (checkCtx: ParseContext): boolean => {
111
+ const token = checkCtx.tokens[checkCtx.pos];
112
+ if (token?.type === "BLOCK_END_OPEN") {
113
+ const closeNameResult = parseBlockName(checkCtx, checkCtx.pos + 1);
114
+ if (closeNameResult?.name.toLowerCase() === "tab") {
115
+ return true;
116
+ }
117
+ }
118
+ return false;
119
+ };
120
+
121
+ // Parse body
122
+ const bodyCtx: ParseContext = { ...ctx, pos };
123
+ const bodyResult = parseBlocksUntil(bodyCtx, closeCondition);
124
+ consumed += bodyResult.consumed;
125
+ pos += bodyResult.consumed;
126
+
127
+ // Check for missing close tag
128
+ if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
129
+ ctx.diagnostics.push({
130
+ severity: "warning",
131
+ code: "unclosed-block",
132
+ message: "Missing closing tag [[/tab]] for [[tab]]",
133
+ position: ctx.tokens[ctx.pos]?.position ?? {
134
+ start: { line: 0, column: 0, offset: 0 },
135
+ end: { line: 0, column: 0, offset: 0 },
136
+ },
137
+ });
138
+ }
139
+
140
+ // Consume [[/tab]]
141
+ if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
142
+ pos++;
143
+ consumed++;
144
+ const closeNameResult = parseBlockName(ctx, pos);
145
+ if (closeNameResult) {
146
+ pos += closeNameResult.consumed;
147
+ consumed += closeNameResult.consumed;
148
+ }
149
+ if (ctx.tokens[pos]?.type === "BLOCK_CLOSE") {
150
+ pos++;
151
+ consumed++;
152
+ }
153
+ // Skip trailing newline
154
+ if (ctx.tokens[pos]?.type === "NEWLINE") {
155
+ pos++;
156
+ consumed++;
157
+ }
158
+ }
159
+
160
+ // Default label if empty
161
+ const finalLabel = label.trim() || "untitled";
162
+
163
+ return {
164
+ tab: {
165
+ label: finalLabel,
166
+ elements: bodyResult.elements,
167
+ },
168
+ consumed,
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Block rule for `[[tabview]]`/`[[tabs]]` with `[[tab]]` children.
174
+ *
175
+ * Parsing strategy:
176
+ * 1. Match BLOCK_OPEN + name "tabview" or "tabs" (case-insensitive).
177
+ * 2. Skip any trailing text/attributes on the opening tag (ignored).
178
+ * 3. Consume `]]` and optional newline.
179
+ * 4. Repeatedly parse `[[tab Label]]...[[/tab]]` blocks via `parseTab()`.
180
+ * 5. Whitespace and newlines between tabs are skipped; other content fails.
181
+ * 6. Consume `[[/tabview]]` or `[[/tabs]]`.
182
+ * 7. If no tabs were found, fail the rule.
183
+ * 8. Emit a `tab-view` element containing the array of tab data.
184
+ */
185
+ export const tabviewRule: BlockRule = {
186
+ name: "tabview",
187
+ startTokens: ["BLOCK_OPEN"],
188
+ requiresLineStart: false,
189
+
190
+ parse(ctx: ParseContext): RuleResult<Element> {
191
+ const openToken = currentToken(ctx);
192
+ if (openToken.type !== "BLOCK_OPEN") {
193
+ return { success: false };
194
+ }
195
+
196
+ let pos = ctx.pos + 1;
197
+ let consumed = 1;
198
+
199
+ // Parse block name
200
+ const nameResult = parseBlockName(ctx, pos);
201
+ if (!nameResult) {
202
+ return { success: false };
203
+ }
204
+
205
+ const blockName = nameResult.name.toLowerCase();
206
+ // Accept tabview or tabs
207
+ if (blockName !== "tabview" && blockName !== "tabs") {
208
+ return { success: false };
209
+ }
210
+
211
+ pos += nameResult.consumed;
212
+ consumed += nameResult.consumed;
213
+
214
+ // Skip any attributes/name (Wikidot ignores [[tabview Foo]])
215
+ while (
216
+ pos < ctx.tokens.length &&
217
+ ctx.tokens[pos]?.type !== "BLOCK_CLOSE" &&
218
+ ctx.tokens[pos]?.type !== "NEWLINE"
219
+ ) {
220
+ pos++;
221
+ consumed++;
222
+ }
223
+
224
+ // Expect ]]
225
+ if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
226
+ return { success: false };
227
+ }
228
+ pos++;
229
+ consumed++;
230
+
231
+ // Skip newline after opening tag
232
+ if (ctx.tokens[pos]?.type === "NEWLINE") {
233
+ pos++;
234
+ consumed++;
235
+ }
236
+
237
+ // Parse tabs
238
+ const tabs: TabData[] = [];
239
+ const tabCtx: ParseContext = { ...ctx, pos };
240
+
241
+ while (pos < ctx.tokens.length) {
242
+ // Check for EOF
243
+ if (ctx.tokens[pos]?.type === "EOF") {
244
+ break;
245
+ }
246
+
247
+ // Check for closing [[/tabview]] or [[/tabs]]
248
+ if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
249
+ const closeNameResult = parseBlockName(ctx, pos + 1);
250
+ const closeName = closeNameResult?.name.toLowerCase();
251
+ if (closeName === "tabview" || closeName === "tabs") {
252
+ break;
253
+ }
254
+ }
255
+
256
+ // Try to parse a tab
257
+ const tabResult = parseTab({ ...tabCtx, pos });
258
+ if (tabResult) {
259
+ tabs.push(tabResult.tab);
260
+ pos += tabResult.consumed;
261
+ consumed += tabResult.consumed;
262
+ } else {
263
+ // Skip whitespace/newlines between tabs
264
+ if (ctx.tokens[pos]?.type === "WHITESPACE" || ctx.tokens[pos]?.type === "NEWLINE") {
265
+ pos++;
266
+ consumed++;
267
+ } else {
268
+ // Non-tab content in tabview - fail
269
+ return { success: false };
270
+ }
271
+ }
272
+ }
273
+
274
+ // Check for missing close tag
275
+ const hasTabviewClose =
276
+ ctx.tokens[pos]?.type === "BLOCK_END_OPEN" &&
277
+ (() => {
278
+ const n = parseBlockName(ctx, pos + 1);
279
+ const name = n?.name.toLowerCase();
280
+ return name === "tabview" || name === "tabs";
281
+ })();
282
+ if (!hasTabviewClose) {
283
+ ctx.diagnostics.push({
284
+ severity: "warning",
285
+ code: "unclosed-block",
286
+ message: `Missing closing tag [[/${blockName}]] for [[${blockName}]]`,
287
+ position: openToken.position,
288
+ });
289
+ }
290
+
291
+ // Consume [[/tabview]] or [[/tabs]]
292
+ if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
293
+ pos++;
294
+ consumed++;
295
+ const closeNameResult = parseBlockName(ctx, pos);
296
+ if (closeNameResult) {
297
+ pos += closeNameResult.consumed;
298
+ consumed += closeNameResult.consumed;
299
+ }
300
+ if (ctx.tokens[pos]?.type === "BLOCK_CLOSE") {
301
+ pos++;
302
+ consumed++;
303
+ }
304
+ if (ctx.tokens[pos]?.type === "NEWLINE") {
305
+ pos++;
306
+ consumed++;
307
+ }
308
+ }
309
+
310
+ // Empty tabview is invalid - return failure so it falls back to plain text.
311
+ // Note: Ideally, a failed tabview like "EMPTY:\n[[tabview]]\n[[/tabview]]" should
312
+ // render as a single paragraph with <br /> separators (matching original Wikidot).
313
+ // Currently, the paragraph parser treats [[tabview]] as a block-start token and
314
+ // splits it into separate paragraphs. We chose to wrap each in <p> tags instead
315
+ // of implementing complex lookahead to detect invalid tabviews.
316
+ if (tabs.length === 0) {
317
+ return { success: false };
318
+ }
319
+
320
+ return {
321
+ success: true,
322
+ elements: [
323
+ {
324
+ element: "tab-view",
325
+ data: tabs,
326
+ },
327
+ ],
328
+ consumed,
329
+ };
330
+ },
331
+ };
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Table of contents rule: [[toc]], [[f<toc ...]], [[f>toc ...]]
3
+ *
4
+ * Variants:
5
+ * - [[toc]] - basic TOC
6
+ * - [[f<toc ...]] - float left
7
+ * - [[f>toc ...]] - float right
8
+ *
9
+ * Note: Wikidot ignores attributes on [[toc]] (class, style, id are not applied)
10
+ * [[>toc]] and [[<toc]] are invalid in Wikidot and not supported.
11
+ */
12
+ import type { Alignment, Element } from "@wdprlib/ast";
13
+ import type { BlockRule, ParseContext, RuleResult } from "../types";
14
+ import { currentToken } from "../types";
15
+
16
+ /**
17
+ * Skip tokens until BLOCK_CLOSE (Wikidot ignores toc attributes)
18
+ */
19
+ function skipUntilClose(ctx: ParseContext, startPos: number): number {
20
+ let pos = startPos;
21
+ while (pos < ctx.tokens.length) {
22
+ const token = ctx.tokens[pos];
23
+ if (
24
+ !token ||
25
+ token.type === "BLOCK_CLOSE" ||
26
+ token.type === "NEWLINE" ||
27
+ token.type === "EOF"
28
+ ) {
29
+ break;
30
+ }
31
+ pos++;
32
+ }
33
+ return pos;
34
+ }
35
+
36
+ export const tocRule: BlockRule = {
37
+ name: "toc",
38
+ startTokens: ["BLOCK_OPEN"],
39
+ requiresLineStart: true,
40
+
41
+ parse(ctx: ParseContext): RuleResult<Element> {
42
+ const openToken = currentToken(ctx);
43
+ if (openToken.type !== "BLOCK_OPEN") {
44
+ return { success: false };
45
+ }
46
+
47
+ let pos = ctx.pos + 1;
48
+ let align: Alignment | null = null;
49
+
50
+ // Skip whitespace after [[
51
+ while (ctx.tokens[pos]?.type === "WHITESPACE") {
52
+ pos++;
53
+ }
54
+
55
+ const firstToken = ctx.tokens[pos];
56
+ if (!firstToken || (firstToken.type !== "TEXT" && firstToken.type !== "IDENTIFIER")) {
57
+ return { success: false };
58
+ }
59
+
60
+ const firstValue = firstToken.value.toLowerCase();
61
+
62
+ if (firstValue === "toc") {
63
+ // [[toc ...]]
64
+ // Page syntax disabled (e.g., forum-post mode)
65
+ if (!ctx.settings.enablePageSyntax) {
66
+ return { success: false };
67
+ }
68
+ pos++;
69
+ } else if (firstValue === "f") {
70
+ // Possibly [[f<toc ...]] or [[f>toc ...]]
71
+ pos++;
72
+ const dirToken = ctx.tokens[pos];
73
+ if (!dirToken) {
74
+ return { success: false };
75
+ }
76
+
77
+ if (dirToken.type === "TEXT" && dirToken.value === "<") {
78
+ align = "left";
79
+ pos++;
80
+ } else if (dirToken.type === "TEXT" && dirToken.value === ">") {
81
+ align = "right";
82
+ pos++;
83
+ } else {
84
+ return { success: false };
85
+ }
86
+
87
+ // Now expect "toc"
88
+ const tocToken = ctx.tokens[pos];
89
+ if (!tocToken || (tocToken.type !== "TEXT" && tocToken.type !== "IDENTIFIER")) {
90
+ return { success: false };
91
+ }
92
+ if (tocToken.value.toLowerCase() !== "toc") {
93
+ return { success: false };
94
+ }
95
+ // Page syntax disabled (e.g., forum-post mode)
96
+ if (!ctx.settings.enablePageSyntax) {
97
+ return { success: false };
98
+ }
99
+ pos++;
100
+ } else {
101
+ return { success: false };
102
+ }
103
+
104
+ // Skip any tokens until ]] (Wikidot ignores toc attributes)
105
+ pos = skipUntilClose(ctx, pos);
106
+
107
+ // Expect ]]
108
+ if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
109
+ return { success: false };
110
+ }
111
+ pos++;
112
+
113
+ const consumed = pos - ctx.pos;
114
+
115
+ return {
116
+ success: true,
117
+ elements: [
118
+ {
119
+ element: "table-of-contents",
120
+ data: {
121
+ attributes: {},
122
+ align,
123
+ },
124
+ },
125
+ ],
126
+ consumed,
127
+ };
128
+ },
129
+ };