@zvndev/darkmown 2.8.0 → 2.10.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.
@@ -24,12 +24,64 @@ export function takeA11yAttrs(rest: string, ctx: Ctx, index: number, what: strin
24
24
  */
25
25
  export function a11yAttrHtml(attrs: [string, string][]): string;
26
26
  /**
27
+ * Compile an `@include` line — inline form (`@include /nav.wd`) or block form
28
+ * (`@include /card.wd … @endinclude`, with `childrenLines` holding the lines
29
+ * between opener and closer, captured by {@link scanIncludeBlock}).
30
+ *
31
+ * Block form is compile-time macro expansion and nothing more: the children
32
+ * lines compile in the CALLER's context (its scope, its `:state`, its loop
33
+ * item when the call sits in a `@loop` body) BEFORE the target compiles, and
34
+ * the resulting HTML rides into the include's scope under
35
+ * {@link INCLUDE_CHILDREN}, where the body parser splices it at each
36
+ * `{ children }` slot line. No runtime marker, no reactivity of its own.
37
+ * Children compile ONCE even when the include places the slot twice, so state
38
+ * a child line declares is declared once in the caller.
39
+ *
40
+ * The include file's own frontmatter may declare `defaults: [name="value" …]`
41
+ * fallback values for `with` arguments (see {@link parseIncludeDefaults}); a
42
+ * caller-provided argument always wins over a default, and an argument with
43
+ * neither keeps the literal-braces behavior.
27
44
  * @param {string} line
28
45
  * @param {Ctx} ctx
29
46
  * @param {number} index 0-based line index for `file:line` errors.
47
+ * @param {string[]} [childrenLines] Lines between `@include` and `@endinclude`
48
+ * for the block form; undefined for the inline form.
30
49
  * @returns {string}
31
50
  */
32
- export function handleInclude(line: string, ctx: Ctx, index: number): string;
51
+ export function handleInclude(line: string, ctx: Ctx, index: number, childrenLines?: string[]): string;
52
+ /**
53
+ * Scan ahead from an `@include` opener for the `@endinclude` that pairs with
54
+ * it. Pairing is nearest-opener-first: a nested `@include` line pushes, an
55
+ * `@endinclude` line pops the most recent unclosed opener, and the first
56
+ * `@endinclude` found with nothing on the stack belongs to THIS opener — so
57
+ * nested blocks pair correctly, and an inline `@include` earlier in the file
58
+ * than a block (the common nav-include-then-card-block page) is unaffected.
59
+ * Fenced code is skipped, exactly as in the other block scanners.
60
+ *
61
+ * Returns null when no closer pairs — the include is the INLINE form, which
62
+ * stays legal (backward compatible): an opener that never pairs is an inline
63
+ * include followed by ordinary prose, and the two are indistinguishable, so
64
+ * there is deliberately no "unclosed block" error.
65
+ * @param {string[]} lines The current body slice.
66
+ * @param {number} start 0-based index of the `@include` opener line.
67
+ * @returns {{ body: string[], end: number } | null} The children lines and the
68
+ * closer's 0-based index, or null for the inline form.
69
+ */
70
+ export function scanIncludeBlock(lines: string[], start: number): {
71
+ body: string[];
72
+ end: number;
73
+ } | null;
74
+ /**
75
+ * `@endinclude` reached the body dispatcher, meaning no `@include` opener
76
+ * claimed it — the one-closer-too-many case (a typo'd closer, or a nested
77
+ * block attempted inside a children block, where the inner opener already
78
+ * took the closer the author meant for the outer one). Always throws.
79
+ * @param {string} line The offending line, for the message.
80
+ * @param {Ctx} ctx
81
+ * @param {number} index 0-based line index for `file:line`.
82
+ * @returns {never}
83
+ */
84
+ export function strayEndinclude(line: string, ctx: Ctx, index: number): never;
33
85
  /**
34
86
  * @param {string} header
35
87
  * @param {string[]} bodyLines
@@ -4,7 +4,7 @@
4
4
  * a different source (see `compileFromMemory`).
5
5
  * @param {string} file Absolute path to the source `.md`/`.wd` file.
6
6
  * @param {import("./config.js").Paths} context Resolved project paths.
7
- * @param {{ feed?: { href: string, title: string }, site?: import("./compiler/page.js").SiteContext, collections?: Map<string, import("./compiler/collections.js").CollectionRow[]>, vars?: Record<string, unknown>, reader?: import("./compiler/reader.js").Reader }} [options]
7
+ * @param {{ feed?: { href: string, title: string }, site?: import("./compiler/page.js").SiteContext, collections?: Map<string, import("./compiler/collections.js").CollectionRow[]>, vars?: Record<string, unknown>, reader?: import("./compiler/reader.js").Reader, imageVariants?: Map<string, number[]> | null }} [options]
8
8
  * @returns {CompiledPage}
9
9
  */
10
10
  export function compilePage(file: string, context: import("./config.js").Paths, options?: {
@@ -16,6 +16,7 @@ export function compilePage(file: string, context: import("./config.js").Paths,
16
16
  collections?: Map<string, import("./compiler/collections.js").CollectionRow[]>;
17
17
  vars?: Record<string, unknown>;
18
18
  reader?: import("./compiler/reader.js").Reader;
19
+ imageVariants?: Map<string, number[]> | null;
19
20
  }): CompiledPage;
20
21
  /**
21
22
  * Compile a source file into its body HTML, frontmatter, and assets (no page
@@ -26,9 +27,11 @@ export function compilePage(file: string, context: import("./config.js").Paths,
26
27
  * @param {Record<string, unknown>} [vars] Initial static scope variables.
27
28
  * @param {Map<string, import("./compiler/collections.js").CollectionRow[]>} [collections]
28
29
  * @param {import("./compiler/reader.js").Reader} [reader] Source reader.
30
+ * @param {Map<string, number[]> | null} [imageVariants] Builder-generated
31
+ * responsive variants (original's absolute source path → widths).
29
32
  * @returns {CompiledDocument}
30
33
  */
31
- export function compileDocument(file: string, context: import("./config.js").Paths, stack?: string[], vars?: Record<string, unknown>, collections?: Map<string, import("./compiler/collections.js").CollectionRow[]>, reader?: import("./compiler/reader.js").Reader): CompiledDocument;
34
+ export function compileDocument(file: string, context: import("./config.js").Paths, stack?: string[], vars?: Record<string, unknown>, collections?: Map<string, import("./compiler/collections.js").CollectionRow[]>, reader?: import("./compiler/reader.js").Reader, imageVariants?: Map<string, number[]> | null): CompiledDocument;
32
35
  /**
33
36
  * Harden every `<img>` in an assembled page body, reading intrinsic dimensions
34
37
  * from the real filesystem by default.
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Subset every project-local font referenced by the emitted stylesheets and
3
+ * rewrite the dist CSS to lead with the subset woff2.
4
+ *
5
+ * Skips, none of them errors: remote/protocol-relative/data/relative URLs
6
+ * (silently), already-woff2 sources (silently), URLs that escape or miss the
7
+ * project (silently — a broken link is an authoring bug, not a subsetting one).
8
+ * An unreadable font file, and one the toolchain fails on, get a hint naming
9
+ * the file and ship as-is. With no `@font-face` in any emitted stylesheet the
10
+ * pass is a no-op and never even scans the HTML.
11
+ * @param {Paths} paths
12
+ * @param {{ cacheDir?: string, quiet?: boolean,
13
+ * runJobs?: (payload: { charset: number[], jobs: SubsetJob[] }) => SubsetResult }} [options]
14
+ * `quiet` suppresses the hints (dev-server rebuilds). `runJobs` is the subset
15
+ * seam — the default spawns the worker process; tests inject fakes for the
16
+ * unavailable/failure paths.
17
+ * @returns {FontSubsetPlan}
18
+ */
19
+ export function subsetFonts(paths: Paths, options?: {
20
+ cacheDir?: string;
21
+ quiet?: boolean;
22
+ runJobs?: (payload: {
23
+ charset: number[];
24
+ jobs: SubsetJob[];
25
+ }) => SubsetResult;
26
+ }): FontSubsetPlan;
27
+ /**
28
+ * The default subset seam: run every pending job in ONE child process (the
29
+ * build is synchronous; the wasm API is not), communicating through temp JSON
30
+ * files rather than stdio so a large payload never hits a pipe buffer. The
31
+ * worker always writes a results file — a missing or unparsable one means the
32
+ * child itself died, which is reported as `unavailable` (the build then behaves
33
+ * as if the toolchain were absent: a hint, no subsets, no failure).
34
+ * @param {{ charset: number[], jobs: SubsetJob[] }} payload
35
+ * @param {(cmd: string, args: string[], opts: object) => unknown} [spawn]
36
+ * Injectable for tests; the real one is `spawnSync`.
37
+ * @returns {SubsetResult}
38
+ */
39
+ export function runSubsetJobs(payload: {
40
+ charset: number[];
41
+ jobs: SubsetJob[];
42
+ }, spawn?: (cmd: string, args: string[], opts: object) => unknown): SubsetResult;
43
+ /**
44
+ * The subset charset for a build: the base set plus every code point appearing
45
+ * in the given texts (the emitted pages' HTML). Numeric character references
46
+ * (`&#9733;`, `&#x2605;`) are decoded first so a character the compiler emitted
47
+ * as an entity still lands in the union. Tags and attribute syntax contribute
48
+ * only ASCII, which the base set already carries, so the whole-file scan can
49
+ * only ever over-include by a handful of glyphs.
50
+ * @param {string[]} texts
51
+ * @returns {number[]} Sorted ascending, deduped.
52
+ */
53
+ export function charsetFromTexts(texts: string[]): number[];
54
+ /**
55
+ * Rewrite the `@font-face` blocks of an emitted stylesheet so each subsetted
56
+ * font's `src` leads with the subset woff2, the original kept as fallback:
57
+ * src: url("/f/a-subset.woff2") format("woff2"), url("/f/a.ttf") format("truetype");
58
+ * Only the `src` declaration changes — font-display and every other descriptor
59
+ * pass through byte-identically. Idempotent: a block already carrying the
60
+ * subset reference (a previous build's rewrite of a since-unedited skin) is
61
+ * left alone.
62
+ * @param {string} css Emitted stylesheet contents.
63
+ * @param {Map<string, string>} subsetByUrl url() target as written → subset URL.
64
+ * @returns {string}
65
+ */
66
+ export function rewriteFontFaceCss(css: string, subsetByUrl: Map<string, string>): string;
67
+ /** The base charset, sorted ascending. */
68
+ export const BASE_CODEPOINTS: number[];
69
+ export type Paths = import("./config.js").Paths;
70
+ /**
71
+ * One subset handed to the worker: reduce `source` to the shared charset and
72
+ * write the woff2 result to `out`, the cache path.
73
+ */
74
+ export type SubsetJob = {
75
+ /**
76
+ * Absolute path of the original font file.
77
+ */
78
+ source: string;
79
+ /**
80
+ * Absolute cache path the subset woff2 is written to.
81
+ */
82
+ out: string;
83
+ };
84
+ /**
85
+ * What the worker reports back. `unavailable` means the toolchain could not be
86
+ * loaded at all (harfbuzzjs/wawoff2 not installed) — every pending font then
87
+ * ships unsubsetted. `failed` carries one entry per font the toolchain itself
88
+ * rejected (corrupt payload, unsupported format).
89
+ */
90
+ export type SubsetResult = {
91
+ unavailable: boolean;
92
+ failed: {
93
+ file: string;
94
+ error: string;
95
+ }[];
96
+ };
97
+ /**
98
+ * The post-pass result, feeding the build summary. `unavailable` marks the
99
+ * toolchain-absent build (which is otherwise identical to not having the
100
+ * feature).
101
+ */
102
+ export type FontSubsetPlan = {
103
+ /**
104
+ * Fonts freshly subsetted this build.
105
+ */
106
+ subsetted: number;
107
+ /**
108
+ * Fonts served from the on-disk cache.
109
+ */
110
+ cached: number;
111
+ /**
112
+ * harfbuzzjs/wawoff2 could not be loaded.
113
+ */
114
+ unavailable: boolean;
115
+ };
116
+ /**
117
+ * A project-local font referenced by an emitted `@font-face` rule.
118
+ */
119
+ export type FontRef = {
120
+ /**
121
+ * The url() target exactly as written in the CSS.
122
+ */
123
+ url: string;
124
+ /**
125
+ * Absolute path of the font file it resolves to.
126
+ */
127
+ source: string;
128
+ /**
129
+ * Served URL of the subset (`<stem>-subset.woff2`).
130
+ */
131
+ subsetUrl: string;
132
+ /**
133
+ * Absolute dist path the subset is written to.
134
+ */
135
+ subsetDist: string;
136
+ /**
137
+ * Absolute cache path, set once the hashes are known.
138
+ */
139
+ cacheFile?: string | undefined;
140
+ };
@@ -1,3 +1,17 @@
1
+ /**
2
+ * The CSP for one route: its base policy (`REACTIVE_CSP`/`STATIC_CSP`) with the
3
+ * page's `:embed` frame hosts unioned into `frame-src`. The compiler records the
4
+ * FINAL iframe src of every `:embed` (after the YouTube/Vimeo rewrite), so a
5
+ * generic embed — a booking widget, a map — is authorized on exactly the pages
6
+ * that carry it instead of being blocked by the shipped policy. YouTube/Vimeo
7
+ * embeds dedupe against the base list, so a page whose embeds are all covered
8
+ * already produces a byte-identical policy, as does a page with no embeds.
9
+ * @param {boolean} reactive Whether the route ships the reactive runtime.
10
+ * @param {Iterable<string>} [embedHosts] Origins (`https://app.example.com`) or
11
+ * `'self'` for a same-origin embed, collected at compile time.
12
+ * @returns {string}
13
+ */
14
+ export function routeCsp(reactive: boolean, embedHosts?: Iterable<string>): string;
1
15
  /**
2
16
  * The full security-header set for one response, including the CSP variant.
3
17
  * @param {boolean} reactive Whether the route ships the reactive runtime.
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Scan `site/pages` for raster images and generate `@<width>` variants into
3
+ * `dist`, returning the variant map the compiler wires into srcsets.
4
+ *
5
+ * Skips, none of them errors: `@<width>` variant files themselves (they are
6
+ * output, not sources), hidden-path and symlinked files (mirroring
7
+ * `emitPageAssets`, which would never publish them), images at or under 700px
8
+ * wide (already small), widths at or above the intrinsic size (never
9
+ * upscale), and widths the author already provided (their file wins). An image
10
+ * whose dimensions cannot be read, and one sharp fails on, get a hint naming
11
+ * the file and ship without variants.
12
+ * @param {Paths} paths
13
+ * @param {{ widths?: number[], cacheDir?: string, quiet?: boolean,
14
+ * runJobs?: (jobs: ResizeJob[]) => ResizeResult }} [options]
15
+ * `widths` overrides {@link DEFAULT_VARIANT_WIDTHS}. `quiet` suppresses the
16
+ * hints (dev-server rebuilds, which already printed them at the initial
17
+ * build). `runJobs` is the resize seam — the default spawns the worker
18
+ * process; tests inject fakes for the unavailable/crash paths.
19
+ * @returns {ImageVariantPlan}
20
+ */
21
+ export function generateImageVariants(paths: Paths, options?: {
22
+ widths?: number[];
23
+ cacheDir?: string;
24
+ quiet?: boolean;
25
+ runJobs?: (jobs: ResizeJob[]) => ResizeResult;
26
+ }): ImageVariantPlan;
27
+ /**
28
+ * The default resize seam: run every pending job in ONE child process (the
29
+ * build is synchronous; sharp is not), communicating through temp JSON files
30
+ * rather than stdio so a large job list never hits a pipe buffer. The worker
31
+ * always writes a results file — a missing or unparsable one means the child
32
+ * itself died, which is reported as `unavailable` (the build then behaves as
33
+ * if sharp were absent: a hint, no variants, no failure).
34
+ * @param {ResizeJob[]} jobs
35
+ * @param {(cmd: string, args: string[], opts: object) => unknown} [spawn]
36
+ * Injectable for tests; the real one is `spawnSync`.
37
+ * @returns {ResizeResult}
38
+ */
39
+ export function runResizeJobs(jobs: ResizeJob[], spawn?: (cmd: string, args: string[], opts: object) => unknown): ResizeResult;
40
+ /**
41
+ * @typedef {import("./config.js").Paths} Paths
42
+ */
43
+ /**
44
+ * One resize handed to the worker: shrink `source` to `width` pixels wide and
45
+ * write it (in its own format) to `out`, the cache path.
46
+ * @typedef {object} ResizeJob
47
+ * @property {string} source Absolute path of the original image.
48
+ * @property {string} out Absolute cache path the resized variant is written to.
49
+ * @property {number} width Target width in pixels (always below the intrinsic).
50
+ * @property {"jpeg" | "png" | "webp"} format Output encoder, from the source extension.
51
+ */
52
+ /**
53
+ * What the worker reports back. `unavailable` means sharp could not be loaded
54
+ * at all (not installed, or a broken native binding) — every pending image
55
+ * then ships without variants. `failed` carries one entry per image that
56
+ * sharp itself rejected (corrupt payload, unsupported encoding).
57
+ * @typedef {object} ResizeResult
58
+ * @property {boolean} unavailable
59
+ * @property {{ file: string, width: number, error: string }[]} failed
60
+ */
61
+ /**
62
+ * The pre-pass result: `variants` is the map the compiler's srcset wiring
63
+ * consults (original's ABSOLUTE source path → sorted widths generated into
64
+ * dist), and the counts feed the build summary. `unavailable` marks the
65
+ * sharp-absent build (which is otherwise identical to not having the feature).
66
+ * @typedef {object} ImageVariantPlan
67
+ * @property {Map<string, number[]>} variants
68
+ * @property {number} generated Variants resized this build.
69
+ * @property {number} cached Variants served from the on-disk cache.
70
+ * @property {boolean} unavailable sharp could not be loaded.
71
+ */
72
+ /** The widths generated by default, both comfortably under a typical source. */
73
+ export const DEFAULT_VARIANT_WIDTHS: number[];
74
+ export type Paths = import("./config.js").Paths;
75
+ /**
76
+ * One resize handed to the worker: shrink `source` to `width` pixels wide and
77
+ * write it (in its own format) to `out`, the cache path.
78
+ */
79
+ export type ResizeJob = {
80
+ /**
81
+ * Absolute path of the original image.
82
+ */
83
+ source: string;
84
+ /**
85
+ * Absolute cache path the resized variant is written to.
86
+ */
87
+ out: string;
88
+ /**
89
+ * Target width in pixels (always below the intrinsic).
90
+ */
91
+ width: number;
92
+ /**
93
+ * Output encoder, from the source extension.
94
+ */
95
+ format: "jpeg" | "png" | "webp";
96
+ };
97
+ /**
98
+ * What the worker reports back. `unavailable` means sharp could not be loaded
99
+ * at all (not installed, or a broken native binding) — every pending image
100
+ * then ships without variants. `failed` carries one entry per image that
101
+ * sharp itself rejected (corrupt payload, unsupported encoding).
102
+ */
103
+ export type ResizeResult = {
104
+ unavailable: boolean;
105
+ failed: {
106
+ file: string;
107
+ width: number;
108
+ error: string;
109
+ }[];
110
+ };
111
+ /**
112
+ * The pre-pass result: `variants` is the map the compiler's srcset wiring
113
+ * consults (original's ABSOLUTE source path → sorted widths generated into
114
+ * dist), and the counts feed the build summary. `unavailable` marks the
115
+ * sharp-absent build (which is otherwise identical to not having the feature).
116
+ */
117
+ export type ImageVariantPlan = {
118
+ variants: Map<string, number[]>;
119
+ /**
120
+ * Variants resized this build.
121
+ */
122
+ generated: number;
123
+ /**
124
+ * Variants served from the on-disk cache.
125
+ */
126
+ cached: number;
127
+ /**
128
+ * sharp could not be loaded.
129
+ */
130
+ unavailable: boolean;
131
+ };