@wdprlib/render 2.1.0 → 3.0.1
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 +2344 -1668
- package/dist/index.d.cts +15 -13
- package/dist/index.d.ts +15 -13
- package/dist/index.js +2375 -1699
- package/package.json +1 -1
- package/src/context/attributes.ts +14 -0
- package/src/context/bibliography.ts +109 -0
- package/src/context/counters.ts +51 -0
- package/src/context/image-urls.ts +31 -0
- package/src/context/index.ts +285 -0
- package/src/context/output.ts +17 -0
- package/src/context/page-urls.ts +81 -0
- package/src/context/style-slots.ts +29 -0
- package/src/context/urls.ts +2 -0
- package/src/elements/bibliography/block.ts +27 -0
- package/src/elements/bibliography/cite.ts +23 -0
- package/src/elements/bibliography/ids.ts +9 -0
- package/src/elements/bibliography/index.ts +9 -0
- package/src/elements/code/contents.ts +18 -0
- package/src/elements/code/index.ts +29 -0
- package/src/elements/collapsible/index.ts +31 -0
- package/src/elements/collapsible/labels.ts +35 -0
- package/src/elements/collapsible/link.ts +11 -0
- package/src/elements/collapsible/sections.ts +39 -0
- package/src/elements/container/attributes.ts +28 -0
- package/src/elements/container/header.ts +27 -0
- package/src/elements/container/index.ts +35 -0
- package/src/elements/container/string-container.ts +40 -0
- package/src/elements/container/string-types.ts +63 -0
- package/src/elements/container/wrappers.ts +32 -0
- package/src/elements/date/format.ts +20 -0
- package/src/elements/{date.ts → date/index.ts} +4 -29
- package/src/elements/date/output.ts +6 -0
- package/src/elements/embed/iframe.ts +8 -0
- package/src/elements/embed/index.ts +28 -0
- package/src/elements/embed/providers.ts +43 -0
- package/src/elements/embed/validation.ts +15 -0
- package/src/elements/embed-block/allowlist.ts +60 -0
- package/src/elements/embed-block/boolean-attributes.ts +38 -0
- package/src/elements/embed-block/iframe.ts +33 -0
- package/src/elements/embed-block/index.ts +31 -0
- package/src/elements/embed-block/sanitize-config.ts +22 -0
- package/src/elements/embed-block/sanitize.ts +44 -0
- package/src/elements/expr/branch.ts +29 -0
- package/src/elements/expr/index.ts +63 -0
- package/src/elements/expr/result.ts +19 -0
- package/src/elements/footnote/body.ts +11 -0
- package/src/elements/footnote/index.ts +35 -0
- package/src/elements/footnote/ref.ts +16 -0
- package/src/elements/html/attributes.ts +24 -0
- package/src/elements/html/index.ts +39 -0
- package/src/elements/html/url.ts +19 -0
- package/src/elements/iframe/attributes.ts +28 -0
- package/src/elements/iframe/index.ts +22 -0
- package/src/elements/iftags/condition.ts +42 -0
- package/src/elements/iftags/index.ts +39 -0
- package/src/elements/iftags/style-slot.ts +23 -0
- package/src/elements/iftags/tokens.ts +36 -0
- package/src/elements/image/alignment.ts +44 -0
- package/src/elements/image/attributes.ts +10 -0
- package/src/elements/image/img-attributes.ts +26 -0
- package/src/elements/image/index.ts +36 -0
- package/src/elements/image/link-href.ts +24 -0
- package/src/elements/image/link.ts +13 -0
- package/src/elements/image/source.ts +16 -0
- package/src/elements/{include.ts → include/index.ts} +5 -13
- package/src/elements/include/missing.ts +15 -0
- package/src/elements/link/anchor-name.ts +6 -0
- package/src/elements/link/anchor.ts +27 -0
- package/src/elements/link/attributes.ts +47 -0
- package/src/elements/link/index.ts +26 -0
- package/src/elements/link/label.ts +23 -0
- package/src/elements/link/target.ts +20 -0
- package/src/elements/list/attributes.ts +19 -0
- package/src/elements/list/definition-list.ts +16 -0
- package/src/elements/list/index.ts +48 -0
- package/src/elements/list/item-rendering.ts +38 -0
- package/src/elements/list/items.ts +61 -0
- package/src/elements/list/no-marker.ts +53 -0
- package/src/elements/list/paragraphs.ts +34 -0
- package/src/elements/list/trim.ts +38 -0
- package/src/elements/math/block.ts +29 -0
- package/src/elements/math/equation-ref.ts +12 -0
- package/src/elements/math/index.ts +14 -0
- package/src/elements/math/inline.ts +19 -0
- package/src/elements/math/latex.ts +27 -0
- package/src/elements/math/source.ts +18 -0
- package/src/elements/module/backlinks.ts +2 -1
- package/src/elements/module/categories.ts +2 -2
- package/src/elements/module/empty-container.ts +10 -0
- package/src/elements/module/index.ts +2 -4
- package/src/elements/module/join-markup.ts +10 -0
- package/src/elements/module/join.ts +2 -7
- package/src/elements/module/listpages.ts +2 -2
- package/src/elements/module/listusers.ts +2 -2
- package/src/elements/module/page-tree.ts +2 -2
- package/src/elements/module/rate-markup.ts +10 -0
- package/src/elements/module/rate.ts +4 -13
- package/src/elements/module/unknown.ts +11 -0
- package/src/elements/tab-view/ids.ts +16 -0
- package/src/elements/tab-view/index.ts +31 -0
- package/src/elements/tab-view/navigation.ts +15 -0
- package/src/elements/tab-view/panels.ts +16 -0
- package/src/elements/table/attributes.ts +23 -0
- package/src/elements/table/cell-attributes.ts +62 -0
- package/src/elements/table/cell.ts +13 -0
- package/src/elements/table/index.ts +27 -0
- package/src/elements/text/email.ts +20 -0
- package/src/elements/text/index.ts +11 -0
- package/src/elements/text/plain.ts +11 -0
- package/src/elements/text/raw.ts +20 -0
- package/src/elements/toc/body.ts +12 -0
- package/src/elements/toc/entries.ts +34 -0
- package/src/elements/toc/frame.ts +27 -0
- package/src/elements/toc/index.ts +17 -0
- package/src/elements/toc/link.ts +26 -0
- package/src/elements/user/index.ts +40 -0
- package/src/elements/user/markup.ts +34 -0
- package/src/elements/user/resolve.ts +6 -0
- package/src/escape/attribute-allowlists.ts +101 -0
- package/src/escape/attributes.ts +62 -0
- package/src/escape/css-color-functions.ts +18 -0
- package/src/escape/css-colors.ts +183 -0
- package/src/escape/css-danger.ts +22 -0
- package/src/escape/css-normalize.ts +54 -0
- package/src/escape/css-style.ts +78 -0
- package/src/escape/css-urls.ts +76 -0
- package/src/escape/css.ts +4 -0
- package/src/escape/email.ts +22 -0
- package/src/escape/html.ts +68 -0
- package/src/escape/index.ts +15 -0
- package/src/escape/url.ts +18 -0
- package/src/libs/highlighter/engine/end-pattern.ts +26 -0
- package/src/libs/highlighter/engine/html.ts +19 -0
- package/src/libs/highlighter/engine/index.ts +3 -0
- package/src/libs/highlighter/engine/keywords.ts +22 -0
- package/src/libs/highlighter/engine/parts.ts +36 -0
- package/src/libs/highlighter/engine/preprocess.ts +10 -0
- package/src/libs/highlighter/engine/render.ts +31 -0
- package/src/libs/highlighter/engine/token.ts +7 -0
- package/src/libs/highlighter/engine/tokenizer.ts +266 -0
- package/src/libs/highlighter/engine/utils.ts +38 -0
- package/src/render/collected-styles.ts +22 -0
- package/src/render/dispatch.ts +181 -0
- package/src/render/index.ts +28 -0
- package/src/render/primitives.ts +17 -0
- package/src/render/style-tag.ts +6 -0
- package/src/render/style.ts +15 -0
- package/src/types.ts +6 -2
- package/src/context.ts +0 -422
- package/src/elements/bibliography.ts +0 -123
- package/src/elements/code.ts +0 -49
- package/src/elements/collapsible.ts +0 -105
- package/src/elements/container.ts +0 -302
- package/src/elements/embed-block.ts +0 -327
- package/src/elements/embed.ts +0 -166
- package/src/elements/expr.ts +0 -102
- package/src/elements/footnote.ts +0 -76
- package/src/elements/html.ts +0 -79
- package/src/elements/iframe.ts +0 -44
- package/src/elements/iftags.ts +0 -118
- package/src/elements/image.ts +0 -154
- package/src/elements/link.ts +0 -201
- package/src/elements/list.ts +0 -241
- package/src/elements/math.ts +0 -177
- package/src/elements/tab-view.ts +0 -75
- package/src/elements/table.ts +0 -101
- package/src/elements/text.ts +0 -57
- package/src/elements/toc.ts +0 -147
- package/src/elements/user.ts +0 -79
- package/src/escape.ts +0 -829
- package/src/libs/highlighter/engine.ts +0 -352
- package/src/render.ts +0 -231
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import type { LanguageDefinition } from "../types";
|
|
2
|
+
import { buildEndPattern } from "./end-pattern";
|
|
3
|
+
import { resolveKeywordClass } from "./keywords";
|
|
4
|
+
import { buildPartTokens } from "./parts";
|
|
5
|
+
import { preprocessHighlightInput } from "./preprocess";
|
|
6
|
+
import type { HighlightToken } from "./token";
|
|
7
|
+
import { findGroupPosition } from "./utils";
|
|
8
|
+
|
|
9
|
+
interface HighlighterState {
|
|
10
|
+
state: number;
|
|
11
|
+
lastdelim: string;
|
|
12
|
+
lastinner: string;
|
|
13
|
+
endpattern: RegExp | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Tokenize source code using a language definition's state machine.
|
|
18
|
+
*/
|
|
19
|
+
export function tokenize(def: LanguageDefinition, input: string): HighlightToken[] {
|
|
20
|
+
const str = preprocessHighlightInput(input);
|
|
21
|
+
const len = str.length;
|
|
22
|
+
if (len === 0) return [];
|
|
23
|
+
|
|
24
|
+
let state = -1;
|
|
25
|
+
let pos = 0;
|
|
26
|
+
let lastinner = def.defClass;
|
|
27
|
+
let lastdelim = def.defClass;
|
|
28
|
+
let endpattern: RegExp | null = null;
|
|
29
|
+
const stateStack: HighlighterState[] = [];
|
|
30
|
+
const tokenStack: HighlightToken[] = [];
|
|
31
|
+
const result: HighlightToken[] = [];
|
|
32
|
+
|
|
33
|
+
function getToken(): HighlightToken | null {
|
|
34
|
+
if (tokenStack.length > 0) {
|
|
35
|
+
return tokenStack.pop()!;
|
|
36
|
+
}
|
|
37
|
+
if (pos >= len) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const endStateMatch = findEndStateMatch(str, pos, state, endpattern);
|
|
42
|
+
const token = matchStateToken({
|
|
43
|
+
def,
|
|
44
|
+
str,
|
|
45
|
+
pos,
|
|
46
|
+
state,
|
|
47
|
+
lastinner,
|
|
48
|
+
lastdelim,
|
|
49
|
+
endpattern,
|
|
50
|
+
stateStack,
|
|
51
|
+
tokenStack,
|
|
52
|
+
endStateMatch,
|
|
53
|
+
setPosition: (nextPos) => {
|
|
54
|
+
pos = nextPos;
|
|
55
|
+
},
|
|
56
|
+
setState: (next) => {
|
|
57
|
+
state = next.state;
|
|
58
|
+
lastinner = next.lastinner;
|
|
59
|
+
lastdelim = next.lastdelim;
|
|
60
|
+
endpattern = next.endpattern;
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
if (token) {
|
|
64
|
+
return token;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (endStateMatch.endpos > -1) {
|
|
68
|
+
tokenStack.push({ class: lastdelim, content: endStateMatch.endmatch });
|
|
69
|
+
if (endStateMatch.endpos > pos) {
|
|
70
|
+
tokenStack.push({ class: lastinner, content: str.substring(pos, endStateMatch.endpos) });
|
|
71
|
+
}
|
|
72
|
+
const prev = stateStack.pop()!;
|
|
73
|
+
state = prev.state;
|
|
74
|
+
lastdelim = prev.lastdelim;
|
|
75
|
+
lastinner = prev.lastinner;
|
|
76
|
+
endpattern = prev.endpattern;
|
|
77
|
+
pos = endStateMatch.endpos + endStateMatch.endmatch.length;
|
|
78
|
+
if (tokenStack.length > 0) {
|
|
79
|
+
return tokenStack.pop()!;
|
|
80
|
+
}
|
|
81
|
+
return getToken();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const p = pos;
|
|
85
|
+
pos = len;
|
|
86
|
+
return { class: lastinner, content: str.substring(p) };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let token: HighlightToken | null;
|
|
90
|
+
while ((token = getToken()) !== null) {
|
|
91
|
+
result.push(token);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface EndStateMatch {
|
|
98
|
+
endpos: number;
|
|
99
|
+
endmatch: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function findEndStateMatch(
|
|
103
|
+
str: string,
|
|
104
|
+
pos: number,
|
|
105
|
+
state: number,
|
|
106
|
+
endpattern: RegExp | null,
|
|
107
|
+
): EndStateMatch {
|
|
108
|
+
if (state === -1 || !endpattern) {
|
|
109
|
+
return { endpos: -1, endmatch: "" };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
endpattern.lastIndex = pos;
|
|
113
|
+
const match = endpattern.exec(str);
|
|
114
|
+
return match ? { endpos: match.index, endmatch: match[0] } : { endpos: -1, endmatch: "" };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
interface MatchStateTokenArgs {
|
|
118
|
+
def: LanguageDefinition;
|
|
119
|
+
str: string;
|
|
120
|
+
pos: number;
|
|
121
|
+
state: number;
|
|
122
|
+
lastinner: string;
|
|
123
|
+
lastdelim: string;
|
|
124
|
+
endpattern: RegExp | null;
|
|
125
|
+
stateStack: HighlighterState[];
|
|
126
|
+
tokenStack: HighlightToken[];
|
|
127
|
+
endStateMatch: EndStateMatch;
|
|
128
|
+
setPosition(pos: number): void;
|
|
129
|
+
setState(state: HighlighterState): void;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function matchStateToken(args: MatchStateTokenArgs): HighlightToken | null {
|
|
133
|
+
const reg = args.def.regs[args.state];
|
|
134
|
+
if (!reg) return null;
|
|
135
|
+
|
|
136
|
+
reg.lastIndex = args.pos;
|
|
137
|
+
const match = reg.exec(args.str);
|
|
138
|
+
if (!match) return null;
|
|
139
|
+
|
|
140
|
+
const countsArr = args.def.counts[args.state]!;
|
|
141
|
+
let captureIndex = 1;
|
|
142
|
+
|
|
143
|
+
for (let patternIndex = 0; patternIndex < countsArr.length; patternIndex++) {
|
|
144
|
+
const count = countsArr[patternIndex]!;
|
|
145
|
+
if (captureIndex >= match.length) break;
|
|
146
|
+
|
|
147
|
+
if (
|
|
148
|
+
match[captureIndex] != null &&
|
|
149
|
+
(args.endStateMatch.endpos === -1 || match.index < args.endStateMatch.endpos)
|
|
150
|
+
) {
|
|
151
|
+
return emitMatchedPattern(args, match, patternIndex, captureIndex, count);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
captureIndex += count + 1;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function emitMatchedPattern(
|
|
161
|
+
args: MatchStateTokenArgs,
|
|
162
|
+
match: RegExpExecArray,
|
|
163
|
+
patternIndex: number,
|
|
164
|
+
captureIndex: number,
|
|
165
|
+
count: number,
|
|
166
|
+
): HighlightToken {
|
|
167
|
+
const statesArr = args.def.states[args.state]!;
|
|
168
|
+
const delimArr = args.def.delim[args.state]!;
|
|
169
|
+
const matchStart = match.index;
|
|
170
|
+
const matchStr = match[captureIndex]!;
|
|
171
|
+
const groupStart = findGroupPosition(args.str, match, captureIndex, matchStart);
|
|
172
|
+
|
|
173
|
+
if (statesArr[patternIndex] !== -1) {
|
|
174
|
+
args.tokenStack.push({ class: delimArr[patternIndex]!, content: matchStr });
|
|
175
|
+
} else {
|
|
176
|
+
pushNonTransitionTokens(args, match, patternIndex, captureIndex, count, groupStart, matchStr);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (groupStart > args.pos) {
|
|
180
|
+
args.tokenStack.push({
|
|
181
|
+
class: args.lastinner,
|
|
182
|
+
content: args.str.substring(args.pos, groupStart),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
args.setPosition(groupStart + matchStr.length);
|
|
187
|
+
|
|
188
|
+
if (statesArr[patternIndex] !== -1) {
|
|
189
|
+
enterState(args, match, patternIndex, captureIndex, count);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return args.tokenStack.pop()!;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function pushNonTransitionTokens(
|
|
196
|
+
args: MatchStateTokenArgs,
|
|
197
|
+
match: RegExpExecArray,
|
|
198
|
+
patternIndex: number,
|
|
199
|
+
captureIndex: number,
|
|
200
|
+
count: number,
|
|
201
|
+
groupStart: number,
|
|
202
|
+
matchStr: string,
|
|
203
|
+
): void {
|
|
204
|
+
let inner = args.def.inner[args.state]![patternIndex]!;
|
|
205
|
+
const partDef = args.def.parts[args.state]?.[patternIndex];
|
|
206
|
+
if (partDef) {
|
|
207
|
+
pushPartTokens(args, match, partDef, captureIndex, count, groupStart, matchStr, inner);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
inner = resolveKeywordClass(args.def, args.state, patternIndex, matchStr, inner);
|
|
212
|
+
args.tokenStack.push({ class: inner, content: matchStr });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function pushPartTokens(
|
|
216
|
+
args: MatchStateTokenArgs,
|
|
217
|
+
match: RegExpExecArray,
|
|
218
|
+
partDef: Record<number, string>,
|
|
219
|
+
captureIndex: number,
|
|
220
|
+
count: number,
|
|
221
|
+
groupStart: number,
|
|
222
|
+
matchStr: string,
|
|
223
|
+
inner: string,
|
|
224
|
+
): void {
|
|
225
|
+
const parts: HighlightToken[] = [];
|
|
226
|
+
parts.push(
|
|
227
|
+
...buildPartTokens(args.str, match, partDef, captureIndex, count, groupStart, matchStr, inner),
|
|
228
|
+
);
|
|
229
|
+
args.tokenStack.push(...parts);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function enterState(
|
|
233
|
+
args: MatchStateTokenArgs,
|
|
234
|
+
match: RegExpExecArray,
|
|
235
|
+
patternIndex: number,
|
|
236
|
+
captureIndex: number,
|
|
237
|
+
count: number,
|
|
238
|
+
): void {
|
|
239
|
+
const statesArr = args.def.states[args.state]!;
|
|
240
|
+
const delimArr = args.def.delim[args.state]!;
|
|
241
|
+
const innerArr = args.def.inner[args.state]!;
|
|
242
|
+
args.stateStack.push({
|
|
243
|
+
state: args.state,
|
|
244
|
+
lastdelim: args.lastdelim,
|
|
245
|
+
lastinner: args.lastinner,
|
|
246
|
+
endpattern: args.endpattern,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const prevState = args.state;
|
|
250
|
+
const nextState = statesArr[patternIndex]!;
|
|
251
|
+
const endRe = args.def.end[nextState];
|
|
252
|
+
args.setState({
|
|
253
|
+
state: nextState,
|
|
254
|
+
lastinner: innerArr[patternIndex]!,
|
|
255
|
+
lastdelim: delimArr[patternIndex]!,
|
|
256
|
+
endpattern: buildEndPattern(
|
|
257
|
+
args.def,
|
|
258
|
+
prevState,
|
|
259
|
+
patternIndex,
|
|
260
|
+
count,
|
|
261
|
+
captureIndex,
|
|
262
|
+
match,
|
|
263
|
+
endRe ?? undefined,
|
|
264
|
+
),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the actual position of capture group `n` within the source string.
|
|
3
|
+
*/
|
|
4
|
+
export function findGroupPosition(
|
|
5
|
+
str: string,
|
|
6
|
+
match: RegExpExecArray,
|
|
7
|
+
groupIndex: number,
|
|
8
|
+
matchStart: number,
|
|
9
|
+
): number {
|
|
10
|
+
const groupStr = match[groupIndex]!;
|
|
11
|
+
const idx = str.indexOf(groupStr, matchStart);
|
|
12
|
+
return idx >= 0 ? idx : matchStart;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Escape regex special characters in a string for safe use in `new RegExp()`.
|
|
17
|
+
*/
|
|
18
|
+
export function escapeRegex(str: string): string {
|
|
19
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Swap bracket characters to their matching counterparts.
|
|
24
|
+
*/
|
|
25
|
+
export function matchingBrackets(str: string): string {
|
|
26
|
+
return str.replace(/[()<>[\]{}]/g, (char) => MATCHING_BRACKETS[char] ?? char);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const MATCHING_BRACKETS: Record<string, string> = {
|
|
30
|
+
"(": ")",
|
|
31
|
+
")": "(",
|
|
32
|
+
"<": ">",
|
|
33
|
+
">": "<",
|
|
34
|
+
"[": "]",
|
|
35
|
+
"]": "[",
|
|
36
|
+
"{": "}",
|
|
37
|
+
"}": "{",
|
|
38
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { STYLE_SLOT_PREFIX } from "@wdprlib/ast";
|
|
2
|
+
import type { RenderContext } from "../context";
|
|
3
|
+
import { pushStyleTag } from "./style-tag";
|
|
4
|
+
|
|
5
|
+
export function renderCollectedStyles(ctx: RenderContext, styles: string[] | undefined): void {
|
|
6
|
+
if (!ctx.settings.allowStyleElements || !styles?.length) return;
|
|
7
|
+
|
|
8
|
+
for (const style of styles) {
|
|
9
|
+
if (style.startsWith(STYLE_SLOT_PREFIX)) {
|
|
10
|
+
renderStyleSlot(ctx, style);
|
|
11
|
+
} else {
|
|
12
|
+
pushStyleTag(ctx, style);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function renderStyleSlot(ctx: RenderContext, marker: string): void {
|
|
18
|
+
const slotId = parseInt(marker.slice(STYLE_SLOT_PREFIX.length), 10);
|
|
19
|
+
for (const css of ctx.getStyleSlotContents(slotId)) {
|
|
20
|
+
pushStyleTag(ctx, css);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { Element } from "@wdprlib/ast";
|
|
2
|
+
import { RenderContext } from "../context";
|
|
3
|
+
import { renderBibliographyBlock, renderBibliographyCite } from "../elements/bibliography";
|
|
4
|
+
import { renderClearFloat } from "../elements/clear-float";
|
|
5
|
+
import { renderCode } from "../elements/code";
|
|
6
|
+
import { renderCollapsible } from "../elements/collapsible";
|
|
7
|
+
import { renderContainer } from "../elements/container";
|
|
8
|
+
import { renderColor } from "../elements/color";
|
|
9
|
+
import { renderDate } from "../elements/date";
|
|
10
|
+
import { renderEmbed } from "../elements/embed";
|
|
11
|
+
import { renderEmbedBlock } from "../elements/embed-block";
|
|
12
|
+
import { renderExpr, renderIf, renderIfExpr } from "../elements/expr";
|
|
13
|
+
import { renderFootnoteBlock, renderFootnoteRef } from "../elements/footnote";
|
|
14
|
+
import { renderHtmlBlock } from "../elements/html";
|
|
15
|
+
import { renderIframe } from "../elements/iframe";
|
|
16
|
+
import { renderIfTags } from "../elements/iftags";
|
|
17
|
+
import { renderImage } from "../elements/image";
|
|
18
|
+
import { renderInclude } from "../elements/include";
|
|
19
|
+
import { renderLineBreaks } from "../elements/line-break";
|
|
20
|
+
import { renderAnchor, renderAnchorName, renderLink } from "../elements/link";
|
|
21
|
+
import { renderDefinitionList, renderList } from "../elements/list";
|
|
22
|
+
import { renderMath, renderMathInline, renderEquationRef } from "../elements/math";
|
|
23
|
+
import { renderModule } from "../elements/module/index";
|
|
24
|
+
import { renderTable } from "../elements/table";
|
|
25
|
+
import { renderTabView } from "../elements/tab-view";
|
|
26
|
+
import { renderRaw, renderEmail, renderText } from "../elements/text";
|
|
27
|
+
import { renderTableOfContents } from "../elements/toc";
|
|
28
|
+
import { renderUser } from "../elements/user";
|
|
29
|
+
import {
|
|
30
|
+
renderContentSeparator,
|
|
31
|
+
renderHorizontalRule,
|
|
32
|
+
renderLineBreak,
|
|
33
|
+
renderTextNode,
|
|
34
|
+
} from "./primitives";
|
|
35
|
+
import { renderStyleElement } from "./style";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Render a list of sibling AST elements in document order.
|
|
39
|
+
*/
|
|
40
|
+
export function renderElements(ctx: RenderContext, elements: Element[]): void {
|
|
41
|
+
for (const element of elements) {
|
|
42
|
+
renderElement(ctx, element);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Dispatch a single AST element to its type-specific renderer.
|
|
48
|
+
*/
|
|
49
|
+
export function renderElement(ctx: RenderContext, element: Element): void {
|
|
50
|
+
switch (element.element) {
|
|
51
|
+
case "text":
|
|
52
|
+
renderTextNode(ctx, element.data);
|
|
53
|
+
break;
|
|
54
|
+
case "raw":
|
|
55
|
+
renderRaw(ctx, element.data);
|
|
56
|
+
break;
|
|
57
|
+
case "variable":
|
|
58
|
+
renderText(ctx, element.data);
|
|
59
|
+
break;
|
|
60
|
+
case "email":
|
|
61
|
+
renderEmail(ctx, element.data);
|
|
62
|
+
break;
|
|
63
|
+
case "container":
|
|
64
|
+
renderContainer(ctx, element.data);
|
|
65
|
+
break;
|
|
66
|
+
case "link":
|
|
67
|
+
renderLink(ctx, element.data);
|
|
68
|
+
break;
|
|
69
|
+
case "anchor":
|
|
70
|
+
renderAnchor(ctx, element.data);
|
|
71
|
+
break;
|
|
72
|
+
case "anchor-name":
|
|
73
|
+
renderAnchorName(ctx, element.data);
|
|
74
|
+
break;
|
|
75
|
+
case "image":
|
|
76
|
+
renderImage(ctx, element.data);
|
|
77
|
+
break;
|
|
78
|
+
case "list":
|
|
79
|
+
renderList(ctx, element.data);
|
|
80
|
+
break;
|
|
81
|
+
case "definition-list":
|
|
82
|
+
renderDefinitionList(ctx, element.data);
|
|
83
|
+
break;
|
|
84
|
+
case "table":
|
|
85
|
+
renderTable(ctx, element.data);
|
|
86
|
+
break;
|
|
87
|
+
case "collapsible":
|
|
88
|
+
renderCollapsible(ctx, element.data);
|
|
89
|
+
break;
|
|
90
|
+
case "code":
|
|
91
|
+
renderCode(ctx, element.data);
|
|
92
|
+
break;
|
|
93
|
+
case "tab-view":
|
|
94
|
+
renderTabView(ctx, element.data);
|
|
95
|
+
break;
|
|
96
|
+
case "footnote":
|
|
97
|
+
renderFootnoteRef(ctx, ctx.nextFootnoteIndex() + 1);
|
|
98
|
+
break;
|
|
99
|
+
case "footnote-ref":
|
|
100
|
+
renderFootnoteRef(ctx, element.data);
|
|
101
|
+
break;
|
|
102
|
+
case "footnote-block":
|
|
103
|
+
renderFootnoteBlock(ctx, element.data);
|
|
104
|
+
break;
|
|
105
|
+
case "bibliography-cite":
|
|
106
|
+
renderBibliographyCite(ctx, element.data);
|
|
107
|
+
break;
|
|
108
|
+
case "bibliography-block":
|
|
109
|
+
renderBibliographyBlock(ctx, element.data, renderElements);
|
|
110
|
+
break;
|
|
111
|
+
case "table-of-contents":
|
|
112
|
+
renderTableOfContents(ctx, element.data);
|
|
113
|
+
break;
|
|
114
|
+
case "math":
|
|
115
|
+
renderMath(ctx, element.data);
|
|
116
|
+
break;
|
|
117
|
+
case "math-inline":
|
|
118
|
+
renderMathInline(ctx, element.data);
|
|
119
|
+
break;
|
|
120
|
+
case "module":
|
|
121
|
+
renderModule(ctx, element.data);
|
|
122
|
+
break;
|
|
123
|
+
case "embed":
|
|
124
|
+
renderEmbed(ctx, element.data);
|
|
125
|
+
break;
|
|
126
|
+
case "embed-block":
|
|
127
|
+
renderEmbedBlock(ctx, element.data);
|
|
128
|
+
break;
|
|
129
|
+
case "user":
|
|
130
|
+
renderUser(ctx, element.data);
|
|
131
|
+
break;
|
|
132
|
+
case "date":
|
|
133
|
+
renderDate(ctx, element.data);
|
|
134
|
+
break;
|
|
135
|
+
case "color":
|
|
136
|
+
renderColor(ctx, element.data);
|
|
137
|
+
break;
|
|
138
|
+
case "html":
|
|
139
|
+
renderHtmlBlock(ctx, element.data);
|
|
140
|
+
break;
|
|
141
|
+
case "iframe":
|
|
142
|
+
renderIframe(ctx, element.data);
|
|
143
|
+
break;
|
|
144
|
+
case "include":
|
|
145
|
+
renderInclude(ctx, element.data);
|
|
146
|
+
break;
|
|
147
|
+
case "if-tags":
|
|
148
|
+
renderIfTags(ctx, element.data);
|
|
149
|
+
break;
|
|
150
|
+
case "style":
|
|
151
|
+
renderStyleElement(ctx, element.data);
|
|
152
|
+
break;
|
|
153
|
+
case "line-break":
|
|
154
|
+
renderLineBreak(ctx);
|
|
155
|
+
break;
|
|
156
|
+
case "line-breaks":
|
|
157
|
+
renderLineBreaks(ctx, element.data);
|
|
158
|
+
break;
|
|
159
|
+
case "clear-float":
|
|
160
|
+
renderClearFloat(ctx, element.data);
|
|
161
|
+
break;
|
|
162
|
+
case "horizontal-rule":
|
|
163
|
+
renderHorizontalRule(ctx);
|
|
164
|
+
break;
|
|
165
|
+
case "content-separator":
|
|
166
|
+
renderContentSeparator(ctx);
|
|
167
|
+
break;
|
|
168
|
+
case "expr":
|
|
169
|
+
renderExpr(ctx, element.data);
|
|
170
|
+
break;
|
|
171
|
+
case "if":
|
|
172
|
+
renderIf(ctx, element.data);
|
|
173
|
+
break;
|
|
174
|
+
case "ifexpr":
|
|
175
|
+
renderIfExpr(ctx, element.data);
|
|
176
|
+
break;
|
|
177
|
+
case "equation-reference":
|
|
178
|
+
renderEquationRef(ctx, element.data);
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { SyntaxTree } from "@wdprlib/ast";
|
|
2
|
+
import { RenderContext } from "../context";
|
|
3
|
+
import type { RenderOptions } from "../types";
|
|
4
|
+
import { renderCollectedStyles } from "./collected-styles";
|
|
5
|
+
import { renderElements } from "./dispatch";
|
|
6
|
+
|
|
7
|
+
export { renderElement, renderElements } from "./dispatch";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Render a {@link SyntaxTree} to an HTML string.
|
|
11
|
+
*
|
|
12
|
+
* This is the main entry point of `@wdprlib/render`. It walks the AST
|
|
13
|
+
* produced by `@wdprlib/parser`, serialises each element to HTML, and
|
|
14
|
+
* appends any collected `[[module CSS]]` styles at the end (when
|
|
15
|
+
* `WikitextSettings.allowStyleElements` is `true`).
|
|
16
|
+
*
|
|
17
|
+
* @param tree - Parsed AST (from `parse()` or `resolveModules()`)
|
|
18
|
+
* @param options - Rendering configuration
|
|
19
|
+
* @returns Complete HTML string
|
|
20
|
+
*
|
|
21
|
+
* @group Render
|
|
22
|
+
*/
|
|
23
|
+
export function renderToHtml(tree: SyntaxTree, options: RenderOptions = {}): string {
|
|
24
|
+
const ctx = new RenderContext(tree, options);
|
|
25
|
+
renderElements(ctx, tree.elements);
|
|
26
|
+
renderCollectedStyles(ctx, tree.styles);
|
|
27
|
+
return ctx.getOutput();
|
|
28
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { RenderContext } from "../context";
|
|
2
|
+
|
|
3
|
+
export function renderTextNode(ctx: RenderContext, text: string): void {
|
|
4
|
+
ctx.pushEscaped(text);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function renderLineBreak(ctx: RenderContext): void {
|
|
8
|
+
ctx.push("<br />");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function renderHorizontalRule(ctx: RenderContext): void {
|
|
12
|
+
ctx.push("<hr />");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function renderContentSeparator(ctx: RenderContext): void {
|
|
16
|
+
ctx.push(`<div class="content-separator" style="display: none:"></div>`);
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RenderContext } from "../context";
|
|
2
|
+
import { pushStyleTag } from "./style-tag";
|
|
3
|
+
|
|
4
|
+
export function renderStyleElement(ctx: RenderContext, css: string): void {
|
|
5
|
+
if (!ctx.renderInlineStyles || !ctx.settings.allowStyleElements) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (ctx.hasActiveStyleSlot()) {
|
|
10
|
+
ctx.pushToStyleSlot(css);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pushStyleTag(ctx, css);
|
|
15
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -13,10 +13,14 @@ import type { EmbedAllowlistEntry } from "./elements/embed-block";
|
|
|
13
13
|
export interface PageContext {
|
|
14
14
|
/** Full page name including category prefix (e.g. `"secret:test2"`) */
|
|
15
15
|
pageName: string;
|
|
16
|
-
/** Site slug
|
|
16
|
+
/** Site slug for the current page (e.g. `"scp-wiki"`) */
|
|
17
17
|
site?: string;
|
|
18
|
-
/**
|
|
18
|
+
/** Current site domain used for absolute URL generation (e.g. `"scp-wiki.example.org"`) */
|
|
19
19
|
domain?: string;
|
|
20
|
+
/** Known domains for cross-site page references keyed by site slug. */
|
|
21
|
+
siteDomains?: Record<string, string>;
|
|
22
|
+
/** Resolve a cross-site page reference site slug to a domain. */
|
|
23
|
+
resolveSiteDomain?: (site: string) => string | null | undefined;
|
|
20
24
|
/**
|
|
21
25
|
* Returns whether a page exists on the site.
|
|
22
26
|
* When a target page does not exist, the renderer adds `class="newpage"`
|