@wdprlib/parser 5.4.0 → 6.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/dist/index.cjs +420 -127
- package/dist/index.d.cts +93 -8
- package/dist/index.d.ts +93 -8
- package/dist/index.js +380 -84
- package/package.json +2 -2
- package/src/build-info.generated.ts +2 -2
- package/src/index.ts +3 -0
- package/src/parser/rules/block/module/index.ts +3 -2
- package/src/parser/rules/block/module/listpages/extraction/listpages.ts +5 -0
- package/src/parser/rules/block/module/listpages/extraction/query.ts +1 -0
- package/src/parser/rules/block/module/listpages/extraction/template.ts +16 -0
- package/src/parser/rules/block/module/listpages/extraction/variables.ts +1 -0
- package/src/parser/rules/block/module/listpages/index.ts +1 -0
- package/src/parser/rules/block/module/listpages/normalization/order-parent.ts +9 -1
- package/src/parser/rules/block/module/listpages/normalize.ts +1 -0
- package/src/parser/rules/block/module/listpages/parser.ts +1 -0
- package/src/parser/rules/block/module/listpages/resolve.ts +4 -2
- package/src/parser/rules/block/module/listpages/template/excerpt.ts +63 -0
- package/src/parser/rules/block/module/listpages/template/format/content.ts +0 -32
- package/src/parser/rules/block/module/listpages/template/format/index.ts +1 -1
- package/src/parser/rules/block/module/listpages/template/getters/index.ts +14 -0
- package/src/parser/rules/block/module/listpages/template/getters/parameterized.ts +3 -1
- package/src/parser/rules/block/module/listpages/template/getters/registered.ts +52 -0
- package/src/parser/rules/block/module/listpages/template/getters/simple.ts +15 -5
- package/src/parser/rules/block/module/listpages/template/literal.ts +6 -0
- package/src/parser/rules/block/module/listpages/template/syntax.ts +10 -1
- package/src/parser/rules/block/module/listpages/types/data-requirements.ts +6 -0
- package/src/parser/rules/block/module/listpages/types/external-data.ts +36 -2
- package/src/parser/rules/block/module/listpages/types/normalized-query.ts +14 -4
- package/src/parser/rules/block/module/listpages/types/query.ts +2 -0
- package/src/parser/rules/block/module/listpages/types/variables.ts +5 -0
- package/src/parser/rules/block/module/listpages/url-resolution/fields.ts +2 -0
- package/src/parser/rules/block/module/listusers/resolve.ts +2 -1
- package/src/parser/rules/block/module/mapping.ts +2 -1
- package/src/parser/rules/block/module/rate/index.ts +16 -19
- package/src/parser/rules/block/module/rate/resolve.ts +108 -0
- package/src/parser/rules/block/module/rate/types.ts +6 -16
- package/src/parser/rules/block/module/resolution/resolve-async.ts +2 -1
- package/src/parser/rules/block/module/resolve.ts +10 -8
- package/src/parser/rules/block/module/types-common.ts +4 -0
- package/src/parser/rules/inline/parsing/inline-content.ts +10 -1
- package/src/parser/rules/inline/size/content.ts +31 -4
- package/src/pipeline/process.ts +13 -0
- package/src/pipeline/types.ts +19 -0
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* All supported ListPages template variables.
|
|
3
3
|
*/
|
|
4
4
|
export type ListPagesVariable =
|
|
5
|
+
| "metadata"
|
|
6
|
+
| "customrate"
|
|
7
|
+
| "customrate_votes"
|
|
8
|
+
| "customrate_percent"
|
|
5
9
|
// Lifecycle - created
|
|
6
10
|
| "created_at"
|
|
7
11
|
| "created_by"
|
|
@@ -38,6 +42,7 @@ export type ListPagesVariable =
|
|
|
38
42
|
| "content_n"
|
|
39
43
|
| "preview"
|
|
40
44
|
| "preview_n"
|
|
45
|
+
| "excerpt"
|
|
41
46
|
| "summary"
|
|
42
47
|
| "first_paragraph"
|
|
43
48
|
// Tags
|
|
@@ -36,6 +36,7 @@ type StringUrlQueryKey = Extract<
|
|
|
36
36
|
| "updatedAt"
|
|
37
37
|
| "createdBy"
|
|
38
38
|
| "rating"
|
|
39
|
+
| "ratingAxis"
|
|
39
40
|
| "votes"
|
|
40
41
|
>;
|
|
41
42
|
|
|
@@ -81,6 +82,7 @@ export const URL_RESOLVABLE_FIELDS: readonly UrlResolvableField[] = [
|
|
|
81
82
|
aliases: ["createdby", "created_by"],
|
|
82
83
|
},
|
|
83
84
|
{ attr: "rating", queryKey: "rating", type: "string" },
|
|
85
|
+
{ attr: "rating-axis", queryKey: "ratingAxis", type: "string" },
|
|
84
86
|
{ attr: "votes", queryKey: "votes", type: "string" },
|
|
85
87
|
{ attr: "reverse", queryKey: "reverse", type: "boolean" },
|
|
86
88
|
];
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
ListUsersVariableContext,
|
|
17
17
|
} from "./types";
|
|
18
18
|
import { getModuleParseAst, type ParseFunction } from "../types";
|
|
19
|
+
import { suppressModuleRatings } from "../rate/resolve";
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Narrowed type for the list-users variant of the Module discriminated union.
|
|
@@ -53,6 +54,6 @@ export function resolveListUsers(
|
|
|
53
54
|
): Element[] {
|
|
54
55
|
const ctx: ListUsersVariableContext = { user: data.user };
|
|
55
56
|
const substituted = compiledTemplate(ctx);
|
|
56
|
-
const itemAst = getModuleParseAst(parse(substituted));
|
|
57
|
+
const itemAst = getModuleParseAst(suppressModuleRatings(parse(substituted)));
|
|
57
58
|
return itemAst.elements;
|
|
58
59
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import type { ModuleRule } from "./types";
|
|
14
|
-
import { rateModuleRule } from "./rate/index";
|
|
14
|
+
import { rateModuleRule, customRateModuleRule } from "./rate/index";
|
|
15
15
|
import { cssModuleRule } from "./css/index";
|
|
16
16
|
import { backlinksModuleRule } from "./backlinks/index";
|
|
17
17
|
import { categoriesModuleRule } from "./categories/index";
|
|
@@ -29,6 +29,7 @@ import { tagCloudModuleRule } from "./tagcloud/parser";
|
|
|
29
29
|
*/
|
|
30
30
|
export const MODULE_RULES: ModuleRule[] = [
|
|
31
31
|
rateModuleRule,
|
|
32
|
+
customRateModuleRule,
|
|
32
33
|
cssModuleRule,
|
|
33
34
|
backlinksModuleRule,
|
|
34
35
|
categoriesModuleRule,
|
|
@@ -1,28 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
3
|
-
* Parser rule for the Wikidot `[[module Rate]]` block.
|
|
4
|
-
*
|
|
5
|
-
* Renders a page rating widget. This is a simple module with no attributes
|
|
6
|
-
* and no body content.
|
|
7
|
-
*
|
|
8
|
-
* @module
|
|
9
|
-
*/
|
|
10
|
-
|
|
1
|
+
import type { CustomRateModuleData, RateModuleData } from "@wdprlib/ast";
|
|
11
2
|
import type { ModuleRule } from "../types";
|
|
12
|
-
import type { RateModuleData } from "./types";
|
|
13
3
|
|
|
14
|
-
/**
|
|
15
|
-
* Module rule for `[[module Rate]]`.
|
|
16
|
-
*
|
|
17
|
-
* Simply produces a `{ module: "rate" }` AST node. The rendering application
|
|
18
|
-
* is responsible for displaying upvote/downvote buttons and the current rating.
|
|
19
|
-
*/
|
|
4
|
+
/** The main rating accepts no page, axis, policy, or permission attributes. */
|
|
20
5
|
export const rateModuleRule: ModuleRule = {
|
|
21
6
|
name: "module-rate",
|
|
22
7
|
acceptsNames: ["rate"],
|
|
23
8
|
hasBody: false,
|
|
9
|
+
parse(_ctx, _pos, args): RateModuleData {
|
|
10
|
+
return { module: "rate", ref: Object.keys(args).length === 0 ? { kind: "main" } : null };
|
|
11
|
+
},
|
|
12
|
+
};
|
|
24
13
|
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
/** Reference an exact, host-registered key on the displayed page. */
|
|
15
|
+
export const customRateModuleRule: ModuleRule = {
|
|
16
|
+
name: "module-custom-rate",
|
|
17
|
+
acceptsNames: ["customrate"],
|
|
18
|
+
hasBody: false,
|
|
19
|
+
parse(_ctx, _pos, args): CustomRateModuleData {
|
|
20
|
+
const key = args.key;
|
|
21
|
+
const valid =
|
|
22
|
+
key !== undefined && key !== "" && Object.keys(args).every((name) => name === "key");
|
|
23
|
+
return { module: "custom-rate", ref: valid ? { kind: "custom", axisKey: key } : null };
|
|
27
24
|
},
|
|
28
25
|
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { Element, RatingRef, RatingState, SyntaxTree } from "@wdprlib/ast";
|
|
2
|
+
import { getModuleParseAst, type ModuleParseResult } from "../types";
|
|
3
|
+
import { mapElementChildren } from "../walk";
|
|
4
|
+
import type { RatingsFetcher } from "./types";
|
|
5
|
+
|
|
6
|
+
function refKey(ref: RatingRef): string {
|
|
7
|
+
return ref.kind === "main" ? "main" : `custom:${ref.axisKey}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Resolve once after includes and module expansion, using only the displayed page. */
|
|
11
|
+
export async function resolveRatings(
|
|
12
|
+
ast: SyntaxTree,
|
|
13
|
+
fetchRatings?: RatingsFetcher,
|
|
14
|
+
): Promise<SyntaxTree> {
|
|
15
|
+
const refs = new Map<string, RatingRef>();
|
|
16
|
+
mapDocument(ast, (element) => {
|
|
17
|
+
if (isRating(element) && element.data.ref) refs.set(refKey(element.data.ref), element.data.ref);
|
|
18
|
+
return element;
|
|
19
|
+
});
|
|
20
|
+
const states = new Map<string, RatingState>();
|
|
21
|
+
if (fetchRatings && refs.size > 0) {
|
|
22
|
+
for (const state of await fetchRatings([...refs.values()])) {
|
|
23
|
+
const key = refKey(state.ref);
|
|
24
|
+
if (refs.has(key)) states.set(key, state);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return mapDocument(ast, (element) => {
|
|
28
|
+
if (!isRating(element)) return element;
|
|
29
|
+
const { state: _previous, ...data } = element.data;
|
|
30
|
+
const state = data.ref ? states.get(refKey(data.ref)) : undefined;
|
|
31
|
+
return { ...element, data: state ? { ...data, state } : data };
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Module-generated source cannot declare ratings, including in its footnote side channel. */
|
|
36
|
+
export function suppressModuleRatings(result: ModuleParseResult): ModuleParseResult {
|
|
37
|
+
const ast = mapDocument(getModuleParseAst(result), (element) =>
|
|
38
|
+
isRating(element) ? null : element,
|
|
39
|
+
);
|
|
40
|
+
return "ast" in result ? { ...result, ast } : ast;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isRating(element: Element): element is Extract<Element, { element: "module" }> & {
|
|
44
|
+
data: Extract<
|
|
45
|
+
Extract<Element, { element: "module" }>["data"],
|
|
46
|
+
{ module: "rate" | "custom-rate" }
|
|
47
|
+
>;
|
|
48
|
+
} {
|
|
49
|
+
return (
|
|
50
|
+
element.element === "module" &&
|
|
51
|
+
(element.data.module === "rate" || element.data.module === "custom-rate")
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function mapDocument(ast: SyntaxTree, transform: (element: Element) => Element | null): SyntaxTree {
|
|
56
|
+
const map = (elements: Element[]): Element[] =>
|
|
57
|
+
elements.flatMap((original) => {
|
|
58
|
+
const element = transform(original);
|
|
59
|
+
if (!element) return [];
|
|
60
|
+
if (element.element === "if") {
|
|
61
|
+
return [
|
|
62
|
+
{
|
|
63
|
+
...element,
|
|
64
|
+
data: {
|
|
65
|
+
...element.data,
|
|
66
|
+
// oxlint-disable-next-line unicorn/no-thenable -- public AST branch name
|
|
67
|
+
then: map(element.data.then),
|
|
68
|
+
else: map(element.data.else),
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
if (element.element === "ifexpr") {
|
|
74
|
+
return [
|
|
75
|
+
{
|
|
76
|
+
...element,
|
|
77
|
+
data: {
|
|
78
|
+
...element.data,
|
|
79
|
+
// oxlint-disable-next-line unicorn/no-thenable -- public AST branch name
|
|
80
|
+
then: map(element.data.then),
|
|
81
|
+
else: map(element.data.else),
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
if (element.element === "bibliography-block") {
|
|
87
|
+
return [
|
|
88
|
+
{
|
|
89
|
+
...element,
|
|
90
|
+
data: {
|
|
91
|
+
...element.data,
|
|
92
|
+
entries: element.data.entries.map((entry) => ({
|
|
93
|
+
...entry,
|
|
94
|
+
key: map(entry.key),
|
|
95
|
+
value: map(entry.value),
|
|
96
|
+
})),
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
return [mapElementChildren(element, map)];
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
...ast,
|
|
105
|
+
elements: map(ast.elements),
|
|
106
|
+
...(ast.footnotes ? { footnotes: ast.footnotes.map(map) } : {}),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -1,19 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Type definitions for the Rate module.
|
|
4
|
-
*
|
|
5
|
-
* The `[[module Rate]]` block renders a page rating widget (upvote/downvote buttons).
|
|
6
|
-
* It takes no attributes and has no body.
|
|
7
|
-
*
|
|
8
|
-
* @module
|
|
9
|
-
*/
|
|
1
|
+
import type { RatingRef, RatingState } from "@wdprlib/ast";
|
|
2
|
+
export type { RateModuleData, CustomRateModuleData } from "@wdprlib/ast";
|
|
10
3
|
|
|
11
4
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* is responsible for displaying the appropriate rating widget.
|
|
5
|
+
* Read authorized ratings for the displayed page. Omit unknown, disabled, or
|
|
6
|
+
* inaccessible references. The host owns registration, category policies,
|
|
7
|
+
* access checks, aggregates, and persistence; rendering must never create data.
|
|
16
8
|
*/
|
|
17
|
-
export
|
|
18
|
-
module: "rate";
|
|
19
|
-
}
|
|
9
|
+
export type RatingsFetcher = (refs: readonly RatingRef[]) => Promise<readonly RatingState[]>;
|
|
@@ -28,6 +28,7 @@ import { ModuleDocumentRegistry } from "./document";
|
|
|
28
28
|
import { collectStyles, mergeCollectedStyles } from "./styles";
|
|
29
29
|
import { createListPagesPager } from "../listpages/resolution/pager";
|
|
30
30
|
import type { ListPagesPaginationState } from "./data-maps";
|
|
31
|
+
import { suppressModuleRatings } from "../rate/resolve";
|
|
31
32
|
|
|
32
33
|
export type AsyncModuleParseFunction = (source: string) => Promise<ModuleParseResult>;
|
|
33
34
|
|
|
@@ -73,7 +74,7 @@ export async function resolveModulesWithAsyncParse(
|
|
|
73
74
|
const registry = new ModuleDocumentRegistry();
|
|
74
75
|
registry.register(ast);
|
|
75
76
|
const parse = async (source: string): Promise<SyntaxTree> =>
|
|
76
|
-
registry.register(await options.parse(source));
|
|
77
|
+
registry.register(suppressModuleRatings(await options.parse(source)));
|
|
77
78
|
|
|
78
79
|
const [listPagesData, listUsersData, tagCloudData] = await Promise.all([
|
|
79
80
|
buildListPagesDataMap(
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
import { walkAndResolve } from "./resolution/walk-resolve";
|
|
32
32
|
import { collectStyles, mergeCollectedStyles } from "./resolution/styles";
|
|
33
33
|
import { containsSyntaxFootnoteBlock, ModuleDocumentRegistry } from "./resolution/document";
|
|
34
|
+
import { resolveRatings, suppressModuleRatings } from "./rate/resolve";
|
|
34
35
|
|
|
35
36
|
const MODULE_SECONDARY_INCLUDE_MAX_ITERATIONS = 5;
|
|
36
37
|
|
|
@@ -188,11 +189,9 @@ export async function resolveModules(
|
|
|
188
189
|
if (mergedStyles.length > 0) result.styles = mergedStyles;
|
|
189
190
|
|
|
190
191
|
options.onDiagnostics?.(registry.diagnostics);
|
|
191
|
-
return
|
|
192
|
-
result,
|
|
193
|
-
|
|
194
|
-
pageTags,
|
|
195
|
-
containsSyntaxFootnoteBlock(ast.elements),
|
|
192
|
+
return resolveRatings(
|
|
193
|
+
registry.finalize(result, finalElements, pageTags, containsSyntaxFootnoteBlock(ast.elements)),
|
|
194
|
+
dataProvider.fetchRatings,
|
|
196
195
|
);
|
|
197
196
|
}
|
|
198
197
|
|
|
@@ -203,9 +202,12 @@ function createModuleParseFunction(
|
|
|
203
202
|
): ParseFunction {
|
|
204
203
|
const transform = createModuleSourceTransform(options, dataProvider);
|
|
205
204
|
return (source: string) =>
|
|
206
|
-
registry.register(
|
|
207
|
-
|
|
208
|
-
|
|
205
|
+
registry.register(
|
|
206
|
+
suppressModuleRatings(options.parse(transform ? transform(source) : source)),
|
|
207
|
+
{
|
|
208
|
+
stripLegacyImplicitFootnoteBlock: true,
|
|
209
|
+
},
|
|
210
|
+
);
|
|
209
211
|
}
|
|
210
212
|
|
|
211
213
|
function createModuleSourceTransform(
|
|
@@ -17,6 +17,7 @@ import type { ListUsersDataFetcher } from "./listusers/types";
|
|
|
17
17
|
import type { TagCloudDataFetcher } from "./tagcloud/types";
|
|
18
18
|
import type { IfTagsResolver } from "./iftags/types";
|
|
19
19
|
import type { IncludeFetcher } from "./include/resolve/types";
|
|
20
|
+
import type { RatingsFetcher } from "./rate/types";
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Callback bag for supplying external data during module resolution.
|
|
@@ -29,6 +30,9 @@ import type { IncludeFetcher } from "./include/resolve/types";
|
|
|
29
30
|
* @group Module Resolution
|
|
30
31
|
*/
|
|
31
32
|
export interface DataProvider {
|
|
33
|
+
/** Read registered ratings for the displayed page; omitted references stay hidden. */
|
|
34
|
+
fetchRatings?: RatingsFetcher;
|
|
35
|
+
|
|
32
36
|
/**
|
|
33
37
|
* Fetch page data for `[[module ListPages]]` expansion.
|
|
34
38
|
*
|
|
@@ -29,6 +29,7 @@ export interface InlineParseResult {
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Parse inline content until a specific token type.
|
|
32
|
+
* EOF mode preserves literal newlines within the inherited inline boundary.
|
|
32
33
|
*
|
|
33
34
|
* When endType is "PARAGRAPH_BREAK", handles NEWLINEs and stops at:
|
|
34
35
|
* - Double NEWLINE (paragraph break)
|
|
@@ -42,7 +43,8 @@ export function parseInlineUntil(ctx: ParseContext, endType: InlineEndType): Inl
|
|
|
42
43
|
let consumedEmptyRaw = false;
|
|
43
44
|
|
|
44
45
|
const paragraphMode = endType === "PARAGRAPH_BREAK";
|
|
45
|
-
const
|
|
46
|
+
const preserveNewlines = endType === "EOF";
|
|
47
|
+
const multiline = paragraphMode || preserveNewlines || FORMATTING_CLOSE_TOKENS.has(endType);
|
|
46
48
|
let inlineEnd = ctx.scope.inlineEnd ?? ctx.tokens.length;
|
|
47
49
|
if (!multiline) {
|
|
48
50
|
for (let end = ctx.pos; end < inlineEnd; end++) {
|
|
@@ -94,6 +96,13 @@ export function parseInlineUntil(ctx: ParseContext, endType: InlineEndType): Inl
|
|
|
94
96
|
break;
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
if (preserveNewlines && token.type === "NEWLINE") {
|
|
100
|
+
nodes.push({ element: "text", data: token.value });
|
|
101
|
+
pos++;
|
|
102
|
+
consumed++;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
97
106
|
if (multiline && token.type === "NEWLINE" && !ctx.scope.tableFormatting) {
|
|
98
107
|
const boundary = getParagraphNewlineBoundary(ctx, pos, nodes.length > 0);
|
|
99
108
|
if (boundary.shouldBreak) {
|
|
@@ -2,6 +2,8 @@ import type { Element } from "@wdprlib/ast";
|
|
|
2
2
|
import type { ParseContext } from "../../types";
|
|
3
3
|
import { parseBlockName } from "../../common";
|
|
4
4
|
import { parseInlineUntil } from "../utils";
|
|
5
|
+
import { protectedInlineRegionEnd } from "../raw/end";
|
|
6
|
+
import { parseSizeOpen } from "./open";
|
|
5
7
|
|
|
6
8
|
export interface SizeContentResult {
|
|
7
9
|
children: Element[];
|
|
@@ -13,8 +15,9 @@ export function parseSizeContent(ctx: ParseContext, startPos: number): SizeConte
|
|
|
13
15
|
const children: Element[] = [];
|
|
14
16
|
let pos = startPos;
|
|
15
17
|
let consumed = 0;
|
|
18
|
+
const inlineEnd = findSizeClose(ctx, startPos);
|
|
16
19
|
|
|
17
|
-
while (pos < ctx.tokens.length) {
|
|
20
|
+
while (pos < (ctx.scope.inlineEnd ?? ctx.tokens.length)) {
|
|
18
21
|
const token = ctx.tokens[pos];
|
|
19
22
|
if (!token || token.type === "EOF") {
|
|
20
23
|
break;
|
|
@@ -29,9 +32,9 @@ export function parseSizeContent(ctx: ParseContext, startPos: number): SizeConte
|
|
|
29
32
|
};
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
const inlineCtx: ParseContext = { ...ctx, pos };
|
|
33
|
-
const inlineResult = parseInlineUntil(inlineCtx, "
|
|
34
|
-
if (inlineResult.
|
|
35
|
+
const inlineCtx: ParseContext = { ...ctx, pos, scope: { ...ctx.scope, inlineEnd } };
|
|
36
|
+
const inlineResult = parseInlineUntil(inlineCtx, "EOF");
|
|
37
|
+
if (inlineResult.consumed > 0) {
|
|
35
38
|
for (const element of inlineResult.elements) children.push(element);
|
|
36
39
|
pos += inlineResult.consumed;
|
|
37
40
|
consumed += inlineResult.consumed;
|
|
@@ -45,6 +48,30 @@ export function parseSizeContent(ctx: ParseContext, startPos: number): SizeConte
|
|
|
45
48
|
return { children, consumed, foundClose: false };
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
function findSizeClose(ctx: ParseContext, startPos: number): number {
|
|
52
|
+
const end = ctx.scope.inlineEnd ?? ctx.tokens.length;
|
|
53
|
+
let depth = 0;
|
|
54
|
+
for (let pos = startPos; pos < end; pos++) {
|
|
55
|
+
const protectedEnd = protectedInlineRegionEnd(ctx.tokens, pos, end);
|
|
56
|
+
if (protectedEnd > pos) {
|
|
57
|
+
pos = protectedEnd - 1;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const open = ctx.tokens[pos]?.type === "BLOCK_OPEN" ? parseSizeOpen({ ...ctx, pos }) : null;
|
|
61
|
+
if (open) {
|
|
62
|
+
depth++;
|
|
63
|
+
pos = open.bodyStart - 1;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const close = tryConsumeSizeClose(ctx, pos);
|
|
67
|
+
if (!close) continue;
|
|
68
|
+
if (depth === 0) return pos;
|
|
69
|
+
depth--;
|
|
70
|
+
pos += close.consumed - 1;
|
|
71
|
+
}
|
|
72
|
+
return end;
|
|
73
|
+
}
|
|
74
|
+
|
|
48
75
|
function tryConsumeSizeClose(ctx: ParseContext, pos: number): { consumed: number } | null {
|
|
49
76
|
if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
|
|
50
77
|
return null;
|
package/src/pipeline/process.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_SETTINGS,
|
|
3
|
+
extractReadableText,
|
|
4
|
+
extractFirstParagraph,
|
|
5
|
+
countCharacters,
|
|
3
6
|
type Diagnostic,
|
|
4
7
|
type PageRef,
|
|
5
8
|
type WikitextPageContext,
|
|
@@ -10,6 +13,7 @@ import type { AsyncIncludeFetcher, IncludeDependency } from "../parser/rules/blo
|
|
|
10
13
|
import { resolveIncludesAsyncWithTraceSelective } from "../parser/rules/block/module/include/resolve";
|
|
11
14
|
import type { DataProvider } from "../parser/rules/block/module/types-common";
|
|
12
15
|
import { resolveModulesWithAsyncParse } from "../parser/rules/block/module/resolution/resolve-async";
|
|
16
|
+
import { resolveRatings } from "../parser/rules/block/module/rate/resolve";
|
|
13
17
|
import type {
|
|
14
18
|
ProcessedWikitextDocument,
|
|
15
19
|
ProcessWikitextCallbackContext,
|
|
@@ -117,8 +121,14 @@ export async function processWikitext<TPage extends WikitextPageContext>(
|
|
|
117
121
|
);
|
|
118
122
|
}
|
|
119
123
|
|
|
124
|
+
ast = await resolveRatings(ast, dataProvider.fetchRatings);
|
|
125
|
+
const readableText = extractReadableText(ast, options.readableText);
|
|
126
|
+
|
|
120
127
|
return {
|
|
121
128
|
ast,
|
|
129
|
+
readableText,
|
|
130
|
+
firstParagraph: extractFirstParagraph(ast, options.readableText),
|
|
131
|
+
characterCount: countCharacters(readableText),
|
|
122
132
|
page: options.page,
|
|
123
133
|
settings,
|
|
124
134
|
diagnostics,
|
|
@@ -167,6 +177,9 @@ function createModuleDataProvider<TPage extends WikitextPageContext>(
|
|
|
167
177
|
): DataProvider {
|
|
168
178
|
const provider = options.dataProvider;
|
|
169
179
|
return {
|
|
180
|
+
fetchRatings: provider?.fetchRatings
|
|
181
|
+
? (refs) => provider.fetchRatings!(refs, context)
|
|
182
|
+
: undefined,
|
|
170
183
|
fetchListPages: provider?.fetchListPages
|
|
171
184
|
? (query, requirement) => provider.fetchListPages!(query, requirement, context)
|
|
172
185
|
: undefined,
|
package/src/pipeline/types.ts
CHANGED
|
@@ -4,6 +4,9 @@ import type {
|
|
|
4
4
|
SyntaxTree,
|
|
5
5
|
WikitextPageContext,
|
|
6
6
|
WikitextSettings,
|
|
7
|
+
RatingRef,
|
|
8
|
+
RatingState,
|
|
9
|
+
ReadableTextOptions,
|
|
7
10
|
} from "@wdprlib/ast";
|
|
8
11
|
import type { IncludeDependency } from "../parser/rules/block/module/include";
|
|
9
12
|
import type {
|
|
@@ -26,6 +29,17 @@ export interface ProcessWikitextCallbackContext<TPage extends WikitextPageContex
|
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
export interface ProcessWikitextDataProvider<TPage extends WikitextPageContext> {
|
|
32
|
+
/**
|
|
33
|
+
* Read only authorized, registered ratings in context.page, including declarations
|
|
34
|
+
* from its includes. Keys match exactly. Omit no-rate and inaccessible references.
|
|
35
|
+
* Main category policy and custom policies are independent. Votes belong to page + ref.
|
|
36
|
+
* ListPages/ListUsers output cannot declare ratings, including its nested includes.
|
|
37
|
+
* The host owns registration, aggregation and invalidation when included declarations change.
|
|
38
|
+
*/
|
|
39
|
+
fetchRatings?: (
|
|
40
|
+
refs: readonly RatingRef[],
|
|
41
|
+
context: ProcessWikitextCallbackContext<TPage>,
|
|
42
|
+
) => Promise<readonly RatingState[]>;
|
|
29
43
|
fetchInclude?: (
|
|
30
44
|
pageRef: PageRef,
|
|
31
45
|
context: ProcessWikitextCallbackContext<TPage>,
|
|
@@ -50,12 +64,17 @@ export interface ProcessWikitextOptions<TPage extends WikitextPageContext> {
|
|
|
50
64
|
settings?: WikitextSettings;
|
|
51
65
|
dataProvider?: ProcessWikitextDataProvider<TPage>;
|
|
52
66
|
includeMaxIterations?: number;
|
|
67
|
+
readableText?: ReadableTextOptions;
|
|
53
68
|
}
|
|
54
69
|
|
|
55
70
|
export interface ProcessedWikitextDocument<
|
|
56
71
|
TPage extends WikitextPageContext = WikitextPageContext,
|
|
57
72
|
> {
|
|
58
73
|
ast: SyntaxTree;
|
|
74
|
+
readableText: string;
|
|
75
|
+
/** First nonempty body paragraph, excluding headings and appended footnotes. */
|
|
76
|
+
firstParagraph: string;
|
|
77
|
+
characterCount: number;
|
|
59
78
|
page: TPage;
|
|
60
79
|
settings: WikitextSettings;
|
|
61
80
|
diagnostics: Diagnostic[];
|