@wdprlib/parser 5.1.6 → 5.3.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 +764 -452
- package/dist/index.d.cts +24 -4
- package/dist/index.d.ts +24 -4
- package/dist/index.js +762 -452
- package/package.json +2 -2
- package/src/build-info.generated.ts +11 -0
- package/src/index.ts +2 -0
- package/src/lexer/lexer.ts +6 -4
- package/src/lexer/punctuation.ts +2 -1
- package/src/lexer/spacing-actions.ts +15 -0
- package/src/lexer/state.ts +30 -4
- package/src/lexer/token-factory.ts +15 -2
- package/src/lexer/tokens.ts +4 -3
- package/src/parser/rules/block/blockquote/build.ts +81 -37
- package/src/parser/rules/block/blockquote/index.ts +5 -6
- package/src/parser/rules/block/blockquote/line.ts +11 -10
- package/src/parser/rules/block/blockquote/lines.ts +102 -4
- package/src/parser/rules/block/code/content.ts +0 -3
- package/src/parser/rules/block/code/index.ts +14 -10
- package/src/parser/rules/block/definition-list/index.ts +4 -10
- package/src/parser/rules/block/definition-list/item-value.ts +2 -11
- package/src/parser/rules/block/definition-list/items.ts +1 -2
- package/src/parser/rules/block/embed-block/index.ts +30 -30
- package/src/parser/rules/block/heading/index.ts +7 -0
- package/src/parser/rules/block/module/listpages/resolution/items.ts +2 -2
- package/src/parser/rules/block/module/listpages/resolution/pager.ts +43 -0
- package/src/parser/rules/block/module/listpages/resolution/wrapper.ts +6 -1
- package/src/parser/rules/block/module/listpages/resolve.ts +1 -1
- package/src/parser/rules/block/module/listpages/types/external-data.ts +13 -0
- package/src/parser/rules/block/module/listpages/url-resolution/params.ts +5 -4
- package/src/parser/rules/block/module/resolution/data-maps.ts +75 -3
- package/src/parser/rules/block/module/resolution/resolve-async.ts +12 -3
- package/src/parser/rules/block/paragraph/index.ts +41 -10
- package/src/parser/rules/block/parsing/content.ts +18 -1
- package/src/parser/rules/block/table/pipe/cell.ts +36 -1
- package/src/parser/rules/block/table/pipe/row.ts +21 -1
- package/src/parser/rules/block/toc/element.ts +2 -2
- package/src/parser/rules/block/toc/index.ts +2 -2
- package/src/parser/rules/block/toc/open.ts +5 -18
- package/src/parser/rules/contracts/scope.ts +4 -0
- package/src/parser/rules/inline/bold.ts +5 -5
- package/src/parser/rules/inline/color/syntax.ts +5 -8
- package/src/parser/rules/inline/formatting/close.ts +39 -0
- package/src/parser/rules/inline/formatting/container.ts +7 -4
- package/src/parser/rules/inline/index.ts +2 -0
- package/src/parser/rules/inline/italic.ts +5 -5
- package/src/parser/rules/inline/monospace.ts +5 -5
- package/src/parser/rules/inline/parsing/block-start-predicates.ts +2 -0
- package/src/parser/rules/inline/parsing/inline-content.ts +58 -4
- package/src/parser/rules/inline/raw/end.ts +28 -0
- package/src/parser/rules/inline/span/content.ts +32 -1
- package/src/parser/rules/inline/strikethrough/index.ts +1 -1
- package/src/parser/rules/inline/strikethrough/parse.ts +2 -9
- package/src/parser/rules/inline/strikethrough/syntax.ts +3 -20
- package/src/parser/rules/inline/subscript.ts +5 -5
- package/src/parser/rules/inline/superscript.ts +5 -5
- package/src/parser/rules/inline/underline/index.ts +5 -78
- package/src/parser/rules/tokens.ts +6 -15
- package/src/pipeline/process.ts +8 -1
- package/src/parser/rules/inline/underline/child.ts +0 -26
- package/src/parser/rules/inline/underline/content.ts +0 -29
|
@@ -27,17 +27,8 @@ export function parseDefinitionItemValue(
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
if (token.type === "NEWLINE") {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
pos++;
|
|
33
|
-
consumed++;
|
|
34
|
-
break;
|
|
35
|
-
}
|
|
36
|
-
if (nextToken?.type === "NEWLINE" || !nextToken || nextToken.type === "EOF") {
|
|
37
|
-
pos++;
|
|
38
|
-
consumed++;
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
30
|
+
consumed++;
|
|
31
|
+
break;
|
|
41
32
|
}
|
|
42
33
|
|
|
43
34
|
const inlineCtx: ParseContext = { ...ctx, pos };
|
|
@@ -21,8 +21,7 @@ export interface ParsedDefinitionItem {
|
|
|
21
21
|
*
|
|
22
22
|
* The function expects `startPos` to point at a line-start COLON token.
|
|
23
23
|
* It consumes the first colon, mandatory whitespace, key tokens up to
|
|
24
|
-
* the second colon, then value tokens until a
|
|
25
|
-
* or end of input.
|
|
24
|
+
* the second colon, then value tokens until a newline or end of input.
|
|
26
25
|
*/
|
|
27
26
|
export function parseDefinitionItem(
|
|
28
27
|
ctx: ParseContext,
|
|
@@ -8,9 +8,6 @@
|
|
|
8
8
|
* between the tags is stored verbatim as an `embed-block` element. Validation
|
|
9
9
|
* and sanitisation are expected to happen at rendering time or on the server.
|
|
10
10
|
*
|
|
11
|
-
* The embed block is wrapped in a paragraph container in the AST, matching
|
|
12
|
-
* Wikidot's rendering behaviour where embeds sit inside `<p>` tags.
|
|
13
|
-
*
|
|
14
11
|
* If no closing tag is found, the rule fails to prevent consuming the rest
|
|
15
12
|
* of the document.
|
|
16
13
|
*
|
|
@@ -18,6 +15,8 @@
|
|
|
18
15
|
*/
|
|
19
16
|
import type { Element } from "@wdprlib/ast";
|
|
20
17
|
import type { BlockRule, ParseContext, RuleResult } from "../../types";
|
|
18
|
+
import { parseInlineUntil } from "../../inline/utils";
|
|
19
|
+
import { normalizeParagraphElements } from "../paragraph/normalize";
|
|
21
20
|
import { currentToken } from "../../types";
|
|
22
21
|
import { collectEmbedContent, consumeEmbedClose } from "./content";
|
|
23
22
|
import { parseEmbedBlockOpen } from "./open";
|
|
@@ -34,6 +33,8 @@ export const embedBlockRule: BlockRule = {
|
|
|
34
33
|
name: "embed-block",
|
|
35
34
|
startTokens: ["BLOCK_OPEN"],
|
|
36
35
|
requiresLineStart: false,
|
|
36
|
+
preservesPrecedingLineBreak: true,
|
|
37
|
+
isStartPattern: (ctx, pos) => parseEmbedBlockOpen(ctx, pos) !== null,
|
|
37
38
|
|
|
38
39
|
parse(ctx: ParseContext): RuleResult<Element> {
|
|
39
40
|
const openToken = currentToken(ctx);
|
|
@@ -53,12 +54,17 @@ export const embedBlockRule: BlockRule = {
|
|
|
53
54
|
consumed += contentResult.consumed;
|
|
54
55
|
|
|
55
56
|
if (!contentResult.foundClose) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
if (
|
|
58
|
+
!ctx.diagnostics.some(
|
|
59
|
+
(d) => d.code === "unclosed-block" && d.position === openToken.position,
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
ctx.diagnostics.push({
|
|
63
|
+
severity: "warning",
|
|
64
|
+
code: "unclosed-block",
|
|
65
|
+
message: `Missing closing tag [[/${openResult.blockName}]] for [[${openResult.blockName}]]`,
|
|
66
|
+
position: openToken.position,
|
|
67
|
+
});
|
|
62
68
|
return { success: false };
|
|
63
69
|
}
|
|
64
70
|
|
|
@@ -66,26 +72,20 @@ export const embedBlockRule: BlockRule = {
|
|
|
66
72
|
pos += closeConsumed;
|
|
67
73
|
consumed += closeConsumed;
|
|
68
74
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
],
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
consumed,
|
|
89
|
-
};
|
|
75
|
+
const elements: Element[] = [
|
|
76
|
+
{ element: "embed-block", data: { contents: contentResult.contents.trim() } },
|
|
77
|
+
];
|
|
78
|
+
if (
|
|
79
|
+
ctx.scope.inlineEnd === undefined &&
|
|
80
|
+
ctx.tokens[pos]?.type !== "NEWLINE" &&
|
|
81
|
+
ctx.tokens[pos]?.type !== "EOF"
|
|
82
|
+
) {
|
|
83
|
+
const after = parseInlineUntil({ ...ctx, pos }, "PARAGRAPH_BREAK");
|
|
84
|
+
const tail = normalizeParagraphElements(after.elements);
|
|
85
|
+
if (tail[0]?.element === "text") tail[0].data = tail[0].data.trimStart();
|
|
86
|
+
elements.push(...tail);
|
|
87
|
+
consumed += after.consumed;
|
|
88
|
+
}
|
|
89
|
+
return { success: true, elements, consumed };
|
|
90
90
|
},
|
|
91
91
|
};
|
|
@@ -55,6 +55,13 @@ export const headingRule: BlockRule = {
|
|
|
55
55
|
const inlineCtx: ParseContext = { ...ctx, pos };
|
|
56
56
|
const inlineResult = parseInlineUntil(inlineCtx, "NEWLINE");
|
|
57
57
|
const children: Element[] = inlineResult.elements;
|
|
58
|
+
while (children.at(-1)?.element === "text") {
|
|
59
|
+
const last = children.at(-1)!;
|
|
60
|
+
if (last.element !== "text") break;
|
|
61
|
+
last.data = last.data.trimEnd();
|
|
62
|
+
if (last.data) break;
|
|
63
|
+
children.pop();
|
|
64
|
+
}
|
|
58
65
|
consumed += inlineResult.consumed;
|
|
59
66
|
pos += inlineResult.consumed;
|
|
60
67
|
|
|
@@ -17,9 +17,9 @@ export function renderListPagesItems(
|
|
|
17
17
|
|
|
18
18
|
const ctx: VariableContext = {
|
|
19
19
|
page,
|
|
20
|
-
index: i + 1,
|
|
20
|
+
index: (data.pagination?.offset ?? 0) + i + 1,
|
|
21
21
|
total: data.totalCount,
|
|
22
|
-
limit: module.limit,
|
|
22
|
+
limit: data.pagination ? data.pagination.limit : module.limit,
|
|
23
23
|
site: data.site,
|
|
24
24
|
};
|
|
25
25
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Element } from "@wdprlib/ast";
|
|
2
|
+
import type { ListPagesExternalData } from "../types";
|
|
3
|
+
|
|
4
|
+
export function createListPagesPager(data: ListPagesExternalData): Element[] {
|
|
5
|
+
const pagination = data.pagination;
|
|
6
|
+
if (!pagination || pagination.totalPages <= 1) return [];
|
|
7
|
+
const { currentPage, totalPages, urlPath, parameter } = pagination;
|
|
8
|
+
const pages = new Set([1, totalPages]);
|
|
9
|
+
for (
|
|
10
|
+
let page = Math.max(1, currentPage - 2);
|
|
11
|
+
page <= Math.min(totalPages, currentPage + 2);
|
|
12
|
+
page++
|
|
13
|
+
) {
|
|
14
|
+
pages.add(page);
|
|
15
|
+
}
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
element: "pager",
|
|
19
|
+
data: {
|
|
20
|
+
currentPage,
|
|
21
|
+
totalPages,
|
|
22
|
+
pages: [...pages]
|
|
23
|
+
.sort((a, b) => a - b)
|
|
24
|
+
.map((page) => ({
|
|
25
|
+
page,
|
|
26
|
+
href: pageUrl(urlPath, parameter, page),
|
|
27
|
+
})),
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function pageUrl(urlPath: string, parameter: string, page: number): string {
|
|
34
|
+
const suffixStart = urlPath.search(/[?#]/);
|
|
35
|
+
const path = suffixStart === -1 ? urlPath : urlPath.slice(0, suffixStart);
|
|
36
|
+
const suffix = suffixStart === -1 ? "" : urlPath.slice(suffixStart);
|
|
37
|
+
const parts = path.split("/");
|
|
38
|
+
const result = parts.slice(0, 2);
|
|
39
|
+
for (let index = 2; index < parts.length; index += 2) {
|
|
40
|
+
if (parts[index] !== parameter) result.push(...parts.slice(index, index + 2));
|
|
41
|
+
}
|
|
42
|
+
return `${result.join("/").replace(/\/$/, "")}/${parameter}/${page}${suffix}`;
|
|
43
|
+
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { Element } from "@wdprlib/ast";
|
|
2
2
|
import { getModuleParseAst, type ParseFunction } from "../../types";
|
|
3
3
|
import type { ListPagesModuleData } from "../resolve";
|
|
4
|
+
import type { ListPagesExternalData } from "../types";
|
|
5
|
+
import { createListPagesPager } from "./pager";
|
|
4
6
|
|
|
5
7
|
export function wrapListPagesResult(
|
|
6
8
|
module: ListPagesModuleData,
|
|
7
9
|
items: Element[],
|
|
8
10
|
parse: ParseFunction,
|
|
11
|
+
data?: ListPagesExternalData,
|
|
9
12
|
): Element[] {
|
|
10
|
-
if (items.length === 0) {
|
|
13
|
+
if (items.length === 0 && (!data || data.pages.length === 0)) {
|
|
11
14
|
return [];
|
|
12
15
|
}
|
|
13
16
|
|
|
@@ -25,6 +28,8 @@ export function wrapListPagesResult(
|
|
|
25
28
|
result.push(...appendAst.elements);
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
if (data) result.push(...createListPagesPager(data));
|
|
32
|
+
|
|
28
33
|
if (module.wrapper) {
|
|
29
34
|
return [
|
|
30
35
|
{
|
|
@@ -56,5 +56,5 @@ export function resolveListPages(
|
|
|
56
56
|
parse: ParseFunction,
|
|
57
57
|
): Element[] {
|
|
58
58
|
const items = renderListPagesItems(module, data, compiledTemplate, parse);
|
|
59
|
-
return wrapListPagesResult(module, items, parse);
|
|
59
|
+
return wrapListPagesResult(module, items, parse, data);
|
|
60
60
|
}
|
|
@@ -94,6 +94,19 @@ export interface SiteContext {
|
|
|
94
94
|
*/
|
|
95
95
|
export interface ListPagesExternalData {
|
|
96
96
|
pages: PageData[];
|
|
97
|
+
/** Matching items before offset and limit; use the same visibility filters as pages. */
|
|
97
98
|
totalCount: number;
|
|
98
99
|
site: SiteContext;
|
|
100
|
+
/** Filled by module resolution when a page URL is available. */
|
|
101
|
+
pagination?: {
|
|
102
|
+
currentPage: number;
|
|
103
|
+
perPage: number;
|
|
104
|
+
totalPages: number;
|
|
105
|
+
/** Actual fetch offset, including the module's starting offset. */
|
|
106
|
+
offset: number;
|
|
107
|
+
/** Total item limit after URL resolution, not the fetch batch size. */
|
|
108
|
+
limit?: number;
|
|
109
|
+
urlPath: string;
|
|
110
|
+
parameter: string;
|
|
111
|
+
};
|
|
99
112
|
}
|
|
@@ -2,12 +2,13 @@ import { URL_RESOLVABLE_FIELDS } from "./fields";
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Parse URL path parameters like /offset/1/page2_limit/1.
|
|
5
|
-
* Returns a map of parameter name -> value.
|
|
5
|
+
* Returns a map of parameter name -> value. Set hasPageName for a full page path;
|
|
6
|
+
* otherwise the legacy parameter-only-path heuristic is used.
|
|
6
7
|
*/
|
|
7
|
-
export function parseUrlParams(url: string): Map<string, string> {
|
|
8
|
+
export function parseUrlParams(url: string, hasPageName = false): Map<string, string> {
|
|
8
9
|
const params = new Map<string, string>();
|
|
9
|
-
const parts = url.split("/").filter(Boolean);
|
|
10
|
-
const pairStart = isUrlParameter(parts[0]) ? 0 : 1;
|
|
10
|
+
const parts = url.split(/[?#]/, 1)[0]!.split("/").filter(Boolean);
|
|
11
|
+
const pairStart = !hasPageName && isUrlParameter(parts[0]) ? 0 : 1;
|
|
11
12
|
|
|
12
13
|
// Skip the page name (first part), parse key/value pairs.
|
|
13
14
|
for (let i = pairStart; i < parts.length - 1; i += 2) {
|
|
@@ -4,25 +4,97 @@ import type { ListUsersDataRequirement, ListUsersExternalData } from "../listuse
|
|
|
4
4
|
import type { TagCloudDataRequirement, TagCloudExternalData } from "../tagcloud/types";
|
|
5
5
|
import { parseUrlParams, resolveAndNormalizeQuery } from "../listpages/url-resolver";
|
|
6
6
|
|
|
7
|
+
export interface ListPagesPaginationState {
|
|
8
|
+
nextUnprefixed: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
7
11
|
export async function buildListPagesDataMap(
|
|
8
12
|
dataProvider: DataProvider,
|
|
9
13
|
requirements: ListPagesDataRequirement[],
|
|
10
14
|
urlPath: string | undefined,
|
|
15
|
+
paginationState: ListPagesPaginationState = { nextUnprefixed: 1 },
|
|
11
16
|
): Promise<Map<number, ListPagesExternalData>> {
|
|
12
17
|
const dataMap = new Map<number, ListPagesExternalData>();
|
|
13
|
-
const urlParams = parseUrlParams(urlPath ?? "");
|
|
18
|
+
const urlParams = parseUrlParams(urlPath ?? "", true);
|
|
14
19
|
|
|
15
20
|
for (const req of requirements) {
|
|
16
21
|
const normalizedQuery = resolveAndNormalizeQuery(req, urlParams);
|
|
17
|
-
|
|
22
|
+
if (!urlPath || !urlPath.startsWith("/") || urlPath.startsWith("//")) {
|
|
23
|
+
const data = await dataProvider.fetchListPages?.(normalizedQuery, req);
|
|
24
|
+
if (data) dataMap.set(req.id, data);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const ordinal = req.urlAttrPrefix ? 0 : paginationState.nextUnprefixed++;
|
|
28
|
+
const parameter = req.urlAttrPrefix
|
|
29
|
+
? `${encodeURIComponent(req.urlAttrPrefix)}_p`
|
|
30
|
+
: ordinal === 1
|
|
31
|
+
? "p"
|
|
32
|
+
: `p${ordinal}`;
|
|
33
|
+
const perPage = Math.min(250, positiveInteger(normalizedQuery.perPage, 20));
|
|
34
|
+
const baseOffset = positiveInteger(normalizedQuery.offset, 0);
|
|
35
|
+
const limit =
|
|
36
|
+
normalizedQuery.limit !== undefined && normalizedQuery.limit >= 0
|
|
37
|
+
? Math.floor(normalizedQuery.limit)
|
|
38
|
+
: undefined;
|
|
39
|
+
const rawPage = urlParams.get(parameter) ?? "1";
|
|
40
|
+
let currentPage = /^\d+$/.test(rawPage) ? positiveInteger(Number(rawPage), 1) : 1;
|
|
41
|
+
currentPage = Math.min(
|
|
42
|
+
currentPage,
|
|
43
|
+
Math.floor((Number.MAX_SAFE_INTEGER - baseOffset) / perPage) + 1,
|
|
44
|
+
);
|
|
45
|
+
if (limit !== undefined)
|
|
46
|
+
currentPage = Math.min(currentPage, Math.max(1, Math.ceil(limit / perPage)));
|
|
47
|
+
const fetchPage = () => {
|
|
48
|
+
const relativeOffset = (currentPage - 1) * perPage;
|
|
49
|
+
return dataProvider.fetchListPages?.(
|
|
50
|
+
{
|
|
51
|
+
...normalizedQuery,
|
|
52
|
+
offset: baseOffset + relativeOffset,
|
|
53
|
+
limit: Math.min(
|
|
54
|
+
perPage,
|
|
55
|
+
limit === undefined ? perPage : Math.max(0, limit - relativeOffset),
|
|
56
|
+
),
|
|
57
|
+
perPage,
|
|
58
|
+
},
|
|
59
|
+
req,
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
let data = await fetchPage();
|
|
18
63
|
if (data) {
|
|
19
|
-
|
|
64
|
+
const count = Math.min(Math.max(0, data.totalCount - baseOffset), limit ?? Infinity);
|
|
65
|
+
const totalPages = Math.ceil(count / perPage);
|
|
66
|
+
const lastPage = Math.max(1, totalPages);
|
|
67
|
+
if (currentPage > lastPage) {
|
|
68
|
+
currentPage = lastPage;
|
|
69
|
+
data = await fetchPage();
|
|
70
|
+
}
|
|
71
|
+
if (data)
|
|
72
|
+
dataMap.set(req.id, {
|
|
73
|
+
...data,
|
|
74
|
+
pages: data.pages.slice(
|
|
75
|
+
0,
|
|
76
|
+
Math.min(perPage, Math.max(0, count - (currentPage - 1) * perPage)),
|
|
77
|
+
),
|
|
78
|
+
pagination: {
|
|
79
|
+
currentPage,
|
|
80
|
+
perPage,
|
|
81
|
+
totalPages,
|
|
82
|
+
offset: baseOffset + (currentPage - 1) * perPage,
|
|
83
|
+
limit,
|
|
84
|
+
urlPath,
|
|
85
|
+
parameter,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
20
88
|
}
|
|
21
89
|
}
|
|
22
90
|
|
|
23
91
|
return dataMap;
|
|
24
92
|
}
|
|
25
93
|
|
|
94
|
+
function positiveInteger(value: number | undefined, fallback: number): number {
|
|
95
|
+
return value !== undefined && Number.isSafeInteger(value) && value > 0 ? value : fallback;
|
|
96
|
+
}
|
|
97
|
+
|
|
26
98
|
export async function buildListUsersDataMap(
|
|
27
99
|
dataProvider: DataProvider,
|
|
28
100
|
requirements: ListUsersDataRequirement[],
|
|
@@ -26,6 +26,8 @@ import {
|
|
|
26
26
|
import { buildListPagesDataMap, buildListUsersDataMap, buildTagCloudDataMap } from "./data-maps";
|
|
27
27
|
import { ModuleDocumentRegistry } from "./document";
|
|
28
28
|
import { collectStyles, mergeCollectedStyles } from "./styles";
|
|
29
|
+
import { createListPagesPager } from "../listpages/resolution/pager";
|
|
30
|
+
import type { ListPagesPaginationState } from "./data-maps";
|
|
29
31
|
|
|
30
32
|
export type AsyncModuleParseFunction = (source: string) => Promise<ModuleParseResult>;
|
|
31
33
|
|
|
@@ -40,6 +42,7 @@ export interface ResolveModulesWithAsyncParseOptions {
|
|
|
40
42
|
};
|
|
41
43
|
urlPath?: string;
|
|
42
44
|
pageTags: string[];
|
|
45
|
+
paginationState?: ListPagesPaginationState;
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
export interface AsyncModuleResolutionResult {
|
|
@@ -73,7 +76,12 @@ export async function resolveModulesWithAsyncParse(
|
|
|
73
76
|
registry.register(await options.parse(source));
|
|
74
77
|
|
|
75
78
|
const [listPagesData, listUsersData, tagCloudData] = await Promise.all([
|
|
76
|
-
buildListPagesDataMap(
|
|
79
|
+
buildListPagesDataMap(
|
|
80
|
+
dataProvider,
|
|
81
|
+
options.requirements.listPages ?? [],
|
|
82
|
+
options.urlPath,
|
|
83
|
+
options.paginationState,
|
|
84
|
+
),
|
|
77
85
|
buildListUsersDataMap(dataProvider, options.requirements.listUsers ?? []),
|
|
78
86
|
buildTagCloudDataMap(dataProvider, options.requirements.tagCloud ?? []),
|
|
79
87
|
]);
|
|
@@ -175,9 +183,9 @@ async function resolveListPagesAsync(
|
|
|
175
183
|
if (!page) continue;
|
|
176
184
|
const variableContext: VariableContext = {
|
|
177
185
|
page,
|
|
178
|
-
index: i + 1,
|
|
186
|
+
index: (data.pagination?.offset ?? 0) + i + 1,
|
|
179
187
|
total: data.totalCount,
|
|
180
|
-
limit: module.limit,
|
|
188
|
+
limit: data.pagination ? data.pagination.limit : module.limit,
|
|
181
189
|
site: data.site,
|
|
182
190
|
};
|
|
183
191
|
const parsed = await parse(template(variableContext));
|
|
@@ -197,6 +205,7 @@ async function resolveListPagesAsync(
|
|
|
197
205
|
if (module["append-line"] && !module.separate) {
|
|
198
206
|
result.push(...(await parse(module["append-line"])).elements);
|
|
199
207
|
}
|
|
208
|
+
result.push(...createListPagesPager(data));
|
|
200
209
|
|
|
201
210
|
return module.wrapper
|
|
202
211
|
? [
|
|
@@ -44,17 +44,48 @@ export const paragraphRule: BlockRule = {
|
|
|
44
44
|
|
|
45
45
|
return {
|
|
46
46
|
success: true,
|
|
47
|
-
elements:
|
|
48
|
-
{
|
|
49
|
-
element: "container",
|
|
50
|
-
data: {
|
|
51
|
-
type: "paragraph",
|
|
52
|
-
attributes: {},
|
|
53
|
-
elements,
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
],
|
|
47
|
+
elements: wrapParagraphElements(elements),
|
|
57
48
|
consumed: result.consumed,
|
|
58
49
|
};
|
|
59
50
|
},
|
|
60
51
|
};
|
|
52
|
+
|
|
53
|
+
/** Block images split paragraphs; ordinary images suppress the surrounding p. */
|
|
54
|
+
export function wrapParagraphElements(elements: Element[]): Element[] {
|
|
55
|
+
const output: Element[] = [];
|
|
56
|
+
let group: Element[] = [];
|
|
57
|
+
let bare = false;
|
|
58
|
+
const flush = (trimBreaks = false) => {
|
|
59
|
+
const content = trimBreaks ? normalizeParagraphElements(group) : group;
|
|
60
|
+
while (content[0]?.element === "line-break") content.shift();
|
|
61
|
+
while (content.length) {
|
|
62
|
+
const last = content.at(-1)!;
|
|
63
|
+
if (last.element !== "text" || last.data.trim() !== "") break;
|
|
64
|
+
content.pop();
|
|
65
|
+
}
|
|
66
|
+
while (content[0]?.element === "text" && content[0].data.trim() === "") content.shift();
|
|
67
|
+
if (content[0]?.element === "text")
|
|
68
|
+
content[0] = { element: "text", data: content[0].data.trimStart() };
|
|
69
|
+
if (content.length)
|
|
70
|
+
output.push(
|
|
71
|
+
...(bare || content.some((el) => el.element === "image")
|
|
72
|
+
? content
|
|
73
|
+
: [
|
|
74
|
+
{
|
|
75
|
+
element: "container" as const,
|
|
76
|
+
data: { type: "paragraph" as const, attributes: {}, elements: content },
|
|
77
|
+
},
|
|
78
|
+
]),
|
|
79
|
+
);
|
|
80
|
+
group = [];
|
|
81
|
+
};
|
|
82
|
+
for (const el of elements) {
|
|
83
|
+
if ((el.element === "image" && el.data.alignment !== null) || el.element === "embed-block") {
|
|
84
|
+
flush(el.element === "image");
|
|
85
|
+
output.push(el);
|
|
86
|
+
bare = el.element === "embed-block";
|
|
87
|
+
} else group.push(el);
|
|
88
|
+
}
|
|
89
|
+
flush();
|
|
90
|
+
return output;
|
|
91
|
+
}
|
|
@@ -52,7 +52,10 @@ export function parseBlocksUntil(
|
|
|
52
52
|
let consumed = 0;
|
|
53
53
|
let pos = ctx.pos;
|
|
54
54
|
|
|
55
|
-
const excluded =
|
|
55
|
+
const excluded = mergeExcludedBlockNames(
|
|
56
|
+
ctx.scope.excludedBlockNames,
|
|
57
|
+
options?.excludedBlockNames,
|
|
58
|
+
);
|
|
56
59
|
const blockRules = excluded ? getExcludedBlockRules(ctx.blockRules, excluded) : ctx.blockRules;
|
|
57
60
|
const blockScope = {
|
|
58
61
|
...ctx.scope,
|
|
@@ -106,6 +109,20 @@ export function parseBlocksUntil(
|
|
|
106
109
|
return { elements, consumed };
|
|
107
110
|
}
|
|
108
111
|
|
|
112
|
+
/**
|
|
113
|
+
* An enclosing container's exclusions stay in force in its body: Wikidot keeps
|
|
114
|
+
* a `[[collapsible]]` nested in a div literal when the div itself sits in a
|
|
115
|
+
* collapsible.
|
|
116
|
+
*/
|
|
117
|
+
function mergeExcludedBlockNames(
|
|
118
|
+
inherited: ReadonlySet<string> | undefined,
|
|
119
|
+
added: ReadonlySet<string> | undefined,
|
|
120
|
+
): ReadonlySet<string> | undefined {
|
|
121
|
+
if (!inherited?.size) return added;
|
|
122
|
+
if (!added?.size) return inherited;
|
|
123
|
+
return new Set([...inherited, ...added]);
|
|
124
|
+
}
|
|
125
|
+
|
|
109
126
|
function getExcludedBlockRules(
|
|
110
127
|
blockRules: ParseContext["blockRules"],
|
|
111
128
|
excluded: ReadonlySet<string>,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { protectedInlineRegionEnd } from "../../../inline/raw/end";
|
|
1
2
|
import type { Element, TableCell } from "@wdprlib/ast";
|
|
2
3
|
import type { ParseContext } from "../../../types";
|
|
3
4
|
import { getCandidateInlineRules } from "../../../inline/utils";
|
|
@@ -20,7 +21,35 @@ export function parseTableCell(
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const { inlineRules } = ctx;
|
|
23
|
-
|
|
24
|
+
let inlineEnd = pos;
|
|
25
|
+
while (inlineEnd < ctx.tokens.length) {
|
|
26
|
+
const rawEnd = protectedInlineRegionEnd(ctx.tokens, inlineEnd, ctx.tokens.length);
|
|
27
|
+
if (rawEnd > inlineEnd) {
|
|
28
|
+
inlineEnd = rawEnd;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const token = ctx.tokens[inlineEnd];
|
|
32
|
+
if (!token || token.type === "EOF" || token.type === "NEWLINE" || isPipeTableToken(token.type))
|
|
33
|
+
break;
|
|
34
|
+
if (
|
|
35
|
+
token.type === "WHITESPACE" &&
|
|
36
|
+
ctx.tokens[inlineEnd + 1]?.type === "UNDERSCORE" &&
|
|
37
|
+
ctx.tokens[inlineEnd + 2]?.type === "NEWLINE"
|
|
38
|
+
)
|
|
39
|
+
inlineEnd += 3;
|
|
40
|
+
else inlineEnd++;
|
|
41
|
+
}
|
|
42
|
+
const inlineCtx: ParseContext = {
|
|
43
|
+
...ctx,
|
|
44
|
+
pos,
|
|
45
|
+
scope: {
|
|
46
|
+
...ctx.scope,
|
|
47
|
+
inlineEnd,
|
|
48
|
+
tableFormatting: isPipeTableToken(ctx.tokens[inlineEnd]?.type ?? "EOF")
|
|
49
|
+
? ctx.scope.tableFormatting
|
|
50
|
+
: undefined,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
24
53
|
|
|
25
54
|
while (pos < ctx.tokens.length) {
|
|
26
55
|
const token = ctx.tokens[pos];
|
|
@@ -31,6 +60,12 @@ export function parseTableCell(
|
|
|
31
60
|
break;
|
|
32
61
|
}
|
|
33
62
|
|
|
63
|
+
if (ctx.scope.tableFormatting?.suppressedClosers.has(pos)) {
|
|
64
|
+
pos++;
|
|
65
|
+
consumed++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
34
69
|
if (tryConsumeUnderscoreLineBreak(ctx, pos, children)) {
|
|
35
70
|
pos += 3;
|
|
36
71
|
consumed += 3;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { protectedInlineRegionEnd } from "../../../inline/raw/end";
|
|
1
2
|
import type { TableCell, TableRow } from "@wdprlib/ast";
|
|
2
3
|
import type { ParseContext } from "../../../types";
|
|
3
4
|
import { parseCellStart } from "./cell-start";
|
|
@@ -8,6 +9,25 @@ export function parsePipeTableRows(
|
|
|
8
9
|
ctx: ParseContext,
|
|
9
10
|
startPos: number,
|
|
10
11
|
): { rows: TableRow[]; consumed: number } {
|
|
12
|
+
let end = startPos;
|
|
13
|
+
while (end < ctx.tokens.length && ctx.tokens[end]?.type !== "EOF") {
|
|
14
|
+
const protectedEnd = protectedInlineRegionEnd(ctx.tokens, end, ctx.tokens.length);
|
|
15
|
+
if (protectedEnd > end) {
|
|
16
|
+
end = protectedEnd;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (
|
|
20
|
+
ctx.tokens[end]?.type === "NEWLINE" &&
|
|
21
|
+
!isPipeTableToken(ctx.tokens[end + 1]?.type ?? "EOF") &&
|
|
22
|
+
!(ctx.tokens[end - 1]?.type === "UNDERSCORE" && ctx.tokens[end - 2]?.type === "WHITESPACE")
|
|
23
|
+
)
|
|
24
|
+
break;
|
|
25
|
+
end++;
|
|
26
|
+
}
|
|
27
|
+
const tableCtx: ParseContext = {
|
|
28
|
+
...ctx,
|
|
29
|
+
scope: { ...ctx.scope, tableFormatting: { end, suppressedClosers: new Set() } },
|
|
30
|
+
};
|
|
11
31
|
const rows: TableRow[] = [];
|
|
12
32
|
let pos = startPos;
|
|
13
33
|
let consumed = 0;
|
|
@@ -19,7 +39,7 @@ export function parsePipeTableRows(
|
|
|
19
39
|
break;
|
|
20
40
|
}
|
|
21
41
|
|
|
22
|
-
const rowResult = parseTableRow(
|
|
42
|
+
const rowResult = parseTableRow(tableCtx, pos);
|
|
23
43
|
rows.push(rowResult.row);
|
|
24
44
|
pos += rowResult.consumed;
|
|
25
45
|
consumed += rowResult.consumed;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { Alignment, Element } from "@wdprlib/ast";
|
|
2
2
|
|
|
3
|
-
export function createTocElement(align: Alignment | null): Element {
|
|
3
|
+
export function createTocElement(align: Alignment | null, title: string | undefined): Element {
|
|
4
4
|
return {
|
|
5
5
|
element: "table-of-contents",
|
|
6
6
|
data: {
|
|
7
|
-
attributes: {},
|
|
7
|
+
attributes: title === undefined ? {} : { title },
|
|
8
8
|
align,
|
|
9
9
|
},
|
|
10
10
|
};
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - [[f<toc ...]] - float left
|
|
7
7
|
* - [[f>toc ...]] - float right
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* The title attribute overrides the heading; class, style, and id are ignored.
|
|
10
10
|
* [[>toc]] and [[<toc]] are invalid in Wikidot and not supported.
|
|
11
11
|
*/
|
|
12
12
|
import type { Element } from "@wdprlib/ast";
|
|
@@ -37,7 +37,7 @@ export const tocRule: BlockRule = {
|
|
|
37
37
|
|
|
38
38
|
return {
|
|
39
39
|
success: true,
|
|
40
|
-
elements: [createTocElement(openResult.align)],
|
|
40
|
+
elements: [createTocElement(openResult.align, openResult.title)],
|
|
41
41
|
consumed: openResult.consumed,
|
|
42
42
|
};
|
|
43
43
|
},
|