@wdprlib/parser 4.2.0 → 4.4.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/README.md +28 -38
- package/dist/index.cjs +973 -134
- package/dist/index.d.cts +195 -158
- package/dist/index.d.ts +195 -158
- package/dist/index.js +955 -113
- package/package.json +2 -2
- package/src/index.ts +17 -0
- package/src/parser/parse/context.ts +1 -0
- package/src/parser/parse/options.ts +6 -0
- package/src/parser/parse/result.ts +3 -1
- package/src/parser/rules/block/gallery/index.ts +215 -0
- package/src/parser/rules/block/gallery/items.ts +62 -0
- package/src/parser/rules/block/index.ts +3 -0
- package/src/parser/rules/block/module/include/index.ts +6 -1
- package/src/parser/rules/block/module/include/resolve/index.ts +23 -3
- package/src/parser/rules/block/module/include/resolve/iterate.ts +71 -0
- package/src/parser/rules/block/module/index.ts +2 -1
- package/src/parser/rules/block/module/listpages/resolution/items.ts +2 -2
- package/src/parser/rules/block/module/listpages/resolution/wrapper.ts +3 -3
- package/src/parser/rules/block/module/listusers/resolve.ts +2 -2
- package/src/parser/rules/block/module/resolution/document.ts +357 -0
- package/src/parser/rules/block/module/resolution/resolve-async.ts +269 -0
- package/src/parser/rules/block/module/resolution/styles.ts +134 -12
- package/src/parser/rules/block/module/resolution/walk-resolve.ts +19 -1
- package/src/parser/rules/block/module/resolve.ts +32 -13
- package/src/parser/rules/block/module/types.ts +7 -2
- package/src/parser/rules/contracts/parse-context.ts +1 -0
- package/src/pipeline/index.ts +7 -0
- package/src/pipeline/process.ts +105 -0
- package/src/pipeline/types.ts +63 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import {
|
|
2
|
+
evaluateExpression,
|
|
3
|
+
isTruthy,
|
|
4
|
+
type Diagnostic,
|
|
5
|
+
type Element,
|
|
6
|
+
type ParseResult,
|
|
7
|
+
type SyntaxTree,
|
|
8
|
+
type TocEntry,
|
|
9
|
+
} from "@wdprlib/ast";
|
|
10
|
+
import { extractHeadingText } from "../../heading/toc-text";
|
|
11
|
+
import { buildTableOfContents } from "../../../../toc";
|
|
12
|
+
import { resolveIfTags } from "../iftags/resolve";
|
|
13
|
+
import {
|
|
14
|
+
getGenericElementChildren,
|
|
15
|
+
listElement,
|
|
16
|
+
withGenericElementChildren,
|
|
17
|
+
} from "../walk/children";
|
|
18
|
+
import type { ModuleParseResult } from "../types";
|
|
19
|
+
|
|
20
|
+
interface RegisterOptions {
|
|
21
|
+
stripLegacyImplicitFootnoteBlock?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Tracks parse-local side channels while module fragments are merged. */
|
|
25
|
+
export class ModuleDocumentRegistry {
|
|
26
|
+
private readonly footnotesByElement = new WeakMap<Element, Element[]>();
|
|
27
|
+
readonly diagnostics: Diagnostic[] = [];
|
|
28
|
+
|
|
29
|
+
register(result: ModuleParseResult, options: RegisterOptions = {}): SyntaxTree {
|
|
30
|
+
const parseResult = isParseResult(result) ? result : null;
|
|
31
|
+
const ast: SyntaxTree = parseResult ? parseResult.ast : (result as SyntaxTree);
|
|
32
|
+
const elements = options.stripLegacyImplicitFootnoteBlock
|
|
33
|
+
? stripTrailingDefaultFootnoteBlock(ast.elements)
|
|
34
|
+
: ast.elements;
|
|
35
|
+
const registeredAst = elements === ast.elements ? ast : { ...ast, elements };
|
|
36
|
+
|
|
37
|
+
if (parseResult) {
|
|
38
|
+
this.diagnostics.push(...parseResult.diagnostics);
|
|
39
|
+
}
|
|
40
|
+
this.registerFootnotes(registeredAst);
|
|
41
|
+
return registeredAst;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
finalize(
|
|
45
|
+
ast: SyntaxTree,
|
|
46
|
+
elements: Element[],
|
|
47
|
+
pageTags: string[] | null,
|
|
48
|
+
ensureFootnoteBlock = true,
|
|
49
|
+
): SyntaxTree {
|
|
50
|
+
const normalizedElements = normalizeFootnoteBlocks(elements, ensureFootnoteBlock);
|
|
51
|
+
const footnotes: Element[][] = [];
|
|
52
|
+
const htmlBlocks: string[] = [];
|
|
53
|
+
const codeBlocks: NonNullable<SyntaxTree["code-blocks"]> = [];
|
|
54
|
+
const tocEntries: TocEntry[] = [];
|
|
55
|
+
|
|
56
|
+
const collectElement = (element: Element): void => {
|
|
57
|
+
if (element.element === "footnote") {
|
|
58
|
+
const content = this.footnotesByElement.get(element);
|
|
59
|
+
if (content) footnotes.push(content);
|
|
60
|
+
} else if (element.element === "html") {
|
|
61
|
+
htmlBlocks.push(element.data.contents);
|
|
62
|
+
} else if (element.element === "code") {
|
|
63
|
+
codeBlocks.push(element.data);
|
|
64
|
+
} else if (isTocHeading(element)) {
|
|
65
|
+
tocEntries.push({
|
|
66
|
+
level: element.data.type.header.level,
|
|
67
|
+
text: extractHeadingText(element.data.elements),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
walkRenderOrder(normalizedElements, pageTags, collectElement);
|
|
72
|
+
|
|
73
|
+
const result: SyntaxTree = { ...ast, elements: normalizedElements };
|
|
74
|
+
setOptionalArray(result, "footnotes", footnotes);
|
|
75
|
+
setOptionalArray(result, "html-blocks", htmlBlocks);
|
|
76
|
+
setOptionalArray(result, "code-blocks", codeBlocks);
|
|
77
|
+
setOptionalArray(result, "table-of-contents", buildTableOfContents(tocEntries));
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private registerFootnotes(ast: SyntaxTree): void {
|
|
82
|
+
const footnoteElements: Element[] = [];
|
|
83
|
+
walkSyntaxOrder(ast.elements, (element) => {
|
|
84
|
+
if (element.element === "footnote") footnoteElements.push(element);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < footnoteElements.length; i++) {
|
|
88
|
+
const content = ast.footnotes?.[i];
|
|
89
|
+
if (content) this.footnotesByElement.set(footnoteElements[i]!, content);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isParseResult(result: ModuleParseResult): result is ParseResult {
|
|
95
|
+
return "ast" in result && "diagnostics" in result;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function setOptionalArray<K extends keyof SyntaxTree>(
|
|
99
|
+
tree: SyntaxTree,
|
|
100
|
+
key: K,
|
|
101
|
+
value: NonNullable<SyntaxTree[K]>,
|
|
102
|
+
): void {
|
|
103
|
+
if ((value as unknown[]).length > 0) {
|
|
104
|
+
tree[key] = value;
|
|
105
|
+
} else {
|
|
106
|
+
delete tree[key];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function stripTrailingDefaultFootnoteBlock(elements: Element[]): Element[] {
|
|
111
|
+
const last = elements.at(-1);
|
|
112
|
+
if (last?.element !== "footnote-block" || last.data.title !== null || last.data.hide === true) {
|
|
113
|
+
return elements;
|
|
114
|
+
}
|
|
115
|
+
return elements.slice(0, -1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function normalizeFootnoteBlocks(elements: Element[], ensureFootnoteBlock: boolean): Element[] {
|
|
119
|
+
const state = { found: false };
|
|
120
|
+
const normalized = mapSyntaxElements(elements, (element) => {
|
|
121
|
+
if (element.element !== "footnote-block") return element;
|
|
122
|
+
if (state.found) return null;
|
|
123
|
+
state.found = true;
|
|
124
|
+
return element;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (!state.found && ensureFootnoteBlock) {
|
|
128
|
+
normalized.push({
|
|
129
|
+
element: "footnote-block",
|
|
130
|
+
data: { title: null, hide: false },
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return normalized;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function containsSyntaxFootnoteBlock(elements: Element[]): boolean {
|
|
137
|
+
let found = false;
|
|
138
|
+
walkSyntaxOrder(elements, (element) => {
|
|
139
|
+
if (element.element === "footnote-block") found = true;
|
|
140
|
+
});
|
|
141
|
+
return found;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function mapSyntaxElements(
|
|
145
|
+
elements: Element[],
|
|
146
|
+
transform: (element: Element) => Element | null,
|
|
147
|
+
): Element[] {
|
|
148
|
+
const result: Element[] = [];
|
|
149
|
+
for (const original of elements) {
|
|
150
|
+
const element = transform(original);
|
|
151
|
+
if (!element) continue;
|
|
152
|
+
result.push(mapSyntaxChildren(element, (children) => mapSyntaxElements(children, transform)));
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function mapSyntaxChildren(
|
|
158
|
+
element: Element,
|
|
159
|
+
transform: (elements: Element[]) => Element[],
|
|
160
|
+
): Element {
|
|
161
|
+
if (element.element === "if") {
|
|
162
|
+
return {
|
|
163
|
+
...element,
|
|
164
|
+
data: {
|
|
165
|
+
...element.data,
|
|
166
|
+
// oxlint-disable-next-line unicorn/no-thenable -- `then` is part of the public AST schema
|
|
167
|
+
then: transform(element.data.then),
|
|
168
|
+
else: transform(element.data.else),
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (element.element === "ifexpr") {
|
|
173
|
+
return {
|
|
174
|
+
...element,
|
|
175
|
+
data: {
|
|
176
|
+
...element.data,
|
|
177
|
+
// oxlint-disable-next-line unicorn/no-thenable -- `then` is part of the public AST schema
|
|
178
|
+
then: transform(element.data.then),
|
|
179
|
+
else: transform(element.data.else),
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (element.element === "bibliography-block") {
|
|
184
|
+
return {
|
|
185
|
+
...element,
|
|
186
|
+
data: {
|
|
187
|
+
...element.data,
|
|
188
|
+
entries: element.data.entries.map((entry) => ({
|
|
189
|
+
...entry,
|
|
190
|
+
key: transform(entry.key),
|
|
191
|
+
value: transform(entry.value),
|
|
192
|
+
})),
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
if (element.element === "list") {
|
|
197
|
+
return {
|
|
198
|
+
...element,
|
|
199
|
+
data: {
|
|
200
|
+
...element.data,
|
|
201
|
+
items: element.data.items.map((item) =>
|
|
202
|
+
item["item-type"] === "elements"
|
|
203
|
+
? { ...item, elements: transform(item.elements) }
|
|
204
|
+
: mapSubList(item, transform),
|
|
205
|
+
),
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (element.element === "table") {
|
|
210
|
+
return {
|
|
211
|
+
...element,
|
|
212
|
+
data: {
|
|
213
|
+
...element.data,
|
|
214
|
+
rows: element.data.rows.map((row) => ({
|
|
215
|
+
...row,
|
|
216
|
+
cells: row.cells.map((cell) => ({ ...cell, elements: transform(cell.elements) })),
|
|
217
|
+
})),
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (element.element === "definition-list") {
|
|
222
|
+
return {
|
|
223
|
+
...element,
|
|
224
|
+
data: element.data.map((entry) => ({
|
|
225
|
+
...entry,
|
|
226
|
+
key: transform(entry.key),
|
|
227
|
+
value: transform(entry.value),
|
|
228
|
+
})),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (element.element === "tab-view") {
|
|
232
|
+
return {
|
|
233
|
+
...element,
|
|
234
|
+
data: element.data.map((tab) => ({ ...tab, elements: transform(tab.elements) })),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const children = getGenericElementChildren(element);
|
|
239
|
+
return children === null ? element : withGenericElementChildren(element, transform(children));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function mapSubList(
|
|
243
|
+
item: Extract<ElementOfListItem, { "item-type": "sub-list" }>,
|
|
244
|
+
transform: (elements: Element[]) => Element[],
|
|
245
|
+
): ElementOfListItem {
|
|
246
|
+
const transformed = transform([listElement(item.data)])[0];
|
|
247
|
+
return transformed?.element === "list" ? { ...item, data: transformed.data } : item;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
type ElementOfListItem = Extract<Element, { element: "list" }>["data"]["items"][number];
|
|
251
|
+
|
|
252
|
+
function walkSyntaxOrder(elements: Element[], callback: (element: Element) => void): void {
|
|
253
|
+
for (const element of elements) {
|
|
254
|
+
callback(element);
|
|
255
|
+
walkAllSyntaxChildren(element, (children) => walkSyntaxOrder(children, callback));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function walkRenderOrder(
|
|
260
|
+
elements: Element[],
|
|
261
|
+
pageTags: string[] | null,
|
|
262
|
+
callback: (element: Element) => void,
|
|
263
|
+
): void {
|
|
264
|
+
for (const element of elements) {
|
|
265
|
+
callback(element);
|
|
266
|
+
if (element.element === "if") {
|
|
267
|
+
walkRenderOrder(
|
|
268
|
+
isTruthy(element.data.condition) ? element.data.then : element.data.else,
|
|
269
|
+
pageTags,
|
|
270
|
+
callback,
|
|
271
|
+
);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (element.element === "ifexpr") {
|
|
275
|
+
const evaluated = evaluateExpression(element.data.expression);
|
|
276
|
+
if (evaluated.success) {
|
|
277
|
+
walkRenderOrder(
|
|
278
|
+
evaluated.value !== 0 ? element.data.then : element.data.else,
|
|
279
|
+
pageTags,
|
|
280
|
+
callback,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (element.element === "if-tags") {
|
|
286
|
+
const resolved = resolveIfTags(element.data, pageTags);
|
|
287
|
+
if (!resolved.evaluated || resolved.matched) {
|
|
288
|
+
walkRenderOrder(element.data.elements, pageTags, callback);
|
|
289
|
+
}
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (element.element === "bibliography-block") {
|
|
293
|
+
if (!element.data.hide) {
|
|
294
|
+
for (const entry of element.data.entries) {
|
|
295
|
+
walkRenderOrder(entry.value, pageTags, callback);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
walkBasicChildren(element, (children) => walkRenderOrder(children, pageTags, callback));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function walkAllSyntaxChildren(element: Element, callback: (elements: Element[]) => void): void {
|
|
305
|
+
if (element.element === "if" || element.element === "ifexpr") {
|
|
306
|
+
callback(element.data.then);
|
|
307
|
+
callback(element.data.else);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (element.element === "bibliography-block") {
|
|
311
|
+
for (const entry of element.data.entries) {
|
|
312
|
+
callback(entry.key);
|
|
313
|
+
callback(entry.value);
|
|
314
|
+
}
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
walkBasicChildren(element, callback);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function walkBasicChildren(element: Element, callback: (elements: Element[]) => void): void {
|
|
321
|
+
if (element.element === "list") {
|
|
322
|
+
for (const item of element.data.items) {
|
|
323
|
+
callback(item["item-type"] === "elements" ? item.elements : [listElement(item.data)]);
|
|
324
|
+
}
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (element.element === "table") {
|
|
328
|
+
for (const row of element.data.rows) {
|
|
329
|
+
for (const cell of row.cells) callback(cell.elements);
|
|
330
|
+
}
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (element.element === "definition-list") {
|
|
334
|
+
for (const entry of element.data) {
|
|
335
|
+
callback(entry.key);
|
|
336
|
+
callback(entry.value);
|
|
337
|
+
}
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (element.element === "tab-view") {
|
|
341
|
+
for (const tab of element.data) callback(tab.elements);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const children = getGenericElementChildren(element);
|
|
345
|
+
if (children !== null) callback(children);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function isTocHeading(element: Element): element is Extract<Element, { element: "container" }> & {
|
|
349
|
+
data: { type: { header: { level: number; "has-toc": true } } };
|
|
350
|
+
} {
|
|
351
|
+
return (
|
|
352
|
+
element.element === "container" &&
|
|
353
|
+
typeof element.data.type === "object" &&
|
|
354
|
+
"header" in element.data.type &&
|
|
355
|
+
element.data.type.header["has-toc"]
|
|
356
|
+
);
|
|
357
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import type { Diagnostic, Element, ParseResult, SyntaxTree } from "@wdprlib/ast";
|
|
2
|
+
import type { DataProvider } from "../types-common";
|
|
3
|
+
import type { ModuleParseResult } from "../types";
|
|
4
|
+
import { isListPagesModule, type ListPagesModuleData } from "../listpages/resolve";
|
|
5
|
+
import type {
|
|
6
|
+
CompiledTemplate,
|
|
7
|
+
ListPagesDataRequirement,
|
|
8
|
+
ListPagesExternalData,
|
|
9
|
+
VariableContext,
|
|
10
|
+
} from "../listpages/types";
|
|
11
|
+
import { isListUsersModule } from "../listusers/resolve";
|
|
12
|
+
import type {
|
|
13
|
+
ListUsersCompiledTemplate,
|
|
14
|
+
ListUsersDataRequirement,
|
|
15
|
+
ListUsersExternalData,
|
|
16
|
+
ListUsersVariableContext,
|
|
17
|
+
} from "../listusers/types";
|
|
18
|
+
import { isTagCloudModule, resolveTagCloud } from "../tagcloud/resolve";
|
|
19
|
+
import type { TagCloudDataRequirement } from "../tagcloud/types";
|
|
20
|
+
import { resolveIfTags } from "../iftags/resolve";
|
|
21
|
+
import {
|
|
22
|
+
getGenericElementChildren,
|
|
23
|
+
listElement,
|
|
24
|
+
withGenericElementChildren,
|
|
25
|
+
} from "../walk/children";
|
|
26
|
+
import { buildListPagesDataMap, buildListUsersDataMap, buildTagCloudDataMap } from "./data-maps";
|
|
27
|
+
import { ModuleDocumentRegistry } from "./document";
|
|
28
|
+
import { collectStyles, mergeCollectedStyles } from "./styles";
|
|
29
|
+
|
|
30
|
+
export type AsyncModuleParseFunction = (source: string) => Promise<ModuleParseResult>;
|
|
31
|
+
|
|
32
|
+
export interface ResolveModulesWithAsyncParseOptions {
|
|
33
|
+
parse: AsyncModuleParseFunction;
|
|
34
|
+
compiledListPagesTemplates: Map<number, CompiledTemplate>;
|
|
35
|
+
compiledListUsersTemplates?: Map<number, ListUsersCompiledTemplate>;
|
|
36
|
+
requirements: {
|
|
37
|
+
listPages?: ListPagesDataRequirement[];
|
|
38
|
+
listUsers?: ListUsersDataRequirement[];
|
|
39
|
+
tagCloud?: TagCloudDataRequirement[];
|
|
40
|
+
};
|
|
41
|
+
urlPath?: string;
|
|
42
|
+
pageTags: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface AsyncModuleResolutionResult {
|
|
46
|
+
ast: SyntaxTree;
|
|
47
|
+
diagnostics: Diagnostic[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface ResolutionState {
|
|
51
|
+
listPagesId: number;
|
|
52
|
+
listUsersId: number;
|
|
53
|
+
tagCloudId: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface ResolutionContext {
|
|
57
|
+
dataProvider: DataProvider;
|
|
58
|
+
listPagesData: Map<number, ListPagesExternalData>;
|
|
59
|
+
listUsersData: Map<number, ListUsersExternalData>;
|
|
60
|
+
tagCloudData: Awaited<ReturnType<typeof buildTagCloudDataMap>>;
|
|
61
|
+
options: ResolveModulesWithAsyncParseOptions;
|
|
62
|
+
parse: (source: string) => Promise<SyntaxTree>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function resolveModulesWithAsyncParse(
|
|
66
|
+
ast: SyntaxTree,
|
|
67
|
+
dataProvider: DataProvider,
|
|
68
|
+
options: ResolveModulesWithAsyncParseOptions,
|
|
69
|
+
): Promise<AsyncModuleResolutionResult> {
|
|
70
|
+
const registry = new ModuleDocumentRegistry();
|
|
71
|
+
registry.register(ast);
|
|
72
|
+
const parse = async (source: string): Promise<SyntaxTree> =>
|
|
73
|
+
registry.register(await options.parse(source));
|
|
74
|
+
|
|
75
|
+
const [listPagesData, listUsersData, tagCloudData] = await Promise.all([
|
|
76
|
+
buildListPagesDataMap(dataProvider, options.requirements.listPages ?? [], options.urlPath),
|
|
77
|
+
buildListUsersDataMap(dataProvider, options.requirements.listUsers ?? []),
|
|
78
|
+
buildTagCloudDataMap(dataProvider, options.requirements.tagCloud ?? []),
|
|
79
|
+
]);
|
|
80
|
+
const context: ResolutionContext = {
|
|
81
|
+
dataProvider,
|
|
82
|
+
listPagesData,
|
|
83
|
+
listUsersData,
|
|
84
|
+
tagCloudData,
|
|
85
|
+
options,
|
|
86
|
+
parse,
|
|
87
|
+
};
|
|
88
|
+
const state: ResolutionState = { listPagesId: 0, listUsersId: 0, tagCloudId: 0 };
|
|
89
|
+
const resolvedElements = await resolveElements(ast.elements, context, state);
|
|
90
|
+
const { elements, styles, anchoredStyles } = collectStyles(resolvedElements);
|
|
91
|
+
const intermediate: SyntaxTree = { ...ast, elements };
|
|
92
|
+
const mergedStyles = mergeCollectedStyles(ast.styles, styles, new Map(), anchoredStyles);
|
|
93
|
+
if (mergedStyles.length > 0) intermediate.styles = mergedStyles;
|
|
94
|
+
else delete intermediate.styles;
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
ast: registry.finalize(intermediate, elements, options.pageTags),
|
|
98
|
+
diagnostics: registry.diagnostics,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function resolveElements(
|
|
103
|
+
elements: Element[],
|
|
104
|
+
context: ResolutionContext,
|
|
105
|
+
state: ResolutionState,
|
|
106
|
+
): Promise<Element[]> {
|
|
107
|
+
const result: Element[] = [];
|
|
108
|
+
for (const element of elements) {
|
|
109
|
+
if (element.element === "module") {
|
|
110
|
+
const resolved = await resolveModuleElement(element, context, state);
|
|
111
|
+
if (resolved !== null) {
|
|
112
|
+
result.push(...resolved);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (element.element === "if-tags") {
|
|
118
|
+
const resolution = resolveIfTags(element.data, context.options.pageTags);
|
|
119
|
+
if (resolution.matched) {
|
|
120
|
+
result.push(...(await resolveElements(element.data.elements, context, state)));
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
result.push(await resolveElementChildren(element, context, state));
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function resolveModuleElement(
|
|
131
|
+
element: Extract<Element, { element: "module" }>,
|
|
132
|
+
context: ResolutionContext,
|
|
133
|
+
state: ResolutionState,
|
|
134
|
+
): Promise<Element[] | null> {
|
|
135
|
+
if (isListPagesModule(element.data)) {
|
|
136
|
+
const id = state.listPagesId++;
|
|
137
|
+
const data = context.listPagesData.get(id);
|
|
138
|
+
const template = context.options.compiledListPagesTemplates.get(id);
|
|
139
|
+
if (data && template) return resolveListPagesAsync(element.data, data, template, context.parse);
|
|
140
|
+
return context.dataProvider.fetchListPages ? [] : [element];
|
|
141
|
+
}
|
|
142
|
+
if (isListUsersModule(element.data)) {
|
|
143
|
+
const id = state.listUsersId++;
|
|
144
|
+
const data = context.listUsersData.get(id);
|
|
145
|
+
const template = context.options.compiledListUsersTemplates?.get(id);
|
|
146
|
+
if (data && template) {
|
|
147
|
+
const variableContext: ListUsersVariableContext = { user: data.user };
|
|
148
|
+
return (await context.parse(template(variableContext))).elements;
|
|
149
|
+
}
|
|
150
|
+
return context.dataProvider.fetchListUsers ? [] : [element];
|
|
151
|
+
}
|
|
152
|
+
if (isTagCloudModule(element.data)) {
|
|
153
|
+
const id = state.tagCloudId++;
|
|
154
|
+
const data = context.tagCloudData.get(id);
|
|
155
|
+
if (data) return resolveTagCloud(element.data, data);
|
|
156
|
+
return context.dataProvider.fetchTagCloud ? [] : [element];
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function resolveListPagesAsync(
|
|
162
|
+
module: ListPagesModuleData,
|
|
163
|
+
data: ListPagesExternalData,
|
|
164
|
+
template: CompiledTemplate,
|
|
165
|
+
parse: (source: string) => Promise<SyntaxTree>,
|
|
166
|
+
): Promise<Element[]> {
|
|
167
|
+
if (data.pages.length === 0) return [];
|
|
168
|
+
const result: Element[] = [];
|
|
169
|
+
|
|
170
|
+
if (module["prepend-line"] && !module.separate) {
|
|
171
|
+
result.push(...(await parse(module["prepend-line"])).elements);
|
|
172
|
+
}
|
|
173
|
+
for (let i = 0; i < data.pages.length; i++) {
|
|
174
|
+
const page = data.pages[i];
|
|
175
|
+
if (!page) continue;
|
|
176
|
+
const variableContext: VariableContext = {
|
|
177
|
+
page,
|
|
178
|
+
index: i + 1,
|
|
179
|
+
total: data.totalCount,
|
|
180
|
+
limit: module.limit,
|
|
181
|
+
site: data.site,
|
|
182
|
+
};
|
|
183
|
+
const parsed = await parse(template(variableContext));
|
|
184
|
+
if (module.separate) {
|
|
185
|
+
result.push({
|
|
186
|
+
element: "container",
|
|
187
|
+
data: {
|
|
188
|
+
type: "div",
|
|
189
|
+
attributes: { class: "list-pages-item" },
|
|
190
|
+
elements: parsed.elements,
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
} else {
|
|
194
|
+
result.push(...parsed.elements);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (module["append-line"] && !module.separate) {
|
|
198
|
+
result.push(...(await parse(module["append-line"])).elements);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return module.wrapper
|
|
202
|
+
? [
|
|
203
|
+
{
|
|
204
|
+
element: "container",
|
|
205
|
+
data: {
|
|
206
|
+
type: "div",
|
|
207
|
+
attributes: { class: "list-pages-box" },
|
|
208
|
+
elements: result,
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
]
|
|
212
|
+
: result;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function resolveElementChildren(
|
|
216
|
+
element: Element,
|
|
217
|
+
context: ResolutionContext,
|
|
218
|
+
state: ResolutionState,
|
|
219
|
+
): Promise<Element> {
|
|
220
|
+
if (element.element === "list") {
|
|
221
|
+
const items = [];
|
|
222
|
+
for (const item of element.data.items) {
|
|
223
|
+
if (item["item-type"] === "elements") {
|
|
224
|
+
items.push({ ...item, elements: await resolveElements(item.elements, context, state) });
|
|
225
|
+
} else {
|
|
226
|
+
const resolved = (await resolveElements([listElement(item.data)], context, state))[0];
|
|
227
|
+
items.push(resolved?.element === "list" ? { ...item, data: resolved.data } : item);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return { ...element, data: { ...element.data, items } };
|
|
231
|
+
}
|
|
232
|
+
if (element.element === "table") {
|
|
233
|
+
const rows = [];
|
|
234
|
+
for (const row of element.data.rows) {
|
|
235
|
+
const cells = [];
|
|
236
|
+
for (const cell of row.cells) {
|
|
237
|
+
cells.push({ ...cell, elements: await resolveElements(cell.elements, context, state) });
|
|
238
|
+
}
|
|
239
|
+
rows.push({ ...row, cells });
|
|
240
|
+
}
|
|
241
|
+
return { ...element, data: { ...element.data, rows } };
|
|
242
|
+
}
|
|
243
|
+
if (element.element === "definition-list") {
|
|
244
|
+
const entries = [];
|
|
245
|
+
for (const entry of element.data) {
|
|
246
|
+
entries.push({
|
|
247
|
+
...entry,
|
|
248
|
+
key: await resolveElements(entry.key, context, state),
|
|
249
|
+
value: await resolveElements(entry.value, context, state),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return { ...element, data: entries };
|
|
253
|
+
}
|
|
254
|
+
if (element.element === "tab-view") {
|
|
255
|
+
const tabs = [];
|
|
256
|
+
for (const tab of element.data) {
|
|
257
|
+
tabs.push({ ...tab, elements: await resolveElements(tab.elements, context, state) });
|
|
258
|
+
}
|
|
259
|
+
return { ...element, data: tabs };
|
|
260
|
+
}
|
|
261
|
+
const children = getGenericElementChildren(element);
|
|
262
|
+
return children === null
|
|
263
|
+
? element
|
|
264
|
+
: withGenericElementChildren(element, await resolveElements(children, context, state));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function asParseResult(ast: SyntaxTree, diagnostics: Diagnostic[]): ParseResult {
|
|
268
|
+
return { ast, diagnostics };
|
|
269
|
+
}
|