@draftably/markdown 0.0.0-stage → 0.1.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/README.md +72 -2
- package/dist/LICENSE +21 -0
- package/dist/chunks/core-DPjuIyPG.js +317 -0
- package/dist/chunks/metadata-Dx6T_2Zy.js +53 -0
- package/dist/client.js +153 -0
- package/dist/files.js +103 -0
- package/dist/index.js +8 -0
- package/dist/markdown-it.js +155 -0
- package/dist/remark.js +86 -0
- package/dist/types/client-validation.d.ts +5 -0
- package/dist/types/client.d.ts +12 -0
- package/dist/types/core.d.ts +99 -0
- package/dist/types/files.d.ts +12 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/markdown-it-helpers.d.ts +9 -0
- package/dist/types/markdown-it.d.ts +30 -0
- package/dist/types/metadata.d.ts +9 -0
- package/dist/types/remark.d.ts +21 -0
- package/dist/types/source.d.ts +4 -0
- package/package.json +51 -3
package/README.md
CHANGED
|
@@ -1,3 +1,73 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@draftably/markdown`
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This package converts canonical `draft` code fences to static SVG image nodes.
|
|
4
|
+
It supports async remark and markdown-it adapters. Static pages do not load or
|
|
5
|
+
compile diagrams in the reader.
|
|
6
|
+
|
|
7
|
+
Start with the [complete Markdown example](https://github.com/byronwall/draftably/blob/main/docs/integrations/README.md#render-markdown)
|
|
8
|
+
for installation, file storage, and a working first diagram.
|
|
9
|
+
|
|
10
|
+
The host must provide `emitAsset`. It receives final SVG bytes and returns the
|
|
11
|
+
public URL. Use a durable content-addressed store with your page cache. Re-emit
|
|
12
|
+
the asset on a warm Markdown transform, and retain asset references with cached
|
|
13
|
+
pages. The host owns base paths, publication, cleanup, and watcher integration.
|
|
14
|
+
|
|
15
|
+
For Node builds, `@draftably/markdown/files` provides a filesystem store for
|
|
16
|
+
content-addressed SVG and interactive JSON assets:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
import { createFileAssets } from '@draftably/markdown/files';
|
|
20
|
+
|
|
21
|
+
const assets = createFileAssets({ directory: '.cache/diagrams', publicPath: '/docs/diagrams/' });
|
|
22
|
+
const options = { emitAsset: assets.emitAsset };
|
|
23
|
+
// Add `assets.emitInit` to the optional interactive bridge.
|
|
24
|
+
await assets.copyTo('dist/diagrams');
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The store checks each filename against the asset bytes and recreates deleted
|
|
28
|
+
files on warm transforms. The host still chooses paths and retains cached page
|
|
29
|
+
references.
|
|
30
|
+
|
|
31
|
+
Use `src="./diagram.draft"` only with `sources.rootDir` and a Markdown file path.
|
|
32
|
+
The resolver rejects remote and absolute paths, non-`.draft` files, and symlink
|
|
33
|
+
escapes. It reports missing paths as dependencies and the nearest existing
|
|
34
|
+
in-root directory as `watchPath`, so the host can detect when a file appears.
|
|
35
|
+
|
|
36
|
+
Supported fence keys are `id`, `alt`, `caption`, `src`, `mode`, and `state`.
|
|
37
|
+
Unknown, repeated, malformed, or control-containing values fail the build.
|
|
38
|
+
Captions and alternatives are plain text. The adapter does not evaluate HTML
|
|
39
|
+
or JSX. Inline SVG is not available.
|
|
40
|
+
|
|
41
|
+
## markdown-it
|
|
42
|
+
|
|
43
|
+
Call `createMarkdownItDraft(options)` and add its `plugin` to markdown-it. Use
|
|
44
|
+
`adapter.render(md, source, env)` to parse, prepare, and render one document. The
|
|
45
|
+
adapter also exposes `prepare(tokens, env)` for hosts that need custom rendering.
|
|
46
|
+
Prepared state stays local to each token tree, so separate documents can prepare
|
|
47
|
+
at the same time.
|
|
48
|
+
|
|
49
|
+
## Optional interactive mode
|
|
50
|
+
|
|
51
|
+
`mode="interactive"` requires a host `interactive` bridge. The bridge emits a
|
|
52
|
+
versioned JSON asset and names the client module. This mode publishes the
|
|
53
|
+
diagram source in that JSON asset by design. Keep the JSON and SVG references
|
|
54
|
+
with the page cache.
|
|
55
|
+
|
|
56
|
+
The host can load `@draftably/markdown/client` after hydration and call
|
|
57
|
+
`enhanceDrafts(root, { policies })`. The client validates same-origin URLs and
|
|
58
|
+
bounded JSON before it mounts `@draftably/runtime/viewer`. It hides the static
|
|
59
|
+
image only after the viewer commits an update. On failure or destroy, the
|
|
60
|
+
fallback image stays or becomes visible.
|
|
61
|
+
|
|
62
|
+
The host owns module loading and teardown. Static adapter entry points do not
|
|
63
|
+
import the client or viewer. Use interactive mode only when public source is
|
|
64
|
+
acceptable.
|
|
65
|
+
|
|
66
|
+
For positioned author errors, preserve mdast positions and inspect
|
|
67
|
+
`file.data.draftably.diagnostics`. Production builds fail by default. Set
|
|
68
|
+
`errors: 'placeholder'` for expected content, metadata, or include errors. Host
|
|
69
|
+
emitter, dependency callback, and renderer failures still fail the build.
|
|
70
|
+
|
|
71
|
+
See [`docs/integrations/markdown.md`](../../docs/integrations/markdown.md) and
|
|
72
|
+
[`integration-examples/unified/`](../../integration-examples/unified/) for
|
|
73
|
+
pinned Unified, MDX, markdown-it, and interactive consumer proofs.
|
package/dist/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Byron Wall
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { createHash as J } from "node:crypto";
|
|
2
|
+
import { runtimeVersion as W, fontMetricsVersion as Z, renderSvg as H } from "@draftably/runtime";
|
|
3
|
+
import { realpath as D, stat as K, readFile as V } from "node:fs/promises";
|
|
4
|
+
import { resolve as k, relative as q, sep as G, isAbsolute as Y } from "node:path";
|
|
5
|
+
function N(e, t) {
|
|
6
|
+
const r = q(e, t);
|
|
7
|
+
return r === "" || r !== ".." && !r.startsWith(`..${G}`) && !Y(r);
|
|
8
|
+
}
|
|
9
|
+
async function Q(e, t, r) {
|
|
10
|
+
if (!e || e.includes("\0") || e.startsWith("/") || e.startsWith("\\") || /^[A-Za-z][A-Za-z\d+.-]*:/.test(e) || /^[A-Za-z]:/.test(e))
|
|
11
|
+
throw new Error("src must be a relative local path.");
|
|
12
|
+
const s = await D(r), g = k(t, "..", e);
|
|
13
|
+
if (!N(k(r), g))
|
|
14
|
+
throw new Error("src is outside the configured content root.");
|
|
15
|
+
let n;
|
|
16
|
+
try {
|
|
17
|
+
n = await D(g);
|
|
18
|
+
} catch (m) {
|
|
19
|
+
let f = g, y;
|
|
20
|
+
for (; f !== k(f, ".."); )
|
|
21
|
+
try {
|
|
22
|
+
const l = await D(f);
|
|
23
|
+
if (!N(s, l))
|
|
24
|
+
throw new Error("src resolves outside the configured content root.", { cause: m });
|
|
25
|
+
y = l;
|
|
26
|
+
break;
|
|
27
|
+
} catch (l) {
|
|
28
|
+
if (l instanceof Error && l.message.includes("outside"))
|
|
29
|
+
throw l;
|
|
30
|
+
f = k(f, "..");
|
|
31
|
+
}
|
|
32
|
+
throw Object.assign(new Error("Included .draft file does not exist.", { cause: m }), {
|
|
33
|
+
missingPath: g,
|
|
34
|
+
watchPath: y
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (!N(s, n))
|
|
38
|
+
throw new Error("src resolves outside the configured content root.");
|
|
39
|
+
if (!n.endsWith(".draft")) throw new Error("src must name a .draft file.");
|
|
40
|
+
const p = await K(n);
|
|
41
|
+
if (!p.isFile()) throw new Error("src must name a regular file.");
|
|
42
|
+
if (p.size > 1e6) throw new Error("Included .draft file exceeds 1 MB.");
|
|
43
|
+
const h = await V(n);
|
|
44
|
+
if (h.byteLength > 1e6) throw new Error("Included .draft file exceeds 1 MB.");
|
|
45
|
+
return { source: new TextDecoder("utf-8", { fatal: !0 }).decode(h), path: n };
|
|
46
|
+
}
|
|
47
|
+
function z(e, t, r, s) {
|
|
48
|
+
return {
|
|
49
|
+
code: e,
|
|
50
|
+
severity: "error",
|
|
51
|
+
message: t,
|
|
52
|
+
location: r,
|
|
53
|
+
itemIds: [],
|
|
54
|
+
...s ? { related: s } : {}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function M(e) {
|
|
58
|
+
return J("sha256").update(e).digest("hex");
|
|
59
|
+
}
|
|
60
|
+
function x(e) {
|
|
61
|
+
for (const t of e) {
|
|
62
|
+
const r = t.charCodeAt(0);
|
|
63
|
+
if (r < 32 || r === 127) return !0;
|
|
64
|
+
}
|
|
65
|
+
return !1;
|
|
66
|
+
}
|
|
67
|
+
function c(e, t = "Markdown host operation failed.") {
|
|
68
|
+
return X(e) ? e : Object.assign(new Error(t, { cause: e }), { hostFailure: !0 });
|
|
69
|
+
}
|
|
70
|
+
function X(e) {
|
|
71
|
+
return !!e && typeof e == "object" && "hostFailure" in e;
|
|
72
|
+
}
|
|
73
|
+
function P(e, t, r) {
|
|
74
|
+
return Object.assign(new Error(t), { diagnostic: z(e, t, r) });
|
|
75
|
+
}
|
|
76
|
+
function se(e) {
|
|
77
|
+
return !!e && typeof e == "object" && "noPlaceholder" in e;
|
|
78
|
+
}
|
|
79
|
+
function F(e) {
|
|
80
|
+
if (e?.aborted)
|
|
81
|
+
throw Object.assign(new Error("Markdown diagram preparation was aborted."), {
|
|
82
|
+
noPlaceholder: !0,
|
|
83
|
+
cause: e.reason
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const v = /* @__PURE__ */ new Map(), ee = 128;
|
|
87
|
+
function te(e, t) {
|
|
88
|
+
const r = e.render ?? {};
|
|
89
|
+
if (!(r.links?.mode === "safe" && r.links.resolve && !r.links.cacheKey))
|
|
90
|
+
return JSON.stringify({
|
|
91
|
+
includeSource: r.includeSource ?? !1,
|
|
92
|
+
links: r.links ?? { mode: "none" },
|
|
93
|
+
limits: r.limits ?? {},
|
|
94
|
+
state: t ?? null,
|
|
95
|
+
target: "image",
|
|
96
|
+
fonts: "embed"
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
async function ce(e, t, r, s, g, n) {
|
|
100
|
+
const p = [];
|
|
101
|
+
F(n.render?.signal);
|
|
102
|
+
let h = e, m = s.path, f, y = !1;
|
|
103
|
+
if (t.src) {
|
|
104
|
+
if (e.trim())
|
|
105
|
+
throw P(
|
|
106
|
+
"DRAFT_INCLUDE",
|
|
107
|
+
"src cannot be combined with a non-empty fence body.",
|
|
108
|
+
r
|
|
109
|
+
);
|
|
110
|
+
if (!s.path || !n.sources)
|
|
111
|
+
throw P(
|
|
112
|
+
"DRAFT_INCLUDE",
|
|
113
|
+
"src requires a document path and configured sources.rootDir.",
|
|
114
|
+
r
|
|
115
|
+
);
|
|
116
|
+
let i;
|
|
117
|
+
try {
|
|
118
|
+
i = await Q(t.src, s.path, n.sources.rootDir);
|
|
119
|
+
} catch (a) {
|
|
120
|
+
const { missingPath: u, watchPath: I } = a;
|
|
121
|
+
if (u)
|
|
122
|
+
try {
|
|
123
|
+
n.sources.onDependency?.({
|
|
124
|
+
documentPath: s.path,
|
|
125
|
+
dependencyPath: u,
|
|
126
|
+
missing: !0,
|
|
127
|
+
watchPath: I
|
|
128
|
+
});
|
|
129
|
+
} catch (A) {
|
|
130
|
+
throw c(A, "Markdown dependency callback failed.");
|
|
131
|
+
}
|
|
132
|
+
const o = a instanceof Error ? a.message : "Could not read included diagram.";
|
|
133
|
+
throw Object.assign(new Error(o), {
|
|
134
|
+
diagnostic: z(
|
|
135
|
+
"DRAFT_INCLUDE",
|
|
136
|
+
o,
|
|
137
|
+
r,
|
|
138
|
+
u ? [{ message: "Included here.", location: r }] : void 0
|
|
139
|
+
)
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
h = i.source, m = i.path, y = !0, f = i.path;
|
|
143
|
+
try {
|
|
144
|
+
n.sources.onDependency?.({
|
|
145
|
+
documentPath: s.path,
|
|
146
|
+
dependencyPath: i.path,
|
|
147
|
+
missing: !1
|
|
148
|
+
});
|
|
149
|
+
} catch (a) {
|
|
150
|
+
throw c(a, "Markdown dependency callback failed.");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (t.mode === "interactive" && !n.interactive) {
|
|
154
|
+
const i = P(
|
|
155
|
+
"DRAFT_CAPABILITY",
|
|
156
|
+
"Interactive diagrams require a configured enhancement bridge.",
|
|
157
|
+
r
|
|
158
|
+
);
|
|
159
|
+
throw Object.assign(i, { noPlaceholder: !0 }), i;
|
|
160
|
+
}
|
|
161
|
+
const l = te(n, t.state), b = l === void 0 ? void 0 : M(
|
|
162
|
+
JSON.stringify([
|
|
163
|
+
h,
|
|
164
|
+
m ?? "",
|
|
165
|
+
W,
|
|
166
|
+
Z,
|
|
167
|
+
l
|
|
168
|
+
])
|
|
169
|
+
);
|
|
170
|
+
let w = b ? v.get(b) : void 0;
|
|
171
|
+
const L = (i) => i.map((a) => ({
|
|
172
|
+
...a,
|
|
173
|
+
location: a.location ? {
|
|
174
|
+
...a.location,
|
|
175
|
+
line: a.location.line + (y ? 0 : r?.line ?? 1),
|
|
176
|
+
...y ? {} : { column: void 0 }
|
|
177
|
+
} : r,
|
|
178
|
+
...y && r ? { related: [...a.related ?? [], { message: "Included here.", location: r }] } : {}
|
|
179
|
+
}));
|
|
180
|
+
if (!w) {
|
|
181
|
+
let i;
|
|
182
|
+
try {
|
|
183
|
+
i = await H(h, {
|
|
184
|
+
...n.render ?? {},
|
|
185
|
+
target: "image",
|
|
186
|
+
fonts: "embed",
|
|
187
|
+
includeSource: n.render?.includeSource ?? !1,
|
|
188
|
+
links: n.render?.links ?? { mode: "none" },
|
|
189
|
+
sourceId: m,
|
|
190
|
+
state: t.state ?? null
|
|
191
|
+
});
|
|
192
|
+
} catch (a) {
|
|
193
|
+
throw Object.assign(new Error("Draftably renderer failed."), {
|
|
194
|
+
noPlaceholder: !0,
|
|
195
|
+
cause: a
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (!i.ok) {
|
|
199
|
+
const a = L(i.diagnostics);
|
|
200
|
+
p.push(...a);
|
|
201
|
+
const u = Object.assign(new Error("Draftably could not render this diagram."), {
|
|
202
|
+
diagnostics: a
|
|
203
|
+
});
|
|
204
|
+
if ((n.errors ?? "fail") === "fail" || !["input", "limit"].includes(i.reason))
|
|
205
|
+
throw Object.assign(u, { noPlaceholder: !0 }), u;
|
|
206
|
+
return {
|
|
207
|
+
node: { type: "paragraph", children: [{ type: "text", value: "Diagram unavailable." }] },
|
|
208
|
+
diagnostics: p
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
w = {
|
|
212
|
+
svg: i.value.svg,
|
|
213
|
+
width: i.value.width,
|
|
214
|
+
height: i.value.height,
|
|
215
|
+
title: i.value.title,
|
|
216
|
+
diagnostics: i.diagnostics
|
|
217
|
+
}, b && (v.set(b, w), v.size > ee && v.delete(v.keys().next().value));
|
|
218
|
+
}
|
|
219
|
+
p.push(...L(w.diagnostics)), F(n.render?.signal);
|
|
220
|
+
const T = new TextEncoder().encode(w.svg), O = `draft-${M(T)}.svg`;
|
|
221
|
+
let d;
|
|
222
|
+
try {
|
|
223
|
+
d = await n.emitAsset(
|
|
224
|
+
{ fileName: O, mediaType: "image/svg+xml", bytes: T },
|
|
225
|
+
{ document: s, fenceIndex: g }
|
|
226
|
+
);
|
|
227
|
+
} catch (i) {
|
|
228
|
+
throw c(i, "SVG asset emitter failed.");
|
|
229
|
+
}
|
|
230
|
+
if (!d || typeof d.url != "string")
|
|
231
|
+
throw c(new TypeError("Asset emitter must return a URL string."));
|
|
232
|
+
let R;
|
|
233
|
+
try {
|
|
234
|
+
R = new URL(d.url, "https://draftably.invalid");
|
|
235
|
+
} catch {
|
|
236
|
+
throw c(new Error("Asset emitter returned an invalid URL."));
|
|
237
|
+
}
|
|
238
|
+
if (!d.url || d.url.charCodeAt(0) <= 32 || x(d.url) || !["https:", "http:"].includes(R.protocol))
|
|
239
|
+
throw c(new Error("Asset emitter returned an unsafe URL."));
|
|
240
|
+
const U = t.alt?.trim() || w.title, _ = {
|
|
241
|
+
type: "image",
|
|
242
|
+
url: d.url,
|
|
243
|
+
alt: U,
|
|
244
|
+
data: { hProperties: { width: Math.ceil(w.width), height: Math.ceil(w.height) } }
|
|
245
|
+
}, B = { fileName: O, url: d.url }, S = [];
|
|
246
|
+
let j;
|
|
247
|
+
const E = [_];
|
|
248
|
+
if (t.mode === "interactive") {
|
|
249
|
+
const i = n.interactive;
|
|
250
|
+
if (!/^[A-Za-z0-9._-]{1,64}$/.test(i.policyId))
|
|
251
|
+
throw c(new TypeError("Interactive policyId must be a short ASCII token."));
|
|
252
|
+
const a = `draft-${M(JSON.stringify([h, t.state ?? null, t.id ?? null, g])).slice(0, 24)}`, u = {
|
|
253
|
+
version: 1,
|
|
254
|
+
key: a,
|
|
255
|
+
source: h,
|
|
256
|
+
state: t.state ?? null,
|
|
257
|
+
label: U,
|
|
258
|
+
policyId: i.policyId
|
|
259
|
+
}, I = new TextEncoder().encode(JSON.stringify(u));
|
|
260
|
+
if (I.byteLength > 1e6)
|
|
261
|
+
throw P("DRAFT_LIMIT", "Interactive initialization data exceeds 1 MB.", r);
|
|
262
|
+
let o;
|
|
263
|
+
try {
|
|
264
|
+
o = await i.emitInit(u);
|
|
265
|
+
} catch ($) {
|
|
266
|
+
throw c($, "Interactive initialization emitter failed.");
|
|
267
|
+
}
|
|
268
|
+
if (!o || typeof o.fileName != "string" || typeof o.url != "string")
|
|
269
|
+
throw c(new TypeError("Initialization emitter must return a filename and URL."));
|
|
270
|
+
let A, C;
|
|
271
|
+
try {
|
|
272
|
+
A = new URL(o.url, "https://draftably.invalid"), C = new URL(i.clientModuleUrl, "https://draftably.invalid");
|
|
273
|
+
} catch {
|
|
274
|
+
throw c(new Error("Interactive bridge returned an invalid URL."));
|
|
275
|
+
}
|
|
276
|
+
if (o.fileName !== `draft-${M(I)}.json` || !o.url || o.url.charCodeAt(0) <= 32 || x(o.url) || !["https:", "http:"].includes(A.protocol))
|
|
277
|
+
throw c(new Error("Initialization emitter returned an invalid asset."));
|
|
278
|
+
if (!i.clientModuleUrl || i.clientModuleUrl.charCodeAt(0) <= 32 || x(i.clientModuleUrl) || !["https:", "http:"].includes(C.protocol))
|
|
279
|
+
throw c(new Error("clientModuleUrl is unsafe."));
|
|
280
|
+
S.push({ fileName: o.fileName, url: o.url }), j = i.clientModuleUrl, E.push({
|
|
281
|
+
type: "div",
|
|
282
|
+
data: {
|
|
283
|
+
hName: "div",
|
|
284
|
+
hProperties: {
|
|
285
|
+
className: ["draftably-viewer-region"],
|
|
286
|
+
"data-draftably-region": "",
|
|
287
|
+
"data-draftably-init": o.url,
|
|
288
|
+
"data-draftably-policy": i.policyId,
|
|
289
|
+
"data-draftably-key": a,
|
|
290
|
+
role: "region",
|
|
291
|
+
"aria-label": U
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
children: []
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return t.caption && E.push({
|
|
298
|
+
type: "figcaption",
|
|
299
|
+
data: { hName: "figcaption" },
|
|
300
|
+
children: [{ type: "text", value: t.caption }]
|
|
301
|
+
}), { node: t.mode === "interactive" || t.caption ? {
|
|
302
|
+
type: "figure",
|
|
303
|
+
data: { hName: "figure", ...t.id ? { hProperties: { id: t.id } } : {} },
|
|
304
|
+
children: E
|
|
305
|
+
} : {
|
|
306
|
+
type: "paragraph",
|
|
307
|
+
...t.id ? { data: { hProperties: { id: t.id } } } : {},
|
|
308
|
+
children: E
|
|
309
|
+
}, asset: B, extraAssets: S, clientModule: j, diagnostics: p, dependency: f };
|
|
310
|
+
}
|
|
311
|
+
export {
|
|
312
|
+
se as a,
|
|
313
|
+
c as h,
|
|
314
|
+
X as i,
|
|
315
|
+
z as m,
|
|
316
|
+
ce as p
|
|
317
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
function w(a) {
|
|
2
|
+
const n = a.charCodeAt(0);
|
|
3
|
+
return n < 32 || n === 127;
|
|
4
|
+
}
|
|
5
|
+
function f(a) {
|
|
6
|
+
const n = /^draft(?:\s+\{([\s\S]*)\})?$/.exec(a.trim());
|
|
7
|
+
if (!n)
|
|
8
|
+
throw new Error('Expected the canonical draft fence, optionally followed by {key="value"}.');
|
|
9
|
+
const r = n[1];
|
|
10
|
+
if (r === void 0) return {};
|
|
11
|
+
const t = {};
|
|
12
|
+
let e = 0;
|
|
13
|
+
const i = () => {
|
|
14
|
+
for (; /\s/.test(r[e] ?? "") && e < r.length; ) e++;
|
|
15
|
+
};
|
|
16
|
+
for (; e < r.length && (i(), e !== r.length); ) {
|
|
17
|
+
const o = /^[A-Za-z][A-Za-z0-9_-]*/.exec(r.slice(e))?.[0];
|
|
18
|
+
if (!o) throw new Error(`Invalid metadata near "${r.slice(e)}".`);
|
|
19
|
+
if (e += o.length, i(), r[e++] !== "=") throw new Error(`Expected '=' after ${o}.`);
|
|
20
|
+
if (i(), r[e++] !== '"') throw new Error(`${o} must use a quoted value.`);
|
|
21
|
+
let c = "", d = !1;
|
|
22
|
+
for (; e < r.length; ) {
|
|
23
|
+
const s = r[e++];
|
|
24
|
+
if (s === '"') {
|
|
25
|
+
d = !0;
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
if (s === "\\") {
|
|
29
|
+
const l = r[e++];
|
|
30
|
+
if (l !== '"' && l !== "\\") throw new Error(`Invalid escape in ${o}.`);
|
|
31
|
+
c += l;
|
|
32
|
+
} else {
|
|
33
|
+
if (w(s)) throw new Error(`${o} cannot contain control characters.`);
|
|
34
|
+
c += s;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!d) throw new Error(`Unclosed value for ${o}.`);
|
|
38
|
+
if (!["id", "alt", "caption", "src", "mode", "state"].includes(o))
|
|
39
|
+
throw new Error(`Unknown draft option "${o}".`);
|
|
40
|
+
if (Object.hasOwn(t, o)) throw new Error(`Duplicate draft option "${o}".`);
|
|
41
|
+
t[o] = c, i();
|
|
42
|
+
}
|
|
43
|
+
if (t.id !== void 0 && !/^[A-Za-z0-9_-]+$/.test(t.id))
|
|
44
|
+
throw new Error("id must be a non-empty ASCII token.");
|
|
45
|
+
if (t.mode !== void 0 && t.mode !== "static" && t.mode !== "interactive")
|
|
46
|
+
throw new Error('mode must be "static" or "interactive".');
|
|
47
|
+
if (t.src !== void 0 && (!t.src.endsWith(".draft") || t.src.trim() !== t.src || t.src.includes("\0") || t.src.startsWith("/") || t.src.startsWith("\\") || /^[A-Za-z][A-Za-z\d+.-]*:/.test(t.src) || /^[A-Za-z]:/.test(t.src)))
|
|
48
|
+
throw new Error("src must name a relative local .draft file.");
|
|
49
|
+
return t;
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
f as p
|
|
53
|
+
};
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { mountViewer as k } from "@draftably/runtime/viewer";
|
|
2
|
+
const m = 1e6, v = 1e6;
|
|
3
|
+
function T(e) {
|
|
4
|
+
for (const a of e) {
|
|
5
|
+
const n = a.charCodeAt(0);
|
|
6
|
+
if (n < 32 || n === 127) return !0;
|
|
7
|
+
}
|
|
8
|
+
return !1;
|
|
9
|
+
}
|
|
10
|
+
function p(e, a, n) {
|
|
11
|
+
if (!e || T(e)) throw new Error("Invalid initialization URL.");
|
|
12
|
+
const i = new URL(e, n);
|
|
13
|
+
if (!["http:", "https:"].includes(i.protocol) || i.origin !== a || i.username || i.password)
|
|
14
|
+
throw new Error("Initialization URL must be same-origin.");
|
|
15
|
+
return i;
|
|
16
|
+
}
|
|
17
|
+
function A(e, a, n) {
|
|
18
|
+
if (new TextEncoder().encode(e).byteLength > m) throw new Error("Initialization data exceeds the size limit.");
|
|
19
|
+
let o;
|
|
20
|
+
try {
|
|
21
|
+
o = JSON.parse(e);
|
|
22
|
+
} catch {
|
|
23
|
+
throw new Error("Initialization data is not valid JSON.");
|
|
24
|
+
}
|
|
25
|
+
if (!o || typeof o != "object" || Array.isArray(o))
|
|
26
|
+
throw new Error("Initialization data has an invalid shape.");
|
|
27
|
+
const t = o;
|
|
28
|
+
if (Object.keys(t).sort().join(",") !== "key,label,policyId,source,state,version")
|
|
29
|
+
throw new Error("Initialization data has unknown or missing fields.");
|
|
30
|
+
if (t.version !== 1 || t.key !== a || !/^draft-[0-9a-f]{24}$/.test(a))
|
|
31
|
+
throw new Error("Initialization identity is invalid.");
|
|
32
|
+
if (t.policyId !== n || typeof t.policyId != "string")
|
|
33
|
+
throw new Error("Initialization policy does not match.");
|
|
34
|
+
if (typeof t.source != "string" || new TextEncoder().encode(t.source).byteLength > v)
|
|
35
|
+
throw new Error("Diagram source is invalid or too large.");
|
|
36
|
+
if (typeof t.label != "string" || t.label.length > 2e3)
|
|
37
|
+
throw new Error("Diagram label is invalid.");
|
|
38
|
+
if (t.state !== null && typeof t.state != "string")
|
|
39
|
+
throw new Error("Diagram state is invalid.");
|
|
40
|
+
return t;
|
|
41
|
+
}
|
|
42
|
+
const u = /* @__PURE__ */ new WeakSet();
|
|
43
|
+
async function S(e) {
|
|
44
|
+
if (!e.ok) throw new Error(`Initialization request failed with HTTP ${e.status}.`);
|
|
45
|
+
const a = Number(e.headers.get("content-length"));
|
|
46
|
+
if (Number.isFinite(a) && a > m)
|
|
47
|
+
throw new Error("Initialization response exceeds the size limit.");
|
|
48
|
+
if (!e.body) throw new Error("Initialization response has no body.");
|
|
49
|
+
const n = e.body.getReader(), i = [];
|
|
50
|
+
let o = 0;
|
|
51
|
+
try {
|
|
52
|
+
for (; ; ) {
|
|
53
|
+
const { done: s, value: f } = await n.read();
|
|
54
|
+
if (s) break;
|
|
55
|
+
if (o += f.byteLength, o > m)
|
|
56
|
+
throw await n.cancel(), new Error("Initialization response exceeds the size limit.");
|
|
57
|
+
i.push(f);
|
|
58
|
+
}
|
|
59
|
+
} finally {
|
|
60
|
+
n.releaseLock();
|
|
61
|
+
}
|
|
62
|
+
const t = new Uint8Array(o);
|
|
63
|
+
let l = 0;
|
|
64
|
+
for (const s of i)
|
|
65
|
+
t.set(s, l), l += s.byteLength;
|
|
66
|
+
return new TextDecoder("utf-8", { fatal: !0 }).decode(t);
|
|
67
|
+
}
|
|
68
|
+
function D(e) {
|
|
69
|
+
return { code: "DRAFT_ENHANCE", severity: "warning", message: e, itemIds: [] };
|
|
70
|
+
}
|
|
71
|
+
function U(e, a) {
|
|
72
|
+
if (u.has(e))
|
|
73
|
+
throw new Error("This Markdown root already has an active Draftably enhancement.");
|
|
74
|
+
u.add(e);
|
|
75
|
+
const n = e.ownerDocument, i = n.defaultView;
|
|
76
|
+
if (!i)
|
|
77
|
+
throw u.delete(e), new Error("The Markdown root has no browser window.");
|
|
78
|
+
let o = !1;
|
|
79
|
+
const t = /* @__PURE__ */ new Set(), l = /* @__PURE__ */ new Set(), s = [], f = (r) => {
|
|
80
|
+
try {
|
|
81
|
+
a.onDiagnostic?.(D(r));
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
}, I = Array.from(
|
|
85
|
+
e.querySelectorAll("[data-draftably-region][data-draftably-init]")
|
|
86
|
+
);
|
|
87
|
+
for (const r of I) {
|
|
88
|
+
const w = r.closest("figure")?.querySelector("img");
|
|
89
|
+
if (w) {
|
|
90
|
+
const h = w.hidden;
|
|
91
|
+
s.push(() => {
|
|
92
|
+
w.hidden = h;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const c = new i.AbortController();
|
|
96
|
+
t.add(c), (async () => {
|
|
97
|
+
let h;
|
|
98
|
+
try {
|
|
99
|
+
const y = p(
|
|
100
|
+
r.dataset.draftablyInit ?? "",
|
|
101
|
+
i.location.origin,
|
|
102
|
+
n.baseURI
|
|
103
|
+
), g = await i.fetch(y, {
|
|
104
|
+
signal: c.signal,
|
|
105
|
+
credentials: "same-origin",
|
|
106
|
+
redirect: "error"
|
|
107
|
+
});
|
|
108
|
+
g.url && p(g.url, i.location.origin, n.baseURI);
|
|
109
|
+
const z = await S(g);
|
|
110
|
+
if (o || c.signal.aborted) return;
|
|
111
|
+
const d = A(
|
|
112
|
+
z,
|
|
113
|
+
r.dataset.draftablyKey ?? "",
|
|
114
|
+
r.dataset.draftablyPolicy ?? ""
|
|
115
|
+
), b = a.policies.get(d.policyId);
|
|
116
|
+
if (!b) throw new Error("No viewer policy is configured for this diagram.");
|
|
117
|
+
if (r.dataset.draftablyKey !== d.key || r.dataset.draftablyPolicy !== d.policyId)
|
|
118
|
+
throw new Error("Figure markers do not match initialization data.");
|
|
119
|
+
if (r.childNodes.length) throw new Error("Viewer region is not empty.");
|
|
120
|
+
h = k(r, { ...b, label: b.label ?? d.label }), l.add(h);
|
|
121
|
+
const E = await h.update(d.source, {
|
|
122
|
+
state: d.state,
|
|
123
|
+
camera: "fit",
|
|
124
|
+
signal: c.signal
|
|
125
|
+
});
|
|
126
|
+
if (o || c.signal.aborted) return;
|
|
127
|
+
E.status === "committed" && w ? w.hidden = !0 : E.status === "error" && f("The interactive diagram could not load. The static image is still available.");
|
|
128
|
+
} catch (y) {
|
|
129
|
+
!o && !c.signal.aborted && f(
|
|
130
|
+
y instanceof Error ? y.message : "The interactive diagram could not load."
|
|
131
|
+
);
|
|
132
|
+
} finally {
|
|
133
|
+
t.delete(c);
|
|
134
|
+
}
|
|
135
|
+
})();
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
destroy() {
|
|
139
|
+
if (!o) {
|
|
140
|
+
o = !0;
|
|
141
|
+
for (const r of t) r.abort();
|
|
142
|
+
t.clear();
|
|
143
|
+
for (const r of l) r.destroy();
|
|
144
|
+
l.clear();
|
|
145
|
+
for (const r of s) r();
|
|
146
|
+
s.length = 0, u.delete(e);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
export {
|
|
152
|
+
U as enhanceDrafts
|
|
153
|
+
};
|
package/dist/files.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { randomUUID as v, createHash as $ } from "node:crypto";
|
|
2
|
+
import { mkdir as m, readdir as x, readFile as p, writeFile as b, link as N, rm as U } from "node:fs/promises";
|
|
3
|
+
import { resolve as h, join as l } from "node:path";
|
|
4
|
+
function A(t) {
|
|
5
|
+
return $("sha256").update(t).digest("hex");
|
|
6
|
+
}
|
|
7
|
+
function g(t, r) {
|
|
8
|
+
return `draft-${A(t)}.${r}`;
|
|
9
|
+
}
|
|
10
|
+
function j(t) {
|
|
11
|
+
for (const r of t) {
|
|
12
|
+
const o = r.charCodeAt(0);
|
|
13
|
+
if (o < 32 || o === 127) return !0;
|
|
14
|
+
}
|
|
15
|
+
return !1;
|
|
16
|
+
}
|
|
17
|
+
function S(t) {
|
|
18
|
+
if (typeof t != "string" || !t || /[\s\\?#]/.test(t) || j(t))
|
|
19
|
+
throw new TypeError(
|
|
20
|
+
"publicPath must be a URL path without query, hash, backslash, or controls."
|
|
21
|
+
);
|
|
22
|
+
let r;
|
|
23
|
+
if (t.startsWith("/")) {
|
|
24
|
+
if (t.startsWith("//"))
|
|
25
|
+
throw new TypeError("publicPath must not be protocol-relative.");
|
|
26
|
+
r = t;
|
|
27
|
+
} else {
|
|
28
|
+
let o;
|
|
29
|
+
try {
|
|
30
|
+
o = new URL(t);
|
|
31
|
+
} catch {
|
|
32
|
+
throw new TypeError("publicPath must be root-relative or an absolute HTTP(S) URL.");
|
|
33
|
+
}
|
|
34
|
+
if (!["http:", "https:"].includes(o.protocol) || o.username || o.password)
|
|
35
|
+
throw new TypeError("publicPath must be root-relative or an absolute HTTP(S) URL.");
|
|
36
|
+
r = `${o.origin}${o.pathname}`;
|
|
37
|
+
}
|
|
38
|
+
return r.replace(/\/+$/, "");
|
|
39
|
+
}
|
|
40
|
+
async function d(t, r, o) {
|
|
41
|
+
await m(t, { recursive: !0 });
|
|
42
|
+
const s = l(t, r), c = l(t, `.${r}.${v()}.tmp`);
|
|
43
|
+
try {
|
|
44
|
+
await b(c, o, { flag: "wx" });
|
|
45
|
+
try {
|
|
46
|
+
await N(c, s);
|
|
47
|
+
} catch (f) {
|
|
48
|
+
if (f.code !== "EEXIST") throw f;
|
|
49
|
+
if (!(await p(s)).equals(o)) throw new Error(`Asset collision: ${r}`, { cause: f });
|
|
50
|
+
}
|
|
51
|
+
} finally {
|
|
52
|
+
await U(c, { force: !0 });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function y(t, r) {
|
|
56
|
+
const o = t.endsWith(".svg") ? "svg" : t.endsWith(".json") ? "json" : void 0;
|
|
57
|
+
return !!o && t === g(r, o);
|
|
58
|
+
}
|
|
59
|
+
function R(t) {
|
|
60
|
+
if (!t || typeof t.directory != "string" || !t.directory)
|
|
61
|
+
throw new TypeError("directory must be a non-empty path.");
|
|
62
|
+
const r = h(t.directory), o = S(t.publicPath), s = /* @__PURE__ */ new Map();
|
|
63
|
+
async function c(e, i) {
|
|
64
|
+
if (!y(e, i))
|
|
65
|
+
throw new TypeError(`Invalid content-addressed asset name: ${e}`);
|
|
66
|
+
let n = s.get(e);
|
|
67
|
+
n || (n = d(r, e, i), s.set(e, n));
|
|
68
|
+
try {
|
|
69
|
+
await n;
|
|
70
|
+
} finally {
|
|
71
|
+
s.get(e) === n && s.delete(e);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const f = (e) => `${o}/${e}`, u = async (e) => {
|
|
75
|
+
if (e.mediaType !== "image/svg+xml" || !e.fileName.endsWith(".svg"))
|
|
76
|
+
throw new TypeError("Expected a content-addressed Draftably SVG.");
|
|
77
|
+
return await c(e.fileName, e.bytes), { url: f(e.fileName) };
|
|
78
|
+
}, T = async (e) => {
|
|
79
|
+
const i = new TextEncoder().encode(JSON.stringify(e)), n = g(i, "json");
|
|
80
|
+
return await c(n, i), { fileName: n, url: f(n) };
|
|
81
|
+
};
|
|
82
|
+
async function E(e) {
|
|
83
|
+
const i = h(e);
|
|
84
|
+
await m(i, { recursive: !0 });
|
|
85
|
+
let n;
|
|
86
|
+
try {
|
|
87
|
+
n = await x(r);
|
|
88
|
+
} catch (a) {
|
|
89
|
+
if (a.code === "ENOENT") return;
|
|
90
|
+
throw a;
|
|
91
|
+
}
|
|
92
|
+
for (const a of n) {
|
|
93
|
+
if (!/^draft-[0-9a-f]{64}\.(?:svg|json)$/.test(a)) continue;
|
|
94
|
+
const w = await p(l(r, a));
|
|
95
|
+
if (!y(a, w)) throw new Error(`Asset content hash mismatch: ${a}`);
|
|
96
|
+
await d(i, a, w);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { directory: r, emitAsset: u, emitInit: T, copyTo: E };
|
|
100
|
+
}
|
|
101
|
+
export {
|
|
102
|
+
R as createFileAssets
|
|
103
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { m as T, p as S, i as P, a as I, h as F } from "./chunks/core-DPjuIyPG.js";
|
|
2
|
+
import { p as j } from "./chunks/metadata-Dx6T_2Zy.js";
|
|
3
|
+
function x(t) {
|
|
4
|
+
return /^draft(?:\s|$)/.test(t.trim());
|
|
5
|
+
}
|
|
6
|
+
function E(t) {
|
|
7
|
+
return t.replace(
|
|
8
|
+
/[&<>"']/g,
|
|
9
|
+
(r) => ({
|
|
10
|
+
"&": "&",
|
|
11
|
+
"<": "<",
|
|
12
|
+
">": ">",
|
|
13
|
+
'"': """,
|
|
14
|
+
"'": "'"
|
|
15
|
+
})[r]
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
function H(t) {
|
|
19
|
+
const r = `${t.width === void 0 ? "" : ` width="${t.width}"`}${t.height === void 0 ? "" : ` height="${t.height}"`}`;
|
|
20
|
+
return `<img src="${E(t.url)}" alt="${E(t.alt)}"${r}>`;
|
|
21
|
+
}
|
|
22
|
+
function A(t, r) {
|
|
23
|
+
const d = t.type;
|
|
24
|
+
if (d === "paragraph") {
|
|
25
|
+
const e = t.data?.hProperties ?? {}, l = typeof e.id == "string" ? ` id="${r(e.id)}"` : "", a = t.children.map((o) => A(o, r)).join("");
|
|
26
|
+
return `<p${l}>${a}</p>
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
if (d === "figure") {
|
|
30
|
+
const l = t.data?.hProperties?.id, h = typeof l == "string" ? ` id="${r(l)}"` : "", a = t.children;
|
|
31
|
+
return `<figure${h}>${a.map((o) => A(o, r)).join("")}</figure>
|
|
32
|
+
`;
|
|
33
|
+
}
|
|
34
|
+
if (d === "image") {
|
|
35
|
+
const e = t.data?.hProperties ?? {};
|
|
36
|
+
return H({
|
|
37
|
+
url: String(t.url ?? ""),
|
|
38
|
+
alt: String(t.alt ?? ""),
|
|
39
|
+
...typeof e.width == "number" ? { width: e.width } : {},
|
|
40
|
+
...typeof e.height == "number" ? { height: e.height } : {}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (d === "figcaption")
|
|
44
|
+
return `<figcaption>${t.children.map((l) => A(l, r)).join("")}</figcaption>`;
|
|
45
|
+
if (d === "div") {
|
|
46
|
+
const e = t.data?.hProperties ?? {};
|
|
47
|
+
if (!(e["data-draftably-region"] === "") || typeof e["data-draftably-init"] != "string" || typeof e["data-draftably-policy"] != "string" || typeof e["data-draftably-key"] != "string")
|
|
48
|
+
throw new Error("Invalid generated viewer region.");
|
|
49
|
+
const h = typeof e["aria-label"] == "string" ? e["aria-label"] : "Diagram viewer";
|
|
50
|
+
return `<div class="draftably-viewer-region" data-draftably-region="" data-draftably-init="${r(String(e["data-draftably-init"]))}" data-draftably-policy="${r(String(e["data-draftably-policy"]))}" data-draftably-key="${r(String(e["data-draftably-key"]))}" role="region" aria-label="${r(h)}"></div>`;
|
|
51
|
+
}
|
|
52
|
+
if (d === "text") return r(String(t.value ?? ""));
|
|
53
|
+
throw new Error(`Unsupported generated AST node: ${String(d)}.`);
|
|
54
|
+
}
|
|
55
|
+
function _(t) {
|
|
56
|
+
if (!t || typeof t.emitAsset != "function")
|
|
57
|
+
throw new TypeError("createMarkdownItDraft requires an emitAsset function.");
|
|
58
|
+
const r = /* @__PURE__ */ new WeakMap(), d = (a) => a.type === "fence" && x(a.info ?? "");
|
|
59
|
+
function e(a) {
|
|
60
|
+
const o = a, p = o.renderer.rules.fence;
|
|
61
|
+
o.renderer.rules.fence = function(c, u, g, y, b) {
|
|
62
|
+
const $ = c[u];
|
|
63
|
+
if (!d($))
|
|
64
|
+
return p ? p(c, u, g, y, b) : b.renderToken(c, u, g);
|
|
65
|
+
const D = r.get($);
|
|
66
|
+
if (!D)
|
|
67
|
+
throw new Error(
|
|
68
|
+
"Draft fence rendered before prepare(). Prepare and render the same token tree."
|
|
69
|
+
);
|
|
70
|
+
return A(D.node, (k) => o.utils.escapeHtml(k));
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
async function l(a, o, p = {}) {
|
|
74
|
+
const c = a.parse(o, p), u = await h(c, p);
|
|
75
|
+
return { html: a.renderer.render(c, a.options, p), manifest: u };
|
|
76
|
+
}
|
|
77
|
+
async function h(a, o = {}) {
|
|
78
|
+
const p = { path: o.draftably?.documentPath }, c = [], u = /* @__PURE__ */ new Set(), g = /* @__PURE__ */ new Set(), y = [], b = /* @__PURE__ */ new Set(), $ = t.sources && {
|
|
79
|
+
...t.sources,
|
|
80
|
+
onDependency: (w) => {
|
|
81
|
+
u.add(w.dependencyPath);
|
|
82
|
+
try {
|
|
83
|
+
t.sources?.onDependency?.(w);
|
|
84
|
+
} catch (s) {
|
|
85
|
+
throw F(s, "Markdown dependency callback failed.");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
let D = 0;
|
|
90
|
+
const k = async (w) => {
|
|
91
|
+
for (const s of w) {
|
|
92
|
+
if (d(s)) {
|
|
93
|
+
const M = D++, v = { sourceId: p.path, line: (s.map?.[0] ?? 0) + 1 };
|
|
94
|
+
r.delete(s);
|
|
95
|
+
try {
|
|
96
|
+
let n;
|
|
97
|
+
try {
|
|
98
|
+
n = j(s.info ?? "");
|
|
99
|
+
} catch (f) {
|
|
100
|
+
const m = f instanceof Error ? f.message : "Invalid draft fence metadata.";
|
|
101
|
+
throw Object.assign(new Error(m), {
|
|
102
|
+
diagnostic: T("DRAFT_METADATA", m, v)
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (n.id && b.has(n.id)) {
|
|
106
|
+
const f = `Duplicate draft id "${n.id}" in this document.`;
|
|
107
|
+
throw Object.assign(new Error(f), {
|
|
108
|
+
diagnostic: T("DRAFT_METADATA", f, v)
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
n.id && b.add(n.id);
|
|
112
|
+
const i = await S(s.content ?? "", n, v, p, M, {
|
|
113
|
+
...t,
|
|
114
|
+
sources: $
|
|
115
|
+
});
|
|
116
|
+
r.set(s, { node: i.node, diagnostics: i.diagnostics }), i.asset && c.push(i.asset), c.push(...i.extraAssets ?? []), i.dependency && u.add(i.dependency), i.clientModule && g.add(i.clientModule), y.push(...i.diagnostics);
|
|
117
|
+
} catch (n) {
|
|
118
|
+
if (P(n)) throw n.cause ?? n;
|
|
119
|
+
if (I(n)) throw n;
|
|
120
|
+
const i = n.diagnostic, f = n.diagnostics, m = i ?? f?.[0] ?? T(
|
|
121
|
+
"DRAFT_BUILD",
|
|
122
|
+
n instanceof Error ? n.message : "Could not prepare diagram.",
|
|
123
|
+
v
|
|
124
|
+
);
|
|
125
|
+
if (y.push(...f ?? [m]), (t.errors ?? "fail") === "fail")
|
|
126
|
+
throw Object.assign(new Error("Draftably Markdown preparation failed."), {
|
|
127
|
+
diagnostics: f ?? [m]
|
|
128
|
+
});
|
|
129
|
+
r.set(s, {
|
|
130
|
+
node: {
|
|
131
|
+
type: "paragraph",
|
|
132
|
+
children: [{ type: "text", value: "Diagram unavailable." }]
|
|
133
|
+
},
|
|
134
|
+
diagnostics: f ?? [m]
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
s.children?.length && await k(s.children);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
await k(a);
|
|
142
|
+
for (const w of y) t.onDiagnostic?.(w);
|
|
143
|
+
return {
|
|
144
|
+
version: 1,
|
|
145
|
+
assets: c,
|
|
146
|
+
clientModules: [...g],
|
|
147
|
+
dependencies: [...u],
|
|
148
|
+
diagnostics: y
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return { plugin: e, prepare: h, render: l };
|
|
152
|
+
}
|
|
153
|
+
export {
|
|
154
|
+
_ as createMarkdownItDraft
|
|
155
|
+
};
|
package/dist/remark.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { m as h, p as b, i as k, a as v, h as F } from "./chunks/core-DPjuIyPG.js";
|
|
2
|
+
import { p as x } from "./chunks/metadata-Dx6T_2Zy.js";
|
|
3
|
+
function j(s) {
|
|
4
|
+
if (!s || typeof s.emitAsset != "function")
|
|
5
|
+
throw new TypeError("remarkDraft requires an emitAsset function.");
|
|
6
|
+
return async (D, d) => {
|
|
7
|
+
const A = { path: d.path }, u = [], m = /* @__PURE__ */ new Set(), p = /* @__PURE__ */ new Set(), E = s.sources && {
|
|
8
|
+
...s.sources,
|
|
9
|
+
onDependency: (i) => {
|
|
10
|
+
p.add(i.dependencyPath);
|
|
11
|
+
try {
|
|
12
|
+
s.sources?.onDependency?.(i);
|
|
13
|
+
} catch (r) {
|
|
14
|
+
throw F(r, "Markdown dependency callback failed.");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}, l = [], g = /* @__PURE__ */ new Set();
|
|
18
|
+
let M = 0;
|
|
19
|
+
async function w(i) {
|
|
20
|
+
const r = i.children;
|
|
21
|
+
if (r)
|
|
22
|
+
for (let o = 0; o < r.length; o++) {
|
|
23
|
+
const n = r[o];
|
|
24
|
+
if (n.type === "code" && n.lang === "draft") {
|
|
25
|
+
const T = M++, f = {
|
|
26
|
+
sourceId: d.path,
|
|
27
|
+
line: n.position?.start?.line ?? 1,
|
|
28
|
+
column: n.position?.start?.column
|
|
29
|
+
};
|
|
30
|
+
try {
|
|
31
|
+
let e;
|
|
32
|
+
try {
|
|
33
|
+
e = x(n.meta ? `draft ${n.meta}` : "draft");
|
|
34
|
+
} catch (a) {
|
|
35
|
+
const c = a instanceof Error ? a.message : "Invalid draft fence metadata.";
|
|
36
|
+
throw Object.assign(new Error(c), {
|
|
37
|
+
diagnostic: h("DRAFT_METADATA", c, f)
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (e.id && g.has(e.id)) {
|
|
41
|
+
const a = `Duplicate draft id "${e.id}" in this document.`;
|
|
42
|
+
throw Object.assign(new Error(a), {
|
|
43
|
+
diagnostic: h("DRAFT_METADATA", a, f)
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
e.id && g.add(e.id);
|
|
47
|
+
const t = await b(n.value ?? "", e, f, A, T, {
|
|
48
|
+
...s,
|
|
49
|
+
sources: E
|
|
50
|
+
});
|
|
51
|
+
r.splice(o, 1, t.node), t.asset && u.push(t.asset), u.push(...t.extraAssets ?? []), t.clientModule && m.add(t.clientModule), t.dependency && p.add(t.dependency), l.push(...t.diagnostics);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
if (k(e)) throw e.cause ?? e;
|
|
54
|
+
if (v(e)) throw e;
|
|
55
|
+
const t = e.diagnostic, a = e.diagnostics, c = t ?? a?.[0] ?? h(
|
|
56
|
+
"DRAFT_BUILD",
|
|
57
|
+
e instanceof Error ? e.message : "Could not prepare diagram.",
|
|
58
|
+
f
|
|
59
|
+
);
|
|
60
|
+
if (l.push(...a ?? [c]), (s.errors ?? "fail") === "fail") {
|
|
61
|
+
const y = new Error("Draftably Markdown transform failed.");
|
|
62
|
+
throw Object.assign(y, { diagnostics: a ?? [c] }), y;
|
|
63
|
+
}
|
|
64
|
+
r.splice(o, 1, {
|
|
65
|
+
type: "paragraph",
|
|
66
|
+
children: [{ type: "text", value: "Diagram unavailable." }]
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
} else
|
|
70
|
+
await w(n);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
await w(D);
|
|
74
|
+
for (const i of l) s.onDiagnostic?.(i);
|
|
75
|
+
d.data ??= {}, d.data.draftably = {
|
|
76
|
+
version: 1,
|
|
77
|
+
assets: u,
|
|
78
|
+
clientModules: [...m],
|
|
79
|
+
dependencies: [...p],
|
|
80
|
+
diagnostics: l
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
j as default
|
|
86
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DraftViewerInitV1 } from './core.js';
|
|
2
|
+
export declare const MAX_INIT_BYTES = 1000000;
|
|
3
|
+
export declare const MAX_SOURCE_BYTES = 1000000;
|
|
4
|
+
export declare function sameOriginInitUrl(value: string, origin: string, baseUrl: string): URL;
|
|
5
|
+
export declare function parseInitJson(text: string, expectedKey: string, expectedPolicy: string): DraftViewerInitV1;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { mountViewer } from '@draftably/runtime/viewer';
|
|
2
|
+
import type { Diagnostic } from '@draftably/runtime';
|
|
3
|
+
type ViewerOptions = Parameters<typeof mountViewer>[1];
|
|
4
|
+
export interface EnhanceDraftsOptions {
|
|
5
|
+
policies: ReadonlyMap<string, ViewerOptions>;
|
|
6
|
+
onDiagnostic?: (diagnostic: Diagnostic) => void;
|
|
7
|
+
}
|
|
8
|
+
/** Load generated init assets and progressively enhance static diagram figures under root. */
|
|
9
|
+
export declare function enhanceDrafts(root: HTMLElement, options: EnhanceDraftsOptions): {
|
|
10
|
+
destroy(): void;
|
|
11
|
+
};
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { Diagnostic } from '@draftably/runtime';
|
|
2
|
+
import type { FenceOptions } from './metadata.js';
|
|
3
|
+
export { resolveSource } from './source.js';
|
|
4
|
+
export { parseFenceOptions } from './metadata.js';
|
|
5
|
+
export type { Diagnostic } from '@draftably/runtime';
|
|
6
|
+
export interface DraftDocument {
|
|
7
|
+
path?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface DraftAsset {
|
|
10
|
+
fileName: string;
|
|
11
|
+
mediaType: 'image/svg+xml';
|
|
12
|
+
bytes: Uint8Array;
|
|
13
|
+
}
|
|
14
|
+
export interface AssetReference {
|
|
15
|
+
fileName: string;
|
|
16
|
+
url: string;
|
|
17
|
+
}
|
|
18
|
+
export interface DraftDocumentResult {
|
|
19
|
+
version: 1;
|
|
20
|
+
assets: readonly AssetReference[];
|
|
21
|
+
clientModules: readonly string[];
|
|
22
|
+
dependencies: readonly string[];
|
|
23
|
+
diagnostics: readonly Diagnostic[];
|
|
24
|
+
}
|
|
25
|
+
export interface SourceDependency {
|
|
26
|
+
documentPath: string;
|
|
27
|
+
dependencyPath: string;
|
|
28
|
+
missing: boolean;
|
|
29
|
+
watchPath?: string;
|
|
30
|
+
}
|
|
31
|
+
export type EmitAsset = (asset: DraftAsset, context: {
|
|
32
|
+
document: DraftDocument;
|
|
33
|
+
fenceIndex: number;
|
|
34
|
+
}) => Promise<{
|
|
35
|
+
url: string;
|
|
36
|
+
}>;
|
|
37
|
+
export interface DraftMarkdownOptions {
|
|
38
|
+
emitAsset: EmitAsset;
|
|
39
|
+
errors?: 'fail' | 'placeholder';
|
|
40
|
+
interactive?: MarkdownInteractiveBridge;
|
|
41
|
+
render?: {
|
|
42
|
+
includeSource?: boolean;
|
|
43
|
+
links?: {
|
|
44
|
+
mode: 'none';
|
|
45
|
+
} | {
|
|
46
|
+
mode: 'safe';
|
|
47
|
+
baseUrl?: string;
|
|
48
|
+
target?: '_self' | '_blank';
|
|
49
|
+
cacheKey?: string;
|
|
50
|
+
resolve?: (request: {
|
|
51
|
+
itemId: string;
|
|
52
|
+
href: string;
|
|
53
|
+
}) => string | null;
|
|
54
|
+
};
|
|
55
|
+
limits?: {
|
|
56
|
+
maxSourceChars?: number;
|
|
57
|
+
maxSourceBytes?: number;
|
|
58
|
+
maxOutputBytes?: number;
|
|
59
|
+
maxObjects?: number;
|
|
60
|
+
maxEdges?: number;
|
|
61
|
+
maxDepth?: number;
|
|
62
|
+
};
|
|
63
|
+
signal?: AbortSignal;
|
|
64
|
+
};
|
|
65
|
+
sources?: {
|
|
66
|
+
rootDir: string;
|
|
67
|
+
onDependency?: (dependency: SourceDependency) => void;
|
|
68
|
+
};
|
|
69
|
+
onDiagnostic?: (diagnostic: Diagnostic) => void;
|
|
70
|
+
}
|
|
71
|
+
export interface DraftViewerInitV1 {
|
|
72
|
+
version: 1;
|
|
73
|
+
key: string;
|
|
74
|
+
source: string;
|
|
75
|
+
state: string | null;
|
|
76
|
+
label: string;
|
|
77
|
+
policyId: string;
|
|
78
|
+
}
|
|
79
|
+
export interface MarkdownInteractiveBridge {
|
|
80
|
+
clientModuleUrl: string;
|
|
81
|
+
policyId: string;
|
|
82
|
+
emitInit(data: DraftViewerInitV1): Promise<{
|
|
83
|
+
fileName: string;
|
|
84
|
+
url: string;
|
|
85
|
+
}>;
|
|
86
|
+
}
|
|
87
|
+
export declare function makeDiagnostic(code: string, message: string, location?: Diagnostic['location'], related?: Diagnostic['related']): Diagnostic;
|
|
88
|
+
export declare function contentHash(value: string | Uint8Array): string;
|
|
89
|
+
export declare function hostFailure(error: unknown, message?: string): Error;
|
|
90
|
+
export declare function isHostFailure(error: unknown): boolean;
|
|
91
|
+
export declare function isNoPlaceholder(error: unknown): boolean;
|
|
92
|
+
export declare function prepare(source: string, metadata: FenceOptions, location: Diagnostic['location'], document: DraftDocument, fenceIndex: number, options: DraftMarkdownOptions): Promise<{
|
|
93
|
+
node: Record<string, unknown>;
|
|
94
|
+
asset?: AssetReference;
|
|
95
|
+
diagnostics: Diagnostic[];
|
|
96
|
+
dependency?: string;
|
|
97
|
+
extraAssets?: AssetReference[];
|
|
98
|
+
clientModule?: string;
|
|
99
|
+
}>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { EmitAsset, MarkdownInteractiveBridge } from './core.js';
|
|
2
|
+
export interface FileAssets {
|
|
3
|
+
directory: string;
|
|
4
|
+
emitAsset: EmitAsset;
|
|
5
|
+
emitInit: MarkdownInteractiveBridge['emitInit'];
|
|
6
|
+
copyTo(directory: string): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
/** Create a content-addressed Node file store for generated SVG and viewer JSON assets. */
|
|
9
|
+
export declare function createFileAssets(options: {
|
|
10
|
+
directory: string;
|
|
11
|
+
publicPath: string;
|
|
12
|
+
}): FileAssets;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { parseFenceOptions } from './core.js';
|
|
2
|
+
export type { FenceOptions } from './metadata.js';
|
|
3
|
+
export type { AssetReference, Diagnostic, DraftAsset, DraftDocument, DraftDocumentResult, DraftMarkdownOptions, DraftViewerInitV1, EmitAsset, MarkdownInteractiveBridge, SourceDependency, } from './core.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function isDraftFenceInfo(info: string): boolean;
|
|
2
|
+
export interface HtmlImage {
|
|
3
|
+
url: string;
|
|
4
|
+
alt: string;
|
|
5
|
+
width?: number;
|
|
6
|
+
height?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function escapeHtml(value: string): string;
|
|
9
|
+
export declare function imageHtml(image: HtmlImage): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type DraftDocumentResult, type DraftMarkdownOptions } from './core.js';
|
|
2
|
+
export interface DraftMarkdownItEnvironment {
|
|
3
|
+
draftably?: {
|
|
4
|
+
documentPath?: string;
|
|
5
|
+
};
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
interface Token {
|
|
9
|
+
type: string;
|
|
10
|
+
info?: string;
|
|
11
|
+
content?: string;
|
|
12
|
+
map?: number[] | null;
|
|
13
|
+
children?: Token[] | null;
|
|
14
|
+
}
|
|
15
|
+
interface MarkdownItRenderHost<Tokens> {
|
|
16
|
+
parse(source: string, env?: unknown): Tokens[];
|
|
17
|
+
options: unknown;
|
|
18
|
+
renderer: {
|
|
19
|
+
render(tokens: Tokens[], options: unknown, env?: unknown): string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export declare function createMarkdownItDraft(options: DraftMarkdownOptions): {
|
|
23
|
+
plugin: (instance: unknown) => void;
|
|
24
|
+
prepare: (tokens: Token[], env?: DraftMarkdownItEnvironment) => Promise<DraftDocumentResult>;
|
|
25
|
+
render: (md: MarkdownItRenderHost<unknown>, source: string, env?: DraftMarkdownItEnvironment) => Promise<{
|
|
26
|
+
html: string;
|
|
27
|
+
manifest: DraftDocumentResult;
|
|
28
|
+
}>;
|
|
29
|
+
};
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type DraftMarkdownOptions } from './core.js';
|
|
2
|
+
interface Node {
|
|
3
|
+
type: string;
|
|
4
|
+
lang?: string | null;
|
|
5
|
+
meta?: string | null;
|
|
6
|
+
value?: string;
|
|
7
|
+
children?: Node[];
|
|
8
|
+
position?: {
|
|
9
|
+
start?: {
|
|
10
|
+
line?: number;
|
|
11
|
+
column?: number;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
interface FileLike {
|
|
16
|
+
path?: string;
|
|
17
|
+
data?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
/** Convert only parser-recognized `draft` code nodes into static image nodes. */
|
|
20
|
+
export default function remarkDraft(options: DraftMarkdownOptions): (tree: Node, file: FileLike) => Promise<void>;
|
|
21
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@draftably/markdown",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Build static SVG diagram assets from Markdown draft fences.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/types/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./remark": {
|
|
18
|
+
"types": "./dist/types/remark.d.ts",
|
|
19
|
+
"import": "./dist/remark.js"
|
|
20
|
+
},
|
|
21
|
+
"./markdown-it": {
|
|
22
|
+
"types": "./dist/types/markdown-it.d.ts",
|
|
23
|
+
"import": "./dist/markdown-it.js"
|
|
24
|
+
},
|
|
25
|
+
"./files": {
|
|
26
|
+
"types": "./dist/types/files.d.ts",
|
|
27
|
+
"import": "./dist/files.js"
|
|
28
|
+
},
|
|
29
|
+
"./client": {
|
|
30
|
+
"types": "./dist/types/client.d.ts",
|
|
31
|
+
"import": "./dist/client.js"
|
|
32
|
+
},
|
|
33
|
+
"./package.json": "./package.json"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@draftably/runtime": "0.1.0"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22.21.0"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/byronwall/draftably.git",
|
|
44
|
+
"directory": "packages/markdown"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public",
|
|
48
|
+
"registry": "https://registry.npmjs.org"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "node ../../scripts/build-markdown.ts",
|
|
52
|
+
"test": "node ../../scripts/prove-markdown-package.ts"
|
|
53
|
+
}
|
|
6
54
|
}
|