@zvndev/darkmown 2.1.0 → 2.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/CHANGELOG.md +22 -0
- package/README.md +65 -0
- package/docs/cli.md +8 -0
- package/docs/spec-alignment.md +18 -0
- package/grammar/wd-directives.gbnf +58 -0
- package/package.json +17 -10
- package/site/pages/docs/index.wd +29 -0
- package/site/pages/playground/index.js +47 -46
- package/src/builder.js +5 -0
- package/src/catalog.js +601 -0
- package/src/cli.js +8 -0
- package/src/compiler/actions.js +87 -34
- package/src/compiler/body.js +36 -14
- package/src/compiler/collections.js +16 -10
- package/src/compiler/context.js +70 -1
- package/src/compiler/fetch.js +51 -19
- package/src/compiler/format.js +5 -2
- package/src/compiler/forms.js +169 -50
- package/src/compiler/frontmatter.js +11 -2
- package/src/compiler/fs-reader.js +2 -1
- package/src/compiler/image-size.js +340 -0
- package/src/compiler/includes.js +8 -4
- package/src/compiler/interpolation.js +15 -7
- package/src/compiler/loops.js +78 -39
- package/src/compiler/markdown.js +4 -3
- package/src/compiler/media.js +38 -10
- package/src/compiler/page.js +8 -6
- package/src/compiler/predicates.js +74 -30
- package/src/compiler/reader.js +12 -2
- package/src/compiler/state.js +83 -18
- package/src/compiler/structure.js +57 -24
- package/src/deploy.js +8 -4
- package/src/errors.js +1156 -0
- package/src/grammar.js +131 -0
- package/src/index.js +1 -0
- package/src/router.js +7 -4
- package/src/scaffold.js +4 -2
- package/src/skin.js +17 -6
- package/types/api-runner.d.ts +94 -0
- package/types/builder.d.ts +141 -10
- package/types/catalog.d.ts +97 -0
- package/types/compiler/actions.d.ts +41 -0
- package/types/compiler/body.d.ts +11 -0
- package/types/compiler/collections.d.ts +76 -0
- package/types/compiler/context.d.ts +661 -0
- package/types/compiler/directives.d.ts +6 -0
- package/types/compiler/expr-ast.d.ts +20 -0
- package/types/compiler/fetch.d.ts +29 -0
- package/types/compiler/format.d.ts +49 -0
- package/types/compiler/forms.d.ts +94 -0
- package/types/compiler/frontmatter.d.ts +33 -0
- package/types/compiler/fs-reader.d.ts +7 -0
- package/types/compiler/image-size.d.ts +14 -0
- package/types/compiler/includes.d.ts +100 -0
- package/types/compiler/index.d.ts +5 -0
- package/types/compiler/interpolation.d.ts +115 -0
- package/types/compiler/loops.d.ts +36 -0
- package/types/compiler/markdown.d.ts +34 -0
- package/types/compiler/media.d.ts +32 -0
- package/types/compiler/page.d.ts +109 -0
- package/types/compiler/predicates.d.ts +50 -0
- package/types/compiler/reader.d.ts +56 -0
- package/types/compiler/state.d.ts +68 -0
- package/types/compiler/structure.d.ts +56 -0
- package/types/compiler.d.ts +35 -223
- package/types/dev.d.ts +47 -2
- package/types/errors.d.ts +100 -0
- package/types/feeds.d.ts +150 -0
- package/types/headers.d.ts +44 -0
- package/types/highlight.d.ts +24 -0
- package/types/router.d.ts +42 -3
- package/types/scaffold.d.ts +15 -3
- package/types/skin.d.ts +28 -1
- package/types/statics.d.ts +2 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a keyword-arg `:fetch` directive into a lifecycle-aware marker.
|
|
3
|
+
* Auto-declares four state keys (value/error/loading/empty), seeds them, and
|
|
4
|
+
* emits `data-wd-fetch-*` attributes (url/method/when/timeout/retry/headers/
|
|
5
|
+
* body/deps) consumed by the runtime's `startFetch`.
|
|
6
|
+
* @param {string} line
|
|
7
|
+
* @param {Ctx} ctx
|
|
8
|
+
* @param {number} index 0-based line index for `file:line` errors.
|
|
9
|
+
* @returns {string}
|
|
10
|
+
*/
|
|
11
|
+
export function handleFetch(line: string, ctx: Ctx, index: number): string;
|
|
12
|
+
/**
|
|
13
|
+
* Validate a `:fetch` (or `refresh=`) URL's scheme at compile time. Mirrors the
|
|
14
|
+
* `:try` href guard: relative paths (`/`, `./`, `../`, bare), an `http(s)://`
|
|
15
|
+
* URL, or a leading `{ state }` interpolation are allowed; a protocol-relative
|
|
16
|
+
* `//host` or any non-http(s) scheme (`file:`, `data:`, `javascript:`, …) is
|
|
17
|
+
* rejected. Interpolated values are percent-encoded by the runtime, so a scheme
|
|
18
|
+
* cannot be injected through state at request time.
|
|
19
|
+
* @param {string} url
|
|
20
|
+
* @param {Ctx} ctx
|
|
21
|
+
* @param {string} [what] Option name for the error message.
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
export function validateFetchUrl(url: string, ctx: Ctx, what?: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {import("./context.js").Ctx} Ctx
|
|
27
|
+
*/
|
|
28
|
+
export const FETCH_EXAMPLE: ":fetch todos from \"/api/todos.json\" when=visible";
|
|
29
|
+
export type Ctx = import("./context.js").Ctx;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse the inside of a `{ … }` binding into its path and an ordered list of
|
|
3
|
+
* pipe stages. `{ price | money }` → `{ path: "price", stages: [{name:"money",args:[]}] }`.
|
|
4
|
+
* Stage validation (unknown formatter) happens in {@link validatePipes}.
|
|
5
|
+
* @param {string} inner
|
|
6
|
+
* @returns {{ path: string, stages: Stage[] }}
|
|
7
|
+
*/
|
|
8
|
+
export function parsePipes(inner: string): {
|
|
9
|
+
path: string;
|
|
10
|
+
stages: Stage[];
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Compile-time validation: every stage must name a known formatter. Throws a
|
|
14
|
+
* corrective compile error otherwise. Returns the parsed `{ path, stages }`.
|
|
15
|
+
* @param {string} inner
|
|
16
|
+
* @param {Ctx} ctx
|
|
17
|
+
* @returns {{ path: string, stages: Stage[] }}
|
|
18
|
+
*/
|
|
19
|
+
export function validatePipes(inner: string, ctx: Ctx): {
|
|
20
|
+
path: string;
|
|
21
|
+
stages: Stage[];
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Run a resolved value through a pipe chain at build time. Returns the final
|
|
25
|
+
* value (a string or number); the caller stringifies for output. Mirrors the
|
|
26
|
+
* runtime's `applyFmt`.
|
|
27
|
+
* @param {any} value
|
|
28
|
+
* @param {Stage[]} stages
|
|
29
|
+
* @returns {any}
|
|
30
|
+
*/
|
|
31
|
+
export function applyPipeline(value: any, stages: Stage[]): any;
|
|
32
|
+
/** @type {Record<string, (list: any[], field?: string) => number>} */
|
|
33
|
+
export const AGGREGATES: Record<string, (list: any[], field?: string) => number>;
|
|
34
|
+
/**
|
|
35
|
+
* The whitelist. Each formatter is a pure function of (value, args) — no clock,
|
|
36
|
+
* no DOM, no globals beyond `Intl` — so a static value folds deterministically at
|
|
37
|
+
* build time and a reactive value formats identically in the browser.
|
|
38
|
+
* @type {Record<string, (value: any, args: any[]) => string | number>}
|
|
39
|
+
*/
|
|
40
|
+
export const FORMATTERS: Record<string, (value: any, args: any[]) => string | number>;
|
|
41
|
+
/** Formatter names, for error messages and the runtime mirror check. */
|
|
42
|
+
export const FORMATTER_NAMES: string[];
|
|
43
|
+
export function fmtAttr(stages: Stage[]): string;
|
|
44
|
+
export function stagesFromAttr(json: string): Stage[];
|
|
45
|
+
export type Ctx = import("./context.js").Ctx;
|
|
46
|
+
export type Stage = {
|
|
47
|
+
name: string;
|
|
48
|
+
args: Array<string | number | boolean | null>;
|
|
49
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} line
|
|
3
|
+
* @param {string[]} bodyLines
|
|
4
|
+
* @param {Ctx} ctx
|
|
5
|
+
* @param {number} index 0-based line index for `file:line` errors.
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
export function handleForm(line: string, bodyLines: string[], ctx: Ctx, index: number): string;
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} line
|
|
11
|
+
* @param {Ctx} ctx
|
|
12
|
+
* @param {number} index 0-based line index for `file:line` errors.
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*/
|
|
15
|
+
export function handleInput(line: string, ctx: Ctx, index: number): string;
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} line
|
|
18
|
+
* @param {Ctx} ctx
|
|
19
|
+
* @param {number} index 0-based line index for `file:line` errors.
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
export function handleBind(line: string, ctx: Ctx, index: number): string;
|
|
23
|
+
/**
|
|
24
|
+
* `:slider name [= value] [min=N] [max=N] [step=N] [aria-label="…"] [persist]` — a
|
|
25
|
+
* range input two-way bound to a NUMBER :state. With `= value` it declares the state
|
|
26
|
+
* inline (seeding it numeric) and may `persist`; without `=` it binds to an already-
|
|
27
|
+
* declared :state. Pure compile-time: it reuses the runtime's input binding (range
|
|
28
|
+
* values coerce to Number), so it ships NO behavior module.
|
|
29
|
+
* @param {string} line
|
|
30
|
+
* @param {Ctx} ctx
|
|
31
|
+
* @param {number} index 0-based line index for `file:line` errors.
|
|
32
|
+
* @returns {string}
|
|
33
|
+
*/
|
|
34
|
+
export function handleSlider(line: string, ctx: Ctx, index: number): string;
|
|
35
|
+
/**
|
|
36
|
+
* @param {string} line
|
|
37
|
+
* @param {Ctx} ctx
|
|
38
|
+
* @param {number} index 0-based line index for `file:line` errors.
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
export function handleSubmit(line: string, ctx: Ctx, index: number): string;
|
|
42
|
+
/**
|
|
43
|
+
* `:textarea name [placeholder="…"] [rows=N] [required]` → a `<textarea>`. Like
|
|
44
|
+
* `:input`, it derives a non-visual aria-label from the placeholder (else the
|
|
45
|
+
* humanized name) when the author supplies none. Captured by the runtime's
|
|
46
|
+
* FormData exactly like `:input` — no runtime change needed.
|
|
47
|
+
* @param {string} line
|
|
48
|
+
* @param {Ctx} ctx
|
|
49
|
+
* @param {number} index 0-based line index for `file:line` errors.
|
|
50
|
+
* @returns {string}
|
|
51
|
+
*/
|
|
52
|
+
export function handleTextarea(line: string, ctx: Ctx, index: number): string;
|
|
53
|
+
/**
|
|
54
|
+
* `:select name [required]` followed by `- Label` list lines → a `<select>` with
|
|
55
|
+
* one `<option>` per label (value === label). Derives an aria-label from the
|
|
56
|
+
* humanized name when none is given. Captured by FormData like the other fields.
|
|
57
|
+
* @param {string} line
|
|
58
|
+
* @param {string[]} optionLines The following `- Label` lines consumed by dispatch.
|
|
59
|
+
* @param {Ctx} ctx
|
|
60
|
+
* @param {number} index 0-based line index for `file:line` errors.
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
63
|
+
export function handleSelect(line: string, optionLines: string[], ctx: Ctx, index: number): string;
|
|
64
|
+
/**
|
|
65
|
+
* `:checkbox name [required]` / `:radio name [required]` followed by `- Label`
|
|
66
|
+
* lines → a labelled group of `<input type=checkbox|radio>`, each sharing `name`
|
|
67
|
+
* with value === label and wrapped in its own `<label>`. The group is a
|
|
68
|
+
* `role="group"` / `role="radiogroup"` container with an aria-label (explicit or
|
|
69
|
+
* humanized from the name). Checkbox groups are marked `data-wd-multi="name"` so
|
|
70
|
+
* the runtime captures every checked value as an array (`FormData.getAll`); radio
|
|
71
|
+
* groups capture a single value like `:input`. Flags: `required` (radio → the
|
|
72
|
+
* group; not emitted on checkboxes, where "at least one" has no native HTML form),
|
|
73
|
+
* `disabled` (whole group), `autofocus` (first control).
|
|
74
|
+
* @param {string} line
|
|
75
|
+
* @param {string[]} optionLines The following `- Label` lines consumed by dispatch.
|
|
76
|
+
* @param {Ctx} ctx
|
|
77
|
+
* @param {"checkbox" | "radio"} kind
|
|
78
|
+
* @param {number} index 0-based line index for `file:line` errors.
|
|
79
|
+
* @returns {string}
|
|
80
|
+
*/
|
|
81
|
+
export function handleChoiceGroup(line: string, optionLines: string[], ctx: Ctx, kind: "checkbox" | "radio", index: number): string;
|
|
82
|
+
/**
|
|
83
|
+
* @typedef {import("./context.js").Ctx} Ctx
|
|
84
|
+
*/
|
|
85
|
+
export const FORM_EXAMPLE: ":form into contact";
|
|
86
|
+
export const INPUT_EXAMPLE: ":input email type=email required";
|
|
87
|
+
export const TEXTAREA_EXAMPLE: ":textarea message placeholder=\"Your message\" rows=4";
|
|
88
|
+
export const SELECT_EXAMPLE: ":select topic";
|
|
89
|
+
export const CHECKBOX_EXAMPLE: ":checkbox toppings";
|
|
90
|
+
export const RADIO_EXAMPLE: ":radio size";
|
|
91
|
+
export const SUBMIT_EXAMPLE: ":submit \"Send\"";
|
|
92
|
+
export const BIND_EXAMPLE: ":bind query placeholder=\"Search\"";
|
|
93
|
+
export const SLIDER_EXAMPLE: ":slider volume = 50 min=0 max=100 step=1";
|
|
94
|
+
export type Ctx = import("./context.js").Ctx;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("./context.js").Meta} Meta
|
|
3
|
+
* @typedef {import("./context.js").FrontmatterValue} FrontmatterValue
|
|
4
|
+
* @typedef {import("./context.js").Compilation} Compilation
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Split a raw file into its frontmatter `meta` and `body`. `bodyLine` is the
|
|
8
|
+
* 0-based file line index the body starts on (the number of lines the
|
|
9
|
+
* frontmatter block consumed) so compile errors can report true file lines.
|
|
10
|
+
* @param {string} raw Full file contents.
|
|
11
|
+
* @param {string} [file] Source path, used only for error messages.
|
|
12
|
+
* @returns {{ meta: Meta, body: string, bodyLine: number }}
|
|
13
|
+
*/
|
|
14
|
+
export function parseFrontmatter(raw: string, file?: string): {
|
|
15
|
+
meta: Meta;
|
|
16
|
+
body: string;
|
|
17
|
+
bodyLine: number;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Warn (non-fatal) when a file looks like it MEANT to open frontmatter but forgot
|
|
21
|
+
* the leading `---`: the first content line is a `key: value` pair and a bare
|
|
22
|
+
* `---` fence appears within the opening lines. Conservative — a normal markdown
|
|
23
|
+
* body whose first line happens to contain a colon (with no early `---`) is left
|
|
24
|
+
* alone, since `---` is also a valid horizontal rule.
|
|
25
|
+
* @param {string} raw
|
|
26
|
+
* @param {string} file
|
|
27
|
+
* @param {Compilation} comp
|
|
28
|
+
* @returns {void}
|
|
29
|
+
*/
|
|
30
|
+
export function warnLikelyFrontmatter(raw: string, file: string, comp: Compilation): void;
|
|
31
|
+
export type Meta = import("./context.js").Meta;
|
|
32
|
+
export type FrontmatterValue = import("./context.js").FrontmatterValue;
|
|
33
|
+
export type Compilation = import("./context.js").Compilation;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A {@link import("./reader.js").Reader} backed by `node:fs`. Each method is a
|
|
3
|
+
* thin pass-through to the synchronous fs call the compiler used before the
|
|
4
|
+
* reader abstraction, so an fs-backed compile stays byte-identical.
|
|
5
|
+
* @returns {import("./reader.js").Reader}
|
|
6
|
+
*/
|
|
7
|
+
export function fsReader(): import("./reader.js").Reader;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read an image's intrinsic dimensions from its header bytes.
|
|
3
|
+
* @param {Uint8Array} bytes Raw file contents (a Node `Buffer` is a `Uint8Array`).
|
|
4
|
+
* @returns {Dimensions | null} The size, or null when the bytes are empty, of an
|
|
5
|
+
* unsupported format, or malformed. Never throws.
|
|
6
|
+
*/
|
|
7
|
+
export function imageSize(bytes: Uint8Array): Dimensions | null;
|
|
8
|
+
/**
|
|
9
|
+
* Intrinsic pixel size.
|
|
10
|
+
*/
|
|
11
|
+
export type Dimensions = {
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("./context.js").Paths} Paths
|
|
3
|
+
* @typedef {import("./context.js").Assets} Assets
|
|
4
|
+
* @typedef {import("./context.js").Compilation} Compilation
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Read a `.skin` file's first meaningful line (skipping `//` line comments,
|
|
8
|
+
* `/* … *\/` block comments, blanks, and decorative dividers — the same skip
|
|
9
|
+
* rules `compileSkin` applies) to decide whether it opts into scoping. A skin
|
|
10
|
+
* scopes its selectors ONLY when that first line is exactly `scoped`; otherwise
|
|
11
|
+
* it is global and untouched. This is the single source for "is this skin
|
|
12
|
+
* scoped + what is its id", so the CSS rewrite (builder) and the HTML stamp
|
|
13
|
+
* (page/include) always agree on both.
|
|
14
|
+
* @param {string} skinPath Absolute path to the colocated `.skin` file.
|
|
15
|
+
* @param {string} cwd Project root, for the path-based scope id.
|
|
16
|
+
* @param {import("./reader.js").Reader} reader Source reader for the skin file.
|
|
17
|
+
* @returns {{ scoped: boolean, scopeId: string }}
|
|
18
|
+
*/
|
|
19
|
+
export function scopeInfoForSkin(skinPath: string, cwd: string, reader: import("./reader.js").Reader): {
|
|
20
|
+
scoped: boolean;
|
|
21
|
+
scopeId: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* The scope info for a page/include's OWN colocated `.skin` sibling, or null
|
|
25
|
+
* when there is no sibling skin. Used by the HTML stamp sites: a route's own
|
|
26
|
+
* scoped skin → stamp the page body; an include's scoped skin → stamp the
|
|
27
|
+
* returned subtree. Resolution mirrors `collectColocatedAssets` (same stem,
|
|
28
|
+
* `.skin` extension).
|
|
29
|
+
* @param {string} file Absolute path to the page/include source file.
|
|
30
|
+
* @param {Paths} context
|
|
31
|
+
* @param {import("./reader.js").Reader} reader Source reader for the skin file.
|
|
32
|
+
* @returns {{ scoped: boolean, scopeId: string } | null}
|
|
33
|
+
*/
|
|
34
|
+
export function scopedSkinFor(file: string, context: Paths, reader: import("./reader.js").Reader): {
|
|
35
|
+
scoped: boolean;
|
|
36
|
+
scopeId: string;
|
|
37
|
+
} | null;
|
|
38
|
+
/**
|
|
39
|
+
* Stamp `data-wd-scope="<id>"` onto every opening tag of a subtree — the HTML
|
|
40
|
+
* half of compile-time scoped styles, mirroring {@link enhanceImages}: a
|
|
41
|
+
* compile-time post-pass over assembled HTML, zero runtime JS. Every element
|
|
42
|
+
* start tag that doesn't already carry a scope attribute gets one, so the scoped
|
|
43
|
+
* stylesheet (whose selectors end in `[data-wd-scope="<id>"]`) matches inside
|
|
44
|
+
* this subtree and nowhere else. Void/self-closing tags are stamped too (the
|
|
45
|
+
* attribute is valid on any element). Closing tags, comments, doctype, and the
|
|
46
|
+
* `<!`/`<?` families are left alone. A tag that ALREADY has a `data-wd-scope`
|
|
47
|
+
* (e.g. a nested include with its own scope) keeps its own — first stamp wins.
|
|
48
|
+
*
|
|
49
|
+
* Raw-text / escapable-raw-text elements (`script`, `style`, `textarea`,
|
|
50
|
+
* `title`) are special-cased: their OPENING tag is stamped (valid), but their
|
|
51
|
+
* BODY is left byte-intact — `<`/`>` inside a `<script>` are JavaScript, not
|
|
52
|
+
* markup, so stamping there would corrupt the content. A small tokenizer walks
|
|
53
|
+
* tag-by-tag (quote-aware in attribute lists) and, on entering a raw-text
|
|
54
|
+
* element, copies through verbatim until the matching close tag.
|
|
55
|
+
* @param {string} html Subtree HTML to scope.
|
|
56
|
+
* @param {string} scopeId Scope id (from {@link import("../skin.js").scopeIdFor}).
|
|
57
|
+
* @returns {string}
|
|
58
|
+
*/
|
|
59
|
+
export function stampScope(html: string, scopeId: string): string;
|
|
60
|
+
/**
|
|
61
|
+
* Register a page's colocated `.skin`/`.js` siblings as emitted assets.
|
|
62
|
+
* @param {string} file
|
|
63
|
+
* @param {Paths} context
|
|
64
|
+
* @param {Assets} assets
|
|
65
|
+
* @param {import("./reader.js").Reader} reader Source reader for sibling probing.
|
|
66
|
+
* @returns {void}
|
|
67
|
+
*/
|
|
68
|
+
export function collectColocatedAssets(file: string, context: Paths, assets: Assets, reader: import("./reader.js").Reader): void;
|
|
69
|
+
/**
|
|
70
|
+
* Resolve an include spec to an absolute path inside `site/pages` or `site/_`.
|
|
71
|
+
* @param {string} spec Include target (may be quoted).
|
|
72
|
+
* @param {string} fromFile File requesting the include.
|
|
73
|
+
* @param {Paths} context
|
|
74
|
+
* @param {boolean} allowAny Allow non-page extensions (e.g. JSON for `@loop`).
|
|
75
|
+
* @param {string} loc Source location (`file:line`) for the error messages, so
|
|
76
|
+
* an unresolved/out-of-sandbox include reports the directive's line. Callers
|
|
77
|
+
* with a line index pass `at(ctx, index)`; pass `fromFile` for the file-only
|
|
78
|
+
* message.
|
|
79
|
+
* @param {import("./reader.js").Reader} reader Source reader for existence
|
|
80
|
+
* probing, threaded from `ctx.comp.reader`.
|
|
81
|
+
* @returns {string}
|
|
82
|
+
*/
|
|
83
|
+
export function resolveInclude(spec: string, fromFile: string, context: Paths, allowAny: boolean, loc: string, reader: import("./reader.js").Reader): string;
|
|
84
|
+
/**
|
|
85
|
+
* @param {string} file
|
|
86
|
+
* @param {Paths} context
|
|
87
|
+
* @returns {boolean}
|
|
88
|
+
*/
|
|
89
|
+
export function isAllowedInclude(file: string, context: Paths): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Warn when a plain `.md` file contains `.wd`-only syntax that stays inert.
|
|
92
|
+
* @param {string} body
|
|
93
|
+
* @param {string} file
|
|
94
|
+
* @param {Compilation} comp
|
|
95
|
+
* @returns {void}
|
|
96
|
+
*/
|
|
97
|
+
export function scanMarkdownHints(body: string, file: string, comp: Compilation): void;
|
|
98
|
+
export type Paths = import("./context.js").Paths;
|
|
99
|
+
export type Assets = import("./context.js").Assets;
|
|
100
|
+
export type Compilation = import("./context.js").Compilation;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { parseFrontmatter } from "./frontmatter.js";
|
|
2
|
+
export { stampScope } from "./includes.js";
|
|
3
|
+
export { escapeHtml } from "./interpolation.js";
|
|
4
|
+
export { loopKeyOf } from "./loops.js";
|
|
5
|
+
export { compileDocument, compileFromMemory, compilePage, enhanceImages } from "./page.js";
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("./context.js").Scope} Scope
|
|
3
|
+
* @typedef {import("./context.js").Ctx} Ctx
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Walk the scope chain for a top-level variable.
|
|
7
|
+
* @param {Scope} scope
|
|
8
|
+
* @param {string} name
|
|
9
|
+
* @returns {{ found: true, value: unknown } | { found: false }}
|
|
10
|
+
*/
|
|
11
|
+
export function lookupVar(scope: Scope, name: string): {
|
|
12
|
+
found: true;
|
|
13
|
+
value: unknown;
|
|
14
|
+
} | {
|
|
15
|
+
found: false;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Resolve a dotted path against the static scope chain.
|
|
19
|
+
* @param {string} expr
|
|
20
|
+
* @param {Ctx} ctx
|
|
21
|
+
* @returns {{ found: true, value: unknown } | { found: false }}
|
|
22
|
+
*/
|
|
23
|
+
export function lookupPath(expr: string, ctx: Ctx): {
|
|
24
|
+
found: true;
|
|
25
|
+
value: unknown;
|
|
26
|
+
} | {
|
|
27
|
+
found: false;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Safely read a dotted path off a value, rejecting prototype-pollution segments.
|
|
31
|
+
* @param {unknown} value
|
|
32
|
+
* @param {string[]} segments
|
|
33
|
+
* @returns {unknown}
|
|
34
|
+
*/
|
|
35
|
+
export function getPath(value: unknown, segments: string[]): unknown;
|
|
36
|
+
/**
|
|
37
|
+
* Validate a dotted path shape, rejecting prototype-pollution segments at every
|
|
38
|
+
* level. Shared by actions/loops/fetch. Throws with a corrective `Use:` hint.
|
|
39
|
+
* @param {string} path Dotted path, e.g. `cart.items`.
|
|
40
|
+
* @param {Ctx} ctx
|
|
41
|
+
* @param {string} use Corrective suggestion for the error message.
|
|
42
|
+
* @returns {string[]} The validated segments.
|
|
43
|
+
*/
|
|
44
|
+
export function validatePath(path: string, ctx: Ctx, use: string): string[];
|
|
45
|
+
/**
|
|
46
|
+
* Resolve a bare state name to its fully-qualified key, walking section scopes.
|
|
47
|
+
* @param {string} name
|
|
48
|
+
* @param {Ctx} ctx
|
|
49
|
+
* @returns {string | null}
|
|
50
|
+
*/
|
|
51
|
+
export function resolveStateKey(name: string, ctx: Ctx): string | null;
|
|
52
|
+
/**
|
|
53
|
+
* Render an interpolated value as text. Arrays join with ", " (matching the
|
|
54
|
+
* documented `{ meta.tags }` behavior); a bare object is a mistake — fail
|
|
55
|
+
* loudly with a fix rather than emitting "[object Object]".
|
|
56
|
+
* @param {unknown} value
|
|
57
|
+
* @param {string} expr
|
|
58
|
+
* @param {Ctx} ctx
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
export function interpolateLeaf(value: unknown, expr: string, ctx: Ctx): string;
|
|
62
|
+
/**
|
|
63
|
+
* JSON-encode a value for an inline `<script>`, escaping `<` to stay HTML-safe.
|
|
64
|
+
* @param {unknown} value
|
|
65
|
+
* @returns {string}
|
|
66
|
+
*/
|
|
67
|
+
export function safeScriptJson(value: unknown): string;
|
|
68
|
+
/**
|
|
69
|
+
* Parse a `:state`/`:store`/`:theme` value: quoted string, boolean, null, number,
|
|
70
|
+
* or JSON literal — falling back to the bare string. Multi-line array/object
|
|
71
|
+
* literals are joined into one line before reaching here (see `joinValueDirective`
|
|
72
|
+
* in body.js), so a value that opens a `[`/`{` but won't parse is genuinely
|
|
73
|
+
* unbalanced (a missing `]`/`}`) and throws, rather than being stored verbatim as
|
|
74
|
+
* the partial string `"["` — the silent-corruption footgun this guard replaces.
|
|
75
|
+
* @param {string} raw
|
|
76
|
+
* @param {string} [where] `file:line` for the error message.
|
|
77
|
+
* @returns {unknown}
|
|
78
|
+
*/
|
|
79
|
+
export function parseStateValue(raw: string, where?: string): unknown;
|
|
80
|
+
/**
|
|
81
|
+
* @param {string} raw
|
|
82
|
+
* @returns {string | number | boolean}
|
|
83
|
+
*/
|
|
84
|
+
export function parseScalar(raw: string): string | number | boolean;
|
|
85
|
+
/**
|
|
86
|
+
* @param {string} [value]
|
|
87
|
+
* @returns {string}
|
|
88
|
+
*/
|
|
89
|
+
export function stripQuotes(value?: string): string;
|
|
90
|
+
/**
|
|
91
|
+
* Turn a field name / state key into a human-readable accessible name, e.g.
|
|
92
|
+
* `quest` → "Quest", `user-name` → "User name", `firstName` → "First name".
|
|
93
|
+
* Hyphens, underscores, and camelCase boundaries become spaces; the first letter
|
|
94
|
+
* is capitalized. Used to auto-derive aria-label on generated form inputs.
|
|
95
|
+
* @param {string} name
|
|
96
|
+
* @returns {string}
|
|
97
|
+
*/
|
|
98
|
+
export function humanizeName(name: string): string;
|
|
99
|
+
/**
|
|
100
|
+
* Escape a value for safe inclusion in HTML text and attribute contexts.
|
|
101
|
+
* @param {unknown} value
|
|
102
|
+
* @returns {string}
|
|
103
|
+
*/
|
|
104
|
+
export function escapeHtml(value: unknown): string;
|
|
105
|
+
/**
|
|
106
|
+
* Reverse {@link escapeHtml}. Used by the loop initial-paint fill to read a
|
|
107
|
+
* `data-wd-fmt` attribute (HTML-escaped JSON) back into pipe stages at build
|
|
108
|
+
* time — the browser does the same decode for the runtime via getAttribute.
|
|
109
|
+
* `&` is decoded last so `&lt;` round-trips to `<`, not `<`.
|
|
110
|
+
* @param {string} value
|
|
111
|
+
* @returns {string}
|
|
112
|
+
*/
|
|
113
|
+
export function unescapeHtml(value: string): string;
|
|
114
|
+
export type Scope = import("./context.js").Scope;
|
|
115
|
+
export type Ctx = import("./context.js").Ctx;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} line
|
|
3
|
+
* @param {string[]} bodyLines
|
|
4
|
+
* @param {string[] | null} emptyLines
|
|
5
|
+
* @param {Ctx} ctx
|
|
6
|
+
* @param {number} index 0-based line index of the `@loop` opener.
|
|
7
|
+
* @param {number} [emptyStart] 0-based index the empty branch starts at within
|
|
8
|
+
* the loop body, so its nested errors report the true file line.
|
|
9
|
+
* @returns {string}
|
|
10
|
+
*/
|
|
11
|
+
export function handleLoop(line: string, bodyLines: string[], emptyLines: string[] | null, ctx: Ctx, index: number, emptyStart?: number): string;
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} str
|
|
14
|
+
* @param {number} start
|
|
15
|
+
* @param {string} tag
|
|
16
|
+
* @returns {number}
|
|
17
|
+
*/
|
|
18
|
+
export function matchElement(str: string, start: number, tag: string): number;
|
|
19
|
+
/**
|
|
20
|
+
* Stable per-render key for a loop row, disambiguating duplicates with `#n`.
|
|
21
|
+
* @param {unknown} item
|
|
22
|
+
* @param {Map<string, number>} counts Mutable seen-count accumulator.
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
export function loopKeyOf(item: unknown, counts: Map<string, number>): string;
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {import("./context.js").Ctx} Ctx
|
|
28
|
+
* @typedef {import("./context.js").Predicate} Predicate
|
|
29
|
+
* @typedef {import("./context.js").NumArg} NumArg
|
|
30
|
+
* @typedef {import("./context.js").LoopOpts} LoopOpts
|
|
31
|
+
*/
|
|
32
|
+
export const LOOP_EXAMPLE: "@loop /products.json into p where p.price < 50 sort by p.price asc";
|
|
33
|
+
export type Ctx = import("./context.js").Ctx;
|
|
34
|
+
export type Predicate = import("./context.js").Predicate;
|
|
35
|
+
export type NumArg = import("./context.js").NumArg;
|
|
36
|
+
export type LoopOpts = import("./context.js").LoopOpts;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pick the markdown-it instance for a page (raw HTML off by default, on with `html: true`).
|
|
3
|
+
* @param {Meta} [meta]
|
|
4
|
+
* @returns {MarkdownIt}
|
|
5
|
+
*/
|
|
6
|
+
export function selectMd(meta?: Meta): MarkdownIt;
|
|
7
|
+
/**
|
|
8
|
+
* @param {string} text
|
|
9
|
+
* @param {Ctx} ctx
|
|
10
|
+
* @returns {string}
|
|
11
|
+
*/
|
|
12
|
+
export function renderProse(text: string, ctx: Ctx): string;
|
|
13
|
+
/**
|
|
14
|
+
* The inline token an attr block attaches to: the immediately-preceding image
|
|
15
|
+
* (self-closing) or the open token matching the immediately-preceding close
|
|
16
|
+
* (link/em/strong/…). Returns null when nothing valid precedes. Exported so its
|
|
17
|
+
* contract — including the defensive unbalanced-close fallback that markdown-it's
|
|
18
|
+
* always-balanced token stream never triggers in practice — is unit-testable.
|
|
19
|
+
* @param {any[]} children markdown-it inline child tokens
|
|
20
|
+
* @param {number} i Index of the attr-block text token.
|
|
21
|
+
* @returns {any} the matching markdown-it Token, or null
|
|
22
|
+
*/
|
|
23
|
+
export function attrTarget(children: any[], i: number): any;
|
|
24
|
+
/**
|
|
25
|
+
* GitHub-style heading slug: lowercase, punctuation stripped (letters, numbers,
|
|
26
|
+
* whitespace, `_`, and `-` survive), each whitespace character becomes a hyphen.
|
|
27
|
+
* Punctuation-only headings fall back to `"section"` so the id is never empty.
|
|
28
|
+
* @param {string} text Plain heading text.
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
export function slugify(text: string): string;
|
|
32
|
+
export type Meta = import("./context.js").Meta;
|
|
33
|
+
export type Ctx = import("./context.js").Ctx;
|
|
34
|
+
import MarkdownIt from "markdown-it";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `:video /clip.mp4 [poster=…] [width=…] [height=…] [preload=…] [controls] [autoplay]
|
|
3
|
+
* [loop] [muted] [playsinline]` and `:audio /track.mp3 [preload=…] [controls] …` →
|
|
4
|
+
* a hardened `<video>`/`<audio>`. Defaults: `preload="metadata"`, and `controls`
|
|
5
|
+
* unless the clip is an `autoplay` background (autoplay also implies `muted`, which
|
|
6
|
+
* browsers require). Author flags/attrs always win. URLs use the `:fetch` scheme
|
|
7
|
+
* guard (relative or http(s)).
|
|
8
|
+
* @param {string} line
|
|
9
|
+
* @param {"video" | "audio"} kind
|
|
10
|
+
* @param {Ctx} ctx
|
|
11
|
+
* @param {number} index 0-based line index for `file:line` errors.
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
export function handleMedia(line: string, kind: "video" | "audio", ctx: Ctx, index: number): string;
|
|
15
|
+
/**
|
|
16
|
+
* `:embed <url> [title="…"]` → a lazy, privacy-friendly responsive iframe. A
|
|
17
|
+
* YouTube or Vimeo URL is rewritten to its no-cookie / player embed; any other
|
|
18
|
+
* http(s) URL becomes a generic 16:9 iframe. Inline styles keep the wrapper
|
|
19
|
+
* self-contained (no framework CSS), so an embed-only page stays zero-JS/zero-CSS.
|
|
20
|
+
* @param {string} line
|
|
21
|
+
* @param {Ctx} ctx
|
|
22
|
+
* @param {number} index 0-based line index for `file:line` errors.
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
export function handleEmbed(line: string, ctx: Ctx, index: number): string;
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {import("./context.js").Ctx} Ctx
|
|
28
|
+
*/
|
|
29
|
+
export const VIDEO_EXAMPLE: ":video /demo.mp4 controls";
|
|
30
|
+
export const AUDIO_EXAMPLE: ":audio /theme.mp3 controls";
|
|
31
|
+
export const EMBED_EXAMPLE: ":embed https://youtu.be/dQw4w9WgXcQ title=\"Demo\"";
|
|
32
|
+
export type Ctx = import("./context.js").Ctx;
|