@wdprlib/ast 2.0.0 → 2.1.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.
- package/dist/index.cjs +391 -0
- package/dist/index.d.cts +46 -1
- package/dist/index.d.ts +46 -1
- package/dist/index.js +391 -0
- package/package.json +4 -2
- package/src/constants.ts +12 -0
- package/src/diagnostic.ts +104 -0
- package/src/element.ts +1287 -0
- package/src/expr-eval.ts +586 -0
- package/src/index.ts +119 -0
- package/src/position.ts +73 -0
- package/src/settings.ts +157 -0
package/src/element.ts
ADDED
|
@@ -0,0 +1,1287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST element types for Wikidot markup.
|
|
3
|
+
*
|
|
4
|
+
* Wikidot markup (`+ heading`, `**bold**`, `[[module ListPages]]`, etc.) is parsed into
|
|
5
|
+
* a structured representation defined here. Each {@link Element} is a tagged union of
|
|
6
|
+
* `{ element: tag, data: payload }`, where the data shape for each tag is defined in
|
|
7
|
+
* {@link ElementDataMap}.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { parse } from "@wdprlib/parser";
|
|
12
|
+
* const tree = parse("**Hello** world");
|
|
13
|
+
* // tree.elements[0] → { element: "container", data: { type: "paragraph", ... } }
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Primitive types
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Key-value map of HTML attributes.
|
|
25
|
+
* Populated from the Wikidot `_ class="foo" style="color:red"` attribute syntax.
|
|
26
|
+
*
|
|
27
|
+
* @group Primitives
|
|
28
|
+
*/
|
|
29
|
+
export type AttributeMap = Record<string, string>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Key-value map of include variables.
|
|
33
|
+
* Populated from `[[include page | key=value]]` pairs.
|
|
34
|
+
*
|
|
35
|
+
* @group Primitives
|
|
36
|
+
*/
|
|
37
|
+
export type VariableMap = Record<string, string>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Text alignment direction.
|
|
41
|
+
* Maps to Wikidot alignment blocks: `[[=]]` (center), `[[<]]` (left),
|
|
42
|
+
* `[[>]]` (right), `[[==]]` (justify).
|
|
43
|
+
*
|
|
44
|
+
* @group Primitives
|
|
45
|
+
*/
|
|
46
|
+
export type Alignment = "left" | "right" | "center" | "justify";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Image float alignment. Used in `[[image]]` positioning.
|
|
50
|
+
*
|
|
51
|
+
* When `float` is true, the image uses CSS float.
|
|
52
|
+
* When false, it uses text-align only.
|
|
53
|
+
*
|
|
54
|
+
* @group Primitives
|
|
55
|
+
*/
|
|
56
|
+
export interface FloatAlignment {
|
|
57
|
+
align: Alignment;
|
|
58
|
+
/** Whether to use CSS float (true) or just text-align (false) */
|
|
59
|
+
float: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Container types
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Heading level (1-6). Corresponds to Wikidot `+` (h1) through `++++++` (h6).
|
|
68
|
+
*
|
|
69
|
+
* @group Container Types
|
|
70
|
+
*/
|
|
71
|
+
export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Heading configuration. Carries the level and whether this heading
|
|
75
|
+
* should appear in the table of contents.
|
|
76
|
+
*
|
|
77
|
+
* In Wikidot, `+*` (asterisk suffix) excludes the heading from the TOC.
|
|
78
|
+
*
|
|
79
|
+
* @group Container Types
|
|
80
|
+
*/
|
|
81
|
+
export interface Heading {
|
|
82
|
+
level: HeadingLevel;
|
|
83
|
+
/** false when the heading uses `+*` syntax to opt out of the TOC */
|
|
84
|
+
"has-toc": boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Discriminator for heading containers within {@link ContainerType}.
|
|
89
|
+
*
|
|
90
|
+
* @group Container Types
|
|
91
|
+
*/
|
|
92
|
+
export interface HeaderType {
|
|
93
|
+
header: Heading;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Discriminator for alignment-block containers within {@link ContainerType}.
|
|
98
|
+
* Produced by `[[=]]`, `[[<]]`, `[[>]]`, and `[[==]]` blocks.
|
|
99
|
+
*
|
|
100
|
+
* @group Container Types
|
|
101
|
+
*/
|
|
102
|
+
export interface AlignType {
|
|
103
|
+
align: Alignment;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Container types expressible as plain string literals.
|
|
108
|
+
* Covers inline formatting (`**bold**`, `//italics//`, etc.) and
|
|
109
|
+
* block-level structures (`div`, `blockquote`, `table-cell`, etc.).
|
|
110
|
+
*
|
|
111
|
+
* @group Container Types
|
|
112
|
+
*/
|
|
113
|
+
export type StringContainerType =
|
|
114
|
+
| "bold"
|
|
115
|
+
| "italics"
|
|
116
|
+
| "underline"
|
|
117
|
+
| "superscript"
|
|
118
|
+
| "subscript"
|
|
119
|
+
| "strikethrough"
|
|
120
|
+
| "monospace"
|
|
121
|
+
| "span"
|
|
122
|
+
| "div"
|
|
123
|
+
| "blockquote"
|
|
124
|
+
| "size"
|
|
125
|
+
| "paragraph"
|
|
126
|
+
| "heading"
|
|
127
|
+
| "collapsible"
|
|
128
|
+
| "definition-list"
|
|
129
|
+
| "definition-list-item"
|
|
130
|
+
| "definition-list-key"
|
|
131
|
+
| "definition-list-value"
|
|
132
|
+
| "table-row"
|
|
133
|
+
| "table-cell";
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Union of all container type discriminators.
|
|
137
|
+
* Every container element in the AST carries one of these to identify
|
|
138
|
+
* what kind of container it is.
|
|
139
|
+
*
|
|
140
|
+
* - String literals: inline formatting and block structures
|
|
141
|
+
* - {@link HeaderType}: heading elements (`+ Heading`)
|
|
142
|
+
* - {@link AlignType}: alignment blocks (`[[=]]...[[/=]]`)
|
|
143
|
+
*
|
|
144
|
+
* @group Container Types
|
|
145
|
+
*/
|
|
146
|
+
export type ContainerType = StringContainerType | HeaderType | AlignType;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Type guard: checks whether a {@link ContainerType} is a plain string literal.
|
|
150
|
+
*
|
|
151
|
+
* @group Container Types
|
|
152
|
+
*/
|
|
153
|
+
export function isStringContainerType(type: ContainerType): type is StringContainerType {
|
|
154
|
+
return typeof type === "string";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Type guard: checks whether a {@link ContainerType} is a {@link HeaderType}.
|
|
159
|
+
*
|
|
160
|
+
* @group Container Types
|
|
161
|
+
*/
|
|
162
|
+
export function isHeaderType(type: ContainerType): type is HeaderType {
|
|
163
|
+
return typeof type === "object" && type !== null && "header" in type;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Type guard: checks whether a {@link ContainerType} is an {@link AlignType}.
|
|
168
|
+
*
|
|
169
|
+
* @group Container Types
|
|
170
|
+
*/
|
|
171
|
+
export function isAlignType(type: ContainerType): type is AlignType {
|
|
172
|
+
return typeof type === "object" && type !== null && "align" in type;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Data payload for container elements (paragraphs, bold, headings, divs, etc.).
|
|
177
|
+
*
|
|
178
|
+
* Every nestable Wikidot construct (`**bold**`, `[[div]]...[[/div]]`,
|
|
179
|
+
* `+ heading`, etc.) is represented as an `{ element: "container", data: ContainerData }`.
|
|
180
|
+
*
|
|
181
|
+
* The `_`-prefixed fields are internal parser bookkeeping that gets stripped before
|
|
182
|
+
* the final AST is returned. They coordinate paragraph splitting and span unwrapping
|
|
183
|
+
* during post-processing.
|
|
184
|
+
*
|
|
185
|
+
* @group Container Types
|
|
186
|
+
*/
|
|
187
|
+
export interface ContainerData {
|
|
188
|
+
/** Identifies the kind of container and determines how it renders */
|
|
189
|
+
type: ContainerType;
|
|
190
|
+
/** HTML attributes specified via `_ class="..." style="..."` syntax */
|
|
191
|
+
attributes: AttributeMap;
|
|
192
|
+
/** Child elements nested inside this container */
|
|
193
|
+
elements: Element[];
|
|
194
|
+
/**
|
|
195
|
+
* Set on `[[span_]]` elements. Signals the post-processor to merge adjacent
|
|
196
|
+
* paragraphs, removing the `<p>` wrapper around the span's content.
|
|
197
|
+
* Consumed during post-processing; never present in the final AST.
|
|
198
|
+
* @internal
|
|
199
|
+
*/
|
|
200
|
+
_paragraphStrip?: boolean;
|
|
201
|
+
/**
|
|
202
|
+
* Set on empty `[[span_]][[/span_]]` elements. Acts as a line-break absorber:
|
|
203
|
+
* adjacent line-breaks are removed around this marker.
|
|
204
|
+
* Consumed during post-processing; never present in the final AST.
|
|
205
|
+
* @internal
|
|
206
|
+
*/
|
|
207
|
+
_emptyParagraphStrip?: boolean;
|
|
208
|
+
/**
|
|
209
|
+
* Set on content that follows a blank line inside `[[span_]]`.
|
|
210
|
+
* Indicates this content should be extracted outside its paragraph wrapper.
|
|
211
|
+
* Consumed during post-processing; never present in the final AST.
|
|
212
|
+
* @internal
|
|
213
|
+
*/
|
|
214
|
+
_escapedFromParagraph?: boolean;
|
|
215
|
+
/**
|
|
216
|
+
* Set on an orphaned `[[/span]]` closing tag (no matching open tag).
|
|
217
|
+
* The paragraph rule uses this to retroactively wrap preceding content in a span.
|
|
218
|
+
* Consumed during post-processing; never present in the final AST.
|
|
219
|
+
* @internal
|
|
220
|
+
*/
|
|
221
|
+
_closeSpan?: boolean;
|
|
222
|
+
/**
|
|
223
|
+
* Set on the 2nd+ segments of a regular `[[span]]` that was split by blank lines.
|
|
224
|
+
* Marks where the post-processor should split the enclosing paragraph.
|
|
225
|
+
* Consumed during post-processing; never present in the final AST.
|
|
226
|
+
* @internal
|
|
227
|
+
*/
|
|
228
|
+
_splitByBlankLine?: boolean;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// Link types
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Link target window. Maps to the HTML `target` attribute.
|
|
237
|
+
* In Wikidot, `*` suffix on a link (`[[[page*]]]`) sets `"new-tab"`.
|
|
238
|
+
*
|
|
239
|
+
* @group Link Types
|
|
240
|
+
*/
|
|
241
|
+
export type AnchorTarget = "new-tab" | "parent" | "top" | "same";
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Reference to an internal wiki page.
|
|
245
|
+
* Produced by `[[[page]]]` or cross-site `[[[site:page]]]` syntax.
|
|
246
|
+
*
|
|
247
|
+
* @group Link Types
|
|
248
|
+
*/
|
|
249
|
+
export interface PageRef {
|
|
250
|
+
/** Site name for cross-site links; null for same-site links */
|
|
251
|
+
site: string | null;
|
|
252
|
+
/** Page UNIX name (e.g. `"scp-001"`, `"system:page-tags"`) */
|
|
253
|
+
page: string;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Link destination: either a {@link PageRef} for internal wiki links
|
|
258
|
+
* or a plain URL string for external links.
|
|
259
|
+
*
|
|
260
|
+
* @group Link Types
|
|
261
|
+
*/
|
|
262
|
+
export type LinkLocation = PageRef | string;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Link display label.
|
|
266
|
+
*
|
|
267
|
+
* - `{ text: string }` — explicit text (`[[[page | label]]]`)
|
|
268
|
+
* - `{ url: string | null }` — use the URL itself as the label
|
|
269
|
+
* - `"page"` — use the page name as the label (`[[[page]]]`)
|
|
270
|
+
*
|
|
271
|
+
* @group Link Types
|
|
272
|
+
*/
|
|
273
|
+
export type LinkLabel = { text: string } | { url: string | null } | "page";
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Link classification, determined by the syntax used.
|
|
277
|
+
*
|
|
278
|
+
* - `"direct"` — bare URL (`[http://...]`)
|
|
279
|
+
* - `"page"` — page link (`[[[some-page]]]`)
|
|
280
|
+
* - `"interwiki"` — interwiki link (`[[[wikipedia:article]]]`)
|
|
281
|
+
* - `"anchor"` — in-page anchor (`[[# section]]`)
|
|
282
|
+
* - `"table-of-contents"` — TOC-generated link
|
|
283
|
+
*
|
|
284
|
+
* @group Link Types
|
|
285
|
+
*/
|
|
286
|
+
export type LinkType = "direct" | "page" | "interwiki" | "anchor" | "table-of-contents";
|
|
287
|
+
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
// Image types
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Image source. Wikidot supports four resolution strategies:
|
|
294
|
+
*
|
|
295
|
+
* - `"url"` — absolute URL
|
|
296
|
+
* - `"file1"` — file attached to the current page (`filename`)
|
|
297
|
+
* - `"file2"` — file on another page (`page/filename`)
|
|
298
|
+
* - `"file3"` — file on another site (`site:page/filename`)
|
|
299
|
+
*
|
|
300
|
+
* @group Image Types
|
|
301
|
+
*/
|
|
302
|
+
export type ImageSource =
|
|
303
|
+
| { type: "url"; data: string }
|
|
304
|
+
| { type: "file1"; data: { file: string } }
|
|
305
|
+
| { type: "file2"; data: { page: string; file: string } }
|
|
306
|
+
| { type: "file3"; data: { site: string; page: string; file: string } };
|
|
307
|
+
|
|
308
|
+
// ---------------------------------------------------------------------------
|
|
309
|
+
// List types
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* List style. `"bullet"` for `*` items, `"numbered"` for `#` items,
|
|
314
|
+
* `"generic"` for `[[li]]` block items.
|
|
315
|
+
*
|
|
316
|
+
* @group List Types
|
|
317
|
+
*/
|
|
318
|
+
export type ListType = "bullet" | "numbered" | "generic";
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* A single list item. Either a leaf with inline content, or a nested sub-list.
|
|
322
|
+
*
|
|
323
|
+
* @group List Types
|
|
324
|
+
*/
|
|
325
|
+
export type ListItem =
|
|
326
|
+
| {
|
|
327
|
+
"item-type": "elements";
|
|
328
|
+
attributes: AttributeMap;
|
|
329
|
+
elements: Element[];
|
|
330
|
+
}
|
|
331
|
+
| {
|
|
332
|
+
"item-type": "sub-list";
|
|
333
|
+
element: "list";
|
|
334
|
+
data: ListData;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Data payload for a list element (`* item`, `# item`, or `[[li]]`).
|
|
339
|
+
*
|
|
340
|
+
* @group List Types
|
|
341
|
+
*/
|
|
342
|
+
export interface ListData {
|
|
343
|
+
type: ListType;
|
|
344
|
+
attributes: AttributeMap;
|
|
345
|
+
items: ListItem[];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* A single entry in a definition list (`: key : value`).
|
|
350
|
+
*
|
|
351
|
+
* @group List Types
|
|
352
|
+
*/
|
|
353
|
+
export interface DefinitionListItem {
|
|
354
|
+
/** Plain-text representation of the key (for quick lookups) */
|
|
355
|
+
key_string: string;
|
|
356
|
+
/** Rich-content key (may contain inline formatting) */
|
|
357
|
+
key: Element[];
|
|
358
|
+
/** Rich-content value */
|
|
359
|
+
value: Element[];
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
// Table types
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* A single table cell (`||` delimited).
|
|
368
|
+
*
|
|
369
|
+
* @group Table Types
|
|
370
|
+
*/
|
|
371
|
+
export interface TableCell {
|
|
372
|
+
/** true if this cell is a header cell (`||~`) */
|
|
373
|
+
header: boolean;
|
|
374
|
+
/** Number of columns this cell spans (via `||` count) */
|
|
375
|
+
"column-span": number;
|
|
376
|
+
/** Explicit alignment, or null for default */
|
|
377
|
+
align: Alignment | null;
|
|
378
|
+
attributes: AttributeMap;
|
|
379
|
+
elements: Element[];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* A single table row.
|
|
384
|
+
*
|
|
385
|
+
* @group Table Types
|
|
386
|
+
*/
|
|
387
|
+
export interface TableRow {
|
|
388
|
+
attributes: AttributeMap;
|
|
389
|
+
cells: TableCell[];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Data payload for a table element.
|
|
394
|
+
*
|
|
395
|
+
* @group Table Types
|
|
396
|
+
*/
|
|
397
|
+
export interface TableData {
|
|
398
|
+
attributes: AttributeMap;
|
|
399
|
+
rows: TableRow[];
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ---------------------------------------------------------------------------
|
|
403
|
+
// Block element data types
|
|
404
|
+
// ---------------------------------------------------------------------------
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* A single tab in a `[[tabview]]` block.
|
|
408
|
+
*
|
|
409
|
+
* @group Block Elements
|
|
410
|
+
*/
|
|
411
|
+
export interface TabData {
|
|
412
|
+
/** Tab title displayed in the tab bar */
|
|
413
|
+
label: string;
|
|
414
|
+
/** Content inside the tab panel */
|
|
415
|
+
elements: Element[];
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Data for a `[[code]]` block.
|
|
420
|
+
*
|
|
421
|
+
* @group Block Elements
|
|
422
|
+
*/
|
|
423
|
+
export interface CodeBlockData {
|
|
424
|
+
/** Raw source text inside the code block */
|
|
425
|
+
contents: string;
|
|
426
|
+
/** Language identifier for syntax highlighting, or null */
|
|
427
|
+
language: string | null;
|
|
428
|
+
/** Optional name/label for the code block */
|
|
429
|
+
name: string | null;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Data for a `[[collapsible]]` block.
|
|
434
|
+
*
|
|
435
|
+
* @group Block Elements
|
|
436
|
+
*/
|
|
437
|
+
export interface CollapsibleData {
|
|
438
|
+
elements: Element[];
|
|
439
|
+
attributes: AttributeMap;
|
|
440
|
+
/** Whether the block starts in the expanded state */
|
|
441
|
+
"start-open": boolean;
|
|
442
|
+
/** Custom text for the "show" toggle, or null for default */
|
|
443
|
+
"show-text": string | null;
|
|
444
|
+
/** Custom text for the "hide" toggle, or null for default */
|
|
445
|
+
"hide-text": string | null;
|
|
446
|
+
/** Whether to show the toggle at the top */
|
|
447
|
+
"show-top": boolean;
|
|
448
|
+
/** Whether to show the toggle at the bottom */
|
|
449
|
+
"show-bottom": boolean;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// ---------------------------------------------------------------------------
|
|
453
|
+
// Module types
|
|
454
|
+
// ---------------------------------------------------------------------------
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Discriminated union of all `[[module ...]]` block types.
|
|
458
|
+
*
|
|
459
|
+
* Known modules have fully typed fields; unknown modules fall back to
|
|
460
|
+
* `{ module: "unknown" }` with raw arguments preserved.
|
|
461
|
+
*
|
|
462
|
+
* @group Module Types
|
|
463
|
+
*/
|
|
464
|
+
export type Module =
|
|
465
|
+
| {
|
|
466
|
+
/** Unrecognized module — preserves raw arguments for pass-through */
|
|
467
|
+
module: "unknown";
|
|
468
|
+
name: string;
|
|
469
|
+
arguments: AttributeMap;
|
|
470
|
+
body?: string;
|
|
471
|
+
}
|
|
472
|
+
| {
|
|
473
|
+
/** `[[module Backlinks]]` — lists pages that link to a given page */
|
|
474
|
+
module: "backlinks";
|
|
475
|
+
/** Target page, or null for the current page */
|
|
476
|
+
page: string | null;
|
|
477
|
+
}
|
|
478
|
+
| {
|
|
479
|
+
/** `[[module Categories]]` — lists site categories */
|
|
480
|
+
module: "categories";
|
|
481
|
+
/** Whether to include categories marked as hidden */
|
|
482
|
+
"include-hidden": boolean;
|
|
483
|
+
}
|
|
484
|
+
| {
|
|
485
|
+
/** `[[module Join]]` — site membership join button */
|
|
486
|
+
module: "join";
|
|
487
|
+
"button-text": string | null;
|
|
488
|
+
attributes: AttributeMap;
|
|
489
|
+
}
|
|
490
|
+
| {
|
|
491
|
+
/** `[[module PageTree]]` — hierarchical page tree */
|
|
492
|
+
module: "page-tree";
|
|
493
|
+
/** Root page, or null for the site root */
|
|
494
|
+
root: string | null;
|
|
495
|
+
"show-root": boolean;
|
|
496
|
+
/** Max depth, or null for unlimited */
|
|
497
|
+
depth: number | null;
|
|
498
|
+
}
|
|
499
|
+
| {
|
|
500
|
+
/** `[[module Rate]]` — page rating widget */
|
|
501
|
+
module: "rate";
|
|
502
|
+
}
|
|
503
|
+
| {
|
|
504
|
+
/** `[[module ListUsers]]` — user listing with template body */
|
|
505
|
+
module: "list-users";
|
|
506
|
+
/** User selector expression (e.g. `"."` for current user) */
|
|
507
|
+
users: string;
|
|
508
|
+
/** Template body with `%%variable%%` placeholders */
|
|
509
|
+
body?: string;
|
|
510
|
+
attributes: AttributeMap;
|
|
511
|
+
}
|
|
512
|
+
| {
|
|
513
|
+
/**
|
|
514
|
+
* `[[module ListPages]]` — the most complex module.
|
|
515
|
+
* Queries pages by various criteria and renders each through a template body.
|
|
516
|
+
*/
|
|
517
|
+
module: "list-pages";
|
|
518
|
+
// -- Selection criteria --
|
|
519
|
+
category?: string;
|
|
520
|
+
tags?: string;
|
|
521
|
+
parent?: string;
|
|
522
|
+
"link-to"?: string;
|
|
523
|
+
"created-by"?: string;
|
|
524
|
+
"created-at"?: string;
|
|
525
|
+
"updated-at"?: string;
|
|
526
|
+
rating?: string;
|
|
527
|
+
votes?: string;
|
|
528
|
+
name?: string;
|
|
529
|
+
fullname?: string;
|
|
530
|
+
range?: string;
|
|
531
|
+
pagetype?: string;
|
|
532
|
+
// -- Pagination --
|
|
533
|
+
offset?: number;
|
|
534
|
+
limit?: number;
|
|
535
|
+
"per-page"?: number;
|
|
536
|
+
// -- Ordering --
|
|
537
|
+
order?: string;
|
|
538
|
+
// -- Display options --
|
|
539
|
+
reverse: boolean;
|
|
540
|
+
separate: boolean;
|
|
541
|
+
wrapper: boolean;
|
|
542
|
+
"prepend-line"?: string;
|
|
543
|
+
"append-line"?: string;
|
|
544
|
+
// -- RSS options --
|
|
545
|
+
rss?: string;
|
|
546
|
+
"rss-description"?: string;
|
|
547
|
+
"rss-home"?: string;
|
|
548
|
+
"rss-limit"?: number;
|
|
549
|
+
"rss-only": boolean;
|
|
550
|
+
// -- Advanced options --
|
|
551
|
+
/** Prefix for URL path parameters (HPC support) */
|
|
552
|
+
"url-attr-prefix"?: string;
|
|
553
|
+
/** Template body with `%%variable%%` placeholders */
|
|
554
|
+
body?: string;
|
|
555
|
+
attributes: AttributeMap;
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
// ---------------------------------------------------------------------------
|
|
559
|
+
// Embed types
|
|
560
|
+
// ---------------------------------------------------------------------------
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Inline embed from `[[embed]]` syntax (not `[[embed]]...[[/embed]]` blocks).
|
|
564
|
+
* Supports a fixed set of providers.
|
|
565
|
+
*
|
|
566
|
+
* @group Embed Types
|
|
567
|
+
*/
|
|
568
|
+
export type Embed =
|
|
569
|
+
| { embed: "youtube"; data: { "video-id": string } }
|
|
570
|
+
| { embed: "vimeo"; data: { "video-id": string } }
|
|
571
|
+
| { embed: "github-gist"; data: { username: string; hash: string } }
|
|
572
|
+
| { embed: "gitlab-snippet"; data: { "snippet-id": string } };
|
|
573
|
+
|
|
574
|
+
// ---------------------------------------------------------------------------
|
|
575
|
+
// Miscellaneous value types
|
|
576
|
+
// ---------------------------------------------------------------------------
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Parsed `[[date]]` value with timezone.
|
|
580
|
+
*
|
|
581
|
+
* @group Value Types
|
|
582
|
+
*/
|
|
583
|
+
export interface DateItem {
|
|
584
|
+
/** Unix timestamp (seconds) */
|
|
585
|
+
timestamp: number;
|
|
586
|
+
/** IANA timezone identifier */
|
|
587
|
+
timezone: string;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Direction for `[[f<]]`, `[[f>]]`, or `[[f=]]` (clear-float).
|
|
592
|
+
*
|
|
593
|
+
* @group Value Types
|
|
594
|
+
*/
|
|
595
|
+
export type ClearFloat = "left" | "right" | "both";
|
|
596
|
+
|
|
597
|
+
// ---------------------------------------------------------------------------
|
|
598
|
+
// Named data types for Element variants
|
|
599
|
+
// ---------------------------------------------------------------------------
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Data for `[[a]]` anchor element.
|
|
603
|
+
*
|
|
604
|
+
* @group Element Data
|
|
605
|
+
*/
|
|
606
|
+
export interface AnchorData {
|
|
607
|
+
target: AnchorTarget | null;
|
|
608
|
+
attributes: AttributeMap;
|
|
609
|
+
elements: Element[];
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Data for link elements (`[[[page]]]`, `[http://...]`, etc.).
|
|
614
|
+
*
|
|
615
|
+
* @group Element Data
|
|
616
|
+
*/
|
|
617
|
+
export interface LinkData {
|
|
618
|
+
type: LinkType;
|
|
619
|
+
link: LinkLocation;
|
|
620
|
+
/** Extra path segment (e.g. anchor fragment) */
|
|
621
|
+
extra: string | null;
|
|
622
|
+
label: LinkLabel;
|
|
623
|
+
target: AnchorTarget | null;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Data for `[[image]]` elements.
|
|
628
|
+
*
|
|
629
|
+
* @group Element Data
|
|
630
|
+
*/
|
|
631
|
+
export interface ImageData {
|
|
632
|
+
source: ImageSource;
|
|
633
|
+
/** If set, the image becomes a clickable link */
|
|
634
|
+
link: LinkLocation | null;
|
|
635
|
+
alignment: FloatAlignment | null;
|
|
636
|
+
attributes: AttributeMap;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Data for `[[toc]]` (table of contents) elements.
|
|
641
|
+
*
|
|
642
|
+
* @group Element Data
|
|
643
|
+
*/
|
|
644
|
+
export interface TableOfContentsData {
|
|
645
|
+
attributes: AttributeMap;
|
|
646
|
+
align: Alignment | null;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Data for `[[footnoteblock]]` elements.
|
|
651
|
+
*
|
|
652
|
+
* @group Element Data
|
|
653
|
+
*/
|
|
654
|
+
export interface FootnoteBlockData {
|
|
655
|
+
/** Custom title for the footnote section */
|
|
656
|
+
title: string | null;
|
|
657
|
+
/** If true, the block is hidden (footnotes rendered inline instead) */
|
|
658
|
+
hide?: boolean;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Data for `[[bibcite label]]` (bibliography citation) elements.
|
|
663
|
+
* Renders as a numbered reference link in the text.
|
|
664
|
+
*
|
|
665
|
+
* @group Element Data
|
|
666
|
+
*/
|
|
667
|
+
export interface BibliographyCiteData {
|
|
668
|
+
/** Citation key that matches an entry in `[[bibliography]]` */
|
|
669
|
+
label: string;
|
|
670
|
+
/** Whether to render the citation number in brackets */
|
|
671
|
+
brackets: boolean;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Data for `[[bibliography]]` block elements.
|
|
676
|
+
* Collects all cited entries and renders as a reference list.
|
|
677
|
+
*
|
|
678
|
+
* @group Element Data
|
|
679
|
+
*/
|
|
680
|
+
export interface BibliographyBlockData {
|
|
681
|
+
/** Definition list entries (`: label : description`) */
|
|
682
|
+
entries: DefinitionListItem[];
|
|
683
|
+
/** Custom section title, or null for default */
|
|
684
|
+
title: string | null;
|
|
685
|
+
/** If true, the block is hidden (for inline citation rendering) */
|
|
686
|
+
hide: boolean;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Data for `[[user name]]` elements.
|
|
691
|
+
*
|
|
692
|
+
* @group Element Data
|
|
693
|
+
*/
|
|
694
|
+
export interface UserData {
|
|
695
|
+
name: string;
|
|
696
|
+
/** Whether to show the user's avatar alongside the name */
|
|
697
|
+
"show-avatar": boolean;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Data for `[[date timestamp]]` elements.
|
|
702
|
+
*
|
|
703
|
+
* @group Element Data
|
|
704
|
+
*/
|
|
705
|
+
export interface DateData {
|
|
706
|
+
value: DateItem;
|
|
707
|
+
/** strftime-style format string, or null for default */
|
|
708
|
+
format: string | null;
|
|
709
|
+
/** Whether to show a tooltip with the full date on hover */
|
|
710
|
+
hover: boolean;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Data for `##color|text##` inline color syntax.
|
|
715
|
+
*
|
|
716
|
+
* @group Element Data
|
|
717
|
+
*/
|
|
718
|
+
export interface ColorData {
|
|
719
|
+
/** CSS color value (name, hex, rgb, etc.) */
|
|
720
|
+
color: string;
|
|
721
|
+
elements: Element[];
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Data for `[[math label]]` block math (LaTeX).
|
|
726
|
+
*
|
|
727
|
+
* @group Element Data
|
|
728
|
+
*/
|
|
729
|
+
export interface MathData {
|
|
730
|
+
/** Optional equation label for cross-references */
|
|
731
|
+
name: string | null;
|
|
732
|
+
/** Raw LaTeX source */
|
|
733
|
+
"latex-source": string;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Data for `[[$ ... $]]` inline math (LaTeX).
|
|
738
|
+
*
|
|
739
|
+
* @group Element Data
|
|
740
|
+
*/
|
|
741
|
+
export interface MathInlineData {
|
|
742
|
+
/** Raw LaTeX source */
|
|
743
|
+
"latex-source": string;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Data for `[[html]]` block elements.
|
|
748
|
+
* Contains raw HTML that is sanitized at render time.
|
|
749
|
+
*
|
|
750
|
+
* @group Element Data
|
|
751
|
+
*/
|
|
752
|
+
export interface HtmlData {
|
|
753
|
+
/** Raw HTML content */
|
|
754
|
+
contents: string;
|
|
755
|
+
/** Optional `<style>` content extracted from the HTML */
|
|
756
|
+
style?: string;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Data for `[[embed]]...[[/embed]]` block elements.
|
|
761
|
+
* Contains raw HTML that is validated against an allowlist at render time.
|
|
762
|
+
* Unlike the `html` element, `embed-block` is paragraph-safe.
|
|
763
|
+
*
|
|
764
|
+
* @group Element Data
|
|
765
|
+
*/
|
|
766
|
+
export interface EmbedBlockData {
|
|
767
|
+
/** Raw HTML content */
|
|
768
|
+
contents: string;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Data for `[[iframe url]]` elements.
|
|
773
|
+
*
|
|
774
|
+
* @group Element Data
|
|
775
|
+
*/
|
|
776
|
+
export interface IframeData {
|
|
777
|
+
url: string;
|
|
778
|
+
attributes: AttributeMap;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Data for `[[include page]]` elements.
|
|
783
|
+
* After resolution via `resolveIncludes()`, `elements` is populated
|
|
784
|
+
* with the included page's parsed content.
|
|
785
|
+
*
|
|
786
|
+
* @group Element Data
|
|
787
|
+
*/
|
|
788
|
+
export interface IncludeData {
|
|
789
|
+
/** Whether this include appeared in an inline (paragraph-safe) context */
|
|
790
|
+
"paragraph-safe": boolean;
|
|
791
|
+
/** Variables passed to the included page (`key=value` pairs) */
|
|
792
|
+
variables: VariableMap;
|
|
793
|
+
/** Target page reference */
|
|
794
|
+
location: PageRef;
|
|
795
|
+
/** Parsed content of the included page (empty before resolution) */
|
|
796
|
+
elements: Element[];
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Data for `[[iftags]]` conditional blocks.
|
|
801
|
+
* Content is shown/hidden based on the current page's tags.
|
|
802
|
+
*
|
|
803
|
+
* @group Element Data
|
|
804
|
+
*/
|
|
805
|
+
export interface IfTagsData {
|
|
806
|
+
/** Tag condition expression (e.g. `"+scp -joke"`) */
|
|
807
|
+
condition: string;
|
|
808
|
+
elements: Element[];
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Data for `[[#expr expression]]` inline expressions.
|
|
813
|
+
* The expression is stored as a string and evaluated at render time.
|
|
814
|
+
*
|
|
815
|
+
* @group Element Data
|
|
816
|
+
*/
|
|
817
|
+
export interface ExprData {
|
|
818
|
+
expression: string;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Data for `[[#if value | then | else]]` conditionals.
|
|
823
|
+
* Simple truthy check — false values: `"false"`, `"null"`, `""`, `"0"`.
|
|
824
|
+
*
|
|
825
|
+
* @group Element Data
|
|
826
|
+
*/
|
|
827
|
+
export interface IfCondData {
|
|
828
|
+
condition: string;
|
|
829
|
+
then: Element[];
|
|
830
|
+
else: Element[];
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Data for `[[#ifexpr expression | then | else]]` conditionals.
|
|
835
|
+
* Evaluates the expression numerically and branches on the result.
|
|
836
|
+
*
|
|
837
|
+
* @group Element Data
|
|
838
|
+
*/
|
|
839
|
+
export interface IfExprData {
|
|
840
|
+
expression: string;
|
|
841
|
+
then: Element[];
|
|
842
|
+
else: Element[];
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// ---------------------------------------------------------------------------
|
|
846
|
+
// Element core types
|
|
847
|
+
// ---------------------------------------------------------------------------
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Maps each element tag name to its data type.
|
|
851
|
+
*
|
|
852
|
+
* `void` means the element carries no data property (e.g. `line-break`).
|
|
853
|
+
*
|
|
854
|
+
* Declared as `type` (not `interface`) to prevent accidental declaration merging.
|
|
855
|
+
*
|
|
856
|
+
* @group Core
|
|
857
|
+
*/
|
|
858
|
+
export type ElementDataMap = {
|
|
859
|
+
container: ContainerData;
|
|
860
|
+
module: Module;
|
|
861
|
+
text: string;
|
|
862
|
+
raw: string;
|
|
863
|
+
variable: string;
|
|
864
|
+
email: string;
|
|
865
|
+
table: TableData;
|
|
866
|
+
"tab-view": TabData[];
|
|
867
|
+
anchor: AnchorData;
|
|
868
|
+
"anchor-name": string;
|
|
869
|
+
link: LinkData;
|
|
870
|
+
image: ImageData;
|
|
871
|
+
list: ListData;
|
|
872
|
+
"definition-list": DefinitionListItem[];
|
|
873
|
+
collapsible: CollapsibleData;
|
|
874
|
+
"table-of-contents": TableOfContentsData;
|
|
875
|
+
footnote: void;
|
|
876
|
+
"footnote-ref": number;
|
|
877
|
+
"footnote-block": FootnoteBlockData;
|
|
878
|
+
"bibliography-cite": BibliographyCiteData;
|
|
879
|
+
"bibliography-block": BibliographyBlockData;
|
|
880
|
+
user: UserData;
|
|
881
|
+
date: DateData;
|
|
882
|
+
color: ColorData;
|
|
883
|
+
code: CodeBlockData;
|
|
884
|
+
math: MathData;
|
|
885
|
+
"math-inline": MathInlineData;
|
|
886
|
+
"equation-reference": string;
|
|
887
|
+
embed: Embed;
|
|
888
|
+
"embed-block": EmbedBlockData;
|
|
889
|
+
html: HtmlData;
|
|
890
|
+
iframe: IframeData;
|
|
891
|
+
include: IncludeData;
|
|
892
|
+
style: string;
|
|
893
|
+
"line-break": void;
|
|
894
|
+
"line-breaks": number;
|
|
895
|
+
"clear-float": ClearFloat;
|
|
896
|
+
"horizontal-rule": void;
|
|
897
|
+
"content-separator": void;
|
|
898
|
+
"if-tags": IfTagsData;
|
|
899
|
+
expr: ExprData;
|
|
900
|
+
if: IfCondData;
|
|
901
|
+
ifexpr: IfExprData;
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Union of all valid element tag names.
|
|
906
|
+
*
|
|
907
|
+
* @group Core
|
|
908
|
+
*/
|
|
909
|
+
export type ElementName = keyof ElementDataMap;
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Resolves the data type for a given element tag name.
|
|
913
|
+
*
|
|
914
|
+
* @group Core
|
|
915
|
+
*/
|
|
916
|
+
export type ElementData<K extends ElementName> = ElementDataMap[K];
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Resolves the full element shape for a given tag name.
|
|
920
|
+
* Elements with `void` data omit the `data` property entirely.
|
|
921
|
+
*
|
|
922
|
+
* @group Core
|
|
923
|
+
*/
|
|
924
|
+
export type ElementOf<K extends ElementName> = ElementDataMap[K] extends void
|
|
925
|
+
? { element: K }
|
|
926
|
+
: { element: K; data: ElementDataMap[K] };
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* A single AST node. Tagged union over all element types.
|
|
930
|
+
*
|
|
931
|
+
* Use `element.element` to discriminate, then access `element.data`
|
|
932
|
+
* with the appropriate type.
|
|
933
|
+
*
|
|
934
|
+
* @example
|
|
935
|
+
* ```ts
|
|
936
|
+
* if (el.element === "text") {
|
|
937
|
+
* console.log(el.data); // string
|
|
938
|
+
* } else if (el.element === "container") {
|
|
939
|
+
* console.log(el.data.type); // ContainerType
|
|
940
|
+
* }
|
|
941
|
+
* ```
|
|
942
|
+
*
|
|
943
|
+
* @group Core
|
|
944
|
+
*/
|
|
945
|
+
export type Element = {
|
|
946
|
+
[K in ElementName]: ElementOf<K>;
|
|
947
|
+
}[ElementName];
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* Table-of-contents entry collected during parsing.
|
|
951
|
+
* Used internally to build the TOC sidebar.
|
|
952
|
+
*
|
|
953
|
+
* @group Core
|
|
954
|
+
*/
|
|
955
|
+
export interface TocEntry {
|
|
956
|
+
/** Heading nesting level (1-6) */
|
|
957
|
+
level: number;
|
|
958
|
+
/** Plain-text heading content */
|
|
959
|
+
text: string;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Root of the parsed AST.
|
|
964
|
+
*
|
|
965
|
+
* Besides the main `elements` array, the tree may carry extracted
|
|
966
|
+
* side-channel data (TOC, styles, code blocks, footnotes) that is
|
|
967
|
+
* collected during parsing and used at render time.
|
|
968
|
+
*
|
|
969
|
+
* @group Core
|
|
970
|
+
*/
|
|
971
|
+
export interface SyntaxTree {
|
|
972
|
+
/** Top-level elements of the document */
|
|
973
|
+
elements: Element[];
|
|
974
|
+
/** Generated table-of-contents entries (if any headings have `has-toc: true`) */
|
|
975
|
+
"table-of-contents"?: Element[];
|
|
976
|
+
/** CSS from `[[module CSS]]` blocks */
|
|
977
|
+
styles?: string[];
|
|
978
|
+
/** Raw HTML from `[[html]]` blocks (rendered in sandboxed iframes) */
|
|
979
|
+
"html-blocks"?: string[];
|
|
980
|
+
/** Code blocks extracted for deferred syntax highlighting */
|
|
981
|
+
"code-blocks"?: CodeBlockData[];
|
|
982
|
+
/** Footnote content arrays, indexed by footnote number */
|
|
983
|
+
footnotes?: Element[][];
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// ---------------------------------------------------------------------------
|
|
987
|
+
// Factory functions
|
|
988
|
+
// ---------------------------------------------------------------------------
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* Create a text element.
|
|
992
|
+
*
|
|
993
|
+
* @group Factories
|
|
994
|
+
*/
|
|
995
|
+
export function text(value: string): Element {
|
|
996
|
+
return { element: "text", data: value };
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* Create a container element with the given type and children.
|
|
1001
|
+
*
|
|
1002
|
+
* @group Factories
|
|
1003
|
+
*/
|
|
1004
|
+
export function container(
|
|
1005
|
+
type: ContainerType,
|
|
1006
|
+
elements: Element[],
|
|
1007
|
+
attributes: AttributeMap = {},
|
|
1008
|
+
): Element {
|
|
1009
|
+
return {
|
|
1010
|
+
element: "container",
|
|
1011
|
+
data: { type, attributes, elements },
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* Create a paragraph container.
|
|
1017
|
+
*
|
|
1018
|
+
* @group Factories
|
|
1019
|
+
*/
|
|
1020
|
+
export function paragraph(elements: Element[], attributes: AttributeMap = {}): Element {
|
|
1021
|
+
return container("paragraph", elements, attributes);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* Create a bold (`**...**`) container.
|
|
1026
|
+
*
|
|
1027
|
+
* @group Factories
|
|
1028
|
+
*/
|
|
1029
|
+
export function bold(elements: Element[], attributes: AttributeMap = {}): Element {
|
|
1030
|
+
return container("bold", elements, attributes);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/**
|
|
1034
|
+
* Create an italics (`//...//`) container.
|
|
1035
|
+
*
|
|
1036
|
+
* @group Factories
|
|
1037
|
+
*/
|
|
1038
|
+
export function italics(elements: Element[], attributes: AttributeMap = {}): Element {
|
|
1039
|
+
return container("italics", elements, attributes);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* Create a heading (`+ ...` through `++++++ ...`) container.
|
|
1044
|
+
*
|
|
1045
|
+
* @param level - Heading depth (1-6)
|
|
1046
|
+
* @param elements - Heading content
|
|
1047
|
+
* @param hasToc - Whether to include in the table of contents (default: true)
|
|
1048
|
+
* @param attributes - Optional HTML attributes
|
|
1049
|
+
*
|
|
1050
|
+
* @group Factories
|
|
1051
|
+
*/
|
|
1052
|
+
export function heading(
|
|
1053
|
+
level: HeadingLevel,
|
|
1054
|
+
elements: Element[],
|
|
1055
|
+
hasToc = true,
|
|
1056
|
+
attributes: AttributeMap = {},
|
|
1057
|
+
): Element {
|
|
1058
|
+
return container({ header: { level, "has-toc": hasToc } }, elements, attributes);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Create a line-break element.
|
|
1063
|
+
*
|
|
1064
|
+
* @group Factories
|
|
1065
|
+
*/
|
|
1066
|
+
export function lineBreak(): Element {
|
|
1067
|
+
return { element: "line-break" };
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
/**
|
|
1071
|
+
* Create a horizontal rule (`----`) element.
|
|
1072
|
+
*
|
|
1073
|
+
* @group Factories
|
|
1074
|
+
*/
|
|
1075
|
+
export function horizontalRule(): Element {
|
|
1076
|
+
return { element: "horizontal-rule" };
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
/**
|
|
1080
|
+
* Create a link element.
|
|
1081
|
+
*
|
|
1082
|
+
* @param linkLocation - Destination (URL string or {@link PageRef})
|
|
1083
|
+
* @param label - Display label
|
|
1084
|
+
* @param options - Optional type, extra path, and target overrides
|
|
1085
|
+
*
|
|
1086
|
+
* @group Factories
|
|
1087
|
+
*/
|
|
1088
|
+
export function link(
|
|
1089
|
+
linkLocation: LinkLocation,
|
|
1090
|
+
label: LinkLabel,
|
|
1091
|
+
options: {
|
|
1092
|
+
type?: LinkType;
|
|
1093
|
+
extra?: string | null;
|
|
1094
|
+
target?: AnchorTarget | null;
|
|
1095
|
+
} = {},
|
|
1096
|
+
): Element {
|
|
1097
|
+
return {
|
|
1098
|
+
element: "link",
|
|
1099
|
+
data: {
|
|
1100
|
+
type: options.type ?? (typeof linkLocation === "string" ? "direct" : "page"),
|
|
1101
|
+
link: linkLocation,
|
|
1102
|
+
extra: options.extra ?? null,
|
|
1103
|
+
label,
|
|
1104
|
+
target: options.target ?? null,
|
|
1105
|
+
},
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* Create a list element.
|
|
1111
|
+
*
|
|
1112
|
+
* @group Factories
|
|
1113
|
+
*/
|
|
1114
|
+
export function list(type: ListType, items: ListItem[], attributes: AttributeMap = {}): Element {
|
|
1115
|
+
return {
|
|
1116
|
+
element: "list",
|
|
1117
|
+
data: { type, attributes, items },
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* Create a list item containing inline elements.
|
|
1123
|
+
*
|
|
1124
|
+
* @group Factories
|
|
1125
|
+
*/
|
|
1126
|
+
export function listItemElements(elements: Element[], attributes: AttributeMap = {}): ListItem {
|
|
1127
|
+
return {
|
|
1128
|
+
"item-type": "elements",
|
|
1129
|
+
attributes,
|
|
1130
|
+
elements,
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
/**
|
|
1135
|
+
* Create a list item containing a nested sub-list.
|
|
1136
|
+
*
|
|
1137
|
+
* @group Factories
|
|
1138
|
+
*/
|
|
1139
|
+
export function listItemSubList(data: ListData): ListItem {
|
|
1140
|
+
return {
|
|
1141
|
+
"item-type": "sub-list",
|
|
1142
|
+
element: "list",
|
|
1143
|
+
data,
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
// ---------------------------------------------------------------------------
|
|
1148
|
+
// Paragraph safety checks
|
|
1149
|
+
// ---------------------------------------------------------------------------
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* Check whether a container type can appear inside a `<p>` element.
|
|
1153
|
+
*
|
|
1154
|
+
* Inline formatting (bold, italics, span, etc.) is paragraph-safe.
|
|
1155
|
+
* Block-level structures (div, blockquote, heading, etc.) are not.
|
|
1156
|
+
*
|
|
1157
|
+
* @group Utilities
|
|
1158
|
+
*/
|
|
1159
|
+
export function isContainerTypeParagraphSafe(type: ContainerType): boolean {
|
|
1160
|
+
if (isHeaderType(type)) return false;
|
|
1161
|
+
if (isAlignType(type)) return false;
|
|
1162
|
+
// String container types
|
|
1163
|
+
switch (type) {
|
|
1164
|
+
case "bold":
|
|
1165
|
+
case "italics":
|
|
1166
|
+
case "underline":
|
|
1167
|
+
case "superscript":
|
|
1168
|
+
case "subscript":
|
|
1169
|
+
case "strikethrough":
|
|
1170
|
+
case "monospace":
|
|
1171
|
+
case "span":
|
|
1172
|
+
case "size":
|
|
1173
|
+
return true;
|
|
1174
|
+
case "div":
|
|
1175
|
+
case "blockquote":
|
|
1176
|
+
case "paragraph":
|
|
1177
|
+
case "heading":
|
|
1178
|
+
case "collapsible":
|
|
1179
|
+
case "definition-list":
|
|
1180
|
+
case "definition-list-item":
|
|
1181
|
+
case "definition-list-key":
|
|
1182
|
+
case "definition-list-value":
|
|
1183
|
+
case "table-row":
|
|
1184
|
+
case "table-cell":
|
|
1185
|
+
return false;
|
|
1186
|
+
default:
|
|
1187
|
+
// Unknown types are treated as not paragraph-safe for safety
|
|
1188
|
+
return false;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* Check whether an element can appear inside a `<p>` element.
|
|
1194
|
+
*
|
|
1195
|
+
* Performs a surface-level check on the element tag (and container type
|
|
1196
|
+
* for containers). Does not recurse into child elements.
|
|
1197
|
+
*
|
|
1198
|
+
* Used by the parser to decide whether to wrap adjacent inline elements
|
|
1199
|
+
* in a paragraph or leave them as block-level siblings.
|
|
1200
|
+
*
|
|
1201
|
+
* @group Utilities
|
|
1202
|
+
*/
|
|
1203
|
+
export function isParagraphSafe(element: Element): boolean {
|
|
1204
|
+
switch (element.element) {
|
|
1205
|
+
case "container": {
|
|
1206
|
+
const data = element.data as ContainerData;
|
|
1207
|
+
return isContainerTypeParagraphSafe(data.type);
|
|
1208
|
+
}
|
|
1209
|
+
case "module":
|
|
1210
|
+
return false;
|
|
1211
|
+
case "text":
|
|
1212
|
+
case "raw":
|
|
1213
|
+
case "variable":
|
|
1214
|
+
case "email":
|
|
1215
|
+
return true;
|
|
1216
|
+
case "table":
|
|
1217
|
+
return false;
|
|
1218
|
+
case "tab-view":
|
|
1219
|
+
return false;
|
|
1220
|
+
case "anchor":
|
|
1221
|
+
case "anchor-name":
|
|
1222
|
+
case "link":
|
|
1223
|
+
return true;
|
|
1224
|
+
case "image":
|
|
1225
|
+
return true;
|
|
1226
|
+
case "list":
|
|
1227
|
+
return false;
|
|
1228
|
+
case "definition-list":
|
|
1229
|
+
return false;
|
|
1230
|
+
case "collapsible":
|
|
1231
|
+
return false;
|
|
1232
|
+
case "table-of-contents":
|
|
1233
|
+
return false;
|
|
1234
|
+
case "footnote":
|
|
1235
|
+
return true;
|
|
1236
|
+
case "footnote-ref":
|
|
1237
|
+
return true;
|
|
1238
|
+
case "footnote-block":
|
|
1239
|
+
return false;
|
|
1240
|
+
case "bibliography-cite":
|
|
1241
|
+
return true;
|
|
1242
|
+
case "bibliography-block":
|
|
1243
|
+
return false;
|
|
1244
|
+
case "user":
|
|
1245
|
+
return true;
|
|
1246
|
+
case "date":
|
|
1247
|
+
return true;
|
|
1248
|
+
case "color":
|
|
1249
|
+
return true;
|
|
1250
|
+
case "code":
|
|
1251
|
+
return false;
|
|
1252
|
+
case "math":
|
|
1253
|
+
return false;
|
|
1254
|
+
case "math-inline":
|
|
1255
|
+
return true;
|
|
1256
|
+
case "embed":
|
|
1257
|
+
return false;
|
|
1258
|
+
case "embed-block":
|
|
1259
|
+
return true;
|
|
1260
|
+
case "html":
|
|
1261
|
+
case "iframe":
|
|
1262
|
+
return false;
|
|
1263
|
+
case "include": {
|
|
1264
|
+
const data = element.data as IncludeData;
|
|
1265
|
+
return data["paragraph-safe"];
|
|
1266
|
+
}
|
|
1267
|
+
case "style":
|
|
1268
|
+
return false;
|
|
1269
|
+
case "line-break":
|
|
1270
|
+
case "line-breaks":
|
|
1271
|
+
return true;
|
|
1272
|
+
case "clear-float":
|
|
1273
|
+
return false;
|
|
1274
|
+
case "horizontal-rule":
|
|
1275
|
+
return false;
|
|
1276
|
+
case "content-separator":
|
|
1277
|
+
return false;
|
|
1278
|
+
case "if-tags":
|
|
1279
|
+
return false;
|
|
1280
|
+
case "expr":
|
|
1281
|
+
case "if":
|
|
1282
|
+
case "ifexpr":
|
|
1283
|
+
return true;
|
|
1284
|
+
default:
|
|
1285
|
+
return false;
|
|
1286
|
+
}
|
|
1287
|
+
}
|