@wdprlib/parser 4.3.0 → 5.0.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 +26 -38
- package/dist/index.cjs +1035 -249
- package/dist/index.d.cts +203 -158
- package/dist/index.d.ts +203 -158
- package/dist/index.js +930 -139
- package/package.json +2 -2
- package/src/index.ts +13 -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/preprocess/expr/index.ts +26 -0
- package/src/parser/preprocess/utils/raw-regions.ts +16 -7
- 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 +4 -1
- package/src/parser/rules/block/module/listpages/index.ts +2 -0
- 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/listpages/selectors.ts +64 -0
- package/src/parser/rules/block/module/listpages/types/external-data.ts +22 -0
- 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 +172 -0
- package/src/pipeline/types.ts +63 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wdprlib/parser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Parser for Wikidot markup",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ast",
|
|
@@ -41,6 +41,6 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@braintree/sanitize-url": "^7.1.1",
|
|
44
|
-
"@wdprlib/ast": "
|
|
44
|
+
"@wdprlib/ast": "3.0.0"
|
|
45
45
|
}
|
|
46
46
|
}
|
package/src/index.ts
CHANGED
|
@@ -98,6 +98,15 @@ export { Lexer, tokenize, createToken } from "./lexer";
|
|
|
98
98
|
export type { ParserOptions } from "./parser";
|
|
99
99
|
export { Parser, parse } from "./parser";
|
|
100
100
|
|
|
101
|
+
// High-level parser pipeline
|
|
102
|
+
export { processWikitext } from "./pipeline";
|
|
103
|
+
export type {
|
|
104
|
+
ProcessedWikitextDocument,
|
|
105
|
+
ProcessWikitextCallbackContext,
|
|
106
|
+
ProcessWikitextDataProvider,
|
|
107
|
+
ProcessWikitextOptions,
|
|
108
|
+
} from "./pipeline";
|
|
109
|
+
|
|
101
110
|
// Modules (ListPages, ListUsers, IfTags, Include, etc.)
|
|
102
111
|
export type {
|
|
103
112
|
// ListPages query types
|
|
@@ -121,6 +130,7 @@ export type {
|
|
|
121
130
|
ExtractionResult,
|
|
122
131
|
// Resolution types
|
|
123
132
|
ParseFunction,
|
|
133
|
+
ModuleParseResult,
|
|
124
134
|
ModuleSourceTransform,
|
|
125
135
|
ResolveOptions,
|
|
126
136
|
// Include resolution
|
|
@@ -163,6 +173,7 @@ export {
|
|
|
163
173
|
extractIncludeReferences,
|
|
164
174
|
resolveIncludes,
|
|
165
175
|
resolveIncludesAsync,
|
|
176
|
+
resolveIncludesAsyncWithTrace,
|
|
166
177
|
resolveIncludesWithTrace,
|
|
167
178
|
// IfTags source-level preprocessing (run between include expansion and parse)
|
|
168
179
|
preprocessIftags,
|
|
@@ -174,6 +185,8 @@ export {
|
|
|
174
185
|
parseParent,
|
|
175
186
|
parseDateSelector,
|
|
176
187
|
parseNumericSelector,
|
|
188
|
+
definePageData,
|
|
189
|
+
matchesListPagesSelectors,
|
|
177
190
|
// ListUsers
|
|
178
191
|
extractListUsersVariables,
|
|
179
192
|
compileListUsersTemplate,
|
|
@@ -10,6 +10,7 @@ export function createParseContext(tokens: Token[], options: ParserOptions = {})
|
|
|
10
10
|
version: options.version ?? "wikidot",
|
|
11
11
|
trackPositions: options.trackPositions ?? true,
|
|
12
12
|
settings: options.settings ?? DEFAULT_SETTINGS,
|
|
13
|
+
appendImplicitFootnoteBlock: options.appendImplicitFootnoteBlock ?? true,
|
|
13
14
|
footnotes: [],
|
|
14
15
|
tocEntries: [],
|
|
15
16
|
codeBlocks: [],
|
|
@@ -31,4 +31,10 @@ export interface ParserOptions {
|
|
|
31
31
|
* - `string[]`: every iftags block is evaluated against the given tags eagerly.
|
|
32
32
|
*/
|
|
33
33
|
pageTags?: string[] | null;
|
|
34
|
+
/**
|
|
35
|
+
* Append the implicit document-level footnote block when no explicit block
|
|
36
|
+
* exists. Defaults to true. Fragment pipelines can disable this and add one
|
|
37
|
+
* block after all fragments have been merged.
|
|
38
|
+
*/
|
|
39
|
+
appendImplicitFootnoteBlock?: boolean;
|
|
34
40
|
}
|
|
@@ -10,7 +10,9 @@ import { containsFootnoteBlock } from "./footnotes";
|
|
|
10
10
|
|
|
11
11
|
export function finalizeParseResult(ctx: ParseContext, children: Element[]): ParseResult {
|
|
12
12
|
const cleanedChildren = postprocessChildren(children);
|
|
13
|
-
appendImplicitFootnoteBlock
|
|
13
|
+
if (ctx.appendImplicitFootnoteBlock) {
|
|
14
|
+
appendImplicitFootnoteBlock(cleanedChildren);
|
|
15
|
+
}
|
|
14
16
|
|
|
15
17
|
return {
|
|
16
18
|
ast: buildSyntaxTree(ctx, cleanedChildren),
|
|
@@ -7,8 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { makeUniqueSentinels, maskRawRegions, restorePlaceholders } from "../utils";
|
|
10
|
+
import { matchDirectiveKind } from "./kind";
|
|
10
11
|
import { expandInnermost } from "./scan";
|
|
11
12
|
|
|
13
|
+
const MAX_EXPR_NESTING = 64;
|
|
14
|
+
|
|
12
15
|
/**
|
|
13
16
|
* Resolve every `[[#if]]` / `[[#ifexpr]]` / `[[#expr]]` that sits inside
|
|
14
17
|
* another block's opener (depth > 0). Top-level directives are left for
|
|
@@ -21,6 +24,7 @@ export function preprocessExpr(source: string): string {
|
|
|
21
24
|
|
|
22
25
|
const sentinels = makeUniqueSentinels(source);
|
|
23
26
|
const { masked, placeholders } = maskRawRegions(source, sentinels);
|
|
27
|
+
if (exceedsExprNestingLimit(masked)) return source;
|
|
24
28
|
const reduced = reduceExpr(masked);
|
|
25
29
|
return restorePlaceholders(reduced, placeholders, sentinels);
|
|
26
30
|
}
|
|
@@ -43,3 +47,25 @@ function reduceExpr(source: string): string {
|
|
|
43
47
|
}
|
|
44
48
|
return current;
|
|
45
49
|
}
|
|
50
|
+
|
|
51
|
+
function exceedsExprNestingLimit(source: string): boolean {
|
|
52
|
+
const expressionStack: boolean[] = [];
|
|
53
|
+
let expressionDepth = 0;
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < source.length; i++) {
|
|
56
|
+
if (source.startsWith("[[", i)) {
|
|
57
|
+
const isExpression = matchDirectiveKind(source, i) !== null;
|
|
58
|
+
expressionStack.push(isExpression);
|
|
59
|
+
if (isExpression && ++expressionDepth > MAX_EXPR_NESTING) return true;
|
|
60
|
+
i++;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (source.startsWith("]]", i)) {
|
|
65
|
+
if (expressionStack.pop()) expressionDepth--;
|
|
66
|
+
i++;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
@@ -13,16 +13,25 @@ export interface Sentinels {
|
|
|
13
13
|
* Choose sentinel strings that are guaranteed not to appear in `source`.
|
|
14
14
|
* The placeholders we splice into the masked source have the form
|
|
15
15
|
* `<open><digits><close>`, so the restore pass must not confuse them
|
|
16
|
-
* with content.
|
|
16
|
+
* with content.
|
|
17
17
|
*/
|
|
18
18
|
export function makeUniqueSentinels(source: string): Sentinels {
|
|
19
|
-
let
|
|
20
|
-
let
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
let openRun = 0;
|
|
20
|
+
let closeRun = 0;
|
|
21
|
+
let longestOpenRun = 0;
|
|
22
|
+
let longestCloseRun = 0;
|
|
23
|
+
|
|
24
|
+
for (const char of source) {
|
|
25
|
+
openRun = char === BASE_PLACEHOLDER_OPEN ? openRun + 1 : 0;
|
|
26
|
+
closeRun = char === BASE_PLACEHOLDER_CLOSE ? closeRun + 1 : 0;
|
|
27
|
+
longestOpenRun = Math.max(longestOpenRun, openRun);
|
|
28
|
+
longestCloseRun = Math.max(longestCloseRun, closeRun);
|
|
24
29
|
}
|
|
25
|
-
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
open: BASE_PLACEHOLDER_OPEN.repeat(longestOpenRun + 1),
|
|
33
|
+
close: BASE_PLACEHOLDER_CLOSE.repeat(longestCloseRun + 1),
|
|
34
|
+
};
|
|
26
35
|
}
|
|
27
36
|
|
|
28
37
|
/**
|
|
@@ -16,7 +16,12 @@
|
|
|
16
16
|
* @module
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
export {
|
|
19
|
+
export {
|
|
20
|
+
resolveIncludes,
|
|
21
|
+
resolveIncludesAsync,
|
|
22
|
+
resolveIncludesAsyncWithTrace,
|
|
23
|
+
resolveIncludesWithTrace,
|
|
24
|
+
} from "./resolve";
|
|
20
25
|
export { extractIncludeReferences } from "./references";
|
|
21
26
|
export type {
|
|
22
27
|
IncludeFetcher,
|
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
import type { PageRef } from "@wdprlib/ast";
|
|
9
9
|
import { createCachedAsyncIncludeFetcher, createCachedIncludeFetcher } from "./cache";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
expandIterative,
|
|
12
|
+
expandIterativeAsyncWithTrace,
|
|
13
|
+
expandIterativeWithTrace,
|
|
14
|
+
} from "./iterate";
|
|
11
15
|
import type {
|
|
12
16
|
AsyncIncludeFetcher,
|
|
13
17
|
IncludeFetcher,
|
|
@@ -84,13 +88,29 @@ export async function resolveIncludesAsync(
|
|
|
84
88
|
fetcher: AsyncIncludeFetcher,
|
|
85
89
|
options?: ResolveIncludesOptions,
|
|
86
90
|
): Promise<string> {
|
|
91
|
+
return (await resolveIncludesAsyncWithTrace(source, fetcher, options)).source;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Async include expansion with dependency and iteration trace data.
|
|
96
|
+
*/
|
|
97
|
+
export async function resolveIncludesAsyncWithTrace(
|
|
98
|
+
source: string,
|
|
99
|
+
fetcher: AsyncIncludeFetcher,
|
|
100
|
+
options?: ResolveIncludesOptions,
|
|
101
|
+
): Promise<ResolveIncludesTraceResult> {
|
|
87
102
|
if (options?.settings && !options.settings.enablePageSyntax) {
|
|
88
|
-
return
|
|
103
|
+
return {
|
|
104
|
+
source,
|
|
105
|
+
dependencies: [],
|
|
106
|
+
iterations: [],
|
|
107
|
+
reachedMaxIterations: false,
|
|
108
|
+
};
|
|
89
109
|
}
|
|
90
110
|
|
|
91
111
|
const maxIterations = options?.maxIterations ?? 10;
|
|
92
112
|
const cachedFetcher = createCachedAsyncIncludeFetcher(fetcher, normalizePageKey);
|
|
93
|
-
return
|
|
113
|
+
return expandIterativeAsyncWithTrace(source, cachedFetcher, maxIterations);
|
|
94
114
|
}
|
|
95
115
|
|
|
96
116
|
/**
|
|
@@ -102,6 +102,41 @@ export async function expandIterativeAsync(
|
|
|
102
102
|
return current;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/** Async counterpart of {@link expandIterativeWithTrace}. */
|
|
106
|
+
export async function expandIterativeAsyncWithTrace(
|
|
107
|
+
source: string,
|
|
108
|
+
fetcher: AsyncIncludeFetcher,
|
|
109
|
+
maxIterations: number,
|
|
110
|
+
): Promise<ResolveIncludesTraceResult> {
|
|
111
|
+
let current = source;
|
|
112
|
+
const replacementCache = new Map<string, Promise<string>>();
|
|
113
|
+
const dependencies: IncludeDependency[] = [];
|
|
114
|
+
const iterations: ResolveIncludesTraceResult["iterations"] = [];
|
|
115
|
+
|
|
116
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
117
|
+
const expanded = await expandOneIterationAsyncWithTrace(current, fetcher, replacementCache, i);
|
|
118
|
+
if (expanded === null) break;
|
|
119
|
+
|
|
120
|
+
dependencies.push(...expanded.dependencies);
|
|
121
|
+
iterations.push({
|
|
122
|
+
iteration: i,
|
|
123
|
+
directives: expanded.references,
|
|
124
|
+
changed: expanded.source !== current,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (expanded.source === current) break;
|
|
128
|
+
current = expanded.source;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
source: current,
|
|
133
|
+
dependencies,
|
|
134
|
+
iterations,
|
|
135
|
+
reachedMaxIterations:
|
|
136
|
+
iterations.length === maxIterations && scanIncludeDirectives(current).length > 0,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
105
140
|
function expandOneIteration(
|
|
106
141
|
source: string,
|
|
107
142
|
fetcher: IncludeFetcher,
|
|
@@ -179,6 +214,42 @@ async function expandOneIterationAsync(
|
|
|
179
214
|
return parts.join("");
|
|
180
215
|
}
|
|
181
216
|
|
|
217
|
+
async function expandOneIterationAsyncWithTrace(
|
|
218
|
+
source: string,
|
|
219
|
+
fetcher: AsyncIncludeFetcher,
|
|
220
|
+
replacementCache: Map<string, Promise<string>>,
|
|
221
|
+
iteration: number,
|
|
222
|
+
): Promise<{
|
|
223
|
+
source: string;
|
|
224
|
+
references: IncludeReference[];
|
|
225
|
+
dependencies: IncludeDependency[];
|
|
226
|
+
} | null> {
|
|
227
|
+
if (!MAYBE_INCLUDE_PATTERN.test(source)) return null;
|
|
228
|
+
|
|
229
|
+
const directives = scanIncludeDirectives(source);
|
|
230
|
+
if (directives.length === 0) return null;
|
|
231
|
+
|
|
232
|
+
const references = directives.map(createIncludeReference);
|
|
233
|
+
const replacements = await Promise.all(
|
|
234
|
+
directives.map(({ inner }) => replaceCachedAsync(inner, fetcher, replacementCache)),
|
|
235
|
+
);
|
|
236
|
+
const parts: string[] = [];
|
|
237
|
+
let lastPos = 0;
|
|
238
|
+
|
|
239
|
+
for (let i = 0; i < directives.length; i++) {
|
|
240
|
+
const { start, end } = directives[i]!;
|
|
241
|
+
parts.push(source.slice(lastPos, start), replacements[i]!);
|
|
242
|
+
lastPos = end;
|
|
243
|
+
}
|
|
244
|
+
parts.push(source.slice(lastPos));
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
source: parts.join(""),
|
|
248
|
+
references,
|
|
249
|
+
dependencies: references.map((reference) => ({ ...reference, iteration })),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
182
253
|
function replaceCached(inner: string, fetcher: IncludeFetcher, cache: Map<string, string>): string {
|
|
183
254
|
const cached = cache.get(inner);
|
|
184
255
|
if (cached !== undefined) return cached;
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
// Module rule types and registry
|
|
23
|
-
export type { ModuleRule } from "./types";
|
|
23
|
+
export type { ModuleParseResult, ModuleRule } from "./types";
|
|
24
24
|
export { MODULE_RULES, getModuleRuleByName } from "./mapping";
|
|
25
25
|
export { moduleRule } from "./rule";
|
|
26
26
|
|
|
@@ -82,6 +82,8 @@ export {
|
|
|
82
82
|
parseParent,
|
|
83
83
|
parseDateSelector,
|
|
84
84
|
parseNumericSelector,
|
|
85
|
+
definePageData,
|
|
86
|
+
matchesListPagesSelectors,
|
|
85
87
|
} from "./listpages";
|
|
86
88
|
|
|
87
89
|
// IfTags module
|
|
@@ -106,6 +108,7 @@ export {
|
|
|
106
108
|
extractIncludeReferences,
|
|
107
109
|
resolveIncludes,
|
|
108
110
|
resolveIncludesAsync,
|
|
111
|
+
resolveIncludesAsyncWithTrace,
|
|
109
112
|
resolveIncludesWithTrace,
|
|
110
113
|
} from "./include";
|
|
111
114
|
|
|
@@ -59,6 +59,8 @@ export { extractDataRequirements } from "./extract";
|
|
|
59
59
|
// Resolution
|
|
60
60
|
export type { ParseFunction, ListPagesModuleData } from "./resolve";
|
|
61
61
|
export { isListPagesModule, resolveListPages } from "./resolve";
|
|
62
|
+
export { definePageData } from "./types/external-data";
|
|
63
|
+
export { matchesListPagesSelectors } from "./selectors";
|
|
62
64
|
|
|
63
65
|
// Compiler
|
|
64
66
|
export { compileTemplate } from "./compiler";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Element } from "@wdprlib/ast";
|
|
2
2
|
import type { CompiledTemplate, ListPagesExternalData, VariableContext } from "../types";
|
|
3
|
-
import type
|
|
3
|
+
import { getModuleParseAst, type ParseFunction } from "../../types";
|
|
4
4
|
import type { ListPagesModuleData } from "../resolve";
|
|
5
5
|
|
|
6
6
|
export function renderListPagesItems(
|
|
@@ -23,7 +23,7 @@ export function renderListPagesItems(
|
|
|
23
23
|
site: data.site,
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
const itemAst = parse(compiledTemplate(ctx));
|
|
26
|
+
const itemAst = getModuleParseAst(parse(compiledTemplate(ctx)));
|
|
27
27
|
|
|
28
28
|
if (module.separate) {
|
|
29
29
|
items.push({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Element } from "@wdprlib/ast";
|
|
2
|
-
import type
|
|
2
|
+
import { getModuleParseAst, type ParseFunction } from "../../types";
|
|
3
3
|
import type { ListPagesModuleData } from "../resolve";
|
|
4
4
|
|
|
5
5
|
export function wrapListPagesResult(
|
|
@@ -14,14 +14,14 @@ export function wrapListPagesResult(
|
|
|
14
14
|
const result: Element[] = [];
|
|
15
15
|
|
|
16
16
|
if (module["prepend-line"] && !module.separate) {
|
|
17
|
-
const prependAst = parse(module["prepend-line"]);
|
|
17
|
+
const prependAst = getModuleParseAst(parse(module["prepend-line"]));
|
|
18
18
|
result.push(...prependAst.elements);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
result.push(...items);
|
|
22
22
|
|
|
23
23
|
if (module["append-line"] && !module.separate) {
|
|
24
|
-
const appendAst = parse(module["append-line"]);
|
|
24
|
+
const appendAst = getModuleParseAst(parse(module["append-line"]));
|
|
25
25
|
result.push(...appendAst.elements);
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { PageData } from "./types/external-data";
|
|
2
|
+
import type {
|
|
3
|
+
NormalizedCategory,
|
|
4
|
+
NormalizedListPagesQuery,
|
|
5
|
+
NormalizedTags,
|
|
6
|
+
} from "./types/normalized-query";
|
|
7
|
+
|
|
8
|
+
type SelectorPage = Pick<PageData, "category" | "tags" | "hiddenTags">;
|
|
9
|
+
type CurrentPage = { category: string; tags: readonly string[] };
|
|
10
|
+
|
|
11
|
+
export function matchesListPagesSelectors(
|
|
12
|
+
page: SelectorPage,
|
|
13
|
+
query: Pick<NormalizedListPagesQuery, "category" | "tags">,
|
|
14
|
+
currentPage: CurrentPage,
|
|
15
|
+
): boolean {
|
|
16
|
+
return (
|
|
17
|
+
matchesCategory(page.category, query.category, currentPage.category) &&
|
|
18
|
+
matchesTags(page, query.tags, currentPage)
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function matchesCategory(
|
|
23
|
+
category: string,
|
|
24
|
+
selector: NormalizedCategory | undefined,
|
|
25
|
+
currentCategory: string,
|
|
26
|
+
): boolean {
|
|
27
|
+
if (!selector) return category === currentCategory;
|
|
28
|
+
if (selector.exclude.includes(category)) return false;
|
|
29
|
+
|
|
30
|
+
const hasPositiveSelector = selector.all || selector.current || selector.include.length > 0;
|
|
31
|
+
return (
|
|
32
|
+
!hasPositiveSelector ||
|
|
33
|
+
selector.all ||
|
|
34
|
+
(selector.current && category === currentCategory) ||
|
|
35
|
+
selector.include.includes(category)
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function matchesTags(
|
|
40
|
+
page: Pick<SelectorPage, "tags" | "hiddenTags">,
|
|
41
|
+
selector: NormalizedTags | undefined,
|
|
42
|
+
currentPage: Pick<CurrentPage, "tags">,
|
|
43
|
+
): boolean {
|
|
44
|
+
if (!selector) return true;
|
|
45
|
+
|
|
46
|
+
const allTags = new Set([...page.tags, ...page.hiddenTags]);
|
|
47
|
+
if (selector.all.some((tag) => !allTags.has(tag))) return false;
|
|
48
|
+
if (selector.any.length > 0 && !selector.any.some((tag) => allTags.has(tag))) return false;
|
|
49
|
+
if (selector.none.some((tag) => allTags.has(tag))) return false;
|
|
50
|
+
|
|
51
|
+
if (selector.special === "none") return allTags.size === 0;
|
|
52
|
+
const currentTags = new Set(currentPage.tags.filter((tag) => !tag.startsWith("_")));
|
|
53
|
+
if (selector.special === "same-visible") {
|
|
54
|
+
return page.tags.some((tag) => currentTags.has(tag));
|
|
55
|
+
}
|
|
56
|
+
if (selector.special === "same-all") {
|
|
57
|
+
return setsEqual(new Set(page.tags), currentTags);
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function setsEqual(left: ReadonlySet<string>, right: ReadonlySet<string>): boolean {
|
|
63
|
+
return left.size === right.size && [...left].every((value) => right.has(value));
|
|
64
|
+
}
|
|
@@ -58,6 +58,28 @@ export interface PageData {
|
|
|
58
58
|
revisions: number;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
type PageDataInput = Pick<PageData, "fullname" | "title" | "createdAt" | "updatedAt" | "tags"> &
|
|
62
|
+
Partial<Omit<PageData, "fullname" | "title" | "createdAt" | "updatedAt" | "tags">>;
|
|
63
|
+
|
|
64
|
+
export function definePageData(input: PageDataInput): PageData {
|
|
65
|
+
const separator = input.fullname.indexOf(":");
|
|
66
|
+
const category = separator === -1 ? "_default" : input.fullname.slice(0, separator);
|
|
67
|
+
const name = separator === -1 ? input.fullname : input.fullname.slice(separator + 1);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
...input,
|
|
71
|
+
name: input.name ?? name,
|
|
72
|
+
category: input.category ?? category,
|
|
73
|
+
hiddenTags: input.hiddenTags ?? [],
|
|
74
|
+
children: input.children ?? 0,
|
|
75
|
+
comments: input.comments ?? 0,
|
|
76
|
+
size: input.size ?? 0,
|
|
77
|
+
rating: input.rating ?? 0,
|
|
78
|
+
ratingVotes: input.ratingVotes ?? 0,
|
|
79
|
+
revisions: input.revisions ?? 0,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
61
83
|
/**
|
|
62
84
|
* Site context information.
|
|
63
85
|
*/
|
|
@@ -15,7 +15,7 @@ import type {
|
|
|
15
15
|
ListUsersCompiledTemplate,
|
|
16
16
|
ListUsersVariableContext,
|
|
17
17
|
} from "./types";
|
|
18
|
-
import type
|
|
18
|
+
import { getModuleParseAst, type ParseFunction } from "../types";
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Narrowed type for the list-users variant of the Module discriminated union.
|
|
@@ -53,6 +53,6 @@ export function resolveListUsers(
|
|
|
53
53
|
): Element[] {
|
|
54
54
|
const ctx: ListUsersVariableContext = { user: data.user };
|
|
55
55
|
const substituted = compiledTemplate(ctx);
|
|
56
|
-
const itemAst = parse(substituted);
|
|
56
|
+
const itemAst = getModuleParseAst(parse(substituted));
|
|
57
57
|
return itemAst.elements;
|
|
58
58
|
}
|