authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-02-17 01:18:48-08:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-02-18 02:46:06-08:00
log06e56b3aa60a307cbf8c6dd1f7a2c59c55ba0de9
tree59a0fa93087a188c2e3c67f032db50a45b0e4183
parentc2487692e6d51da16194cd38ba2b42f095ee7594
signaturebadge-check Signed by SSH key SHA256:xbd+BjjhyBfwk7GVoURf9Yx0gzDerHbvYv7SddNWmAs

chore: some stuff silly


12 files changed, 150 insertions(+), 57 deletions(-)

README.md+1
...@@ -22,6 +22,7 @@ leveraging the existing ecosystem....@@ -22,6 +22,7 @@ leveraging the existing ecosystem.
22> - [Paragraph Detection](#paragraph-detection)22> - [Paragraph Detection](#paragraph-detection)
23> - [Static Statements](#static-statements)23> - [Static Statements](#static-statements)
24> - [Config](#config)24> - [Config](#config)
25> - [Frontmatter Layout Configuration](#frontmatter-layout-configuration)
2526
26Here's a glance at how things look. Complete example documents in <./examples>27Here's a glance at how things look. Complete example documents in <./examples>
2728
lib/jsr.json+1-1
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1{1{
2 "name": "@clo/markodown",2 "name": "@clo/markodown",
3 "version": "1.0.0-rc.6",3 "version": "1.0.0-rc.9",
4 "license": "ISC",4 "license": "ISC",
5 "exports": {5 "exports": {
6 ".": "./mod.ts",6 ".": "./mod.ts",
lib/mod.ts+116-31
...@@ -3,9 +3,63 @@ import bytes from "./bindgen/wasm_bytes.js";...@@ -3,9 +3,63 @@ import bytes from "./bindgen/wasm_bytes.js";
33
4wasm.initSync({ module: bytes });4wasm.initSync({ module: bytes });
55
6type Transformed = Success | Failure;6/** The result of a Markodown transform */
7export type Transformed = Success | Failure;
78
9/** Convert Markodown (`.mdo`) source code into Marko source code (`.marko`) */
10export function transform(options: TransformOptions): Transformed {
11 const formats = options.format ?? ["marko"];
12 let forceFormat = null;
13 if (formats.includes("html") && !formats.includes("marko")) {
14 forceFormat = wasm.OutputFormat.Html;
15 } else if (formats.includes("marko") && !formats.includes("html")) {
16 forceFormat = wasm.OutputFormat.Marko;
17 } else if (!formats.includes("html") && !formats.includes("marko")) {
18 throw new Error("No supported formats in " + JSON.stringify(formats));
19 }
20
21 return wasm.transform(
22 options.source,
23 forceFormat,
24 options?.layoutImport,
25 options?.componentImports,
26 options?.selfImport,
27 options?.markdownOnly,
28 options?.cloverExtensions,
29 );
30}
31
32/** Converts a flat document outline into a nested tree. */
33export function outlineToTree(outline: Heading[]): HeadingTree[] {
34 const root: HeadingTree[] = [];
35 const stack: HeadingTree[] = [];
36
37 for (const heading of outline) {
38 if (heading.level === 1) continue;
39
40 const node: HeadingTree = { ...heading, children: [] };
41
42 while (
43 stack.length && stack[stack.length - 1].level >= heading.level
44 ) {
45 stack.pop();
46 }
47
48 if (stack.length) {
49 stack[stack.length - 1].children.push(node);
50 } else {
51 root.push(node);
52 }
53
54 stack.push(node);
55 }
56
57 return root;
58}
59
60/** Options for {@linkcode transform}. */
8export interface TransformOptions {61export interface TransformOptions {
62 /** The Markodown source code to be transformed */
9 source: string;63 source: string;
10 /**64 /**
11 * Specify the allowed output formats. For simplicity, pass `marko`. By65 * Specify the allowed output formats. For simplicity, pass `marko`. By
...@@ -29,7 +83,8 @@ export interface TransformOptions {...@@ -29,7 +83,8 @@ export interface TransformOptions {
29 markdownOnly?: boolean;83 markdownOnly?: boolean;
30 /**84 /**
31 * These extensions are special-cased so that Clover can re-use this on85 * These extensions are special-cased so that Clover can re-use this on
32 * her website without86 * her website without maintaining a second markdown parser. I promise
87 * we are not wasting your bundle size on my features.
33 */88 */
34 cloverExtensions?: CloverQuestionExtensions;89 cloverExtensions?: CloverQuestionExtensions;
35}90}
...@@ -40,7 +95,7 @@ export interface ComponentImports {...@@ -40,7 +95,7 @@ export interface ComponentImports {
40 heading?: string;95 heading?: string;
41 /** Replace `pre > code` with this import. */96 /** Replace `pre > code` with this import. */
42 codeBlock?: string;97 codeBlock?: string;
43 /** Replace markdown links with this import. */98 /** Replace links with this import. */
44 link?: string;99 link?: string;
45 /** Replace images with this import. */100 /** Replace images with this import. */
46 image?: string;101 image?: string;
...@@ -56,6 +111,8 @@ export interface ComponentImports {...@@ -56,6 +111,8 @@ export interface ComponentImports {
56 * emit custom HTML elements instead of imported components.111 * emit custom HTML elements instead of imported components.
57 *112 *
58 * Also includes `@html <raw>` block syntax for raw HTML passthrough.113 * Also includes `@html <raw>` block syntax for raw HTML passthrough.
114 *
115 * @internal
59 */116 */
60export interface CloverQuestionExtensions {117export interface CloverQuestionExtensions {
61 /**118 /**
...@@ -85,61 +142,89 @@ export interface CloverQuestionExtensions {...@@ -85,61 +142,89 @@ export interface CloverQuestionExtensions {
85 labelledRedaction: string;142 labelledRedaction: string;
86}143}
87144
145/** The transformer currently supports two output formats. */
88export type OutputFormat = "marko" | "html";146export type OutputFormat = "marko" | "html";
89147
148/** The transform is a success when `success: true` or there are no errors. */
90export interface Success {149export interface Success {
150 /** Easy boolean to discriminate {@linkcode TransformResult} */
91 success: true;151 success: true;
152 /** The transformed text. Format is determined by `format` */
92 text: string;153 text: string;
93 errors: [];154 /** The resolved output format of `text` */
94 format: OutputFormat;155 format: OutputFormat;
156 /** List of errors, if any */
157 errors: [];
95}158}
96159
160/** The transform is a success when `success: false` or there is at least one error. */
97export interface Failure {161export interface Failure {
162 /** Easy boolean to discriminate {@linkcode TransformResult} */
98 success: false;163 success: false;
164 /** The transformed text. Format is determined by `format` */
99 text: null;165 text: null;
100 errors: TransformError[];166 /** The resolved output format of `text` */
101 outline: Heading[] | null;167 format: null;
168 /** List of errors, if any */
169 errors: [TransformError, ...TransformError[]];
102}170}
103171
172/** This is passed to Markodown layouts */
173export interface LayoutInput {
174 /**
175 * Scanned from heading tags, the document outline is provided flat here. You
176 * can convert it into a nested tree with {@linkcode outlineToTree}
177 */
178 outline: Heading[];
179 /**
180 * A copy of the Module Namespace object of the page. Use this to reflect
181 * frontmatter or other customizable exports. Don't render `module.default` as
182 * a component, since that will recursively call this layout.
183 */
184 module: Record<string, unknown>;
185 /** The rendered document */
186 // @ts-ignore fails if marko types not chilling
187 content: Marko.Body;
188}
189
190/**
191 * Scanned from heading tags, this represents one heading in the document. You
192 * can convert it into a nested tree with {@linkcode outlineToTree}
193 */
104export interface Heading {194export interface Heading {
195 /** Which header element this corresponds to. */
105 level: 1 | 2 | 3 | 4 | 5 | 6;196 level: 1 | 2 | 3 | 4 | 5 | 6;
197 /** The link ID. Derived from the `id` attribute of the heading, or generated for you otherwise. */
106 id: string;198 id: string;
199 /** The rendered heading name */
107 // @ts-ignore fails if marko types not chilling200 // @ts-ignore fails if marko types not chilling
108 content: Marko.Body;201 content: Marko.Body;
109}202}
110203
204/** Generated by {@linkcode outlineToTree} */
205export interface HeadingTree extends Heading {
206 /** Sub-headings */
207 children: HeadingTree[];
208}
209
111export interface TransformError {210export interface TransformError {
211 /** What went wrong? */
112 message: string;212 message: string;
113 labels: LabelledSpan[];213 /** Additional notes for the failure */
214 notes: TransformNote[];
215 /** One-based line */
114 line: number;216 line: number;
217 /** One-based column, byte offset */
115 column: number;218 column: number;
116}219}
117220
118export interface LabelledSpan {221export interface TransformNote {
119 message: string;222 /** What this span is communicating */
223 message?: string | null;
224 /** One-based line */
120 line: number;225 line: number;
226 /** One-based column, byte offset */
121 column: number;227 column: number;
228 /** Byte length */
122 width: number;229 width: number;
123}230}
124
125export function transform(options: TransformOptions): Transformed {
126 const formats = options.format ?? ["marko"];
127 let forceFormat = null;
128 if (formats.includes("html") && !formats.includes("marko")) {
129 forceFormat = wasm.OutputFormat.Html;
130 } else if (formats.includes("marko") && !formats.includes("html")) {
131 forceFormat = wasm.OutputFormat.Marko;
132 } else if (!formats.includes("html") && !formats.includes("marko")) {
133 throw new Error("No supported formats in " + JSON.stringify(formats));
134 }
135
136 return wasm.transform(
137 options.source,
138 forceFormat,
139 options?.layoutImport,
140 options?.componentImports,
141 options?.selfImport,
142 options?.markdownOnly,
143 options?.cloverExtensions,
144 );
145}
npm.sh deleted-17
...@@ -1,17 +0,0 @@
1set -e
2
3rm -rf lib/dist-npm
4mkdir lib/dist-npm
5
6cd lib
7VERSION="$(cat jsr.json | jq .version -r)"
8
9cd dist-npm
10echo '{}' > package.json
11npx jsr add "@clo/markodown@$VERSION"
12
13cd node_modules/@clo/markodown
14rm jsr.json
15sed "s/VERSION/$VERSION/g" ../../../../package.npm.json > package.json
16
17npm publish --tag rc
publish.sh created+24
...@@ -0,0 +1,24 @@
1set -e
2
3cargo test --all
4bash wasm.sh
5
6cd lib
7cp ../README.md README.md
8
9npx jsr publish --allow-dirty
10
11rm -rf dist-npm
12mkdir dist-npm
13
14VERSION="$(cat jsr.json | jq .version -r)"
15
16cd dist-npm
17echo '{}' > package.json
18npx jsr add "@clo/markodown@$VERSION"
19
20cd node_modules/@clo/markodown
21rm jsr.json
22sed "s/VERSION/$VERSION/g" ../../../../package.npm.json > package.json
23
24npm publish --tag rc
src/component_transforms.rs+1-1
...@@ -150,7 +150,7 @@ pub fn generate_layout_boilerplate(used: &UsedElements) -> String {...@@ -150,7 +150,7 @@ pub fn generate_layout_boilerplate(used: &UsedElements) -> String {
150 // Heading — always present when a layout is active150 // Heading — always present when a layout is active
151 out.push_str(concat!(151 out.push_str(concat!(
152 "<define/HeadingComponentFallback__markodown__|{ level, content, ...attrs }|>\n",152 "<define/HeadingComponentFallback__markodown__|{ level, content, ...attrs }|>\n",
153 " <${'h' + level} ...attrs><${content}></>\n",153 " <${'h' + level} ...attrs><${content} /></>\n",
154 "</>\n",154 "</>\n",
155 "<const/HeadingComponent__markodown__ = LayoutModule__markodown__.components?.heading ?? HeadingComponentFallback__markodown__ />\n",155 "<const/HeadingComponent__markodown__ = LayoutModule__markodown__.components?.heading ?? HeadingComponentFallback__markodown__ />\n",
156 ));156 ));
src/wasm.rs+2-2
...@@ -14,7 +14,7 @@ struct TransformResult {...@@ -14,7 +14,7 @@ struct TransformResult {
14#[derive(Serialize)]14#[derive(Serialize)]
15struct WasmDiagnostic {15struct WasmDiagnostic {
16 message: String,16 message: String,
17 labels: Vec<WasmLabel>,17 notes: Vec<WasmLabel>,
18 line: u32,18 line: u32,
19 column: u32,19 column: u32,
20}20}
...@@ -89,7 +89,7 @@ fn convert_diagnostic(src: &str, diag: &OxcDiagnostic) -> WasmDiagnostic {...@@ -89,7 +89,7 @@ fn convert_diagnostic(src: &str, diag: &OxcDiagnostic) -> WasmDiagnostic {
8989
90 WasmDiagnostic {90 WasmDiagnostic {
91 message: diag.message.to_string(),91 message: diag.message.to_string(),
92 labels: forward_labels,92 notes: forward_labels,
93 line,93 line,
94 column,94 column,
95 }95 }
tests/fixtures/23-outline-extracting.marko+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1import Layout__markodown__ from "./layout.marko";1import Layout__markodown__ from "./layout.marko";
2import * as LayoutModule__markodown__ from "./layout.marko";2import * as LayoutModule__markodown__ from "./layout.marko";
3<define/HeadingComponentFallback__markodown__|{ level, content, ...attrs }|>3<define/HeadingComponentFallback__markodown__|{ level, content, ...attrs }|>
4 <${'h' + level} ...attrs><${content}></>4 <${'h' + level} ...attrs><${content} /></>
5</>5</>
6<const/HeadingComponent__markodown__ = LayoutModule__markodown__.components?.heading ?? HeadingComponentFallback__markodown__ />6<const/HeadingComponent__markodown__ = LayoutModule__markodown__.components?.heading ?? HeadingComponentFallback__markodown__ />
77
tests/fixtures/27-frontmatter-layout.marko+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1import Layout__markodown__ from "./page-layout.marko";1import Layout__markodown__ from "./page-layout.marko";
2import * as LayoutModule__markodown__ from "./page-layout.marko";2import * as LayoutModule__markodown__ from "./page-layout.marko";
3<define/HeadingComponentFallback__markodown__|{ level, content, ...attrs }|>3<define/HeadingComponentFallback__markodown__|{ level, content, ...attrs }|>
4 <${'h' + level} ...attrs><${content}></>4 <${'h' + level} ...attrs><${content} /></>
5</>5</>
6<const/HeadingComponent__markodown__ = LayoutModule__markodown__.components?.heading ?? HeadingComponentFallback__markodown__ />6<const/HeadingComponent__markodown__ = LayoutModule__markodown__.components?.heading ?? HeadingComponentFallback__markodown__ />
77
tests/fixtures/28-outline-self-import.marko+1-1
...@@ -2,7 +2,7 @@ import Layout__markodown__ from "./layout.marko";...@@ -2,7 +2,7 @@ import Layout__markodown__ from "./layout.marko";
2import * as LayoutModule__markodown__ from "./layout.marko";2import * as LayoutModule__markodown__ from "./layout.marko";
3import * as self__markodown__ from "./self.marko";3import * as self__markodown__ from "./self.marko";
4<define/HeadingComponentFallback__markodown__|{ level, content, ...attrs }|>4<define/HeadingComponentFallback__markodown__|{ level, content, ...attrs }|>
5 <${'h' + level} ...attrs><${content}></>5 <${'h' + level} ...attrs><${content} /></>
6</>6</>
7<const/HeadingComponent__markodown__ = LayoutModule__markodown__.components?.heading ?? HeadingComponentFallback__markodown__ />7<const/HeadingComponent__markodown__ = LayoutModule__markodown__.components?.heading ?? HeadingComponentFallback__markodown__ />
88
tests/fixtures/29-layout-all-components.marko+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1import Layout__markodown__ from "./layout.marko";1import Layout__markodown__ from "./layout.marko";
2import * as LayoutModule__markodown__ from "./layout.marko";2import * as LayoutModule__markodown__ from "./layout.marko";
3<define/HeadingComponentFallback__markodown__|{ level, content, ...attrs }|>3<define/HeadingComponentFallback__markodown__|{ level, content, ...attrs }|>
4 <${'h' + level} ...attrs><${content}></>4 <${'h' + level} ...attrs><${content} /></>
5</>5</>
6<const/HeadingComponent__markodown__ = LayoutModule__markodown__.components?.heading ?? HeadingComponentFallback__markodown__ />6<const/HeadingComponent__markodown__ = LayoutModule__markodown__.components?.heading ?? HeadingComponentFallback__markodown__ />
7<define/CodeBlockComponentFallback__markodown__|{ language, content }|>7<define/CodeBlockComponentFallback__markodown__|{ language, content }|>
tests/fixtures/30-layout-selective-components.marko+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1import Layout__markodown__ from "./layout.marko";1import Layout__markodown__ from "./layout.marko";
2import * as LayoutModule__markodown__ from "./layout.marko";2import * as LayoutModule__markodown__ from "./layout.marko";
3<define/HeadingComponentFallback__markodown__|{ level, content, ...attrs }|>3<define/HeadingComponentFallback__markodown__|{ level, content, ...attrs }|>
4 <${'h' + level} ...attrs><${content}></>4 <${'h' + level} ...attrs><${content} /></>
5</>5</>
6<const/HeadingComponent__markodown__ = LayoutModule__markodown__.components?.heading ?? HeadingComponentFallback__markodown__ />6<const/HeadingComponent__markodown__ = LayoutModule__markodown__.components?.heading ?? HeadingComponentFallback__markodown__ />
7<const/LinkComponent__markodown__ = LayoutModule__markodown__.components?.link ?? 'a' />7<const/LinkComponent__markodown__ = LayoutModule__markodown__.components?.link ?? 'a' />