sdocs 0.0.50 → 0.0.52
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/CHANGELOG.md +41 -0
- package/dist/grammar/sdoc.tmLanguage.json +91 -2
- package/dist/language/index.d.ts +1 -0
- package/dist/language/index.js +1 -0
- package/dist/language/page-islands.d.ts +23 -0
- package/dist/language/page-islands.js +122 -0
- package/dist/server/page-markdown.d.ts +7 -0
- package/dist/server/page-markdown.js +82 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.0.52] - 2026-07-05
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Svelte islands in `[PAGE]` bodies.** A line opening a component/HTML tag
|
|
15
|
+
or a Svelte block (`{#snippet`, `{@render`) after a blank line starts an
|
|
16
|
+
island that passes to Svelte verbatim, until its tags and blocks balance —
|
|
17
|
+
blank lines inside are fine. Markdown can no longer split a snippet across
|
|
18
|
+
paragraphs or cut an HTML section in half. Snippets declared anywhere on
|
|
19
|
+
the page are renderable from anywhere (they land at the top level of the
|
|
20
|
+
compiled fragment), so one `{#snippet}` can serve many sections.
|
|
21
|
+
- **Svelte coloring inside page bodies.** `{#…}…{/…}` blocks highlight as
|
|
22
|
+
Svelte throughout — keywords, typed params, and the markup inside them —
|
|
23
|
+
and `{@…}` tags color as keywords with TypeScript interiors, instead of
|
|
24
|
+
HTML-guess colors. Fenced code and inline code stay inert.
|
|
25
|
+
|
|
26
|
+
## [0.0.51] - 2026-07-05
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- **The sdoc grammar colors `[PAGE]` bodies as markdown.** Headings, bold,
|
|
31
|
+
lists, and fences highlight in page prose (the body's block indent is
|
|
32
|
+
accounted for, so indented bodies don't read as code blocks). Applies
|
|
33
|
+
everywhere the grammar is used — the VS Code extension and ` ```sdoc `
|
|
34
|
+
fences on the site.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- **Page expressions with string literals no longer break the page.** The
|
|
39
|
+
markdown pass HTML-escaped quotes (and `&&`, `<`) inside `{…}` expressions,
|
|
40
|
+
so `{@render colorBox("#ff0000")}` or `{format("x")}` in a `[PAGE]` body
|
|
41
|
+
failed to compile. Balanced `{…}` spans now pass through verbatim; prose
|
|
42
|
+
around them escapes exactly as before, and code fences and inline code stay
|
|
43
|
+
inert.
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- **`\{` escapes a literal brace in page prose.** Previously a lone brace
|
|
48
|
+
written in prose could break the page; a backslash-escaped brace now renders
|
|
49
|
+
as an inert literal `{`.
|
|
50
|
+
|
|
10
51
|
## [0.0.50] - 2026-07-05
|
|
11
52
|
|
|
12
53
|
### Fixed
|
|
@@ -5,6 +5,18 @@
|
|
|
5
5
|
"fileTypes": [
|
|
6
6
|
"sdoc"
|
|
7
7
|
],
|
|
8
|
+
"injections": {
|
|
9
|
+
"L:meta.embedded.block.markdown - (markup.fenced_code.block.markdown, markup.inline.raw.string.markdown, meta.template.block.sdoc)": {
|
|
10
|
+
"patterns": [
|
|
11
|
+
{
|
|
12
|
+
"include": "#page-svelte-tag"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"include": "#page-svelte-expression"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
},
|
|
8
20
|
"patterns": [
|
|
9
21
|
{
|
|
10
22
|
"include": "#comment"
|
|
@@ -229,15 +241,92 @@
|
|
|
229
241
|
"patterns": [
|
|
230
242
|
{
|
|
231
243
|
"begin": "(^|\\G)",
|
|
232
|
-
"while": "(^|\\G)(?!\\s*\\[/PAGE\\]\\s*$)",
|
|
244
|
+
"while": "(^|\\G)(?!\\s*\\[/PAGE\\]\\s*$)(?:\\t|[ ]{1,4})?",
|
|
245
|
+
"contentName": "meta.embedded.block.markdown",
|
|
233
246
|
"patterns": [
|
|
234
247
|
{
|
|
235
|
-
"include": "
|
|
248
|
+
"include": "#page-svelte-block"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"include": "text.html.markdown"
|
|
236
252
|
}
|
|
237
253
|
]
|
|
238
254
|
}
|
|
239
255
|
]
|
|
240
256
|
},
|
|
257
|
+
"page-svelte-block": {
|
|
258
|
+
"name": "meta.template.block.sdoc",
|
|
259
|
+
"begin": "(\\{#[a-zA-Z]+)([^}]*)(\\})",
|
|
260
|
+
"beginCaptures": {
|
|
261
|
+
"1": {
|
|
262
|
+
"name": "keyword.control.svelte.sdoc"
|
|
263
|
+
},
|
|
264
|
+
"2": {
|
|
265
|
+
"patterns": [
|
|
266
|
+
{
|
|
267
|
+
"include": "source.ts"
|
|
268
|
+
}
|
|
269
|
+
]
|
|
270
|
+
},
|
|
271
|
+
"3": {
|
|
272
|
+
"name": "keyword.control.svelte.sdoc"
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
"end": "(\\{/[a-zA-Z]+\\})",
|
|
276
|
+
"endCaptures": {
|
|
277
|
+
"1": {
|
|
278
|
+
"name": "keyword.control.svelte.sdoc"
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
"patterns": [
|
|
282
|
+
{
|
|
283
|
+
"include": "source.svelte"
|
|
284
|
+
}
|
|
285
|
+
]
|
|
286
|
+
},
|
|
287
|
+
"page-svelte-tag": {
|
|
288
|
+
"name": "meta.template.block.sdoc",
|
|
289
|
+
"begin": "(\\{)([@:][a-zA-Z]+)",
|
|
290
|
+
"beginCaptures": {
|
|
291
|
+
"1": {
|
|
292
|
+
"name": "punctuation.definition.block.begin.sdoc"
|
|
293
|
+
},
|
|
294
|
+
"2": {
|
|
295
|
+
"name": "keyword.control.svelte.sdoc"
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
"end": "(\\})",
|
|
299
|
+
"endCaptures": {
|
|
300
|
+
"1": {
|
|
301
|
+
"name": "punctuation.definition.block.end.sdoc"
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
"patterns": [
|
|
305
|
+
{
|
|
306
|
+
"include": "source.ts"
|
|
307
|
+
}
|
|
308
|
+
]
|
|
309
|
+
},
|
|
310
|
+
"page-svelte-expression": {
|
|
311
|
+
"name": "meta.template.expression.sdoc",
|
|
312
|
+
"begin": "(\\{)(?![#/@:\\s}])(?=[^{}]*\\})",
|
|
313
|
+
"beginCaptures": {
|
|
314
|
+
"1": {
|
|
315
|
+
"name": "punctuation.definition.expression.begin.sdoc"
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
"end": "(\\})",
|
|
319
|
+
"endCaptures": {
|
|
320
|
+
"1": {
|
|
321
|
+
"name": "punctuation.definition.expression.end.sdoc"
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
"patterns": [
|
|
325
|
+
{
|
|
326
|
+
"include": "source.ts"
|
|
327
|
+
}
|
|
328
|
+
]
|
|
329
|
+
},
|
|
241
330
|
"layout": {
|
|
242
331
|
"name": "meta.block.layout.sdoc",
|
|
243
332
|
"begin": "^\\s*(\\[)(LAYOUT)\\b(.*)$",
|
package/dist/language/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { scanSdoc, offsetToPosition, ENTITY_KINDS, SUB_BLOCK_KINDS, type Span, type EntityKind, type SubBlockKind, type AttrValue, type Attrs, type SubBlock, type Entity, type TagBlock, type ScanError, type SdocFile, } from './scanner.js';
|
|
2
2
|
export { projectSdoc, type SdocProjection, type ProjectedLineKind, } from './projection.js';
|
|
3
|
+
export { segmentPageBody, type PageSegment, } from './page-islands.js';
|
|
3
4
|
export { parseSdoc, parseArgsLiteral, slugifyTitle, normalizeBody, type ArgValue, type PreviewBlock, type ExampleBlock, type DocsEntity, type PageEntity, type LayoutEntity, type SdocEntity, type SdocDocument, } from './parser.js';
|
package/dist/language/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { scanSdoc, offsetToPosition, ENTITY_KINDS, SUB_BLOCK_KINDS, } from './scanner.js';
|
|
2
2
|
export { projectSdoc, } from './projection.js';
|
|
3
|
+
export { segmentPageBody, } from './page-islands.js';
|
|
3
4
|
export { parseSdoc, parseArgsLiteral, slugifyTitle, normalizeBody, } from './parser.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Island segmentation for [PAGE] bodies.
|
|
3
|
+
*
|
|
4
|
+
* A page body is markdown prose interleaved with Svelte "islands" — component
|
|
5
|
+
* tags, HTML sections, and template blocks like {#snippet}…{/snippet}. Markdown
|
|
6
|
+
* tooling (the renderer, prettier) must never look inside an island: markdown's
|
|
7
|
+
* paragraph and HTML-block rules split Svelte structure apart (a snippet opened
|
|
8
|
+
* inside a <p> and closed outside fails to compile; a blank line cuts a <div>
|
|
9
|
+
* section in half; a formatter re-indents block lines it can't understand).
|
|
10
|
+
*
|
|
11
|
+
* The rule an author sees: a line that starts a Svelte block ({#…}, {@render …})
|
|
12
|
+
* or an HTML/component tag, sitting at the start of a markdown block (preceded
|
|
13
|
+
* by a blank line or the body edge), begins an island. The island runs until
|
|
14
|
+
* its Svelte blocks and HTML tags are balanced — blank lines inside are part of
|
|
15
|
+
* the island. Everything else is prose.
|
|
16
|
+
*/
|
|
17
|
+
export interface PageSegment {
|
|
18
|
+
kind: 'prose' | 'island';
|
|
19
|
+
/** Verbatim lines of the segment */
|
|
20
|
+
lines: string[];
|
|
21
|
+
}
|
|
22
|
+
/** Split a page body into prose and island segments. */
|
|
23
|
+
export declare function segmentPageBody(body: string): PageSegment[];
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Island segmentation for [PAGE] bodies.
|
|
3
|
+
*
|
|
4
|
+
* A page body is markdown prose interleaved with Svelte "islands" — component
|
|
5
|
+
* tags, HTML sections, and template blocks like {#snippet}…{/snippet}. Markdown
|
|
6
|
+
* tooling (the renderer, prettier) must never look inside an island: markdown's
|
|
7
|
+
* paragraph and HTML-block rules split Svelte structure apart (a snippet opened
|
|
8
|
+
* inside a <p> and closed outside fails to compile; a blank line cuts a <div>
|
|
9
|
+
* section in half; a formatter re-indents block lines it can't understand).
|
|
10
|
+
*
|
|
11
|
+
* The rule an author sees: a line that starts a Svelte block ({#…}, {@render …})
|
|
12
|
+
* or an HTML/component tag, sitting at the start of a markdown block (preceded
|
|
13
|
+
* by a blank line or the body edge), begins an island. The island runs until
|
|
14
|
+
* its Svelte blocks and HTML tags are balanced — blank lines inside are part of
|
|
15
|
+
* the island. Everything else is prose.
|
|
16
|
+
*/
|
|
17
|
+
/** Void elements never contribute to tag depth. */
|
|
18
|
+
const VOID_TAGS = new Set([
|
|
19
|
+
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
|
|
20
|
+
'link', 'meta', 'param', 'source', 'track', 'wbr',
|
|
21
|
+
]);
|
|
22
|
+
/** Does this line begin an island (at a markdown block start)? */
|
|
23
|
+
function startsIsland(line) {
|
|
24
|
+
const t = line.trimStart();
|
|
25
|
+
return /^\{[#/@]/.test(t) || /^<[A-Za-z]/.test(t) || /^<\//.test(t);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Net Svelte block depth and HTML tag depth contributed by one line.
|
|
29
|
+
* Line-based and heuristic by design: quoted attribute values are skipped,
|
|
30
|
+
* self-closing and void tags are neutral. Unbalanced input never throws —
|
|
31
|
+
* the island simply extends (and Svelte reports the real error).
|
|
32
|
+
*/
|
|
33
|
+
function lineDepths(line) {
|
|
34
|
+
let svelte = 0;
|
|
35
|
+
let html = 0;
|
|
36
|
+
for (let i = 0; i < line.length; i++) {
|
|
37
|
+
const ch = line[i];
|
|
38
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
39
|
+
for (i++; i < line.length && line[i] !== ch; i++) {
|
|
40
|
+
if (line[i] === '\\')
|
|
41
|
+
i++;
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (ch === '{') {
|
|
46
|
+
if (line[i + 1] === '#')
|
|
47
|
+
svelte++;
|
|
48
|
+
else if (line[i + 1] === '/')
|
|
49
|
+
svelte--;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (ch !== '<')
|
|
53
|
+
continue;
|
|
54
|
+
const rest = line.slice(i);
|
|
55
|
+
const close = rest.match(/^<\/([A-Za-z][A-Za-z0-9-]*)/);
|
|
56
|
+
if (close) {
|
|
57
|
+
if (!VOID_TAGS.has(close[1].toLowerCase()))
|
|
58
|
+
html--;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const open = rest.match(/^<([A-Za-z][A-Za-z0-9-]*)/);
|
|
62
|
+
if (!open)
|
|
63
|
+
continue;
|
|
64
|
+
if (VOID_TAGS.has(open[1].toLowerCase()))
|
|
65
|
+
continue;
|
|
66
|
+
// Find the tag's own '>' (skipping quoted attrs) to detect self-closing.
|
|
67
|
+
let j = i + open[0].length;
|
|
68
|
+
for (; j < line.length; j++) {
|
|
69
|
+
const c = line[j];
|
|
70
|
+
if (c === '"' || c === "'") {
|
|
71
|
+
for (j++; j < line.length && line[j] !== c; j++)
|
|
72
|
+
;
|
|
73
|
+
}
|
|
74
|
+
else if (c === '>')
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
if (j >= line.length || line[j - 1] !== '/')
|
|
78
|
+
html++;
|
|
79
|
+
// Self-closing (`/>`) is neutral; an unterminated tag counts as open.
|
|
80
|
+
}
|
|
81
|
+
return { svelte, html };
|
|
82
|
+
}
|
|
83
|
+
/** Split a page body into prose and island segments. */
|
|
84
|
+
export function segmentPageBody(body) {
|
|
85
|
+
const lines = body.split('\n');
|
|
86
|
+
const segments = [];
|
|
87
|
+
let current = null;
|
|
88
|
+
const push = (kind, line) => {
|
|
89
|
+
if (current?.kind !== kind) {
|
|
90
|
+
current = { kind, lines: [] };
|
|
91
|
+
segments.push(current);
|
|
92
|
+
}
|
|
93
|
+
current.lines.push(line);
|
|
94
|
+
};
|
|
95
|
+
let i = 0;
|
|
96
|
+
while (i < lines.length) {
|
|
97
|
+
const line = lines[i];
|
|
98
|
+
const prevBlank = i === 0 || lines[i - 1].trim() === '';
|
|
99
|
+
if (prevBlank && line.trim() !== '' && startsIsland(line)) {
|
|
100
|
+
// Consume the island: until Svelte blocks and HTML tags balance out.
|
|
101
|
+
let svelte = 0;
|
|
102
|
+
let html = 0;
|
|
103
|
+
let j = i;
|
|
104
|
+
for (; j < lines.length; j++) {
|
|
105
|
+
const d = lineDepths(lines[j]);
|
|
106
|
+
svelte += d.svelte;
|
|
107
|
+
html += d.html;
|
|
108
|
+
if (svelte <= 0 && html <= 0)
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
const end = Math.min(j, lines.length - 1);
|
|
112
|
+
for (let k = i; k <= end; k++)
|
|
113
|
+
push('island', lines[k]);
|
|
114
|
+
current = null; // consecutive islands stay separate segments
|
|
115
|
+
i = end + 1;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
push('prose', line);
|
|
119
|
+
i++;
|
|
120
|
+
}
|
|
121
|
+
return segments;
|
|
122
|
+
}
|
|
@@ -12,4 +12,11 @@ export interface RenderedPage {
|
|
|
12
12
|
html: string;
|
|
13
13
|
toc: TocHeading[];
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Render a [PAGE] body: Svelte islands (see segmentPageBody) pass through
|
|
17
|
+
* verbatim — markdown never splits a snippet or an HTML section — and the
|
|
18
|
+
* prose between them renders as markdown. Islands land at the top level of
|
|
19
|
+
* the compiled fragment, so a snippet declared anywhere is renderable from
|
|
20
|
+
* anywhere on the page.
|
|
21
|
+
*/
|
|
15
22
|
export declare function renderPageMarkdown(source: string): Promise<RenderedPage>;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { Marked } from 'marked';
|
|
11
11
|
import { highlight } from './highlighter.js';
|
|
12
|
+
import { segmentPageBody } from '../language/page-islands.js';
|
|
12
13
|
function slugify(text) {
|
|
13
14
|
return (text
|
|
14
15
|
.toLowerCase()
|
|
@@ -27,6 +28,55 @@ function escapeHtml(text) {
|
|
|
27
28
|
.replace(/>/g, '>')
|
|
28
29
|
.replace(/"/g, '"');
|
|
29
30
|
}
|
|
31
|
+
/** marked-equivalent prose escaping (entities already present survive). */
|
|
32
|
+
function escapeProse(text) {
|
|
33
|
+
return text
|
|
34
|
+
.replace(/&(?!(?:#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g, '&')
|
|
35
|
+
.replace(/</g, '<')
|
|
36
|
+
.replace(/>/g, '>')
|
|
37
|
+
.replace(/"/g, '"')
|
|
38
|
+
.replace(/'/g, ''');
|
|
39
|
+
}
|
|
40
|
+
/** Index of the `}` closing the `{` at `open`, or -1. Skips quoted strings. */
|
|
41
|
+
function findExpressionEnd(text, open) {
|
|
42
|
+
let depth = 0;
|
|
43
|
+
for (let i = open; i < text.length; i++) {
|
|
44
|
+
const ch = text[i];
|
|
45
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
46
|
+
for (i++; i < text.length && text[i] !== ch; i++) {
|
|
47
|
+
if (text[i] === '\\')
|
|
48
|
+
i++;
|
|
49
|
+
}
|
|
50
|
+
if (i >= text.length)
|
|
51
|
+
return -1;
|
|
52
|
+
}
|
|
53
|
+
else if (ch === '{')
|
|
54
|
+
depth++;
|
|
55
|
+
else if (ch === '}' && --depth === 0)
|
|
56
|
+
return i;
|
|
57
|
+
}
|
|
58
|
+
return -1;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Escape prose while passing balanced `{…}` spans through verbatim, so Svelte
|
|
62
|
+
* expressions survive the markdown pass — string literals, `&&`, comparisons.
|
|
63
|
+
* An unmatched `{` falls back to prose escaping, exactly as before.
|
|
64
|
+
*/
|
|
65
|
+
function escapeTextKeepingExpressions(text) {
|
|
66
|
+
let out = '';
|
|
67
|
+
let from = 0;
|
|
68
|
+
for (let i = 0; i < text.length; i++) {
|
|
69
|
+
if (text[i] !== '{')
|
|
70
|
+
continue;
|
|
71
|
+
const end = findExpressionEnd(text, i);
|
|
72
|
+
if (end === -1)
|
|
73
|
+
break;
|
|
74
|
+
out += escapeProse(text.slice(from, i)) + text.slice(i, end + 1);
|
|
75
|
+
from = end + 1;
|
|
76
|
+
i = end;
|
|
77
|
+
}
|
|
78
|
+
return out + escapeProse(text.slice(from));
|
|
79
|
+
}
|
|
30
80
|
function plainText(tokens) {
|
|
31
81
|
let out = '';
|
|
32
82
|
for (const token of tokens) {
|
|
@@ -37,9 +87,28 @@ function plainText(tokens) {
|
|
|
37
87
|
}
|
|
38
88
|
return out;
|
|
39
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Render a [PAGE] body: Svelte islands (see segmentPageBody) pass through
|
|
92
|
+
* verbatim — markdown never splits a snippet or an HTML section — and the
|
|
93
|
+
* prose between them renders as markdown. Islands land at the top level of
|
|
94
|
+
* the compiled fragment, so a snippet declared anywhere is renderable from
|
|
95
|
+
* anywhere on the page.
|
|
96
|
+
*/
|
|
40
97
|
export async function renderPageMarkdown(source) {
|
|
41
98
|
const toc = [];
|
|
42
99
|
const usedIds = new Set();
|
|
100
|
+
const parts = [];
|
|
101
|
+
for (const segment of segmentPageBody(source)) {
|
|
102
|
+
if (segment.kind === 'island') {
|
|
103
|
+
parts.push(segment.lines.join('\n'));
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
parts.push(await renderProse(segment.lines.join('\n'), toc, usedIds));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { html: parts.join('\n'), toc };
|
|
110
|
+
}
|
|
111
|
+
async function renderProse(source, toc, usedIds) {
|
|
43
112
|
const headingIds = new WeakMap();
|
|
44
113
|
const fenceHtml = new WeakMap();
|
|
45
114
|
const marked = new Marked({ gfm: true });
|
|
@@ -97,8 +166,20 @@ export async function renderPageMarkdown(source) {
|
|
|
97
166
|
// tags, {expressions} stay expressions.
|
|
98
167
|
html(token) {
|
|
99
168
|
return token.text;
|
|
169
|
+
},
|
|
170
|
+
// Prose escapes for HTML, but balanced {…} spans stay verbatim so
|
|
171
|
+
// expressions keep their quotes and operators. A backslash-escaped
|
|
172
|
+
// brace (`\{`) becomes an inert literal brace.
|
|
173
|
+
text(token) {
|
|
174
|
+
if (token.type === 'escape')
|
|
175
|
+
return escapeBraces(escapeProse(token.text));
|
|
176
|
+
if ('tokens' in token && token.tokens)
|
|
177
|
+
return this.parser.parseInline(token.tokens);
|
|
178
|
+
if ('escaped' in token && token.escaped)
|
|
179
|
+
return token.text;
|
|
180
|
+
return escapeTextKeepingExpressions(token.text);
|
|
100
181
|
}
|
|
101
182
|
}
|
|
102
183
|
});
|
|
103
|
-
return
|
|
184
|
+
return marked.parser(tokens);
|
|
104
185
|
}
|