| author | |
| committer | |
| log | 06e56b3aa60a307cbf8c6dd1f7a2c59c55ba0de9 |
| tree | 59a0fa93087a188c2e3c67f032db50a45b0e4183 |
| parent | c2487692e6d51da16194cd38ba2b42f095ee7594 |
| signature |
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) | ||
| 25 | 26 | ||
| 26 | Here's a glance at how things look. Complete example documents in <./examples> | 27 | Here's a glance at how things look. Complete example documents in <./examples> |
| 27 | 28 |
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"; |
| 3 | 3 | ||
| 4 | wasm.initSync({ module: bytes }); | 4 | wasm.initSync({ module: bytes }); |
| 5 | 5 | ||
| 6 | type Transformed = Success | Failure; | 6 | /** The result of a Markodown transform */ |
| 7 | export type Transformed = Success | Failure; | ||
| 7 | 8 | ||
| 9 | /** Convert Markodown (`.mdo`) source code into Marko source code (`.marko`) */ | ||
| 10 | export 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. */ | ||
| 33 | export 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}. */ | ||
| 8 | export interface TransformOptions { | 61 | export 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`. By | 65 | * 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 on | 85 | * These extensions are special-cased so that Clover can re-use this on |
| 32 | * her website without | 86 | * 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 | */ |
| 60 | export interface CloverQuestionExtensions { | 117 | export 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 | } |
| 87 | 144 | ||
| 145 | /** The transformer currently supports two output formats. */ | ||
| 88 | export type OutputFormat = "marko" | "html"; | 146 | export type OutputFormat = "marko" | "html"; |
| 89 | 147 | ||
| 148 | /** The transform is a success when `success: true` or there are no errors. */ | ||
| 90 | export interface Success { | 149 | export 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 | } |
| 96 | 159 | ||
| 160 | /** The transform is a success when `success: false` or there is at least one error. */ | ||
| 97 | export interface Failure { | 161 | export 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 | } |
| 103 | 171 | ||
| 172 | /** This is passed to Markodown layouts */ | ||
| 173 | export 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 | */ | ||
| 104 | export interface Heading { | 194 | export 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 chilling | 200 | // @ts-ignore fails if marko types not chilling |
| 108 | content: Marko.Body; | 201 | content: Marko.Body; |
| 109 | } | 202 | } |
| 110 | 203 | ||
| 204 | /** Generated by {@linkcode outlineToTree} */ | ||
| 205 | export interface HeadingTree extends Heading { | ||
| 206 | /** Sub-headings */ | ||
| 207 | children: HeadingTree[]; | ||
| 208 | } | ||
| 209 | |||
| 111 | export interface TransformError { | 210 | export 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 | } |
| 117 | 220 | ||
| 118 | export interface LabelledSpan { | 221 | export 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 | |||
| 125 | export 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 @@ | ||
| 1 | set -e | ||
| 2 | |||
| 3 | rm -rf lib/dist-npm | ||
| 4 | mkdir lib/dist-npm | ||
| 5 | |||
| 6 | cd lib | ||
| 7 | VERSION="$(cat jsr.json | jq .version -r)" | ||
| 8 | |||
| 9 | cd dist-npm | ||
| 10 | echo '{}' > package.json | ||
| 11 | npx jsr add "@clo/markodown@$VERSION" | ||
| 12 | |||
| 13 | cd node_modules/@clo/markodown | ||
| 14 | rm jsr.json | ||
| 15 | sed "s/VERSION/$VERSION/g" ../../../../package.npm.json > package.json | ||
| 16 | |||
| 17 | npm publish --tag rc | ||
publish.sh created+24| ... | @@ -0,0 +1,24 @@ | ||
| 1 | set -e | ||
| 2 | |||
| 3 | cargo test --all | ||
| 4 | bash wasm.sh | ||
| 5 | |||
| 6 | cd lib | ||
| 7 | cp ../README.md README.md | ||
| 8 | |||
| 9 | npx jsr publish --allow-dirty | ||
| 10 | |||
| 11 | rm -rf dist-npm | ||
| 12 | mkdir dist-npm | ||
| 13 | |||
| 14 | VERSION="$(cat jsr.json | jq .version -r)" | ||
| 15 | |||
| 16 | cd dist-npm | ||
| 17 | echo '{}' > package.json | ||
| 18 | npx jsr add "@clo/markodown@$VERSION" | ||
| 19 | |||
| 20 | cd node_modules/@clo/markodown | ||
| 21 | rm jsr.json | ||
| 22 | sed "s/VERSION/$VERSION/g" ../../../../package.npm.json > package.json | ||
| 23 | |||
| 24 | npm 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 active | 150 | // 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)] |
| 15 | struct WasmDiagnostic { | 15 | struct 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 { |
| 89 | 89 | ||
| 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 @@ |
| 1 | import Layout__markodown__ from "./layout.marko"; | 1 | import Layout__markodown__ from "./layout.marko"; |
| 2 | import * as LayoutModule__markodown__ from "./layout.marko"; | 2 | import * 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 | 7 |
tests/fixtures/27-frontmatter-layout.marko+1-1| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | import Layout__markodown__ from "./page-layout.marko"; | 1 | import Layout__markodown__ from "./page-layout.marko"; |
| 2 | import * as LayoutModule__markodown__ from "./page-layout.marko"; | 2 | import * 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__ /> |
| 7 | 7 |
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"; |
| 2 | import * as LayoutModule__markodown__ from "./layout.marko"; | 2 | import * as LayoutModule__markodown__ from "./layout.marko"; |
| 3 | import * as self__markodown__ from "./self.marko"; | 3 | import * 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__ /> |
| 8 | 8 |
tests/fixtures/29-layout-all-components.marko+1-1| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | import Layout__markodown__ from "./layout.marko"; | 1 | import Layout__markodown__ from "./layout.marko"; |
| 2 | import * as LayoutModule__markodown__ from "./layout.marko"; | 2 | import * 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 @@ |
| 1 | import Layout__markodown__ from "./layout.marko"; | 1 | import Layout__markodown__ from "./layout.marko"; |
| 2 | import * as LayoutModule__markodown__ from "./layout.marko"; | 2 | import * 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' /> |