@wdprlib/parser 3.1.2 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +295 -118
- package/dist/index.js +272 -95
- package/package.json +5 -3
- package/src/index.ts +163 -0
- package/src/lexer/index.ts +20 -0
- package/src/lexer/lexer.ts +687 -0
- package/src/lexer/tokens.ts +141 -0
- package/src/parser/constants.ts +173 -0
- package/src/parser/depth.ts +251 -0
- package/src/parser/index.ts +18 -0
- package/src/parser/parse.ts +315 -0
- package/src/parser/postprocess/divAdjacentParagraph.ts +76 -0
- package/src/parser/postprocess/index.ts +15 -0
- package/src/parser/postprocess/spanStrip.ts +697 -0
- package/src/parser/preprocess/expr.ts +265 -0
- package/src/parser/preprocess/index.ts +38 -0
- package/src/parser/preprocess/typography.ts +67 -0
- package/src/parser/preprocess/utils.ts +250 -0
- package/src/parser/preprocess/whitespace.ts +111 -0
- package/src/parser/rules/block/align.ts +282 -0
- package/src/parser/rules/block/bibliography.ts +359 -0
- package/src/parser/rules/block/block-list.ts +689 -0
- package/src/parser/rules/block/blockquote.ts +238 -0
- package/src/parser/rules/block/center.ts +87 -0
- package/src/parser/rules/block/clear-float.ts +75 -0
- package/src/parser/rules/block/code.ts +187 -0
- package/src/parser/rules/block/collapsible.ts +337 -0
- package/src/parser/rules/block/comment.ts +73 -0
- package/src/parser/rules/block/content-separator.ts +79 -0
- package/src/parser/rules/block/definition-list.ts +270 -0
- package/src/parser/rules/block/div.ts +400 -0
- package/src/parser/rules/block/embed-block.ts +153 -0
- package/src/parser/rules/block/footnoteblock.ts +200 -0
- package/src/parser/rules/block/heading.ts +142 -0
- package/src/parser/rules/block/horizontal-rule.ts +61 -0
- package/src/parser/rules/block/html.ts +222 -0
- package/src/parser/rules/block/iframe.ts +239 -0
- package/src/parser/rules/block/iftags.ts +150 -0
- package/src/parser/rules/block/include.ts +179 -0
- package/src/parser/rules/block/index.ts +127 -0
- package/src/parser/rules/block/list.ts +244 -0
- package/src/parser/rules/block/math.ts +183 -0
- package/src/parser/rules/block/module/backlinks/index.ts +31 -0
- package/src/parser/rules/block/module/backlinks/types.ts +21 -0
- package/src/parser/rules/block/module/categories/index.ts +34 -0
- package/src/parser/rules/block/module/categories/types.ts +21 -0
- package/src/parser/rules/block/module/css/index.ts +37 -0
- package/src/parser/rules/block/module/iftags/condition.ts +109 -0
- package/src/parser/rules/block/module/iftags/index.ts +26 -0
- package/src/parser/rules/block/module/iftags/preprocess.ts +140 -0
- package/src/parser/rules/block/module/iftags/resolve.ts +73 -0
- package/src/parser/rules/block/module/iftags/types.ts +63 -0
- package/src/parser/rules/block/module/include/index.ts +20 -0
- package/src/parser/rules/block/module/include/resolve.ts +556 -0
- package/src/parser/rules/block/module/index.ts +122 -0
- package/src/parser/rules/block/module/join/index.ts +34 -0
- package/src/parser/rules/block/module/join/types.ts +23 -0
- package/src/parser/rules/block/module/listpages/compiler.ts +453 -0
- package/src/parser/rules/block/module/listpages/extract.ts +410 -0
- package/src/parser/rules/block/module/listpages/index.ts +83 -0
- package/src/parser/rules/block/module/listpages/normalize.ts +390 -0
- package/src/parser/rules/block/module/listpages/parser.ts +106 -0
- package/src/parser/rules/block/module/listpages/resolve.ts +130 -0
- package/src/parser/rules/block/module/listpages/types.ts +513 -0
- package/src/parser/rules/block/module/listpages/url-resolver.ts +186 -0
- package/src/parser/rules/block/module/listusers/compiler.ts +77 -0
- package/src/parser/rules/block/module/listusers/extract.ts +45 -0
- package/src/parser/rules/block/module/listusers/index.ts +36 -0
- package/src/parser/rules/block/module/listusers/parser.ts +54 -0
- package/src/parser/rules/block/module/listusers/resolve.ts +58 -0
- package/src/parser/rules/block/module/listusers/types.ts +93 -0
- package/src/parser/rules/block/module/mapping.ts +61 -0
- package/src/parser/rules/block/module/page-tree/index.ts +38 -0
- package/src/parser/rules/block/module/page-tree/types.ts +29 -0
- package/src/parser/rules/block/module/rate/index.ts +28 -0
- package/src/parser/rules/block/module/rate/types.ts +19 -0
- package/src/parser/rules/block/module/resolve.ts +411 -0
- package/src/parser/rules/block/module/types-common.ts +59 -0
- package/src/parser/rules/block/module/types.ts +61 -0
- package/src/parser/rules/block/module/utils.ts +43 -0
- package/src/parser/rules/block/module/walk.ts +380 -0
- package/src/parser/rules/block/module.ts +164 -0
- package/src/parser/rules/block/orphan-li.ts +177 -0
- package/src/parser/rules/block/paragraph.ts +157 -0
- package/src/parser/rules/block/table-block.ts +726 -0
- package/src/parser/rules/block/table.ts +441 -0
- package/src/parser/rules/block/tabview.ts +331 -0
- package/src/parser/rules/block/toc.ts +129 -0
- package/src/parser/rules/block/utils.ts +615 -0
- package/src/parser/rules/index.ts +49 -0
- package/src/parser/rules/inline/anchor-name.ts +154 -0
- package/src/parser/rules/inline/anchor.ts +327 -0
- package/src/parser/rules/inline/bibcite.ts +153 -0
- package/src/parser/rules/inline/bold.ts +86 -0
- package/src/parser/rules/inline/color.ts +140 -0
- package/src/parser/rules/inline/comment.ts +90 -0
- package/src/parser/rules/inline/equation-ref.ts +115 -0
- package/src/parser/rules/inline/expr.ts +526 -0
- package/src/parser/rules/inline/footnote.ts +223 -0
- package/src/parser/rules/inline/guillemet.ts +64 -0
- package/src/parser/rules/inline/html.ts +132 -0
- package/src/parser/rules/inline/image.ts +328 -0
- package/src/parser/rules/inline/index.ts +150 -0
- package/src/parser/rules/inline/italic.ts +74 -0
- package/src/parser/rules/inline/line-break.ts +326 -0
- package/src/parser/rules/inline/link-anchor.ts +147 -0
- package/src/parser/rules/inline/link-single.ts +164 -0
- package/src/parser/rules/inline/link-star.ts +134 -0
- package/src/parser/rules/inline/link-triple.ts +267 -0
- package/src/parser/rules/inline/math-inline.ts +126 -0
- package/src/parser/rules/inline/monospace.ts +78 -0
- package/src/parser/rules/inline/raw.ts +262 -0
- package/src/parser/rules/inline/size.ts +244 -0
- package/src/parser/rules/inline/span.ts +424 -0
- package/src/parser/rules/inline/strikethrough.ts +115 -0
- package/src/parser/rules/inline/subscript.ts +84 -0
- package/src/parser/rules/inline/superscript.ts +84 -0
- package/src/parser/rules/inline/text.ts +84 -0
- package/src/parser/rules/inline/underline.ts +127 -0
- package/src/parser/rules/inline/user.ts +147 -0
- package/src/parser/rules/inline/utils.ts +344 -0
- package/src/parser/rules/types.ts +252 -0
- package/src/parser/rules/utils.ts +155 -0
- package/src/parser/toc.ts +130 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Type definitions for the ListPages module system.
|
|
4
|
+
*
|
|
5
|
+
* This file defines the complete type vocabulary for the ListPages lifecycle:
|
|
6
|
+
*
|
|
7
|
+
* - **Query types**: Raw and normalized representations of ListPages filter/sort parameters
|
|
8
|
+
* - **Variable types**: The `%%variable%%` names supported in ListPages templates
|
|
9
|
+
* - **Data requirement types**: What the parser tells the application it needs to fetch
|
|
10
|
+
* - **External data types**: What the application provides back (page data, user info, site context)
|
|
11
|
+
* - **Template types**: Compiled template function signatures and their execution context
|
|
12
|
+
* - **Normalized query types**: Structured representations of parsed query parameters
|
|
13
|
+
*
|
|
14
|
+
* Security note: Several fields contain untrusted user input from wikitext.
|
|
15
|
+
* See `ListPagesQuery` documentation for safe usage guidelines.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// =============================================================================
|
|
21
|
+
// Query Types
|
|
22
|
+
// =============================================================================
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* ListPages query parameters for page selection
|
|
26
|
+
*
|
|
27
|
+
* @security All string fields contain **untrusted user input** from wikitext.
|
|
28
|
+
* When using these values in database queries:
|
|
29
|
+
* - **NEVER** interpolate directly into SQL/NoSQL query strings
|
|
30
|
+
* - **ALWAYS** use parameterized queries or prepared statements
|
|
31
|
+
* - For ORDER BY clauses, use a whitelist of allowed column names
|
|
32
|
+
*
|
|
33
|
+
* @example Safe usage with SQL
|
|
34
|
+
* ```typescript
|
|
35
|
+
* // GOOD: Parameterized query
|
|
36
|
+
* db.query("SELECT * FROM pages WHERE category = ?", [query.category]);
|
|
37
|
+
*
|
|
38
|
+
* // BAD: String interpolation (SQL injection vulnerable!)
|
|
39
|
+
* db.query(`SELECT * FROM pages WHERE category = '${query.category}'`);
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export interface ListPagesQuery {
|
|
43
|
+
/** Page type selector */
|
|
44
|
+
pagetype?: "normal" | "hidden" | "*";
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Category selector
|
|
48
|
+
* @untrusted User input - use parameterized queries
|
|
49
|
+
*/
|
|
50
|
+
category?: string;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Tag selector (e.g., "+fruit -admin")
|
|
54
|
+
* @untrusted User input - use parameterized queries
|
|
55
|
+
*/
|
|
56
|
+
tags?: string;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Parent page selector
|
|
60
|
+
* @untrusted User input - use parameterized queries
|
|
61
|
+
*/
|
|
62
|
+
parent?: string;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Link target selector
|
|
66
|
+
* @untrusted User input - use parameterized queries
|
|
67
|
+
*/
|
|
68
|
+
linkTo?: string;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Created date selector
|
|
72
|
+
* @untrusted User input - use parameterized queries
|
|
73
|
+
*/
|
|
74
|
+
createdAt?: string;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Updated date selector
|
|
78
|
+
* @untrusted User input - use parameterized queries
|
|
79
|
+
*/
|
|
80
|
+
updatedAt?: string;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Author selector
|
|
84
|
+
* @untrusted User input - use parameterized queries
|
|
85
|
+
*/
|
|
86
|
+
createdBy?: string;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Rating selector
|
|
90
|
+
* @untrusted User input - use parameterized queries
|
|
91
|
+
*/
|
|
92
|
+
rating?: string;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Votes selector
|
|
96
|
+
* @untrusted User input - use parameterized queries
|
|
97
|
+
*/
|
|
98
|
+
votes?: string;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Page name selector
|
|
102
|
+
* @untrusted User input - use parameterized queries
|
|
103
|
+
*/
|
|
104
|
+
name?: string;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Full page name selector (category:name)
|
|
108
|
+
* @untrusted User input - use parameterized queries
|
|
109
|
+
*/
|
|
110
|
+
fullname?: string;
|
|
111
|
+
|
|
112
|
+
/** Range selector relative to current page */
|
|
113
|
+
range?: "." | "before" | "after" | "others";
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Data form field selectors
|
|
117
|
+
* @untrusted Both keys and values are user input
|
|
118
|
+
*/
|
|
119
|
+
dataFormFields?: Record<string, string>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Ordering specification
|
|
123
|
+
* @untrusted User input - use whitelist validation for ORDER BY
|
|
124
|
+
*/
|
|
125
|
+
order?: string;
|
|
126
|
+
|
|
127
|
+
/** Pagination offset */
|
|
128
|
+
offset?: number;
|
|
129
|
+
|
|
130
|
+
/** Maximum number of results */
|
|
131
|
+
limit?: number;
|
|
132
|
+
|
|
133
|
+
/** Results per page */
|
|
134
|
+
perPage?: number;
|
|
135
|
+
|
|
136
|
+
/** Reverse order */
|
|
137
|
+
reverse?: boolean;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// =============================================================================
|
|
141
|
+
// Variable Types
|
|
142
|
+
// =============================================================================
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* All supported ListPages template variables
|
|
146
|
+
*/
|
|
147
|
+
export type ListPagesVariable =
|
|
148
|
+
// Lifecycle - created
|
|
149
|
+
| "created_at"
|
|
150
|
+
| "created_by"
|
|
151
|
+
| "created_by_unix"
|
|
152
|
+
| "created_by_id"
|
|
153
|
+
| "created_by_linked"
|
|
154
|
+
// Lifecycle - updated
|
|
155
|
+
| "updated_at"
|
|
156
|
+
| "updated_by"
|
|
157
|
+
| "updated_by_unix"
|
|
158
|
+
| "updated_by_id"
|
|
159
|
+
| "updated_by_linked"
|
|
160
|
+
// Lifecycle - commented
|
|
161
|
+
| "commented_at"
|
|
162
|
+
| "commented_by"
|
|
163
|
+
| "commented_by_unix"
|
|
164
|
+
| "commented_by_id"
|
|
165
|
+
| "commented_by_linked"
|
|
166
|
+
// Structure - page
|
|
167
|
+
| "name"
|
|
168
|
+
| "category"
|
|
169
|
+
| "fullname"
|
|
170
|
+
| "title"
|
|
171
|
+
| "title_linked"
|
|
172
|
+
| "link"
|
|
173
|
+
// Structure - parent
|
|
174
|
+
| "parent_name"
|
|
175
|
+
| "parent_category"
|
|
176
|
+
| "parent_fullname"
|
|
177
|
+
| "parent_title"
|
|
178
|
+
| "parent_title_linked"
|
|
179
|
+
// Content
|
|
180
|
+
| "content"
|
|
181
|
+
| "content_n" // content{n}
|
|
182
|
+
| "preview"
|
|
183
|
+
| "preview_n" // preview(n)
|
|
184
|
+
| "summary"
|
|
185
|
+
| "first_paragraph"
|
|
186
|
+
// Tags
|
|
187
|
+
| "tags"
|
|
188
|
+
| "tags_linked"
|
|
189
|
+
| "_tags"
|
|
190
|
+
| "_tags_linked"
|
|
191
|
+
// Form data
|
|
192
|
+
| "form_data"
|
|
193
|
+
| "form_raw"
|
|
194
|
+
| "form_label"
|
|
195
|
+
| "form_hint"
|
|
196
|
+
// Metrics
|
|
197
|
+
| "children"
|
|
198
|
+
| "comments"
|
|
199
|
+
| "size"
|
|
200
|
+
| "rating"
|
|
201
|
+
| "rating_votes"
|
|
202
|
+
| "rating_percent"
|
|
203
|
+
| "revisions"
|
|
204
|
+
// Pagination
|
|
205
|
+
| "index"
|
|
206
|
+
| "total"
|
|
207
|
+
| "limit"
|
|
208
|
+
| "total_or_limit"
|
|
209
|
+
// Site context
|
|
210
|
+
| "site_title"
|
|
211
|
+
| "site_name"
|
|
212
|
+
| "site_domain";
|
|
213
|
+
|
|
214
|
+
// =============================================================================
|
|
215
|
+
// Data Requirement Types
|
|
216
|
+
// =============================================================================
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Data requirement for a single ListPages module
|
|
220
|
+
*/
|
|
221
|
+
export interface ListPagesDataRequirement {
|
|
222
|
+
/** Unique identifier for this module instance */
|
|
223
|
+
id: number;
|
|
224
|
+
|
|
225
|
+
/** Query parameters */
|
|
226
|
+
query: ListPagesQuery;
|
|
227
|
+
|
|
228
|
+
/** Variables used in the template */
|
|
229
|
+
neededVariables: ListPagesVariable[];
|
|
230
|
+
|
|
231
|
+
/** Indices needed for content{n} */
|
|
232
|
+
contentSectionIndices?: number[];
|
|
233
|
+
|
|
234
|
+
/** Lengths needed for preview(n) */
|
|
235
|
+
previewLengths?: number[];
|
|
236
|
+
|
|
237
|
+
/** Field names needed for form_data{field} etc */
|
|
238
|
+
formFields?: string[];
|
|
239
|
+
|
|
240
|
+
/** Prefix for tags_linked|prefix */
|
|
241
|
+
tagsLinkPrefix?: string;
|
|
242
|
+
|
|
243
|
+
/** Prefix for _tags_linked|prefix */
|
|
244
|
+
hiddenTagsLinkPrefix?: string;
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* URL attribute prefix for multiple ListPages modules
|
|
248
|
+
* When set, URL parameters are prefixed (e.g., "page2" -> "/page2_limit/1")
|
|
249
|
+
*/
|
|
250
|
+
urlAttrPrefix?: string;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Raw attribute values before URL resolution
|
|
254
|
+
* Contains original string values that may include "@URL" or "@URL|default" format
|
|
255
|
+
* External applications should use these to resolve URL parameters
|
|
256
|
+
*/
|
|
257
|
+
rawAttributes: Record<string, string>;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* All data requirements from parsing
|
|
262
|
+
*/
|
|
263
|
+
export interface DataRequirements {
|
|
264
|
+
listPages: ListPagesDataRequirement[];
|
|
265
|
+
listUsers: import("../listusers/types").ListUsersDataRequirement[];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// =============================================================================
|
|
269
|
+
// External Data Types
|
|
270
|
+
// =============================================================================
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* User information
|
|
274
|
+
*/
|
|
275
|
+
export interface UserInfo {
|
|
276
|
+
id: number;
|
|
277
|
+
name: string;
|
|
278
|
+
unixName: string;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Page data provided by external source
|
|
283
|
+
*/
|
|
284
|
+
export interface PageData {
|
|
285
|
+
// Identity
|
|
286
|
+
name: string;
|
|
287
|
+
category: string;
|
|
288
|
+
fullname: string;
|
|
289
|
+
title: string;
|
|
290
|
+
|
|
291
|
+
// Lifecycle - created
|
|
292
|
+
createdAt: Date;
|
|
293
|
+
createdBy?: UserInfo;
|
|
294
|
+
|
|
295
|
+
// Lifecycle - updated
|
|
296
|
+
updatedAt: Date;
|
|
297
|
+
updatedBy?: UserInfo;
|
|
298
|
+
|
|
299
|
+
// Lifecycle - commented
|
|
300
|
+
commentedAt?: Date;
|
|
301
|
+
commentedBy?: UserInfo;
|
|
302
|
+
|
|
303
|
+
// Parent
|
|
304
|
+
parentName?: string;
|
|
305
|
+
parentCategory?: string;
|
|
306
|
+
parentFullname?: string;
|
|
307
|
+
parentTitle?: string;
|
|
308
|
+
|
|
309
|
+
// Content (====で区切られた形式。wdparserが%%content{n}%%解決時に分割する)
|
|
310
|
+
content?: string;
|
|
311
|
+
|
|
312
|
+
// Tags
|
|
313
|
+
tags: string[];
|
|
314
|
+
hiddenTags: string[]; // Starting with _
|
|
315
|
+
|
|
316
|
+
// Form data
|
|
317
|
+
formData?: Record<string, string>;
|
|
318
|
+
formRaw?: Record<string, string>;
|
|
319
|
+
formLabel?: Record<string, string>;
|
|
320
|
+
formHint?: Record<string, string>;
|
|
321
|
+
|
|
322
|
+
// Metrics
|
|
323
|
+
children: number;
|
|
324
|
+
comments: number;
|
|
325
|
+
size: number;
|
|
326
|
+
rating: number;
|
|
327
|
+
ratingVotes: number;
|
|
328
|
+
ratingPercent?: number;
|
|
329
|
+
revisions: number;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Site context information
|
|
334
|
+
*/
|
|
335
|
+
export interface SiteContext {
|
|
336
|
+
title: string;
|
|
337
|
+
name: string;
|
|
338
|
+
domain: string;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* External data for a single ListPages module
|
|
343
|
+
*/
|
|
344
|
+
export interface ListPagesExternalData {
|
|
345
|
+
pages: PageData[];
|
|
346
|
+
totalCount: number;
|
|
347
|
+
site: SiteContext;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Callback to fetch data for a ListPages module
|
|
352
|
+
*
|
|
353
|
+
* Called by resolveModules for each ListPages module in the AST.
|
|
354
|
+
* Receives a normalized query with all `@URL` parameters resolved.
|
|
355
|
+
* Return null/undefined to skip the module (outputs nothing).
|
|
356
|
+
*
|
|
357
|
+
* @param query - Normalized query with structured types (tags, category, order, etc.)
|
|
358
|
+
* @param requirement - Original data requirement (for accessing id, neededVariables, etc.)
|
|
359
|
+
*/
|
|
360
|
+
export type ListPagesDataFetcher = (
|
|
361
|
+
query: NormalizedListPagesQuery,
|
|
362
|
+
requirement: ListPagesDataRequirement,
|
|
363
|
+
) => ListPagesExternalData | null | undefined | Promise<ListPagesExternalData | null | undefined>;
|
|
364
|
+
|
|
365
|
+
// Note: DataProvider is in ../types-common.ts to avoid circular dependency
|
|
366
|
+
|
|
367
|
+
// =============================================================================
|
|
368
|
+
// Compiled Template Types
|
|
369
|
+
// =============================================================================
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Context passed to compiled template
|
|
373
|
+
*/
|
|
374
|
+
export interface VariableContext {
|
|
375
|
+
page: PageData;
|
|
376
|
+
index: number;
|
|
377
|
+
total: number;
|
|
378
|
+
limit?: number;
|
|
379
|
+
site: SiteContext;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Compiled template function
|
|
384
|
+
*/
|
|
385
|
+
export type CompiledTemplate = (ctx: VariableContext) => string;
|
|
386
|
+
|
|
387
|
+
// =============================================================================
|
|
388
|
+
// Normalized Query Types
|
|
389
|
+
// =============================================================================
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Normalized tags selector
|
|
393
|
+
*/
|
|
394
|
+
export interface NormalizedTags {
|
|
395
|
+
/** AND conditions - pages must have ALL of these tags (+tag) */
|
|
396
|
+
all: string[];
|
|
397
|
+
/** OR conditions - pages must have ANY of these tags (no prefix) */
|
|
398
|
+
any: string[];
|
|
399
|
+
/** NOT conditions - pages must NOT have these tags (-tag) */
|
|
400
|
+
none: string[];
|
|
401
|
+
/** Special selector */
|
|
402
|
+
special: "same-visible" | "same-all" | "none" | null;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Normalized category selector
|
|
407
|
+
*/
|
|
408
|
+
export interface NormalizedCategory {
|
|
409
|
+
/** Categories to include */
|
|
410
|
+
include: string[];
|
|
411
|
+
/** Categories to exclude (-category) */
|
|
412
|
+
exclude: string[];
|
|
413
|
+
/** Select all categories (*) */
|
|
414
|
+
all: boolean;
|
|
415
|
+
/** Select current category (.) */
|
|
416
|
+
current: boolean;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Order field options
|
|
421
|
+
*/
|
|
422
|
+
export type OrderField =
|
|
423
|
+
| "created_at"
|
|
424
|
+
| "updated_at"
|
|
425
|
+
| "title"
|
|
426
|
+
| "fullname"
|
|
427
|
+
| "rating"
|
|
428
|
+
| "votes"
|
|
429
|
+
| "revisions"
|
|
430
|
+
| "comments"
|
|
431
|
+
| "size"
|
|
432
|
+
| "random";
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Order direction
|
|
436
|
+
*/
|
|
437
|
+
export type OrderDirection = "asc" | "desc";
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Normalized order specification
|
|
441
|
+
*/
|
|
442
|
+
export interface NormalizedOrder {
|
|
443
|
+
field: OrderField;
|
|
444
|
+
direction: OrderDirection;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Normalized parent selector
|
|
449
|
+
*/
|
|
450
|
+
export type NormalizedParent =
|
|
451
|
+
| { type: "none" } // "-": orphan pages
|
|
452
|
+
| { type: "same" } // "=": sibling pages
|
|
453
|
+
| { type: "different" } // "-=": different parent
|
|
454
|
+
| { type: "children" } // ".": children of current page
|
|
455
|
+
| { type: "page"; name: string }; // specific page name
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Date comparison operators
|
|
459
|
+
*/
|
|
460
|
+
export type DateComparisonOp = "=" | "<" | ">" | "<=" | ">=" | "<>";
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Normalized date selector
|
|
464
|
+
*/
|
|
465
|
+
export type NormalizedDateSelector =
|
|
466
|
+
| { type: "year"; year: number }
|
|
467
|
+
| { type: "month"; year: number; month: number }
|
|
468
|
+
| { type: "comparison"; op: DateComparisonOp; date: string }
|
|
469
|
+
| { type: "relative"; unit: "day" | "week" | "month"; count: number };
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Numeric comparison operators
|
|
473
|
+
*/
|
|
474
|
+
export type NumericComparisonOp = "=" | "<" | ">" | "<=" | ">=";
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Normalized numeric selector (for rating/votes)
|
|
478
|
+
*/
|
|
479
|
+
export interface NormalizedNumericSelector {
|
|
480
|
+
op: NumericComparisonOp;
|
|
481
|
+
value: number;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Fully normalized ListPages query
|
|
486
|
+
*
|
|
487
|
+
* All string fields are parsed and structured into type-safe objects.
|
|
488
|
+
* Use `normalizeQuery()` to convert from `ListPagesQuery`.
|
|
489
|
+
*
|
|
490
|
+
* Note: This is structural normalization, not full validation.
|
|
491
|
+
* Invalid inputs are either rejected (return undefined) or ignored.
|
|
492
|
+
*/
|
|
493
|
+
export interface NormalizedListPagesQuery {
|
|
494
|
+
pagetype?: "normal" | "hidden" | "*";
|
|
495
|
+
category?: NormalizedCategory;
|
|
496
|
+
tags?: NormalizedTags;
|
|
497
|
+
parent?: NormalizedParent;
|
|
498
|
+
linkTo?: string;
|
|
499
|
+
createdAt?: NormalizedDateSelector;
|
|
500
|
+
updatedAt?: NormalizedDateSelector;
|
|
501
|
+
createdBy?: string;
|
|
502
|
+
rating?: NormalizedNumericSelector;
|
|
503
|
+
votes?: NormalizedNumericSelector;
|
|
504
|
+
name?: string;
|
|
505
|
+
fullname?: string;
|
|
506
|
+
range?: "." | "before" | "after" | "others";
|
|
507
|
+
dataFormFields?: Record<string, string>;
|
|
508
|
+
order?: NormalizedOrder;
|
|
509
|
+
offset?: number;
|
|
510
|
+
limit?: number;
|
|
511
|
+
perPage?: number;
|
|
512
|
+
reverse?: boolean;
|
|
513
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* URL parameter resolver for the `@URL|default` format in ListPages modules.
|
|
4
|
+
*
|
|
5
|
+
* Wikidot's ListPages module supports a dynamic parameter syntax where attribute
|
|
6
|
+
* values can be set to `@URL` or `@URL|default`. At render time, the actual value
|
|
7
|
+
* is read from the page's URL path parameters. This enables "HPC" (Hyper Page
|
|
8
|
+
* Changer) style multi-page content where a single page definition can display
|
|
9
|
+
* different data based on URL parameters.
|
|
10
|
+
*
|
|
11
|
+
* URL parameters follow the pattern `/key/value/key/value/...` in the URL path.
|
|
12
|
+
* When a `url-attr-prefix` is set (e.g., `"page2"`), parameter names are prefixed
|
|
13
|
+
* (e.g., `page2_offset`, `page2_limit`), allowing multiple independent ListPages
|
|
14
|
+
* modules on the same page.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* Wikidot markup:
|
|
18
|
+
* ```
|
|
19
|
+
* [[module ListPages offset="@URL|0" limit="@URL|10" url-attr-prefix="p2"]]
|
|
20
|
+
* ```
|
|
21
|
+
* URL: `/my-page/p2_offset/20/p2_limit/5`
|
|
22
|
+
* Result: offset=20, limit=5
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { ListPagesDataRequirement, ListPagesQuery, NormalizedListPagesQuery } from "./types";
|
|
28
|
+
import { normalizeQuery } from "./normalize";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Mapping of module attribute names to their corresponding `ListPagesQuery` keys
|
|
32
|
+
* and expected value types. Only fields listed here support `@URL` resolution.
|
|
33
|
+
*/
|
|
34
|
+
const URL_RESOLVABLE_FIELDS: ReadonlyArray<{
|
|
35
|
+
attr: string;
|
|
36
|
+
queryKey: keyof ListPagesQuery;
|
|
37
|
+
type: "string" | "number" | "boolean";
|
|
38
|
+
}> = [
|
|
39
|
+
{ attr: "offset", queryKey: "offset", type: "number" },
|
|
40
|
+
{ attr: "limit", queryKey: "limit", type: "number" },
|
|
41
|
+
{ attr: "per-page", queryKey: "perPage", type: "number" },
|
|
42
|
+
{ attr: "order", queryKey: "order", type: "string" },
|
|
43
|
+
{ attr: "tags", queryKey: "tags", type: "string" },
|
|
44
|
+
{ attr: "category", queryKey: "category", type: "string" },
|
|
45
|
+
{ attr: "parent", queryKey: "parent", type: "string" },
|
|
46
|
+
{ attr: "range", queryKey: "range", type: "string" },
|
|
47
|
+
{ attr: "name", queryKey: "name", type: "string" },
|
|
48
|
+
{ attr: "fullname", queryKey: "fullname", type: "string" },
|
|
49
|
+
{ attr: "created-at", queryKey: "createdAt", type: "string" },
|
|
50
|
+
{ attr: "updated-at", queryKey: "updatedAt", type: "string" },
|
|
51
|
+
{ attr: "created-by", queryKey: "createdBy", type: "string" },
|
|
52
|
+
{ attr: "rating", queryKey: "rating", type: "string" },
|
|
53
|
+
{ attr: "votes", queryKey: "votes", type: "string" },
|
|
54
|
+
{ attr: "reverse", queryKey: "reverse", type: "boolean" },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Parse URL path parameters like /offset/1/page2_limit/1
|
|
59
|
+
* Returns a map of parameter name -> value
|
|
60
|
+
*/
|
|
61
|
+
export function parseUrlParams(url: string): Map<string, string> {
|
|
62
|
+
const params = new Map<string, string>();
|
|
63
|
+
const parts = url.split("/").filter(Boolean);
|
|
64
|
+
|
|
65
|
+
// Skip the page name (first part), parse key/value pairs
|
|
66
|
+
for (let i = 1; i < parts.length - 1; i += 2) {
|
|
67
|
+
const key = parts[i];
|
|
68
|
+
const value = parts[i + 1];
|
|
69
|
+
if (key && value) {
|
|
70
|
+
params.set(key, value);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return params;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Resolve @URL|default format with actual URL parameters
|
|
79
|
+
*
|
|
80
|
+
* @param rawValue - The raw attribute value (e.g., "@URL|0")
|
|
81
|
+
* @param paramName - The parameter name (e.g., "offset")
|
|
82
|
+
* @param urlParams - Parsed URL parameters
|
|
83
|
+
* @param prefix - Optional URL attribute prefix (e.g., "page2")
|
|
84
|
+
* @returns Resolved value
|
|
85
|
+
*/
|
|
86
|
+
export function resolveUrlValue(
|
|
87
|
+
rawValue: string | undefined,
|
|
88
|
+
paramName: string,
|
|
89
|
+
urlParams: Map<string, string>,
|
|
90
|
+
prefix?: string,
|
|
91
|
+
): string | undefined {
|
|
92
|
+
if (!rawValue) return undefined;
|
|
93
|
+
|
|
94
|
+
// Check for @URL or @URL|default format
|
|
95
|
+
if (!rawValue.startsWith("@URL")) {
|
|
96
|
+
return rawValue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Extract default value if present
|
|
100
|
+
const defaultValue = rawValue.includes("|") ? rawValue.split("|")[1] : undefined;
|
|
101
|
+
|
|
102
|
+
// Build the actual parameter name with prefix
|
|
103
|
+
const actualParamName = prefix ? `${prefix}_${paramName}` : paramName;
|
|
104
|
+
|
|
105
|
+
// Get from URL params or use default
|
|
106
|
+
return urlParams.get(actualParamName) ?? defaultValue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Resolve all `@URL` parameters and build a ListPagesQuery
|
|
111
|
+
*
|
|
112
|
+
* Takes a ListPagesDataRequirement and URL parameters, resolves all `@URL|default`
|
|
113
|
+
* values, and returns a complete ListPagesQuery ready for database queries.
|
|
114
|
+
*
|
|
115
|
+
* @param requirement - The data requirement from AST extraction
|
|
116
|
+
* @param urlParams - Parsed URL parameters (from parseUrlParams)
|
|
117
|
+
* @returns Resolved ListPagesQuery
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```typescript
|
|
121
|
+
* const urlParams = parseUrlParams("/page/scp-001/page2_offset/10/page2_limit/5");
|
|
122
|
+
* const query = resolveQuery(requirement, urlParams);
|
|
123
|
+
* // query.offset = 10, query.limit = 5 (if urlAttrPrefix = "page2")
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export function resolveQuery(
|
|
127
|
+
requirement: ListPagesDataRequirement,
|
|
128
|
+
urlParams: Map<string, string>,
|
|
129
|
+
): ListPagesQuery {
|
|
130
|
+
const { query, rawAttributes, urlAttrPrefix } = requirement;
|
|
131
|
+
const resolved: ListPagesQuery = { ...query };
|
|
132
|
+
|
|
133
|
+
for (const field of URL_RESOLVABLE_FIELDS) {
|
|
134
|
+
const rawValue = rawAttributes[field.attr];
|
|
135
|
+
if (!rawValue) continue;
|
|
136
|
+
|
|
137
|
+
const resolvedValue = resolveUrlValue(rawValue, field.attr, urlParams, urlAttrPrefix);
|
|
138
|
+
if (resolvedValue === undefined) continue;
|
|
139
|
+
|
|
140
|
+
// Convert to appropriate type
|
|
141
|
+
switch (field.type) {
|
|
142
|
+
case "number": {
|
|
143
|
+
const num = parseInt(resolvedValue, 10);
|
|
144
|
+
if (!Number.isNaN(num)) {
|
|
145
|
+
(resolved as Record<string, unknown>)[field.queryKey] = num;
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case "boolean":
|
|
150
|
+
(resolved as Record<string, unknown>)[field.queryKey] =
|
|
151
|
+
resolvedValue === "true" || resolvedValue === "yes" || resolvedValue === "1";
|
|
152
|
+
break;
|
|
153
|
+
case "string":
|
|
154
|
+
default:
|
|
155
|
+
(resolved as Record<string, unknown>)[field.queryKey] = resolvedValue;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return resolved;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Resolve all `@URL` parameters and normalize the query
|
|
165
|
+
*
|
|
166
|
+
* Combines URL resolution with query normalization in a single call.
|
|
167
|
+
* This is the recommended way to process ListPages queries for HPC.
|
|
168
|
+
*
|
|
169
|
+
* @param requirement - The data requirement from AST extraction
|
|
170
|
+
* @param urlParams - Parsed URL parameters (from parseUrlParams)
|
|
171
|
+
* @returns Normalized query with all `@URL` values resolved
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* const urlParams = parseUrlParams(window.location.pathname);
|
|
176
|
+
* const normalizedQuery = resolveAndNormalizeQuery(requirement, urlParams);
|
|
177
|
+
* // Ready for database query building
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
export function resolveAndNormalizeQuery(
|
|
181
|
+
requirement: ListPagesDataRequirement,
|
|
182
|
+
urlParams: Map<string, string>,
|
|
183
|
+
): NormalizedListPagesQuery {
|
|
184
|
+
const resolved = resolveQuery(requirement, urlParams);
|
|
185
|
+
return normalizeQuery(resolved);
|
|
186
|
+
}
|