@boceto/remark 0.1.0 → 0.2.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/dist/index.cjs +49 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -2
- package/dist/index.d.ts +29 -2
- package/dist/index.js +50 -6
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -4,12 +4,35 @@ var unistUtilVisit = require('unist-util-visit');
|
|
|
4
4
|
var core = require('@boceto/core');
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
|
+
function parseFenceMeta(meta) {
|
|
8
|
+
if (!meta) return { page: null, opts: {} };
|
|
9
|
+
const opts = {};
|
|
10
|
+
const rest = [];
|
|
11
|
+
for (const tok of meta.trim().split(/\s+/)) {
|
|
12
|
+
const eq = tok.indexOf("=");
|
|
13
|
+
if (eq > 0) {
|
|
14
|
+
const key = tok.slice(0, eq);
|
|
15
|
+
const val = tok.slice(eq + 1);
|
|
16
|
+
if (key === "fit" && (val === "content" || val === "fixed")) {
|
|
17
|
+
opts.fit = val;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (key === "width" || key === "height" || key === "padding") {
|
|
21
|
+
const n = Number(val);
|
|
22
|
+
if (Number.isFinite(n) && n >= 0) {
|
|
23
|
+
opts[key] = n;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
rest.push(tok);
|
|
29
|
+
}
|
|
30
|
+
return { page: rest.length ? rest.join(" ") : null, opts };
|
|
31
|
+
}
|
|
7
32
|
function remarkBoceto(options = {}) {
|
|
8
33
|
const mode = options.mode ?? "wc";
|
|
9
34
|
const tag = options.tag ?? "boceto-view";
|
|
10
35
|
const extraAttrs = options.attributes ?? {};
|
|
11
|
-
const width = options.width ?? 860;
|
|
12
|
-
const height = options.height ?? 600;
|
|
13
36
|
const svgRenderer = mode === "svg" ? new core.SvgRenderer() : null;
|
|
14
37
|
if (mode !== "svg") {
|
|
15
38
|
return (tree) => {
|
|
@@ -22,8 +45,9 @@ function remarkBoceto(options = {}) {
|
|
|
22
45
|
if (options.render) {
|
|
23
46
|
html = options.render(source, { lang: "boceto", meta });
|
|
24
47
|
} else {
|
|
48
|
+
const { page } = parseFenceMeta(meta);
|
|
25
49
|
const attrs = { ...extraAttrs, code: source };
|
|
26
|
-
if (
|
|
50
|
+
if (page) attrs["data-page"] = page;
|
|
27
51
|
html = renderTag(tag, attrs);
|
|
28
52
|
}
|
|
29
53
|
parent.children[index] = { type: "html", value: html };
|
|
@@ -46,9 +70,29 @@ function remarkBoceto(options = {}) {
|
|
|
46
70
|
if (options.render) {
|
|
47
71
|
html = options.render(source, { lang: "boceto", meta });
|
|
48
72
|
} else {
|
|
49
|
-
const
|
|
73
|
+
const { page: pageName, opts: fence } = parseFenceMeta(meta);
|
|
74
|
+
const fit = fence.fit ?? options.fit ?? "content";
|
|
75
|
+
const padding = fence.padding ?? options.padding ?? 16;
|
|
76
|
+
const minW = fence.width ?? options.width;
|
|
77
|
+
const minH = fence.height ?? options.height;
|
|
78
|
+
const wrapped = "```boceto" + (pageName ? ":" + pageName : "") + "\n" + source + "\n```";
|
|
50
79
|
const doc = core.applyFlexLayout(core.parse(wrapped));
|
|
51
|
-
|
|
80
|
+
let w, h;
|
|
81
|
+
if (fit === "fixed") {
|
|
82
|
+
w = minW ?? 860;
|
|
83
|
+
h = minH ?? 600;
|
|
84
|
+
} else {
|
|
85
|
+
const pg = core.selectPage(doc);
|
|
86
|
+
const box = pg ? core.pageContentBox(pg.elements) : null;
|
|
87
|
+
if (box) {
|
|
88
|
+
w = Math.max(minW ?? 0, Math.ceil(box.x + box.w + padding));
|
|
89
|
+
h = Math.max(minH ?? 0, Math.ceil(box.y + box.h + padding));
|
|
90
|
+
} else {
|
|
91
|
+
w = minW ?? 860;
|
|
92
|
+
h = minH ?? 600;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
html = svgRenderer.renderToString(doc, { width: w, height: h });
|
|
52
96
|
}
|
|
53
97
|
parent.children[index] = { type: "html", value: html };
|
|
54
98
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["SvgRenderer","visit","initYoga","applyFlexLayout","parse"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Code, Html, Root } from 'mdast'\nimport { visit } from 'unist-util-visit'\nimport { applyFlexLayout, initYoga, parse, SvgRenderer } from '@boceto/core'\n\nexport interface RemarkBocetoOptions {\n /**\n * Output mode.\n * - `'wc'` (default): emit `<boceto-view>` custom element. Requires the WC\n * runtime in the browser.\n * - `'svg'`: parse the source and inline a complete `<svg>` document.\n * Renders with **zero JS at runtime** — works in GitHub READMEs, RSS\n * readers, and SSGs.\n */\n mode?: 'wc' | 'svg'\n /** Tag to emit in `'wc'` mode. Default `'boceto-view'`. Use `'boceto-edit'` for editable blocks. */\n tag?: string\n /** Extra static attributes to attach to every emitted element (`'wc'` mode only). */\n attributes?: Record<string, string>\n /** SVG render dimensions (`'svg'` mode only). Defaults: 860 × 600. */\n width?: number\n height?: number\n /**\n * Receive the boceto source and return arbitrary HTML. If provided, all\n * other options are ignored. Use this if you want a custom wrapper or to\n * swap renderers entirely.\n */\n render?: (source: string, info: { lang: string; meta: string | null }) => string\n}\n\nexport type Plugin = () => (tree: Root) => void | Promise<void>\n\n/**\n * remark plugin that transforms `code` nodes whose language is `boceto`\n * into `html` nodes.\n *\n * - `mode: 'wc'` (default): emits `<boceto-view code=\"…\">`.\n * - `mode: 'svg'`: emits a full `<svg>…</svg>` rendered server-side. The\n * transformer is async in this mode so it can `await initYoga()` once\n * before resolving FlexContainer layout.\n */\nexport default function remarkBoceto(\n options: RemarkBocetoOptions = {},\n): (tree: Root) => void | Promise<void> {\n const mode = options.mode ?? 'wc'\n const tag = options.tag ?? 'boceto-view'\n const extraAttrs = options.attributes ?? {}\n const width = options.width ?? 860\n const height = options.height ?? 600\n const svgRenderer = mode === 'svg' ? new SvgRenderer() : null\n\n if (mode !== 'svg') {\n // Synchronous transformer for WC mode — no layout to resolve.\n return (tree) => {\n visit(tree, 'code', (node: Code, index, parent) => {\n if (!parent || index == null) return\n if ((node.lang ?? '') !== 'boceto') return\n const source = node.value\n const meta = node.meta ?? null\n let html: string\n if (options.render) {\n html = options.render(source, { lang: 'boceto', meta })\n } else {\n const attrs: Record<string, string> = { ...extraAttrs, code: source }\n if (meta) attrs['data-page'] = meta.trim()\n html = renderTag(tag, attrs)\n }\n parent.children[index] = { type: 'html', value: html } as Html\n })\n }\n }\n\n // SVG mode: collect every boceto block, ensure Yoga is loaded once, then\n // parse + lay out + render each in place.\n return async (tree) => {\n const targets: Array<{ node: Code; index: number; parent: Root | Code['data'] }> = []\n visit(tree, 'code', (node: Code, index, parent) => {\n if (!parent || index == null) return\n if ((node.lang ?? '') !== 'boceto') return\n targets.push({ node, index, parent: parent as Root })\n })\n if (targets.length === 0) return\n await initYoga()\n for (const { node, index, parent } of targets) {\n const source = node.value\n const meta = node.meta ?? null\n let html: string\n if (options.render) {\n html = options.render(source, { lang: 'boceto', meta })\n } else {\n const wrapped = '```boceto' + (meta ? ':' + meta : '') + '\\n' + source + '\\n```'\n const doc = applyFlexLayout(parse(wrapped))\n html = svgRenderer!.renderToString(doc, { width, height })\n }\n ;(parent as Root).children[index] = { type: 'html', value: html } as Html\n }\n }\n}\n\nfunction renderTag(tag: string, attrs: Record<string, string>): string {\n const parts = [tag]\n for (const [k, v] of Object.entries(attrs)) parts.push(`${k}=\"${escapeAttr(v)}\"`)\n return `<${parts.join(' ')}></${tag}>`\n}\n\nfunction escapeAttr(s: string): string {\n return s.replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>')\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["SvgRenderer","visit","initYoga","applyFlexLayout","parse","selectPage","pageContentBox"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Code, Html, Root } from 'mdast'\nimport { visit } from 'unist-util-visit'\nimport { applyFlexLayout, initYoga, pageContentBox, parse, selectPage, SvgRenderer } from '@boceto/core'\n\nexport interface RemarkBocetoOptions {\n /**\n * Output mode.\n * - `'wc'` (default): emit `<boceto-view>` custom element. Requires the WC\n * runtime in the browser.\n * - `'svg'`: parse the source and inline a complete `<svg>` document.\n * Renders with **zero JS at runtime** — works in GitHub READMEs, RSS\n * readers, and SSGs.\n */\n mode?: 'wc' | 'svg'\n /** Tag to emit in `'wc'` mode. Default `'boceto-view'`. Use `'boceto-edit'` for editable blocks. */\n tag?: string\n /** Extra static attributes to attach to every emitted element (`'wc'` mode only). */\n attributes?: Record<string, string>\n /**\n * SVG sizing strategy (`'svg'` mode only).\n * - `'content'` (default): canvas grows to fit the page's content plus\n * `padding`. `width` / `height`, if set, act as a **minimum floor** —\n * same semantics as `<boceto-view fit=\"content\">`.\n * - `'fixed'`: legacy behavior — canvas is exactly `width × height`\n * (defaults `860 × 600`), content outside is clipped.\n *\n * Can be overridden per fence: ` ```boceto fit=fixed `.\n */\n fit?: 'content' | 'fixed'\n /**\n * SVG canvas dimensions (`'svg'` mode only).\n * In `fit: 'content'` (default) these are floors — the canvas only grows.\n * In `fit: 'fixed'` they are the exact canvas size (defaults 860 / 600).\n *\n * Can be overridden per fence: ` ```boceto width=1280 height=800 `.\n */\n width?: number\n height?: number\n /**\n * Breathing room around content in `fit: 'content'` mode. Default `16`,\n * matching `<boceto-view>`'s `padding` attribute. Can be overridden per\n * fence: ` ```boceto padding=32 `.\n */\n padding?: number\n /**\n * Receive the boceto source and return arbitrary HTML. If provided, all\n * other options are ignored. Use this if you want a custom wrapper or to\n * swap renderers entirely.\n */\n render?: (source: string, info: { lang: string; meta: string | null }) => string\n}\n\nexport type Plugin = () => (tree: Root) => void | Promise<void>\n\ntype FenceOpts = { fit?: 'content' | 'fixed'; width?: number; height?: number; padding?: number }\n\n/**\n * Parse the fence info-string portion after `boceto`. Recognized `key=value`\n * tokens (`fit`, `width`, `height`, `padding`) are extracted as per-fence\n * overrides; everything else is joined back as the page name (preserving the\n * original meta semantics). Unknown keys or invalid values fall through to\n * the page name — no throws on typos.\n */\nfunction parseFenceMeta(meta: string | null): { page: string | null; opts: FenceOpts } {\n if (!meta) return { page: null, opts: {} }\n const opts: FenceOpts = {}\n const rest: string[] = []\n for (const tok of meta.trim().split(/\\s+/)) {\n const eq = tok.indexOf('=')\n if (eq > 0) {\n const key = tok.slice(0, eq)\n const val = tok.slice(eq + 1)\n if (key === 'fit' && (val === 'content' || val === 'fixed')) {\n opts.fit = val\n continue\n }\n if (key === 'width' || key === 'height' || key === 'padding') {\n const n = Number(val)\n if (Number.isFinite(n) && n >= 0) {\n opts[key] = n\n continue\n }\n }\n }\n rest.push(tok)\n }\n return { page: rest.length ? rest.join(' ') : null, opts }\n}\n\n/**\n * remark plugin that transforms `code` nodes whose language is `boceto`\n * into `html` nodes.\n *\n * - `mode: 'wc'` (default): emits `<boceto-view code=\"…\">`.\n * - `mode: 'svg'`: emits a full `<svg>…</svg>` rendered server-side. The\n * transformer is async in this mode so it can `await initYoga()` once\n * before resolving FlexContainer layout. By default each fence auto-sizes\n * to its content (plus 16px padding); `width` / `height` act as minimum\n * floors. Pass `fit: 'fixed'` for a fixed canvas. Authors can override\n * any of `fit` / `width` / `height` / `padding` per fence via the info\n * string, e.g. ` ```boceto Login width=1280 fit=content `.\n */\nexport default function remarkBoceto(\n options: RemarkBocetoOptions = {},\n): (tree: Root) => void | Promise<void> {\n const mode = options.mode ?? 'wc'\n const tag = options.tag ?? 'boceto-view'\n const extraAttrs = options.attributes ?? {}\n const svgRenderer = mode === 'svg' ? new SvgRenderer() : null\n\n if (mode !== 'svg') {\n // Synchronous transformer for WC mode — no layout to resolve.\n return (tree) => {\n visit(tree, 'code', (node: Code, index, parent) => {\n if (!parent || index == null) return\n if ((node.lang ?? '') !== 'boceto') return\n const source = node.value\n const meta = node.meta ?? null\n let html: string\n if (options.render) {\n html = options.render(source, { lang: 'boceto', meta })\n } else {\n const { page } = parseFenceMeta(meta)\n const attrs: Record<string, string> = { ...extraAttrs, code: source }\n if (page) attrs['data-page'] = page\n html = renderTag(tag, attrs)\n }\n parent.children[index] = { type: 'html', value: html } as Html\n })\n }\n }\n\n // SVG mode: collect every boceto block, ensure Yoga is loaded once, then\n // parse + lay out + render each in place.\n return async (tree) => {\n const targets: Array<{ node: Code; index: number; parent: Root | Code['data'] }> = []\n visit(tree, 'code', (node: Code, index, parent) => {\n if (!parent || index == null) return\n if ((node.lang ?? '') !== 'boceto') return\n targets.push({ node, index, parent: parent as Root })\n })\n if (targets.length === 0) return\n await initYoga()\n for (const { node, index, parent } of targets) {\n const source = node.value\n const meta = node.meta ?? null\n let html: string\n if (options.render) {\n html = options.render(source, { lang: 'boceto', meta })\n } else {\n const { page: pageName, opts: fence } = parseFenceMeta(meta)\n const fit = fence.fit ?? options.fit ?? 'content'\n const padding = fence.padding ?? options.padding ?? 16\n const minW = fence.width ?? options.width\n const minH = fence.height ?? options.height\n const wrapped = '```boceto' + (pageName ? ':' + pageName : '') + '\\n' + source + '\\n```'\n const doc = applyFlexLayout(parse(wrapped))\n let w: number, h: number\n if (fit === 'fixed') {\n w = minW ?? 860\n h = minH ?? 600\n } else {\n const pg = selectPage(doc)\n const box = pg ? pageContentBox(pg.elements) : null\n if (box) {\n w = Math.max(minW ?? 0, Math.ceil(box.x + box.w + padding))\n h = Math.max(minH ?? 0, Math.ceil(box.y + box.h + padding))\n } else {\n w = minW ?? 860\n h = minH ?? 600\n }\n }\n html = svgRenderer!.renderToString(doc, { width: w, height: h })\n }\n ;(parent as Root).children[index] = { type: 'html', value: html } as Html\n }\n }\n}\n\nfunction renderTag(tag: string, attrs: Record<string, string>): string {\n const parts = [tag]\n for (const [k, v] of Object.entries(attrs)) parts.push(`${k}=\"${escapeAttr(v)}\"`)\n return `<${parts.join(' ')}></${tag}>`\n}\n\nfunction escapeAttr(s: string): string {\n return s.replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>')\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -14,9 +14,32 @@ interface RemarkBocetoOptions {
|
|
|
14
14
|
tag?: string;
|
|
15
15
|
/** Extra static attributes to attach to every emitted element (`'wc'` mode only). */
|
|
16
16
|
attributes?: Record<string, string>;
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* SVG sizing strategy (`'svg'` mode only).
|
|
19
|
+
* - `'content'` (default): canvas grows to fit the page's content plus
|
|
20
|
+
* `padding`. `width` / `height`, if set, act as a **minimum floor** —
|
|
21
|
+
* same semantics as `<boceto-view fit="content">`.
|
|
22
|
+
* - `'fixed'`: legacy behavior — canvas is exactly `width × height`
|
|
23
|
+
* (defaults `860 × 600`), content outside is clipped.
|
|
24
|
+
*
|
|
25
|
+
* Can be overridden per fence: ` ```boceto fit=fixed `.
|
|
26
|
+
*/
|
|
27
|
+
fit?: 'content' | 'fixed';
|
|
28
|
+
/**
|
|
29
|
+
* SVG canvas dimensions (`'svg'` mode only).
|
|
30
|
+
* In `fit: 'content'` (default) these are floors — the canvas only grows.
|
|
31
|
+
* In `fit: 'fixed'` they are the exact canvas size (defaults 860 / 600).
|
|
32
|
+
*
|
|
33
|
+
* Can be overridden per fence: ` ```boceto width=1280 height=800 `.
|
|
34
|
+
*/
|
|
18
35
|
width?: number;
|
|
19
36
|
height?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Breathing room around content in `fit: 'content'` mode. Default `16`,
|
|
39
|
+
* matching `<boceto-view>`'s `padding` attribute. Can be overridden per
|
|
40
|
+
* fence: ` ```boceto padding=32 `.
|
|
41
|
+
*/
|
|
42
|
+
padding?: number;
|
|
20
43
|
/**
|
|
21
44
|
* Receive the boceto source and return arbitrary HTML. If provided, all
|
|
22
45
|
* other options are ignored. Use this if you want a custom wrapper or to
|
|
@@ -35,7 +58,11 @@ type Plugin = () => (tree: Root) => void | Promise<void>;
|
|
|
35
58
|
* - `mode: 'wc'` (default): emits `<boceto-view code="…">`.
|
|
36
59
|
* - `mode: 'svg'`: emits a full `<svg>…</svg>` rendered server-side. The
|
|
37
60
|
* transformer is async in this mode so it can `await initYoga()` once
|
|
38
|
-
* before resolving FlexContainer layout.
|
|
61
|
+
* before resolving FlexContainer layout. By default each fence auto-sizes
|
|
62
|
+
* to its content (plus 16px padding); `width` / `height` act as minimum
|
|
63
|
+
* floors. Pass `fit: 'fixed'` for a fixed canvas. Authors can override
|
|
64
|
+
* any of `fit` / `width` / `height` / `padding` per fence via the info
|
|
65
|
+
* string, e.g. ` ```boceto Login width=1280 fit=content `.
|
|
39
66
|
*/
|
|
40
67
|
declare function remarkBoceto(options?: RemarkBocetoOptions): (tree: Root) => void | Promise<void>;
|
|
41
68
|
|
package/dist/index.d.ts
CHANGED
|
@@ -14,9 +14,32 @@ interface RemarkBocetoOptions {
|
|
|
14
14
|
tag?: string;
|
|
15
15
|
/** Extra static attributes to attach to every emitted element (`'wc'` mode only). */
|
|
16
16
|
attributes?: Record<string, string>;
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* SVG sizing strategy (`'svg'` mode only).
|
|
19
|
+
* - `'content'` (default): canvas grows to fit the page's content plus
|
|
20
|
+
* `padding`. `width` / `height`, if set, act as a **minimum floor** —
|
|
21
|
+
* same semantics as `<boceto-view fit="content">`.
|
|
22
|
+
* - `'fixed'`: legacy behavior — canvas is exactly `width × height`
|
|
23
|
+
* (defaults `860 × 600`), content outside is clipped.
|
|
24
|
+
*
|
|
25
|
+
* Can be overridden per fence: ` ```boceto fit=fixed `.
|
|
26
|
+
*/
|
|
27
|
+
fit?: 'content' | 'fixed';
|
|
28
|
+
/**
|
|
29
|
+
* SVG canvas dimensions (`'svg'` mode only).
|
|
30
|
+
* In `fit: 'content'` (default) these are floors — the canvas only grows.
|
|
31
|
+
* In `fit: 'fixed'` they are the exact canvas size (defaults 860 / 600).
|
|
32
|
+
*
|
|
33
|
+
* Can be overridden per fence: ` ```boceto width=1280 height=800 `.
|
|
34
|
+
*/
|
|
18
35
|
width?: number;
|
|
19
36
|
height?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Breathing room around content in `fit: 'content'` mode. Default `16`,
|
|
39
|
+
* matching `<boceto-view>`'s `padding` attribute. Can be overridden per
|
|
40
|
+
* fence: ` ```boceto padding=32 `.
|
|
41
|
+
*/
|
|
42
|
+
padding?: number;
|
|
20
43
|
/**
|
|
21
44
|
* Receive the boceto source and return arbitrary HTML. If provided, all
|
|
22
45
|
* other options are ignored. Use this if you want a custom wrapper or to
|
|
@@ -35,7 +58,11 @@ type Plugin = () => (tree: Root) => void | Promise<void>;
|
|
|
35
58
|
* - `mode: 'wc'` (default): emits `<boceto-view code="…">`.
|
|
36
59
|
* - `mode: 'svg'`: emits a full `<svg>…</svg>` rendered server-side. The
|
|
37
60
|
* transformer is async in this mode so it can `await initYoga()` once
|
|
38
|
-
* before resolving FlexContainer layout.
|
|
61
|
+
* before resolving FlexContainer layout. By default each fence auto-sizes
|
|
62
|
+
* to its content (plus 16px padding); `width` / `height` act as minimum
|
|
63
|
+
* floors. Pass `fit: 'fixed'` for a fixed canvas. Authors can override
|
|
64
|
+
* any of `fit` / `width` / `height` / `padding` per fence via the info
|
|
65
|
+
* string, e.g. ` ```boceto Login width=1280 fit=content `.
|
|
39
66
|
*/
|
|
40
67
|
declare function remarkBoceto(options?: RemarkBocetoOptions): (tree: Root) => void | Promise<void>;
|
|
41
68
|
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
import { visit } from 'unist-util-visit';
|
|
2
|
-
import { SvgRenderer, initYoga, applyFlexLayout, parse } from '@boceto/core';
|
|
2
|
+
import { SvgRenderer, initYoga, applyFlexLayout, parse, selectPage, pageContentBox } from '@boceto/core';
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
|
+
function parseFenceMeta(meta) {
|
|
6
|
+
if (!meta) return { page: null, opts: {} };
|
|
7
|
+
const opts = {};
|
|
8
|
+
const rest = [];
|
|
9
|
+
for (const tok of meta.trim().split(/\s+/)) {
|
|
10
|
+
const eq = tok.indexOf("=");
|
|
11
|
+
if (eq > 0) {
|
|
12
|
+
const key = tok.slice(0, eq);
|
|
13
|
+
const val = tok.slice(eq + 1);
|
|
14
|
+
if (key === "fit" && (val === "content" || val === "fixed")) {
|
|
15
|
+
opts.fit = val;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (key === "width" || key === "height" || key === "padding") {
|
|
19
|
+
const n = Number(val);
|
|
20
|
+
if (Number.isFinite(n) && n >= 0) {
|
|
21
|
+
opts[key] = n;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
rest.push(tok);
|
|
27
|
+
}
|
|
28
|
+
return { page: rest.length ? rest.join(" ") : null, opts };
|
|
29
|
+
}
|
|
5
30
|
function remarkBoceto(options = {}) {
|
|
6
31
|
const mode = options.mode ?? "wc";
|
|
7
32
|
const tag = options.tag ?? "boceto-view";
|
|
8
33
|
const extraAttrs = options.attributes ?? {};
|
|
9
|
-
const width = options.width ?? 860;
|
|
10
|
-
const height = options.height ?? 600;
|
|
11
34
|
const svgRenderer = mode === "svg" ? new SvgRenderer() : null;
|
|
12
35
|
if (mode !== "svg") {
|
|
13
36
|
return (tree) => {
|
|
@@ -20,8 +43,9 @@ function remarkBoceto(options = {}) {
|
|
|
20
43
|
if (options.render) {
|
|
21
44
|
html = options.render(source, { lang: "boceto", meta });
|
|
22
45
|
} else {
|
|
46
|
+
const { page } = parseFenceMeta(meta);
|
|
23
47
|
const attrs = { ...extraAttrs, code: source };
|
|
24
|
-
if (
|
|
48
|
+
if (page) attrs["data-page"] = page;
|
|
25
49
|
html = renderTag(tag, attrs);
|
|
26
50
|
}
|
|
27
51
|
parent.children[index] = { type: "html", value: html };
|
|
@@ -44,9 +68,29 @@ function remarkBoceto(options = {}) {
|
|
|
44
68
|
if (options.render) {
|
|
45
69
|
html = options.render(source, { lang: "boceto", meta });
|
|
46
70
|
} else {
|
|
47
|
-
const
|
|
71
|
+
const { page: pageName, opts: fence } = parseFenceMeta(meta);
|
|
72
|
+
const fit = fence.fit ?? options.fit ?? "content";
|
|
73
|
+
const padding = fence.padding ?? options.padding ?? 16;
|
|
74
|
+
const minW = fence.width ?? options.width;
|
|
75
|
+
const minH = fence.height ?? options.height;
|
|
76
|
+
const wrapped = "```boceto" + (pageName ? ":" + pageName : "") + "\n" + source + "\n```";
|
|
48
77
|
const doc = applyFlexLayout(parse(wrapped));
|
|
49
|
-
|
|
78
|
+
let w, h;
|
|
79
|
+
if (fit === "fixed") {
|
|
80
|
+
w = minW ?? 860;
|
|
81
|
+
h = minH ?? 600;
|
|
82
|
+
} else {
|
|
83
|
+
const pg = selectPage(doc);
|
|
84
|
+
const box = pg ? pageContentBox(pg.elements) : null;
|
|
85
|
+
if (box) {
|
|
86
|
+
w = Math.max(minW ?? 0, Math.ceil(box.x + box.w + padding));
|
|
87
|
+
h = Math.max(minH ?? 0, Math.ceil(box.y + box.h + padding));
|
|
88
|
+
} else {
|
|
89
|
+
w = minW ?? 860;
|
|
90
|
+
h = minH ?? 600;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
html = svgRenderer.renderToString(doc, { width: w, height: h });
|
|
50
94
|
}
|
|
51
95
|
parent.children[index] = { type: "html", value: html };
|
|
52
96
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Code, Html, Root } from 'mdast'\nimport { visit } from 'unist-util-visit'\nimport { applyFlexLayout, initYoga, parse, SvgRenderer } from '@boceto/core'\n\nexport interface RemarkBocetoOptions {\n /**\n * Output mode.\n * - `'wc'` (default): emit `<boceto-view>` custom element. Requires the WC\n * runtime in the browser.\n * - `'svg'`: parse the source and inline a complete `<svg>` document.\n * Renders with **zero JS at runtime** — works in GitHub READMEs, RSS\n * readers, and SSGs.\n */\n mode?: 'wc' | 'svg'\n /** Tag to emit in `'wc'` mode. Default `'boceto-view'`. Use `'boceto-edit'` for editable blocks. */\n tag?: string\n /** Extra static attributes to attach to every emitted element (`'wc'` mode only). */\n attributes?: Record<string, string>\n /** SVG render dimensions (`'svg'` mode only). Defaults: 860 × 600. */\n width?: number\n height?: number\n /**\n * Receive the boceto source and return arbitrary HTML. If provided, all\n * other options are ignored. Use this if you want a custom wrapper or to\n * swap renderers entirely.\n */\n render?: (source: string, info: { lang: string; meta: string | null }) => string\n}\n\nexport type Plugin = () => (tree: Root) => void | Promise<void>\n\n/**\n * remark plugin that transforms `code` nodes whose language is `boceto`\n * into `html` nodes.\n *\n * - `mode: 'wc'` (default): emits `<boceto-view code=\"…\">`.\n * - `mode: 'svg'`: emits a full `<svg>…</svg>` rendered server-side. The\n * transformer is async in this mode so it can `await initYoga()` once\n * before resolving FlexContainer layout.\n */\nexport default function remarkBoceto(\n options: RemarkBocetoOptions = {},\n): (tree: Root) => void | Promise<void> {\n const mode = options.mode ?? 'wc'\n const tag = options.tag ?? 'boceto-view'\n const extraAttrs = options.attributes ?? {}\n const width = options.width ?? 860\n const height = options.height ?? 600\n const svgRenderer = mode === 'svg' ? new SvgRenderer() : null\n\n if (mode !== 'svg') {\n // Synchronous transformer for WC mode — no layout to resolve.\n return (tree) => {\n visit(tree, 'code', (node: Code, index, parent) => {\n if (!parent || index == null) return\n if ((node.lang ?? '') !== 'boceto') return\n const source = node.value\n const meta = node.meta ?? null\n let html: string\n if (options.render) {\n html = options.render(source, { lang: 'boceto', meta })\n } else {\n const attrs: Record<string, string> = { ...extraAttrs, code: source }\n if (meta) attrs['data-page'] = meta.trim()\n html = renderTag(tag, attrs)\n }\n parent.children[index] = { type: 'html', value: html } as Html\n })\n }\n }\n\n // SVG mode: collect every boceto block, ensure Yoga is loaded once, then\n // parse + lay out + render each in place.\n return async (tree) => {\n const targets: Array<{ node: Code; index: number; parent: Root | Code['data'] }> = []\n visit(tree, 'code', (node: Code, index, parent) => {\n if (!parent || index == null) return\n if ((node.lang ?? '') !== 'boceto') return\n targets.push({ node, index, parent: parent as Root })\n })\n if (targets.length === 0) return\n await initYoga()\n for (const { node, index, parent } of targets) {\n const source = node.value\n const meta = node.meta ?? null\n let html: string\n if (options.render) {\n html = options.render(source, { lang: 'boceto', meta })\n } else {\n const wrapped = '```boceto' + (meta ? ':' + meta : '') + '\\n' + source + '\\n```'\n const doc = applyFlexLayout(parse(wrapped))\n html = svgRenderer!.renderToString(doc, { width, height })\n }\n ;(parent as Root).children[index] = { type: 'html', value: html } as Html\n }\n }\n}\n\nfunction renderTag(tag: string, attrs: Record<string, string>): string {\n const parts = [tag]\n for (const [k, v] of Object.entries(attrs)) parts.push(`${k}=\"${escapeAttr(v)}\"`)\n return `<${parts.join(' ')}></${tag}>`\n}\n\nfunction escapeAttr(s: string): string {\n return s.replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>')\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Code, Html, Root } from 'mdast'\nimport { visit } from 'unist-util-visit'\nimport { applyFlexLayout, initYoga, pageContentBox, parse, selectPage, SvgRenderer } from '@boceto/core'\n\nexport interface RemarkBocetoOptions {\n /**\n * Output mode.\n * - `'wc'` (default): emit `<boceto-view>` custom element. Requires the WC\n * runtime in the browser.\n * - `'svg'`: parse the source and inline a complete `<svg>` document.\n * Renders with **zero JS at runtime** — works in GitHub READMEs, RSS\n * readers, and SSGs.\n */\n mode?: 'wc' | 'svg'\n /** Tag to emit in `'wc'` mode. Default `'boceto-view'`. Use `'boceto-edit'` for editable blocks. */\n tag?: string\n /** Extra static attributes to attach to every emitted element (`'wc'` mode only). */\n attributes?: Record<string, string>\n /**\n * SVG sizing strategy (`'svg'` mode only).\n * - `'content'` (default): canvas grows to fit the page's content plus\n * `padding`. `width` / `height`, if set, act as a **minimum floor** —\n * same semantics as `<boceto-view fit=\"content\">`.\n * - `'fixed'`: legacy behavior — canvas is exactly `width × height`\n * (defaults `860 × 600`), content outside is clipped.\n *\n * Can be overridden per fence: ` ```boceto fit=fixed `.\n */\n fit?: 'content' | 'fixed'\n /**\n * SVG canvas dimensions (`'svg'` mode only).\n * In `fit: 'content'` (default) these are floors — the canvas only grows.\n * In `fit: 'fixed'` they are the exact canvas size (defaults 860 / 600).\n *\n * Can be overridden per fence: ` ```boceto width=1280 height=800 `.\n */\n width?: number\n height?: number\n /**\n * Breathing room around content in `fit: 'content'` mode. Default `16`,\n * matching `<boceto-view>`'s `padding` attribute. Can be overridden per\n * fence: ` ```boceto padding=32 `.\n */\n padding?: number\n /**\n * Receive the boceto source and return arbitrary HTML. If provided, all\n * other options are ignored. Use this if you want a custom wrapper or to\n * swap renderers entirely.\n */\n render?: (source: string, info: { lang: string; meta: string | null }) => string\n}\n\nexport type Plugin = () => (tree: Root) => void | Promise<void>\n\ntype FenceOpts = { fit?: 'content' | 'fixed'; width?: number; height?: number; padding?: number }\n\n/**\n * Parse the fence info-string portion after `boceto`. Recognized `key=value`\n * tokens (`fit`, `width`, `height`, `padding`) are extracted as per-fence\n * overrides; everything else is joined back as the page name (preserving the\n * original meta semantics). Unknown keys or invalid values fall through to\n * the page name — no throws on typos.\n */\nfunction parseFenceMeta(meta: string | null): { page: string | null; opts: FenceOpts } {\n if (!meta) return { page: null, opts: {} }\n const opts: FenceOpts = {}\n const rest: string[] = []\n for (const tok of meta.trim().split(/\\s+/)) {\n const eq = tok.indexOf('=')\n if (eq > 0) {\n const key = tok.slice(0, eq)\n const val = tok.slice(eq + 1)\n if (key === 'fit' && (val === 'content' || val === 'fixed')) {\n opts.fit = val\n continue\n }\n if (key === 'width' || key === 'height' || key === 'padding') {\n const n = Number(val)\n if (Number.isFinite(n) && n >= 0) {\n opts[key] = n\n continue\n }\n }\n }\n rest.push(tok)\n }\n return { page: rest.length ? rest.join(' ') : null, opts }\n}\n\n/**\n * remark plugin that transforms `code` nodes whose language is `boceto`\n * into `html` nodes.\n *\n * - `mode: 'wc'` (default): emits `<boceto-view code=\"…\">`.\n * - `mode: 'svg'`: emits a full `<svg>…</svg>` rendered server-side. The\n * transformer is async in this mode so it can `await initYoga()` once\n * before resolving FlexContainer layout. By default each fence auto-sizes\n * to its content (plus 16px padding); `width` / `height` act as minimum\n * floors. Pass `fit: 'fixed'` for a fixed canvas. Authors can override\n * any of `fit` / `width` / `height` / `padding` per fence via the info\n * string, e.g. ` ```boceto Login width=1280 fit=content `.\n */\nexport default function remarkBoceto(\n options: RemarkBocetoOptions = {},\n): (tree: Root) => void | Promise<void> {\n const mode = options.mode ?? 'wc'\n const tag = options.tag ?? 'boceto-view'\n const extraAttrs = options.attributes ?? {}\n const svgRenderer = mode === 'svg' ? new SvgRenderer() : null\n\n if (mode !== 'svg') {\n // Synchronous transformer for WC mode — no layout to resolve.\n return (tree) => {\n visit(tree, 'code', (node: Code, index, parent) => {\n if (!parent || index == null) return\n if ((node.lang ?? '') !== 'boceto') return\n const source = node.value\n const meta = node.meta ?? null\n let html: string\n if (options.render) {\n html = options.render(source, { lang: 'boceto', meta })\n } else {\n const { page } = parseFenceMeta(meta)\n const attrs: Record<string, string> = { ...extraAttrs, code: source }\n if (page) attrs['data-page'] = page\n html = renderTag(tag, attrs)\n }\n parent.children[index] = { type: 'html', value: html } as Html\n })\n }\n }\n\n // SVG mode: collect every boceto block, ensure Yoga is loaded once, then\n // parse + lay out + render each in place.\n return async (tree) => {\n const targets: Array<{ node: Code; index: number; parent: Root | Code['data'] }> = []\n visit(tree, 'code', (node: Code, index, parent) => {\n if (!parent || index == null) return\n if ((node.lang ?? '') !== 'boceto') return\n targets.push({ node, index, parent: parent as Root })\n })\n if (targets.length === 0) return\n await initYoga()\n for (const { node, index, parent } of targets) {\n const source = node.value\n const meta = node.meta ?? null\n let html: string\n if (options.render) {\n html = options.render(source, { lang: 'boceto', meta })\n } else {\n const { page: pageName, opts: fence } = parseFenceMeta(meta)\n const fit = fence.fit ?? options.fit ?? 'content'\n const padding = fence.padding ?? options.padding ?? 16\n const minW = fence.width ?? options.width\n const minH = fence.height ?? options.height\n const wrapped = '```boceto' + (pageName ? ':' + pageName : '') + '\\n' + source + '\\n```'\n const doc = applyFlexLayout(parse(wrapped))\n let w: number, h: number\n if (fit === 'fixed') {\n w = minW ?? 860\n h = minH ?? 600\n } else {\n const pg = selectPage(doc)\n const box = pg ? pageContentBox(pg.elements) : null\n if (box) {\n w = Math.max(minW ?? 0, Math.ceil(box.x + box.w + padding))\n h = Math.max(minH ?? 0, Math.ceil(box.y + box.h + padding))\n } else {\n w = minW ?? 860\n h = minH ?? 600\n }\n }\n html = svgRenderer!.renderToString(doc, { width: w, height: h })\n }\n ;(parent as Root).children[index] = { type: 'html', value: html } as Html\n }\n }\n}\n\nfunction renderTag(tag: string, attrs: Record<string, string>): string {\n const parts = [tag]\n for (const [k, v] of Object.entries(attrs)) parts.push(`${k}=\"${escapeAttr(v)}\"`)\n return `<${parts.join(' ')}></${tag}>`\n}\n\nfunction escapeAttr(s: string): string {\n return s.replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>')\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boceto/remark",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "remark plugin: render fenced ```boceto blocks as <boceto-view> custom elements.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Maravilla Labs",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"unist-util-visit": "^5.0.0",
|
|
45
|
-
"@boceto/core": "0.
|
|
45
|
+
"@boceto/core": "0.2.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/mdast": "^4.0.4",
|