@wdprlib/parser 1.1.0 → 1.1.2
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 +46 -11
- package/dist/index.d.cts +269 -60
- package/dist/index.d.ts +269 -60
- package/dist/index.js +46 -11
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5113,6 +5113,10 @@ function parseBibliographyEntry(ctx, startPos) {
|
|
|
5113
5113
|
consumed++;
|
|
5114
5114
|
break;
|
|
5115
5115
|
}
|
|
5116
|
+
contentNodes.push({ element: "line-break" });
|
|
5117
|
+
pos++;
|
|
5118
|
+
consumed++;
|
|
5119
|
+
continue;
|
|
5116
5120
|
}
|
|
5117
5121
|
const inlineCtx = { ...ctx, pos };
|
|
5118
5122
|
const result = parseInlineUntil(inlineCtx, "NEWLINE");
|
|
@@ -5610,6 +5614,11 @@ var linkTripleRule = {
|
|
|
5610
5614
|
break;
|
|
5611
5615
|
}
|
|
5612
5616
|
if (token.type === "NEWLINE") {
|
|
5617
|
+
if (foundPipe) {
|
|
5618
|
+
labelText += " ";
|
|
5619
|
+
} else {
|
|
5620
|
+
target += " ";
|
|
5621
|
+
}
|
|
5613
5622
|
consumed++;
|
|
5614
5623
|
pos++;
|
|
5615
5624
|
continue;
|
|
@@ -6003,10 +6012,8 @@ var newlineLineBreakRule = {
|
|
|
6003
6012
|
}
|
|
6004
6013
|
const nextMeaningfulToken = ctx.tokens[ctx.pos + lookAhead];
|
|
6005
6014
|
let isValidBlock = isBlockStartToken(nextMeaningfulToken?.type);
|
|
6006
|
-
if (isValidBlock &&
|
|
6007
|
-
|
|
6008
|
-
isValidBlock = false;
|
|
6009
|
-
}
|
|
6015
|
+
if (isValidBlock && !nextMeaningfulToken?.lineStart) {
|
|
6016
|
+
isValidBlock = false;
|
|
6010
6017
|
}
|
|
6011
6018
|
if (isValidBlock && nextMeaningfulToken?.type === "HEADING_MARKER") {
|
|
6012
6019
|
const markerLen = nextMeaningfulToken.value.length;
|
|
@@ -6718,7 +6725,15 @@ var footnoteRule = {
|
|
|
6718
6725
|
if (token.type === "NEWLINE") {
|
|
6719
6726
|
pos++;
|
|
6720
6727
|
consumed++;
|
|
6721
|
-
|
|
6728
|
+
let peekPos = pos;
|
|
6729
|
+
let peekConsumed = 0;
|
|
6730
|
+
while (ctx.tokens[peekPos]?.type === "WHITESPACE") {
|
|
6731
|
+
peekPos++;
|
|
6732
|
+
peekConsumed++;
|
|
6733
|
+
}
|
|
6734
|
+
if (ctx.tokens[peekPos]?.type === "NEWLINE") {
|
|
6735
|
+
pos = peekPos;
|
|
6736
|
+
consumed += peekConsumed;
|
|
6722
6737
|
while (ctx.tokens[pos]?.type === "NEWLINE") {
|
|
6723
6738
|
pos++;
|
|
6724
6739
|
consumed++;
|
|
@@ -7215,7 +7230,7 @@ var anchorRule = {
|
|
|
7215
7230
|
consumed++;
|
|
7216
7231
|
}
|
|
7217
7232
|
foundClose = true;
|
|
7218
|
-
|
|
7233
|
+
if (paragraphStrip && ctx.tokens[pos]?.type === "NEWLINE" && ctx.tokens[pos + 1]?.type !== "NEWLINE") {
|
|
7219
7234
|
pos++;
|
|
7220
7235
|
consumed++;
|
|
7221
7236
|
}
|
|
@@ -7753,6 +7768,7 @@ var bibciteRule = {
|
|
|
7753
7768
|
return { success: false };
|
|
7754
7769
|
}
|
|
7755
7770
|
let label = "";
|
|
7771
|
+
let foundClose = false;
|
|
7756
7772
|
while (pos < ctx.tokens.length) {
|
|
7757
7773
|
const t = ctx.tokens[pos];
|
|
7758
7774
|
if (!t)
|
|
@@ -7761,6 +7777,7 @@ var bibciteRule = {
|
|
|
7761
7777
|
const nextT = ctx.tokens[pos + 1];
|
|
7762
7778
|
if (nextT?.type === "TEXT" && nextT.value === ")") {
|
|
7763
7779
|
consumed += 2;
|
|
7780
|
+
foundClose = true;
|
|
7764
7781
|
break;
|
|
7765
7782
|
}
|
|
7766
7783
|
}
|
|
@@ -7771,6 +7788,9 @@ var bibciteRule = {
|
|
|
7771
7788
|
pos++;
|
|
7772
7789
|
consumed++;
|
|
7773
7790
|
}
|
|
7791
|
+
if (!foundClose) {
|
|
7792
|
+
return { success: false };
|
|
7793
|
+
}
|
|
7774
7794
|
label = label.trim();
|
|
7775
7795
|
if (!label) {
|
|
7776
7796
|
return { success: false };
|
|
@@ -9627,16 +9647,31 @@ function resolveIncludes(source, fetcher, options) {
|
|
|
9627
9647
|
};
|
|
9628
9648
|
return expandText(source, cachedFetcher, 0, maxDepth, []);
|
|
9629
9649
|
}
|
|
9630
|
-
var INCLUDE_PATTERN = /\[\[include\s
|
|
9650
|
+
var INCLUDE_PATTERN = /\[\[include\s([^\]]*(?:\](?!\])[^\]]*)*)\]\]/gi;
|
|
9631
9651
|
function parseIncludeDirective(inner) {
|
|
9632
9652
|
const normalized = inner.replace(/\n/g, " ");
|
|
9633
9653
|
const parts = normalized.split("|");
|
|
9634
|
-
const
|
|
9635
|
-
const
|
|
9654
|
+
const firstSegment = parts[0].trim();
|
|
9655
|
+
const spaceIndex = firstSegment.indexOf(" ");
|
|
9656
|
+
let target;
|
|
9657
|
+
const varSegments = [];
|
|
9658
|
+
if (spaceIndex !== -1) {
|
|
9659
|
+
target = firstSegment.slice(0, spaceIndex);
|
|
9660
|
+
const rest = firstSegment.slice(spaceIndex + 1).trim();
|
|
9661
|
+
if (rest) {
|
|
9662
|
+
varSegments.push(rest);
|
|
9663
|
+
}
|
|
9664
|
+
} else {
|
|
9665
|
+
target = firstSegment;
|
|
9666
|
+
}
|
|
9636
9667
|
for (let i = 1;i < parts.length; i++) {
|
|
9637
9668
|
const segment = parts[i].trim();
|
|
9638
|
-
if (
|
|
9639
|
-
|
|
9669
|
+
if (segment) {
|
|
9670
|
+
varSegments.push(segment);
|
|
9671
|
+
}
|
|
9672
|
+
}
|
|
9673
|
+
const variables = {};
|
|
9674
|
+
for (const segment of varSegments) {
|
|
9640
9675
|
const eqIndex = segment.indexOf("=");
|
|
9641
9676
|
if (eqIndex !== -1) {
|
|
9642
9677
|
const key = segment.slice(0, eqIndex).trim();
|
package/dist/index.d.cts
CHANGED
|
@@ -4,32 +4,77 @@ import { WikitextMode, WikitextSettings as WikitextSettings4 } from "@wdprlib/as
|
|
|
4
4
|
import { createSettings, DEFAULT_SETTINGS } from "@wdprlib/ast";
|
|
5
5
|
import { Position } from "@wdprlib/ast";
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Every distinct lexeme the Wikidot lexer can produce.
|
|
8
|
+
*
|
|
9
|
+
* Each value corresponds to a fixed character sequence (or class of
|
|
10
|
+
* sequences) in Wikidot markup. The inline comments show the literal
|
|
11
|
+
* text that produces each token type.
|
|
12
|
+
*
|
|
13
|
+
* @group Lexer
|
|
8
14
|
*/
|
|
9
15
|
type TokenType = "EOF" | "TEXT" | "IDENTIFIER" | "NEWLINE" | "WHITESPACE" | "BLOCK_OPEN" | "BLOCK_CLOSE" | "BLOCK_END_OPEN" | "BOLD_MARKER" | "ITALIC_MARKER" | "UNDERLINE_MARKER" | "STRIKE_MARKER" | "SUPER_MARKER" | "SUB_MARKER" | "MONO_MARKER" | "MONO_CLOSE" | "HEADING_MARKER" | "HR_MARKER" | "LIST_BULLET" | "LIST_NUMBER" | "BLOCKQUOTE_MARKER" | "TABLE_MARKER" | "TABLE_HEADER" | "TABLE_LEFT" | "TABLE_CENTER" | "TABLE_RIGHT" | "CODE_OPEN" | "CODE_CLOSE" | "LINK_OPEN" | "LINK_CLOSE" | "BRACKET_OPEN" | "BRACKET_CLOSE" | "BRACKET_ANCHOR" | "BRACKET_STAR" | "PIPE" | "EQUALS" | "COLON" | "SLASH" | "STAR" | "HASH" | "AT" | "AMPERSAND" | "BACKSLASH" | "QUOTED_STRING" | "RAW_OPEN" | "RAW_CLOSE" | "RAW_BLOCK_OPEN" | "RAW_BLOCK_CLOSE" | "COLOR_MARKER" | "UNDERSCORE" | "BACKSLASH_BREAK" | "COMMENT_OPEN" | "COMMENT_CLOSE" | "CLEAR_FLOAT" | "CLEAR_FLOAT_LEFT" | "CLEAR_FLOAT_RIGHT" | "LEFT_DOUBLE_ANGLE" | "RIGHT_DOUBLE_ANGLE";
|
|
10
16
|
/**
|
|
11
|
-
*
|
|
17
|
+
* A single lexical token produced by the `Lexer`.
|
|
18
|
+
*
|
|
19
|
+
* Tokens are the input to the parser stage. Each token carries its
|
|
20
|
+
* literal text (`value`), source location (`position`), and a flag
|
|
21
|
+
* indicating whether it appeared at the beginning of a line — which
|
|
22
|
+
* matters because several Wikidot constructs (headings, lists,
|
|
23
|
+
* blockquotes, horizontal rules) are only valid at line start.
|
|
24
|
+
*
|
|
25
|
+
* @group Lexer
|
|
12
26
|
*/
|
|
13
27
|
interface Token {
|
|
28
|
+
/** The lexeme category */
|
|
14
29
|
type: TokenType;
|
|
30
|
+
/** The literal source text that produced this token */
|
|
15
31
|
value: string;
|
|
32
|
+
/** Start/end location in the original source string */
|
|
16
33
|
position: Position;
|
|
17
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* `true` when this token is the first non-whitespace token on its
|
|
36
|
+
* line. Block-level rules (headings, lists, blockquotes) check this
|
|
37
|
+
* flag before attempting to match.
|
|
38
|
+
*/
|
|
18
39
|
lineStart: boolean;
|
|
19
40
|
}
|
|
20
41
|
/**
|
|
21
|
-
*
|
|
42
|
+
* Construct a {@link Token} value.
|
|
43
|
+
*
|
|
44
|
+
* @param type - The lexeme category
|
|
45
|
+
* @param value - Literal source text
|
|
46
|
+
* @param position - Source location range
|
|
47
|
+
* @param lineStart - Whether the token starts a new line
|
|
48
|
+
* @returns A new token object
|
|
49
|
+
*
|
|
50
|
+
* @group Lexer
|
|
22
51
|
*/
|
|
23
52
|
declare function createToken(type: TokenType, value: string, position: Position, lineStart?: boolean): Token;
|
|
24
53
|
/**
|
|
25
|
-
* Lexer
|
|
54
|
+
* Configuration for the {@link Lexer}.
|
|
55
|
+
*
|
|
56
|
+
* @group Lexer
|
|
26
57
|
*/
|
|
27
58
|
interface LexerOptions {
|
|
28
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* When `true` (default), every token carries accurate line/column/offset
|
|
61
|
+
* data. Set to `false` to skip position tracking for faster tokenisation
|
|
62
|
+
* when source-map information is not needed.
|
|
63
|
+
*/
|
|
29
64
|
trackPositions?: boolean;
|
|
30
65
|
}
|
|
31
66
|
/**
|
|
32
|
-
* Wikidot markup
|
|
67
|
+
* Converts a Wikidot markup source string into a flat array of {@link Token}s.
|
|
68
|
+
*
|
|
69
|
+
* The lexer is single-pass and greedy: it tries the longest-matching
|
|
70
|
+
* multi-character pattern first (e.g. `[[[` before `[[`, `**` before `*`).
|
|
71
|
+
* Context-sensitive constructs (line-start headings, blockquote markers)
|
|
72
|
+
* are disambiguated via the `lineStart` state flag.
|
|
73
|
+
*
|
|
74
|
+
* For convenience, use the standalone {@link tokenize} function instead
|
|
75
|
+
* of constructing a `Lexer` directly.
|
|
76
|
+
*
|
|
77
|
+
* @group Lexer
|
|
33
78
|
*/
|
|
34
79
|
declare class Lexer {
|
|
35
80
|
private state;
|
|
@@ -78,23 +123,59 @@ declare class Lexer {
|
|
|
78
123
|
private isAlphanumeric;
|
|
79
124
|
}
|
|
80
125
|
/**
|
|
81
|
-
*
|
|
126
|
+
* Tokenise a Wikidot markup source string in one call.
|
|
127
|
+
*
|
|
128
|
+
* Shorthand for `new Lexer(source, options).tokenize()`.
|
|
129
|
+
*
|
|
130
|
+
* @param source - Raw Wikidot markup
|
|
131
|
+
* @param options - Optional lexer configuration
|
|
132
|
+
* @returns A flat array of tokens, ending with an `EOF` token
|
|
133
|
+
*
|
|
134
|
+
* @group Lexer
|
|
82
135
|
*/
|
|
83
136
|
declare function tokenize(source: string, options?: LexerOptions): Token[];
|
|
84
137
|
import { SyntaxTree, WikitextSettings } from "@wdprlib/ast";
|
|
85
138
|
/**
|
|
86
|
-
* Parser
|
|
139
|
+
* Configuration for the {@link Parser} and the {@link parse} function.
|
|
140
|
+
*
|
|
141
|
+
* All fields are optional; sensible defaults are applied when omitted.
|
|
142
|
+
*
|
|
143
|
+
* @group Parser
|
|
87
144
|
*/
|
|
88
145
|
interface ParserOptions {
|
|
89
|
-
/**
|
|
146
|
+
/** Markup dialect. Currently only `"wikidot"` is supported. */
|
|
90
147
|
version?: "wikidot";
|
|
91
|
-
/**
|
|
148
|
+
/**
|
|
149
|
+
* Propagate source-position data into every AST node.
|
|
150
|
+
* Defaults to `true`. Set to `false` for smaller output when positions
|
|
151
|
+
* are not needed.
|
|
152
|
+
*/
|
|
92
153
|
trackPositions?: boolean;
|
|
93
|
-
/**
|
|
154
|
+
/**
|
|
155
|
+
* Context-dependent feature flags (page vs. forum-post, etc.).
|
|
156
|
+
* Defaults to {@link DEFAULT_SETTINGS} (full page mode).
|
|
157
|
+
*/
|
|
94
158
|
settings?: WikitextSettings;
|
|
95
159
|
}
|
|
96
160
|
/**
|
|
97
|
-
* Wikidot
|
|
161
|
+
* Converts a token stream into a Wikidot {@link SyntaxTree}.
|
|
162
|
+
*
|
|
163
|
+
* The parser consumes tokens produced by the `Lexer` and emits a
|
|
164
|
+
* tree of {@link Element} nodes. Block-level rules are tried in priority
|
|
165
|
+
* order; when none match, the fallback paragraph rule collects inline
|
|
166
|
+
* tokens until the next blank line.
|
|
167
|
+
*
|
|
168
|
+
* After the main parse pass, two post-processing steps run:
|
|
169
|
+
*
|
|
170
|
+
* 1. **Span-strip merging** — `[[span_]]` elements that set
|
|
171
|
+
* `_paragraphStrip` are merged with adjacent paragraphs.
|
|
172
|
+
* 2. **Internal-flag cleanup** — all `_`-prefixed bookkeeping fields
|
|
173
|
+
* are removed from the final AST.
|
|
174
|
+
*
|
|
175
|
+
* For most use-cases the standalone {@link parse} function is simpler
|
|
176
|
+
* than constructing a `Parser` directly.
|
|
177
|
+
*
|
|
178
|
+
* @group Parser
|
|
98
179
|
*/
|
|
99
180
|
declare class Parser {
|
|
100
181
|
private ctx;
|
|
@@ -131,58 +212,119 @@ declare class Parser {
|
|
|
131
212
|
declare function parse(source: string, options?: ParserOptions): SyntaxTree;
|
|
132
213
|
import { Element as Element2 } from "@wdprlib/ast";
|
|
133
214
|
/**
|
|
134
|
-
* Parser function type for re-parsing substituted
|
|
135
|
-
*
|
|
215
|
+
* Parser function type for re-parsing substituted template output as wikitext.
|
|
216
|
+
*
|
|
217
|
+
* Used by ListPages and ListUsers modules during the resolution phase. After
|
|
218
|
+
* template variables are substituted with actual data, the resulting string
|
|
219
|
+
* needs to be parsed as wikitext to produce AST elements.
|
|
220
|
+
*
|
|
221
|
+
* @param input - Wikitext string to parse
|
|
222
|
+
* @returns Object containing the parsed elements
|
|
136
223
|
*/
|
|
137
224
|
type ParseFunction = (input: string) => {
|
|
138
225
|
elements: Element2[];
|
|
139
226
|
};
|
|
140
227
|
/**
|
|
141
|
-
*
|
|
228
|
+
*
|
|
229
|
+
* Type definitions for the ListUsers module.
|
|
230
|
+
*
|
|
231
|
+
* The `[[module ListUsers users="."]]` block displays information about site
|
|
232
|
+
* members. Currently, only `users="."` (the logged-in user) is supported.
|
|
233
|
+
* The template body can reference three variables: `%%number%%`, `%%title%%`,
|
|
234
|
+
* and `%%name%%`.
|
|
235
|
+
*
|
|
236
|
+
* @module
|
|
142
237
|
*/
|
|
143
238
|
/**
|
|
144
|
-
* Supported template variables
|
|
239
|
+
* Supported template variables for ListUsers.
|
|
240
|
+
*
|
|
241
|
+
* - `number` - The user's numeric ID
|
|
242
|
+
* - `title` - The user's display title/nickname
|
|
243
|
+
* - `name` - The user's account name
|
|
145
244
|
*/
|
|
146
245
|
type ListUsersVariable = "number" | "title" | "name";
|
|
147
246
|
/**
|
|
148
|
-
* User data provided by external source
|
|
247
|
+
* User data that must be provided by the external data source.
|
|
248
|
+
*
|
|
249
|
+
* Each field corresponds to a template variable of the same name.
|
|
149
250
|
*/
|
|
150
251
|
interface ListUsersUserData {
|
|
252
|
+
/** The user's numeric ID, rendered by `%%number%%` */
|
|
151
253
|
number: number;
|
|
254
|
+
/** The user's display title, rendered by `%%title%%` */
|
|
152
255
|
title: string;
|
|
256
|
+
/** The user's account name, rendered by `%%name%%` */
|
|
153
257
|
name: string;
|
|
154
258
|
}
|
|
155
259
|
/**
|
|
156
|
-
* Data requirement for a single ListUsers module
|
|
260
|
+
* Data requirement for a single ListUsers module instance.
|
|
261
|
+
*
|
|
262
|
+
* Produced by the extraction phase and consumed by the application to
|
|
263
|
+
* determine what data to fetch.
|
|
157
264
|
*/
|
|
158
265
|
interface ListUsersDataRequirement {
|
|
266
|
+
/** Unique identifier for this module instance (sequential, 0-based) */
|
|
159
267
|
id: number;
|
|
268
|
+
/** The `users` attribute value (currently only `"."` is supported) */
|
|
160
269
|
users: string;
|
|
270
|
+
/** Template variables that need data from the external source */
|
|
161
271
|
neededVariables: ListUsersVariable[];
|
|
162
272
|
}
|
|
163
273
|
/**
|
|
164
|
-
* External data for a single ListUsers module
|
|
165
|
-
*
|
|
274
|
+
* External data provided by the application for a single ListUsers module.
|
|
275
|
+
*
|
|
276
|
+
* Currently ListUsers only returns information about the logged-in user.
|
|
277
|
+
* Return null/undefined from the fetcher to indicate no user is logged in.
|
|
166
278
|
*/
|
|
167
279
|
interface ListUsersExternalData {
|
|
280
|
+
/** Data for the logged-in user */
|
|
168
281
|
user: ListUsersUserData;
|
|
169
282
|
}
|
|
170
283
|
/**
|
|
171
|
-
* Callback to fetch data for a ListUsers module
|
|
284
|
+
* Callback to fetch user data for a ListUsers module.
|
|
285
|
+
*
|
|
286
|
+
* Called during the resolution phase for each ListUsers module in the AST.
|
|
287
|
+
* Return null/undefined to skip the module (outputs nothing, e.g., when
|
|
288
|
+
* no user is logged in).
|
|
289
|
+
*
|
|
290
|
+
* @param requirement - The data requirement describing what data is needed
|
|
291
|
+
* @returns User data, null/undefined to skip, or a Promise of the same
|
|
172
292
|
*/
|
|
173
293
|
type ListUsersDataFetcher = (requirement: ListUsersDataRequirement) => ListUsersExternalData | null | undefined | Promise<ListUsersExternalData | null | undefined>;
|
|
174
294
|
/**
|
|
175
|
-
* Context passed to compiled template
|
|
295
|
+
* Context passed to a compiled ListUsers template function during rendering.
|
|
176
296
|
*/
|
|
177
297
|
interface ListUsersVariableContext {
|
|
298
|
+
/** The user whose data is being rendered */
|
|
178
299
|
user: ListUsersUserData;
|
|
179
300
|
}
|
|
180
301
|
/**
|
|
181
|
-
*
|
|
302
|
+
* A compiled ListUsers template function.
|
|
303
|
+
*
|
|
304
|
+
* Accepts a variable context and returns the rendered wikitext string
|
|
305
|
+
* with all `%%variable%%` placeholders substituted.
|
|
306
|
+
*
|
|
307
|
+
* @param ctx - The variable context containing user data
|
|
308
|
+
* @returns Rendered wikitext string
|
|
182
309
|
*/
|
|
183
310
|
type ListUsersCompiledTemplate = (ctx: ListUsersVariableContext) => string;
|
|
184
311
|
/**
|
|
185
|
-
*
|
|
312
|
+
*
|
|
313
|
+
* Type definitions for the ListPages module system.
|
|
314
|
+
*
|
|
315
|
+
* This file defines the complete type vocabulary for the ListPages lifecycle:
|
|
316
|
+
*
|
|
317
|
+
* - **Query types**: Raw and normalized representations of ListPages filter/sort parameters
|
|
318
|
+
* - **Variable types**: The `%%variable%%` names supported in ListPages templates
|
|
319
|
+
* - **Data requirement types**: What the parser tells the application it needs to fetch
|
|
320
|
+
* - **External data types**: What the application provides back (page data, user info, site context)
|
|
321
|
+
* - **Template types**: Compiled template function signatures and their execution context
|
|
322
|
+
* - **Normalized query types**: Structured representations of parsed query parameters
|
|
323
|
+
*
|
|
324
|
+
* Security note: Several fields contain untrusted user input from wikitext.
|
|
325
|
+
* See `ListPagesQuery` documentation for safe usage guidelines.
|
|
326
|
+
*
|
|
327
|
+
* @module
|
|
186
328
|
*/
|
|
187
329
|
/**
|
|
188
330
|
* ListPages query parameters for page selection
|
|
@@ -385,7 +527,7 @@ interface ListPagesExternalData {
|
|
|
385
527
|
* Callback to fetch data for a ListPages module
|
|
386
528
|
*
|
|
387
529
|
* Called by resolveModules for each ListPages module in the AST.
|
|
388
|
-
* Receives a normalized query with all
|
|
530
|
+
* Receives a normalized query with all `@URL` parameters resolved.
|
|
389
531
|
* Return null/undefined to skip the module (outputs nothing).
|
|
390
532
|
*
|
|
391
533
|
* @param query - Normalized query with structured types (tags, category, order, etc.)
|
|
@@ -527,47 +669,60 @@ interface NormalizedListPagesQuery {
|
|
|
527
669
|
reverse?: boolean;
|
|
528
670
|
}
|
|
529
671
|
/**
|
|
530
|
-
* Callback to
|
|
672
|
+
* Callback to retrieve the current page's tags during the resolve phase.
|
|
531
673
|
*
|
|
532
|
-
* Called
|
|
674
|
+
* Called when evaluating `[[iftags]]` conditions. The application must provide
|
|
675
|
+
* this callback with access to the current page's tag list.
|
|
533
676
|
*
|
|
534
677
|
* @returns Array of tag names for the current page
|
|
535
678
|
*/
|
|
536
679
|
type IfTagsResolver = () => string[];
|
|
537
680
|
/**
|
|
538
|
-
*
|
|
681
|
+
* Callback bag for supplying external data during module resolution.
|
|
539
682
|
*
|
|
540
|
-
*
|
|
541
|
-
*
|
|
683
|
+
* Pass an instance to `resolveModules()`. Every callback is optional:
|
|
684
|
+
* when a callback is missing the corresponding module node is kept as-is
|
|
685
|
+
* in the output AST — useful when you only need to resolve a subset of
|
|
686
|
+
* modules (e.g. only `[[iftags]]` on the client side).
|
|
542
687
|
*
|
|
543
|
-
*
|
|
688
|
+
* @group Module Resolution
|
|
544
689
|
*/
|
|
545
690
|
interface DataProvider {
|
|
546
691
|
/**
|
|
547
|
-
* Fetch data for ListPages
|
|
548
|
-
* Called during resolve phase with query parameters
|
|
692
|
+
* Fetch page data for `[[module ListPages]]` expansion.
|
|
549
693
|
*
|
|
550
|
-
*
|
|
551
|
-
*
|
|
552
|
-
*
|
|
553
|
-
*
|
|
554
|
-
*
|
|
694
|
+
* Called once per ListPages instance in the AST with the normalised
|
|
695
|
+
* query parameters extracted from the module's wikitext attributes.
|
|
696
|
+
*
|
|
697
|
+
* @security The query fields originate from **untrusted user input**.
|
|
698
|
+
* When building database queries from the returned requirement:
|
|
699
|
+
* - **Never** interpolate `req.query` / `req.rawAttributes` into SQL
|
|
700
|
+
* - **Always** use parameterised queries or prepared statements
|
|
701
|
+
* - For `order` (ORDER BY), validate against a whitelist of column names
|
|
555
702
|
*/
|
|
556
703
|
fetchListPages?: ListPagesDataFetcher;
|
|
557
704
|
/**
|
|
558
|
-
* Fetch data for ListUsers
|
|
559
|
-
*
|
|
705
|
+
* Fetch user data for `[[module ListUsers]]` expansion.
|
|
706
|
+
*
|
|
707
|
+
* Called once per ListUsers instance with the parsed query parameters.
|
|
560
708
|
*/
|
|
561
709
|
fetchListUsers?: ListUsersDataFetcher;
|
|
562
710
|
/**
|
|
563
|
-
*
|
|
564
|
-
*
|
|
711
|
+
* Return the current page's tags for `[[iftags]]` evaluation.
|
|
712
|
+
*
|
|
713
|
+
* If provided, `[[iftags]]` blocks are evaluated and either kept or
|
|
714
|
+
* discarded based on whether the page's tags satisfy the condition.
|
|
715
|
+
* If omitted, `[[iftags]]` blocks pass through unresolved.
|
|
565
716
|
*/
|
|
566
717
|
getPageTags?: IfTagsResolver;
|
|
567
718
|
}
|
|
568
719
|
import { SyntaxTree as SyntaxTree2 } from "@wdprlib/ast";
|
|
569
720
|
/**
|
|
570
|
-
*
|
|
721
|
+
* Complete result of extracting data requirements from an AST.
|
|
722
|
+
*
|
|
723
|
+
* Contains everything needed to fetch external data and then resolve modules:
|
|
724
|
+
* the data requirements tell the application what to fetch, and the pre-compiled
|
|
725
|
+
* templates are used during the resolution phase to efficiently render results.
|
|
571
726
|
*/
|
|
572
727
|
interface ExtractionResult {
|
|
573
728
|
/** Data requirements for external fetching */
|
|
@@ -578,11 +733,29 @@ interface ExtractionResult {
|
|
|
578
733
|
compiledListUsersTemplates: Map<number, ListUsersCompiledTemplate>;
|
|
579
734
|
}
|
|
580
735
|
/**
|
|
581
|
-
* Extract data requirements from a parsed AST
|
|
736
|
+
* Extract all data requirements from a parsed AST.
|
|
737
|
+
*
|
|
738
|
+
* Walks the entire AST to find ListPages and ListUsers module elements,
|
|
739
|
+
* analyzes their templates to determine which variables are used, builds
|
|
740
|
+
* query objects from their attributes, and pre-compiles their templates.
|
|
741
|
+
*
|
|
742
|
+
* Each module is assigned a sequential ID (separate counters for ListPages
|
|
743
|
+
* and ListUsers) that is used to correlate requirements with fetched data
|
|
744
|
+
* and compiled templates during the resolution phase.
|
|
745
|
+
*
|
|
746
|
+
* @param ast - The parsed syntax tree to analyze
|
|
747
|
+
* @returns Extraction result containing requirements and compiled templates
|
|
582
748
|
*/
|
|
583
749
|
declare function extractDataRequirements(ast: SyntaxTree2): ExtractionResult;
|
|
584
750
|
/**
|
|
585
|
-
* Compile a template string into an executable function
|
|
751
|
+
* Compile a ListPages template string into an executable function.
|
|
752
|
+
*
|
|
753
|
+
* The template is split into alternating static strings and dynamic getter
|
|
754
|
+
* functions. The returned function concatenates these parts with the getter
|
|
755
|
+
* functions evaluated against the provided variable context.
|
|
756
|
+
*
|
|
757
|
+
* @param template - The template string containing `%%variable%%` placeholders
|
|
758
|
+
* @returns A compiled function that accepts a `VariableContext` and returns the rendered string
|
|
586
759
|
*/
|
|
587
760
|
declare function compileTemplate(template: string): CompiledTemplate;
|
|
588
761
|
/**
|
|
@@ -694,49 +867,85 @@ interface ResolveIncludesOptions {
|
|
|
694
867
|
*/
|
|
695
868
|
declare function resolveIncludes(source: string, fetcher: IncludeFetcher, options?: ResolveIncludesOptions): string;
|
|
696
869
|
/**
|
|
697
|
-
* Compile a template string into an executable function
|
|
870
|
+
* Compile a ListUsers template string into an executable function.
|
|
871
|
+
*
|
|
872
|
+
* The template is split into alternating static strings and dynamic getter
|
|
873
|
+
* functions. The returned function concatenates these parts for each call.
|
|
874
|
+
*
|
|
875
|
+
* @param template - The template string containing `%%variable%%` placeholders
|
|
876
|
+
* @returns A compiled function that accepts a `ListUsersVariableContext` and returns rendered text
|
|
698
877
|
*/
|
|
699
878
|
declare function compileListUsersTemplate(template: string): ListUsersCompiledTemplate;
|
|
700
879
|
/**
|
|
701
|
-
* Extract
|
|
880
|
+
* Extract the set of template variables referenced in a ListUsers template string.
|
|
881
|
+
*
|
|
882
|
+
* Scans for `%%variable%%` patterns and returns only those that match known
|
|
883
|
+
* ListUsers variables. Unknown variables are silently ignored.
|
|
884
|
+
*
|
|
885
|
+
* @param template - The template string from the module body
|
|
886
|
+
* @returns Deduplicated array of referenced variable names
|
|
702
887
|
*/
|
|
703
888
|
declare function extractListUsersVariables(template: string): ListUsersVariable[];
|
|
704
889
|
import { Element as Element5, Module as Module3 } from "@wdprlib/ast";
|
|
705
890
|
/**
|
|
706
|
-
*
|
|
891
|
+
* Narrowed type for the list-users variant of the Module discriminated union.
|
|
707
892
|
*/
|
|
708
893
|
type ListUsersModuleData = Extract<Module3, {
|
|
709
894
|
module: "list-users";
|
|
710
895
|
}>;
|
|
711
896
|
/**
|
|
712
|
-
* Type guard
|
|
897
|
+
* Type guard to check if a Module is a list-users module.
|
|
898
|
+
*
|
|
899
|
+
* @param module - A Module discriminated union value
|
|
900
|
+
* @returns true if the module is a list-users module
|
|
713
901
|
*/
|
|
714
902
|
declare function isListUsersModule(module: Module3): module is ListUsersModuleData;
|
|
715
903
|
/**
|
|
716
|
-
* Resolve a single ListUsers module
|
|
717
|
-
*
|
|
904
|
+
* Resolve a single ListUsers module by substituting fetched user data into
|
|
905
|
+
* the pre-compiled template and re-parsing the result as wikitext.
|
|
906
|
+
*
|
|
907
|
+
* Currently ListUsers only renders the logged-in user (no iteration over
|
|
908
|
+
* multiple users), so the template is executed exactly once.
|
|
909
|
+
*
|
|
910
|
+
* @param _module - The list-users module data from the AST (unused, reserved for future use)
|
|
911
|
+
* @param data - External user data fetched by the application
|
|
912
|
+
* @param compiledTemplate - Pre-compiled template function from the extraction phase
|
|
913
|
+
* @param parse - Parser function for re-parsing the substituted template as wikitext
|
|
914
|
+
* @returns Array of AST elements produced by parsing the rendered template
|
|
718
915
|
*/
|
|
719
916
|
declare function resolveListUsers(_module: ListUsersModuleData, data: ListUsersExternalData, compiledTemplate: ListUsersCompiledTemplate, parse: ParseFunction): Element5[];
|
|
720
917
|
import { SyntaxTree as SyntaxTree3 } from "@wdprlib/ast";
|
|
721
918
|
/**
|
|
722
|
-
*
|
|
919
|
+
* Configuration for {@link resolveModules}.
|
|
920
|
+
*
|
|
921
|
+
* Callers must supply pre-extracted requirements and pre-compiled
|
|
922
|
+
* templates (obtained from `extractDataRequirements()` and
|
|
923
|
+
* `compileTemplate()` / `compileListUsersTemplate()`).
|
|
924
|
+
*
|
|
925
|
+
* @group Module Resolution
|
|
723
926
|
*/
|
|
724
927
|
interface ResolveOptions {
|
|
725
|
-
/** Parser function
|
|
928
|
+
/** Parser function used to re-parse expanded template markup into AST nodes */
|
|
726
929
|
parse: ParseFunction;
|
|
727
|
-
/** Pre-compiled templates
|
|
930
|
+
/** Pre-compiled ListPages body templates, keyed by requirement ID */
|
|
728
931
|
compiledListPagesTemplates: Map<number, CompiledTemplate>;
|
|
729
|
-
/** Pre-compiled templates
|
|
932
|
+
/** Pre-compiled ListUsers body templates, keyed by requirement ID */
|
|
730
933
|
compiledListUsersTemplates?: Map<number, ListUsersCompiledTemplate>;
|
|
731
|
-
/**
|
|
934
|
+
/**
|
|
935
|
+
* Data requirements grouped by module type.
|
|
936
|
+
* Obtained from `extractDataRequirements()`.
|
|
937
|
+
*/
|
|
732
938
|
requirements: {
|
|
733
939
|
listPages?: ListPagesDataRequirement[];
|
|
734
940
|
listUsers?: ListUsersDataRequirement[];
|
|
735
941
|
};
|
|
736
942
|
/**
|
|
737
|
-
* URL path for
|
|
738
|
-
*
|
|
739
|
-
*
|
|
943
|
+
* URL path for `@URL` parameter resolution (HPC / pagination support).
|
|
944
|
+
*
|
|
945
|
+
* Wikidot encodes pagination state in the URL path as key/value pairs
|
|
946
|
+
* after the page name, e.g. `"/scp-001/offset/10/page2_limit/5"`.
|
|
947
|
+
* When provided, `@URL` references in ListPages queries are replaced
|
|
948
|
+
* with the corresponding values from this path.
|
|
740
949
|
*/
|
|
741
950
|
urlPath?: string;
|
|
742
951
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,32 +4,77 @@ import { WikitextMode, WikitextSettings as WikitextSettings4 } from "@wdprlib/as
|
|
|
4
4
|
import { createSettings, DEFAULT_SETTINGS } from "@wdprlib/ast";
|
|
5
5
|
import { Position } from "@wdprlib/ast";
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Every distinct lexeme the Wikidot lexer can produce.
|
|
8
|
+
*
|
|
9
|
+
* Each value corresponds to a fixed character sequence (or class of
|
|
10
|
+
* sequences) in Wikidot markup. The inline comments show the literal
|
|
11
|
+
* text that produces each token type.
|
|
12
|
+
*
|
|
13
|
+
* @group Lexer
|
|
8
14
|
*/
|
|
9
15
|
type TokenType = "EOF" | "TEXT" | "IDENTIFIER" | "NEWLINE" | "WHITESPACE" | "BLOCK_OPEN" | "BLOCK_CLOSE" | "BLOCK_END_OPEN" | "BOLD_MARKER" | "ITALIC_MARKER" | "UNDERLINE_MARKER" | "STRIKE_MARKER" | "SUPER_MARKER" | "SUB_MARKER" | "MONO_MARKER" | "MONO_CLOSE" | "HEADING_MARKER" | "HR_MARKER" | "LIST_BULLET" | "LIST_NUMBER" | "BLOCKQUOTE_MARKER" | "TABLE_MARKER" | "TABLE_HEADER" | "TABLE_LEFT" | "TABLE_CENTER" | "TABLE_RIGHT" | "CODE_OPEN" | "CODE_CLOSE" | "LINK_OPEN" | "LINK_CLOSE" | "BRACKET_OPEN" | "BRACKET_CLOSE" | "BRACKET_ANCHOR" | "BRACKET_STAR" | "PIPE" | "EQUALS" | "COLON" | "SLASH" | "STAR" | "HASH" | "AT" | "AMPERSAND" | "BACKSLASH" | "QUOTED_STRING" | "RAW_OPEN" | "RAW_CLOSE" | "RAW_BLOCK_OPEN" | "RAW_BLOCK_CLOSE" | "COLOR_MARKER" | "UNDERSCORE" | "BACKSLASH_BREAK" | "COMMENT_OPEN" | "COMMENT_CLOSE" | "CLEAR_FLOAT" | "CLEAR_FLOAT_LEFT" | "CLEAR_FLOAT_RIGHT" | "LEFT_DOUBLE_ANGLE" | "RIGHT_DOUBLE_ANGLE";
|
|
10
16
|
/**
|
|
11
|
-
*
|
|
17
|
+
* A single lexical token produced by the `Lexer`.
|
|
18
|
+
*
|
|
19
|
+
* Tokens are the input to the parser stage. Each token carries its
|
|
20
|
+
* literal text (`value`), source location (`position`), and a flag
|
|
21
|
+
* indicating whether it appeared at the beginning of a line — which
|
|
22
|
+
* matters because several Wikidot constructs (headings, lists,
|
|
23
|
+
* blockquotes, horizontal rules) are only valid at line start.
|
|
24
|
+
*
|
|
25
|
+
* @group Lexer
|
|
12
26
|
*/
|
|
13
27
|
interface Token {
|
|
28
|
+
/** The lexeme category */
|
|
14
29
|
type: TokenType;
|
|
30
|
+
/** The literal source text that produced this token */
|
|
15
31
|
value: string;
|
|
32
|
+
/** Start/end location in the original source string */
|
|
16
33
|
position: Position;
|
|
17
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* `true` when this token is the first non-whitespace token on its
|
|
36
|
+
* line. Block-level rules (headings, lists, blockquotes) check this
|
|
37
|
+
* flag before attempting to match.
|
|
38
|
+
*/
|
|
18
39
|
lineStart: boolean;
|
|
19
40
|
}
|
|
20
41
|
/**
|
|
21
|
-
*
|
|
42
|
+
* Construct a {@link Token} value.
|
|
43
|
+
*
|
|
44
|
+
* @param type - The lexeme category
|
|
45
|
+
* @param value - Literal source text
|
|
46
|
+
* @param position - Source location range
|
|
47
|
+
* @param lineStart - Whether the token starts a new line
|
|
48
|
+
* @returns A new token object
|
|
49
|
+
*
|
|
50
|
+
* @group Lexer
|
|
22
51
|
*/
|
|
23
52
|
declare function createToken(type: TokenType, value: string, position: Position, lineStart?: boolean): Token;
|
|
24
53
|
/**
|
|
25
|
-
* Lexer
|
|
54
|
+
* Configuration for the {@link Lexer}.
|
|
55
|
+
*
|
|
56
|
+
* @group Lexer
|
|
26
57
|
*/
|
|
27
58
|
interface LexerOptions {
|
|
28
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* When `true` (default), every token carries accurate line/column/offset
|
|
61
|
+
* data. Set to `false` to skip position tracking for faster tokenisation
|
|
62
|
+
* when source-map information is not needed.
|
|
63
|
+
*/
|
|
29
64
|
trackPositions?: boolean;
|
|
30
65
|
}
|
|
31
66
|
/**
|
|
32
|
-
* Wikidot markup
|
|
67
|
+
* Converts a Wikidot markup source string into a flat array of {@link Token}s.
|
|
68
|
+
*
|
|
69
|
+
* The lexer is single-pass and greedy: it tries the longest-matching
|
|
70
|
+
* multi-character pattern first (e.g. `[[[` before `[[`, `**` before `*`).
|
|
71
|
+
* Context-sensitive constructs (line-start headings, blockquote markers)
|
|
72
|
+
* are disambiguated via the `lineStart` state flag.
|
|
73
|
+
*
|
|
74
|
+
* For convenience, use the standalone {@link tokenize} function instead
|
|
75
|
+
* of constructing a `Lexer` directly.
|
|
76
|
+
*
|
|
77
|
+
* @group Lexer
|
|
33
78
|
*/
|
|
34
79
|
declare class Lexer {
|
|
35
80
|
private state;
|
|
@@ -78,23 +123,59 @@ declare class Lexer {
|
|
|
78
123
|
private isAlphanumeric;
|
|
79
124
|
}
|
|
80
125
|
/**
|
|
81
|
-
*
|
|
126
|
+
* Tokenise a Wikidot markup source string in one call.
|
|
127
|
+
*
|
|
128
|
+
* Shorthand for `new Lexer(source, options).tokenize()`.
|
|
129
|
+
*
|
|
130
|
+
* @param source - Raw Wikidot markup
|
|
131
|
+
* @param options - Optional lexer configuration
|
|
132
|
+
* @returns A flat array of tokens, ending with an `EOF` token
|
|
133
|
+
*
|
|
134
|
+
* @group Lexer
|
|
82
135
|
*/
|
|
83
136
|
declare function tokenize(source: string, options?: LexerOptions): Token[];
|
|
84
137
|
import { SyntaxTree, WikitextSettings } from "@wdprlib/ast";
|
|
85
138
|
/**
|
|
86
|
-
* Parser
|
|
139
|
+
* Configuration for the {@link Parser} and the {@link parse} function.
|
|
140
|
+
*
|
|
141
|
+
* All fields are optional; sensible defaults are applied when omitted.
|
|
142
|
+
*
|
|
143
|
+
* @group Parser
|
|
87
144
|
*/
|
|
88
145
|
interface ParserOptions {
|
|
89
|
-
/**
|
|
146
|
+
/** Markup dialect. Currently only `"wikidot"` is supported. */
|
|
90
147
|
version?: "wikidot";
|
|
91
|
-
/**
|
|
148
|
+
/**
|
|
149
|
+
* Propagate source-position data into every AST node.
|
|
150
|
+
* Defaults to `true`. Set to `false` for smaller output when positions
|
|
151
|
+
* are not needed.
|
|
152
|
+
*/
|
|
92
153
|
trackPositions?: boolean;
|
|
93
|
-
/**
|
|
154
|
+
/**
|
|
155
|
+
* Context-dependent feature flags (page vs. forum-post, etc.).
|
|
156
|
+
* Defaults to {@link DEFAULT_SETTINGS} (full page mode).
|
|
157
|
+
*/
|
|
94
158
|
settings?: WikitextSettings;
|
|
95
159
|
}
|
|
96
160
|
/**
|
|
97
|
-
* Wikidot
|
|
161
|
+
* Converts a token stream into a Wikidot {@link SyntaxTree}.
|
|
162
|
+
*
|
|
163
|
+
* The parser consumes tokens produced by the `Lexer` and emits a
|
|
164
|
+
* tree of {@link Element} nodes. Block-level rules are tried in priority
|
|
165
|
+
* order; when none match, the fallback paragraph rule collects inline
|
|
166
|
+
* tokens until the next blank line.
|
|
167
|
+
*
|
|
168
|
+
* After the main parse pass, two post-processing steps run:
|
|
169
|
+
*
|
|
170
|
+
* 1. **Span-strip merging** — `[[span_]]` elements that set
|
|
171
|
+
* `_paragraphStrip` are merged with adjacent paragraphs.
|
|
172
|
+
* 2. **Internal-flag cleanup** — all `_`-prefixed bookkeeping fields
|
|
173
|
+
* are removed from the final AST.
|
|
174
|
+
*
|
|
175
|
+
* For most use-cases the standalone {@link parse} function is simpler
|
|
176
|
+
* than constructing a `Parser` directly.
|
|
177
|
+
*
|
|
178
|
+
* @group Parser
|
|
98
179
|
*/
|
|
99
180
|
declare class Parser {
|
|
100
181
|
private ctx;
|
|
@@ -131,58 +212,119 @@ declare class Parser {
|
|
|
131
212
|
declare function parse(source: string, options?: ParserOptions): SyntaxTree;
|
|
132
213
|
import { Element as Element2 } from "@wdprlib/ast";
|
|
133
214
|
/**
|
|
134
|
-
* Parser function type for re-parsing substituted
|
|
135
|
-
*
|
|
215
|
+
* Parser function type for re-parsing substituted template output as wikitext.
|
|
216
|
+
*
|
|
217
|
+
* Used by ListPages and ListUsers modules during the resolution phase. After
|
|
218
|
+
* template variables are substituted with actual data, the resulting string
|
|
219
|
+
* needs to be parsed as wikitext to produce AST elements.
|
|
220
|
+
*
|
|
221
|
+
* @param input - Wikitext string to parse
|
|
222
|
+
* @returns Object containing the parsed elements
|
|
136
223
|
*/
|
|
137
224
|
type ParseFunction = (input: string) => {
|
|
138
225
|
elements: Element2[];
|
|
139
226
|
};
|
|
140
227
|
/**
|
|
141
|
-
*
|
|
228
|
+
*
|
|
229
|
+
* Type definitions for the ListUsers module.
|
|
230
|
+
*
|
|
231
|
+
* The `[[module ListUsers users="."]]` block displays information about site
|
|
232
|
+
* members. Currently, only `users="."` (the logged-in user) is supported.
|
|
233
|
+
* The template body can reference three variables: `%%number%%`, `%%title%%`,
|
|
234
|
+
* and `%%name%%`.
|
|
235
|
+
*
|
|
236
|
+
* @module
|
|
142
237
|
*/
|
|
143
238
|
/**
|
|
144
|
-
* Supported template variables
|
|
239
|
+
* Supported template variables for ListUsers.
|
|
240
|
+
*
|
|
241
|
+
* - `number` - The user's numeric ID
|
|
242
|
+
* - `title` - The user's display title/nickname
|
|
243
|
+
* - `name` - The user's account name
|
|
145
244
|
*/
|
|
146
245
|
type ListUsersVariable = "number" | "title" | "name";
|
|
147
246
|
/**
|
|
148
|
-
* User data provided by external source
|
|
247
|
+
* User data that must be provided by the external data source.
|
|
248
|
+
*
|
|
249
|
+
* Each field corresponds to a template variable of the same name.
|
|
149
250
|
*/
|
|
150
251
|
interface ListUsersUserData {
|
|
252
|
+
/** The user's numeric ID, rendered by `%%number%%` */
|
|
151
253
|
number: number;
|
|
254
|
+
/** The user's display title, rendered by `%%title%%` */
|
|
152
255
|
title: string;
|
|
256
|
+
/** The user's account name, rendered by `%%name%%` */
|
|
153
257
|
name: string;
|
|
154
258
|
}
|
|
155
259
|
/**
|
|
156
|
-
* Data requirement for a single ListUsers module
|
|
260
|
+
* Data requirement for a single ListUsers module instance.
|
|
261
|
+
*
|
|
262
|
+
* Produced by the extraction phase and consumed by the application to
|
|
263
|
+
* determine what data to fetch.
|
|
157
264
|
*/
|
|
158
265
|
interface ListUsersDataRequirement {
|
|
266
|
+
/** Unique identifier for this module instance (sequential, 0-based) */
|
|
159
267
|
id: number;
|
|
268
|
+
/** The `users` attribute value (currently only `"."` is supported) */
|
|
160
269
|
users: string;
|
|
270
|
+
/** Template variables that need data from the external source */
|
|
161
271
|
neededVariables: ListUsersVariable[];
|
|
162
272
|
}
|
|
163
273
|
/**
|
|
164
|
-
* External data for a single ListUsers module
|
|
165
|
-
*
|
|
274
|
+
* External data provided by the application for a single ListUsers module.
|
|
275
|
+
*
|
|
276
|
+
* Currently ListUsers only returns information about the logged-in user.
|
|
277
|
+
* Return null/undefined from the fetcher to indicate no user is logged in.
|
|
166
278
|
*/
|
|
167
279
|
interface ListUsersExternalData {
|
|
280
|
+
/** Data for the logged-in user */
|
|
168
281
|
user: ListUsersUserData;
|
|
169
282
|
}
|
|
170
283
|
/**
|
|
171
|
-
* Callback to fetch data for a ListUsers module
|
|
284
|
+
* Callback to fetch user data for a ListUsers module.
|
|
285
|
+
*
|
|
286
|
+
* Called during the resolution phase for each ListUsers module in the AST.
|
|
287
|
+
* Return null/undefined to skip the module (outputs nothing, e.g., when
|
|
288
|
+
* no user is logged in).
|
|
289
|
+
*
|
|
290
|
+
* @param requirement - The data requirement describing what data is needed
|
|
291
|
+
* @returns User data, null/undefined to skip, or a Promise of the same
|
|
172
292
|
*/
|
|
173
293
|
type ListUsersDataFetcher = (requirement: ListUsersDataRequirement) => ListUsersExternalData | null | undefined | Promise<ListUsersExternalData | null | undefined>;
|
|
174
294
|
/**
|
|
175
|
-
* Context passed to compiled template
|
|
295
|
+
* Context passed to a compiled ListUsers template function during rendering.
|
|
176
296
|
*/
|
|
177
297
|
interface ListUsersVariableContext {
|
|
298
|
+
/** The user whose data is being rendered */
|
|
178
299
|
user: ListUsersUserData;
|
|
179
300
|
}
|
|
180
301
|
/**
|
|
181
|
-
*
|
|
302
|
+
* A compiled ListUsers template function.
|
|
303
|
+
*
|
|
304
|
+
* Accepts a variable context and returns the rendered wikitext string
|
|
305
|
+
* with all `%%variable%%` placeholders substituted.
|
|
306
|
+
*
|
|
307
|
+
* @param ctx - The variable context containing user data
|
|
308
|
+
* @returns Rendered wikitext string
|
|
182
309
|
*/
|
|
183
310
|
type ListUsersCompiledTemplate = (ctx: ListUsersVariableContext) => string;
|
|
184
311
|
/**
|
|
185
|
-
*
|
|
312
|
+
*
|
|
313
|
+
* Type definitions for the ListPages module system.
|
|
314
|
+
*
|
|
315
|
+
* This file defines the complete type vocabulary for the ListPages lifecycle:
|
|
316
|
+
*
|
|
317
|
+
* - **Query types**: Raw and normalized representations of ListPages filter/sort parameters
|
|
318
|
+
* - **Variable types**: The `%%variable%%` names supported in ListPages templates
|
|
319
|
+
* - **Data requirement types**: What the parser tells the application it needs to fetch
|
|
320
|
+
* - **External data types**: What the application provides back (page data, user info, site context)
|
|
321
|
+
* - **Template types**: Compiled template function signatures and their execution context
|
|
322
|
+
* - **Normalized query types**: Structured representations of parsed query parameters
|
|
323
|
+
*
|
|
324
|
+
* Security note: Several fields contain untrusted user input from wikitext.
|
|
325
|
+
* See `ListPagesQuery` documentation for safe usage guidelines.
|
|
326
|
+
*
|
|
327
|
+
* @module
|
|
186
328
|
*/
|
|
187
329
|
/**
|
|
188
330
|
* ListPages query parameters for page selection
|
|
@@ -385,7 +527,7 @@ interface ListPagesExternalData {
|
|
|
385
527
|
* Callback to fetch data for a ListPages module
|
|
386
528
|
*
|
|
387
529
|
* Called by resolveModules for each ListPages module in the AST.
|
|
388
|
-
* Receives a normalized query with all
|
|
530
|
+
* Receives a normalized query with all `@URL` parameters resolved.
|
|
389
531
|
* Return null/undefined to skip the module (outputs nothing).
|
|
390
532
|
*
|
|
391
533
|
* @param query - Normalized query with structured types (tags, category, order, etc.)
|
|
@@ -527,47 +669,60 @@ interface NormalizedListPagesQuery {
|
|
|
527
669
|
reverse?: boolean;
|
|
528
670
|
}
|
|
529
671
|
/**
|
|
530
|
-
* Callback to
|
|
672
|
+
* Callback to retrieve the current page's tags during the resolve phase.
|
|
531
673
|
*
|
|
532
|
-
* Called
|
|
674
|
+
* Called when evaluating `[[iftags]]` conditions. The application must provide
|
|
675
|
+
* this callback with access to the current page's tag list.
|
|
533
676
|
*
|
|
534
677
|
* @returns Array of tag names for the current page
|
|
535
678
|
*/
|
|
536
679
|
type IfTagsResolver = () => string[];
|
|
537
680
|
/**
|
|
538
|
-
*
|
|
681
|
+
* Callback bag for supplying external data during module resolution.
|
|
539
682
|
*
|
|
540
|
-
*
|
|
541
|
-
*
|
|
683
|
+
* Pass an instance to `resolveModules()`. Every callback is optional:
|
|
684
|
+
* when a callback is missing the corresponding module node is kept as-is
|
|
685
|
+
* in the output AST — useful when you only need to resolve a subset of
|
|
686
|
+
* modules (e.g. only `[[iftags]]` on the client side).
|
|
542
687
|
*
|
|
543
|
-
*
|
|
688
|
+
* @group Module Resolution
|
|
544
689
|
*/
|
|
545
690
|
interface DataProvider {
|
|
546
691
|
/**
|
|
547
|
-
* Fetch data for ListPages
|
|
548
|
-
* Called during resolve phase with query parameters
|
|
692
|
+
* Fetch page data for `[[module ListPages]]` expansion.
|
|
549
693
|
*
|
|
550
|
-
*
|
|
551
|
-
*
|
|
552
|
-
*
|
|
553
|
-
*
|
|
554
|
-
*
|
|
694
|
+
* Called once per ListPages instance in the AST with the normalised
|
|
695
|
+
* query parameters extracted from the module's wikitext attributes.
|
|
696
|
+
*
|
|
697
|
+
* @security The query fields originate from **untrusted user input**.
|
|
698
|
+
* When building database queries from the returned requirement:
|
|
699
|
+
* - **Never** interpolate `req.query` / `req.rawAttributes` into SQL
|
|
700
|
+
* - **Always** use parameterised queries or prepared statements
|
|
701
|
+
* - For `order` (ORDER BY), validate against a whitelist of column names
|
|
555
702
|
*/
|
|
556
703
|
fetchListPages?: ListPagesDataFetcher;
|
|
557
704
|
/**
|
|
558
|
-
* Fetch data for ListUsers
|
|
559
|
-
*
|
|
705
|
+
* Fetch user data for `[[module ListUsers]]` expansion.
|
|
706
|
+
*
|
|
707
|
+
* Called once per ListUsers instance with the parsed query parameters.
|
|
560
708
|
*/
|
|
561
709
|
fetchListUsers?: ListUsersDataFetcher;
|
|
562
710
|
/**
|
|
563
|
-
*
|
|
564
|
-
*
|
|
711
|
+
* Return the current page's tags for `[[iftags]]` evaluation.
|
|
712
|
+
*
|
|
713
|
+
* If provided, `[[iftags]]` blocks are evaluated and either kept or
|
|
714
|
+
* discarded based on whether the page's tags satisfy the condition.
|
|
715
|
+
* If omitted, `[[iftags]]` blocks pass through unresolved.
|
|
565
716
|
*/
|
|
566
717
|
getPageTags?: IfTagsResolver;
|
|
567
718
|
}
|
|
568
719
|
import { SyntaxTree as SyntaxTree2 } from "@wdprlib/ast";
|
|
569
720
|
/**
|
|
570
|
-
*
|
|
721
|
+
* Complete result of extracting data requirements from an AST.
|
|
722
|
+
*
|
|
723
|
+
* Contains everything needed to fetch external data and then resolve modules:
|
|
724
|
+
* the data requirements tell the application what to fetch, and the pre-compiled
|
|
725
|
+
* templates are used during the resolution phase to efficiently render results.
|
|
571
726
|
*/
|
|
572
727
|
interface ExtractionResult {
|
|
573
728
|
/** Data requirements for external fetching */
|
|
@@ -578,11 +733,29 @@ interface ExtractionResult {
|
|
|
578
733
|
compiledListUsersTemplates: Map<number, ListUsersCompiledTemplate>;
|
|
579
734
|
}
|
|
580
735
|
/**
|
|
581
|
-
* Extract data requirements from a parsed AST
|
|
736
|
+
* Extract all data requirements from a parsed AST.
|
|
737
|
+
*
|
|
738
|
+
* Walks the entire AST to find ListPages and ListUsers module elements,
|
|
739
|
+
* analyzes their templates to determine which variables are used, builds
|
|
740
|
+
* query objects from their attributes, and pre-compiles their templates.
|
|
741
|
+
*
|
|
742
|
+
* Each module is assigned a sequential ID (separate counters for ListPages
|
|
743
|
+
* and ListUsers) that is used to correlate requirements with fetched data
|
|
744
|
+
* and compiled templates during the resolution phase.
|
|
745
|
+
*
|
|
746
|
+
* @param ast - The parsed syntax tree to analyze
|
|
747
|
+
* @returns Extraction result containing requirements and compiled templates
|
|
582
748
|
*/
|
|
583
749
|
declare function extractDataRequirements(ast: SyntaxTree2): ExtractionResult;
|
|
584
750
|
/**
|
|
585
|
-
* Compile a template string into an executable function
|
|
751
|
+
* Compile a ListPages template string into an executable function.
|
|
752
|
+
*
|
|
753
|
+
* The template is split into alternating static strings and dynamic getter
|
|
754
|
+
* functions. The returned function concatenates these parts with the getter
|
|
755
|
+
* functions evaluated against the provided variable context.
|
|
756
|
+
*
|
|
757
|
+
* @param template - The template string containing `%%variable%%` placeholders
|
|
758
|
+
* @returns A compiled function that accepts a `VariableContext` and returns the rendered string
|
|
586
759
|
*/
|
|
587
760
|
declare function compileTemplate(template: string): CompiledTemplate;
|
|
588
761
|
/**
|
|
@@ -694,49 +867,85 @@ interface ResolveIncludesOptions {
|
|
|
694
867
|
*/
|
|
695
868
|
declare function resolveIncludes(source: string, fetcher: IncludeFetcher, options?: ResolveIncludesOptions): string;
|
|
696
869
|
/**
|
|
697
|
-
* Compile a template string into an executable function
|
|
870
|
+
* Compile a ListUsers template string into an executable function.
|
|
871
|
+
*
|
|
872
|
+
* The template is split into alternating static strings and dynamic getter
|
|
873
|
+
* functions. The returned function concatenates these parts for each call.
|
|
874
|
+
*
|
|
875
|
+
* @param template - The template string containing `%%variable%%` placeholders
|
|
876
|
+
* @returns A compiled function that accepts a `ListUsersVariableContext` and returns rendered text
|
|
698
877
|
*/
|
|
699
878
|
declare function compileListUsersTemplate(template: string): ListUsersCompiledTemplate;
|
|
700
879
|
/**
|
|
701
|
-
* Extract
|
|
880
|
+
* Extract the set of template variables referenced in a ListUsers template string.
|
|
881
|
+
*
|
|
882
|
+
* Scans for `%%variable%%` patterns and returns only those that match known
|
|
883
|
+
* ListUsers variables. Unknown variables are silently ignored.
|
|
884
|
+
*
|
|
885
|
+
* @param template - The template string from the module body
|
|
886
|
+
* @returns Deduplicated array of referenced variable names
|
|
702
887
|
*/
|
|
703
888
|
declare function extractListUsersVariables(template: string): ListUsersVariable[];
|
|
704
889
|
import { Element as Element5, Module as Module3 } from "@wdprlib/ast";
|
|
705
890
|
/**
|
|
706
|
-
*
|
|
891
|
+
* Narrowed type for the list-users variant of the Module discriminated union.
|
|
707
892
|
*/
|
|
708
893
|
type ListUsersModuleData = Extract<Module3, {
|
|
709
894
|
module: "list-users";
|
|
710
895
|
}>;
|
|
711
896
|
/**
|
|
712
|
-
* Type guard
|
|
897
|
+
* Type guard to check if a Module is a list-users module.
|
|
898
|
+
*
|
|
899
|
+
* @param module - A Module discriminated union value
|
|
900
|
+
* @returns true if the module is a list-users module
|
|
713
901
|
*/
|
|
714
902
|
declare function isListUsersModule(module: Module3): module is ListUsersModuleData;
|
|
715
903
|
/**
|
|
716
|
-
* Resolve a single ListUsers module
|
|
717
|
-
*
|
|
904
|
+
* Resolve a single ListUsers module by substituting fetched user data into
|
|
905
|
+
* the pre-compiled template and re-parsing the result as wikitext.
|
|
906
|
+
*
|
|
907
|
+
* Currently ListUsers only renders the logged-in user (no iteration over
|
|
908
|
+
* multiple users), so the template is executed exactly once.
|
|
909
|
+
*
|
|
910
|
+
* @param _module - The list-users module data from the AST (unused, reserved for future use)
|
|
911
|
+
* @param data - External user data fetched by the application
|
|
912
|
+
* @param compiledTemplate - Pre-compiled template function from the extraction phase
|
|
913
|
+
* @param parse - Parser function for re-parsing the substituted template as wikitext
|
|
914
|
+
* @returns Array of AST elements produced by parsing the rendered template
|
|
718
915
|
*/
|
|
719
916
|
declare function resolveListUsers(_module: ListUsersModuleData, data: ListUsersExternalData, compiledTemplate: ListUsersCompiledTemplate, parse: ParseFunction): Element5[];
|
|
720
917
|
import { SyntaxTree as SyntaxTree3 } from "@wdprlib/ast";
|
|
721
918
|
/**
|
|
722
|
-
*
|
|
919
|
+
* Configuration for {@link resolveModules}.
|
|
920
|
+
*
|
|
921
|
+
* Callers must supply pre-extracted requirements and pre-compiled
|
|
922
|
+
* templates (obtained from `extractDataRequirements()` and
|
|
923
|
+
* `compileTemplate()` / `compileListUsersTemplate()`).
|
|
924
|
+
*
|
|
925
|
+
* @group Module Resolution
|
|
723
926
|
*/
|
|
724
927
|
interface ResolveOptions {
|
|
725
|
-
/** Parser function
|
|
928
|
+
/** Parser function used to re-parse expanded template markup into AST nodes */
|
|
726
929
|
parse: ParseFunction;
|
|
727
|
-
/** Pre-compiled templates
|
|
930
|
+
/** Pre-compiled ListPages body templates, keyed by requirement ID */
|
|
728
931
|
compiledListPagesTemplates: Map<number, CompiledTemplate>;
|
|
729
|
-
/** Pre-compiled templates
|
|
932
|
+
/** Pre-compiled ListUsers body templates, keyed by requirement ID */
|
|
730
933
|
compiledListUsersTemplates?: Map<number, ListUsersCompiledTemplate>;
|
|
731
|
-
/**
|
|
934
|
+
/**
|
|
935
|
+
* Data requirements grouped by module type.
|
|
936
|
+
* Obtained from `extractDataRequirements()`.
|
|
937
|
+
*/
|
|
732
938
|
requirements: {
|
|
733
939
|
listPages?: ListPagesDataRequirement[];
|
|
734
940
|
listUsers?: ListUsersDataRequirement[];
|
|
735
941
|
};
|
|
736
942
|
/**
|
|
737
|
-
* URL path for
|
|
738
|
-
*
|
|
739
|
-
*
|
|
943
|
+
* URL path for `@URL` parameter resolution (HPC / pagination support).
|
|
944
|
+
*
|
|
945
|
+
* Wikidot encodes pagination state in the URL path as key/value pairs
|
|
946
|
+
* after the page name, e.g. `"/scp-001/offset/10/page2_limit/5"`.
|
|
947
|
+
* When provided, `@URL` references in ListPages queries are replaced
|
|
948
|
+
* with the corresponding values from this path.
|
|
740
949
|
*/
|
|
741
950
|
urlPath?: string;
|
|
742
951
|
}
|
package/dist/index.js
CHANGED
|
@@ -5059,6 +5059,10 @@ function parseBibliographyEntry(ctx, startPos) {
|
|
|
5059
5059
|
consumed++;
|
|
5060
5060
|
break;
|
|
5061
5061
|
}
|
|
5062
|
+
contentNodes.push({ element: "line-break" });
|
|
5063
|
+
pos++;
|
|
5064
|
+
consumed++;
|
|
5065
|
+
continue;
|
|
5062
5066
|
}
|
|
5063
5067
|
const inlineCtx = { ...ctx, pos };
|
|
5064
5068
|
const result = parseInlineUntil(inlineCtx, "NEWLINE");
|
|
@@ -5556,6 +5560,11 @@ var linkTripleRule = {
|
|
|
5556
5560
|
break;
|
|
5557
5561
|
}
|
|
5558
5562
|
if (token.type === "NEWLINE") {
|
|
5563
|
+
if (foundPipe) {
|
|
5564
|
+
labelText += " ";
|
|
5565
|
+
} else {
|
|
5566
|
+
target += " ";
|
|
5567
|
+
}
|
|
5559
5568
|
consumed++;
|
|
5560
5569
|
pos++;
|
|
5561
5570
|
continue;
|
|
@@ -5949,10 +5958,8 @@ var newlineLineBreakRule = {
|
|
|
5949
5958
|
}
|
|
5950
5959
|
const nextMeaningfulToken = ctx.tokens[ctx.pos + lookAhead];
|
|
5951
5960
|
let isValidBlock = isBlockStartToken(nextMeaningfulToken?.type);
|
|
5952
|
-
if (isValidBlock &&
|
|
5953
|
-
|
|
5954
|
-
isValidBlock = false;
|
|
5955
|
-
}
|
|
5961
|
+
if (isValidBlock && !nextMeaningfulToken?.lineStart) {
|
|
5962
|
+
isValidBlock = false;
|
|
5956
5963
|
}
|
|
5957
5964
|
if (isValidBlock && nextMeaningfulToken?.type === "HEADING_MARKER") {
|
|
5958
5965
|
const markerLen = nextMeaningfulToken.value.length;
|
|
@@ -6664,7 +6671,15 @@ var footnoteRule = {
|
|
|
6664
6671
|
if (token.type === "NEWLINE") {
|
|
6665
6672
|
pos++;
|
|
6666
6673
|
consumed++;
|
|
6667
|
-
|
|
6674
|
+
let peekPos = pos;
|
|
6675
|
+
let peekConsumed = 0;
|
|
6676
|
+
while (ctx.tokens[peekPos]?.type === "WHITESPACE") {
|
|
6677
|
+
peekPos++;
|
|
6678
|
+
peekConsumed++;
|
|
6679
|
+
}
|
|
6680
|
+
if (ctx.tokens[peekPos]?.type === "NEWLINE") {
|
|
6681
|
+
pos = peekPos;
|
|
6682
|
+
consumed += peekConsumed;
|
|
6668
6683
|
while (ctx.tokens[pos]?.type === "NEWLINE") {
|
|
6669
6684
|
pos++;
|
|
6670
6685
|
consumed++;
|
|
@@ -7161,7 +7176,7 @@ var anchorRule = {
|
|
|
7161
7176
|
consumed++;
|
|
7162
7177
|
}
|
|
7163
7178
|
foundClose = true;
|
|
7164
|
-
|
|
7179
|
+
if (paragraphStrip && ctx.tokens[pos]?.type === "NEWLINE" && ctx.tokens[pos + 1]?.type !== "NEWLINE") {
|
|
7165
7180
|
pos++;
|
|
7166
7181
|
consumed++;
|
|
7167
7182
|
}
|
|
@@ -7699,6 +7714,7 @@ var bibciteRule = {
|
|
|
7699
7714
|
return { success: false };
|
|
7700
7715
|
}
|
|
7701
7716
|
let label = "";
|
|
7717
|
+
let foundClose = false;
|
|
7702
7718
|
while (pos < ctx.tokens.length) {
|
|
7703
7719
|
const t = ctx.tokens[pos];
|
|
7704
7720
|
if (!t)
|
|
@@ -7707,6 +7723,7 @@ var bibciteRule = {
|
|
|
7707
7723
|
const nextT = ctx.tokens[pos + 1];
|
|
7708
7724
|
if (nextT?.type === "TEXT" && nextT.value === ")") {
|
|
7709
7725
|
consumed += 2;
|
|
7726
|
+
foundClose = true;
|
|
7710
7727
|
break;
|
|
7711
7728
|
}
|
|
7712
7729
|
}
|
|
@@ -7717,6 +7734,9 @@ var bibciteRule = {
|
|
|
7717
7734
|
pos++;
|
|
7718
7735
|
consumed++;
|
|
7719
7736
|
}
|
|
7737
|
+
if (!foundClose) {
|
|
7738
|
+
return { success: false };
|
|
7739
|
+
}
|
|
7720
7740
|
label = label.trim();
|
|
7721
7741
|
if (!label) {
|
|
7722
7742
|
return { success: false };
|
|
@@ -9573,16 +9593,31 @@ function resolveIncludes(source, fetcher, options) {
|
|
|
9573
9593
|
};
|
|
9574
9594
|
return expandText(source, cachedFetcher, 0, maxDepth, []);
|
|
9575
9595
|
}
|
|
9576
|
-
var INCLUDE_PATTERN = /\[\[include\s
|
|
9596
|
+
var INCLUDE_PATTERN = /\[\[include\s([^\]]*(?:\](?!\])[^\]]*)*)\]\]/gi;
|
|
9577
9597
|
function parseIncludeDirective(inner) {
|
|
9578
9598
|
const normalized = inner.replace(/\n/g, " ");
|
|
9579
9599
|
const parts = normalized.split("|");
|
|
9580
|
-
const
|
|
9581
|
-
const
|
|
9600
|
+
const firstSegment = parts[0].trim();
|
|
9601
|
+
const spaceIndex = firstSegment.indexOf(" ");
|
|
9602
|
+
let target;
|
|
9603
|
+
const varSegments = [];
|
|
9604
|
+
if (spaceIndex !== -1) {
|
|
9605
|
+
target = firstSegment.slice(0, spaceIndex);
|
|
9606
|
+
const rest = firstSegment.slice(spaceIndex + 1).trim();
|
|
9607
|
+
if (rest) {
|
|
9608
|
+
varSegments.push(rest);
|
|
9609
|
+
}
|
|
9610
|
+
} else {
|
|
9611
|
+
target = firstSegment;
|
|
9612
|
+
}
|
|
9582
9613
|
for (let i = 1;i < parts.length; i++) {
|
|
9583
9614
|
const segment = parts[i].trim();
|
|
9584
|
-
if (
|
|
9585
|
-
|
|
9615
|
+
if (segment) {
|
|
9616
|
+
varSegments.push(segment);
|
|
9617
|
+
}
|
|
9618
|
+
}
|
|
9619
|
+
const variables = {};
|
|
9620
|
+
for (const segment of varSegments) {
|
|
9586
9621
|
const eqIndex = segment.indexOf("=");
|
|
9587
9622
|
if (eqIndex !== -1) {
|
|
9588
9623
|
const key = segment.slice(0, eqIndex).trim();
|