@godxjp/markdown 31.26.2 → 31.27.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 CHANGED
@@ -53,6 +53,69 @@ Images: wrap the body in `<Prose imagePreview>` and every body image opens the k
53
53
  through the others — no `img` override needed. Images in table cells keep min(natural width, 20rem),
54
54
  so a narrow table scrolls instead of squashing them.
55
55
 
56
+ ## Grammar v1 and the codec (`@godxjp/markdown/codec`)
57
+
58
+ The renderer (`RENDERER_VERSION` 4), the codec and `@godxjp/block-editor` read one syntax, from one
59
+ set of parsers, so a body means the same thing in the editor, on a server and on the page.
60
+
61
+ ### Syntax
62
+
63
+ - **GFM** — tables, task lists, strikethrough, autolinks, footnotes.
64
+ - **CJK-friendly emphasis** — `の**「強調」**です` is strong. CommonMark's flanking rules leave
65
+ literal asterisks next to 、。「」(); the codec, the renderer and the editor all use the
66
+ CJK-friendly extension.
67
+ - **Callout** — a GitHub alert: `> [!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, `[!CAUTION]`
68
+ (kind case-insensitive on input, written uppercase), optional plain-text title on the same line
69
+ (`> [!WARNING] Title`); the rest of the quote is the body. A fold marker (`[!NOTE]-` / `+`) or an
70
+ unknown kind is NOT a callout: it stays an ordinary blockquote, marker verbatim.
71
+ - **Toggle** — `:::toggle[Summary *inline*]` … `:::`. Body is any blocks; toggles nest, and sit
72
+ inside callouts and columns. Open state is not stored.
73
+ - **Columns** — 2–4 columns, each with an optional `width` (integer percent 1–100; columns without
74
+ one share the remainder equally). Columns never nest.
75
+
76
+ ```md
77
+ ::::columns
78
+ :::column{width=40}
79
+ Left
80
+ :::
81
+
82
+ :::column
83
+ Right
84
+ :::
85
+ ::::
86
+ ```
87
+
88
+ Only container directives exist: the fence of a directive is one colon longer than the deepest
89
+ directive inside it, so the innermost fence is always `:::`. There are no text (`:name`) or leaf
90
+ (`::name`) directives — `10:30` is prose.
91
+
92
+ - **Wikilinks and embeds, verbatim** — `[[target]]`, `[[target#heading]]`, `[[target|label]]`,
93
+ `![[embed]]` (a block when alone on its line; the inner text is kept verbatim — `![[B#見出し]]`, `![[img.png|300]]`). Marks around them survive (`**[[x]]**`). Inside a table cell write `[[x\|label]]`.
94
+ - **`asset:` images** — `![alt](asset:id)` round-trips as written (the renderer resolves it through
95
+ `resolveUrl`).
96
+ - **Raw blocks** — anything the document does not model (raw HTML, footnote definitions, link
97
+ reference definitions, an unknown or malformed directive, a fence with a meta string) is kept as
98
+ Markdown that parses back to the same thing. Mermaid is an ordinary ` ```mermaid ` fence.
99
+
100
+ ### Codec
101
+
102
+ ```ts
103
+ import { CODEC_ID, normalize, parse, serialize } from "@godxjp/markdown/codec";
104
+
105
+ CODEC_ID; // "kit-md@1" — bumped whenever serialize would write a body differently
106
+ const doc = parse(markdown); // plain JSON, ProseMirror/Tiptap shape
107
+ const md = serialize(doc); // canonical Markdown
108
+ normalize(markdown) === serialize(parse(markdown)); // what the editor saves
109
+ normalize(normalize(x)) === normalize(x); // idempotent, for every fixture
110
+ ```
111
+
112
+ Pure: no React, no DOM, no editor — it runs on a server and in a Cloudflare Worker.
113
+
114
+ **Canonical form** (what `serialize` writes): ATX headings; `-` bullets; `1.` ordered lists;
115
+ `*em*`; `**strong**`; ` ``` ` fences (an indented code block becomes fenced); `---` rules;
116
+ `- [ ]` / `- [x]` todos; GFM tables with padded cells and an alignment row; a callout's body after a
117
+ `>` blank line; one blank line between blocks; LF; a trailing newline; an empty document is `""`.
118
+
56
119
  ## Stored versions
57
120
 
58
121
  Record `MARKDOWN_FORMAT` (`"md"`) and `RENDERER_VERSION` on every saved version.
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The codec's document: plain JSON in the ProseMirror/Tiptap shape (`type` / `attrs` / `content` /
3
+ * `text` / `marks`), so `@godxjp/block-editor` loads it with `setContent` and hands back what
4
+ * `getJSON()` returns. No DOM, no editor instance — this module runs in a Worker (gh#1156).
5
+ *
6
+ * Node names follow Tiptap's StarterKit / Table / Details extensions where one exists:
7
+ * doc · paragraph · heading{level} · blockquote · codeBlock{language} · horizontalRule ·
8
+ * bulletList{tight} · orderedList{start,tight} · listItem · taskList{tight} · taskItem{checked} ·
9
+ * table · tableRow · tableHeader{align} · tableCell{align} · image{src,alt,title} · hardBreak
10
+ * and the kit's own:
11
+ * callout{kind,title} · details · detailsSummary · detailsContent · columns · column{width} ·
12
+ * embed{target} (block `![[x]]`) · wikilink{target,heading,label} · embedInline{target} ·
13
+ * rawBlock{source} · rawInline{source}
14
+ * Marks: bold · italic · strike · code · link{href,title}.
15
+ */
16
+ type Mark = {
17
+ type: "bold" | "italic" | "strike" | "code";
18
+ } | {
19
+ type: "link";
20
+ attrs: {
21
+ href: string;
22
+ title: string | null;
23
+ };
24
+ };
25
+ type DocNode = {
26
+ type: string;
27
+ attrs?: Record<string, unknown>;
28
+ content?: DocNode[];
29
+ text?: string;
30
+ marks?: Mark[];
31
+ };
32
+ type Doc = DocNode & {
33
+ type: "doc";
34
+ content: DocNode[];
35
+ };
36
+ /** GitHub alert kinds, the only callouts v1 knows. Serialized uppercase. */
37
+ declare const CALLOUT_KINDS: readonly ["note", "tip", "important", "warning", "caution"];
38
+ type CalloutKind = (typeof CALLOUT_KINDS)[number];
39
+ /** Columns per `:::columns` block (grammar v1). */
40
+ declare const COLUMNS_MIN = 2;
41
+ declare const COLUMNS_MAX = 4;
42
+
43
+ /** Markdown → document (plain JSON, ProseMirror/Tiptap shape). Pure: no DOM. */
44
+ declare function parse(markdown: string): Doc;
45
+
46
+ /** Document → canonical Markdown. Pure: no DOM. */
47
+ declare function serialize(doc: Doc | DocNode): string;
48
+
49
+ /**
50
+ * Identifies the serializer's output. Bumped whenever `serialize` would write a stored body
51
+ * differently, so a host can tell which normalization a saved `normalized_body` came from.
52
+ */
53
+ declare const CODEC_ID = "kit-md@1";
54
+ /** `serialize(parse(markdown))` — the canonical form of a body, exactly what the editor saves. */
55
+ declare function normalize(markdown: string): string;
56
+
57
+ export { CALLOUT_KINDS, CODEC_ID, COLUMNS_MAX, COLUMNS_MIN, type CalloutKind, type Doc, type DocNode, type Mark, normalize, parse, serialize };
@@ -0,0 +1,576 @@
1
+ // src/codec/parse.ts
2
+ import { fromMarkdown } from "mdast-util-from-markdown";
3
+ import { toMarkdown } from "mdast-util-to-markdown";
4
+
5
+ // src/codec/model.ts
6
+ var CALLOUT_KINDS = ["note", "tip", "important", "warning", "caution"];
7
+ var COLUMNS_MIN = 2;
8
+ var COLUMNS_MAX = 4;
9
+
10
+ // src/codec/readers.ts
11
+ var CALLOUT = /^\[!([A-Za-z]+)\](?:[ \t]+([^\n]*))?$/;
12
+ function readCallout(node) {
13
+ const [first, ...rest] = node.children;
14
+ if (first?.type !== "paragraph") return null;
15
+ const [head, ...tail] = first.children;
16
+ if (head?.type !== "text") return null;
17
+ const newline = head.value.indexOf("\n");
18
+ if (newline === -1 && tail.length > 0) return null;
19
+ const line = newline === -1 ? head.value : head.value.slice(0, newline);
20
+ const match = CALLOUT.exec(line);
21
+ const kind = match?.[1]?.toLowerCase();
22
+ if (!match || !kind || !CALLOUT_KINDS.includes(kind)) return null;
23
+ const remainder = [
24
+ ...newline === -1 ? [] : [{ ...head, value: head.value.slice(newline + 1) }],
25
+ ...tail
26
+ ].filter((n) => !(n.type === "text" && n.value === ""));
27
+ return {
28
+ kind,
29
+ title: match[2]?.trim() || null,
30
+ body: [
31
+ ...remainder.length ? [{ type: "paragraph", children: remainder }] : [],
32
+ ...rest
33
+ ]
34
+ };
35
+ }
36
+ function labelOf(node) {
37
+ const first = node.children[0];
38
+ return first?.type === "paragraph" && first.data?.directiveLabel ? first : null;
39
+ }
40
+ function readToggle(node) {
41
+ if (node.name !== "toggle" || Object.keys(node.attributes ?? {}).length > 0) return null;
42
+ const label = labelOf(node);
43
+ return {
44
+ summary: label?.children ?? [],
45
+ body: label ? node.children.slice(1) : node.children
46
+ };
47
+ }
48
+ function readColumns(node, nested) {
49
+ if (node.name !== "columns" || nested || labelOf(node) || Object.keys(node.attributes ?? {}).length) {
50
+ return null;
51
+ }
52
+ const columns = node.children;
53
+ if (columns.length < COLUMNS_MIN || columns.length > COLUMNS_MAX) return null;
54
+ const parts = [];
55
+ for (const column of columns) {
56
+ if (column.type !== "containerDirective" || column.name !== "column" || labelOf(column))
57
+ return null;
58
+ const attributes = Object.entries(column.attributes ?? {});
59
+ if (attributes.length > 1) return null;
60
+ let width = null;
61
+ if (attributes.length === 1) {
62
+ const [key, value] = attributes[0];
63
+ if (key !== "width" || typeof value !== "string" || !/^(?:[1-9]\d?|100)$/.test(value))
64
+ return null;
65
+ width = Number(value);
66
+ }
67
+ parts.push({ width, body: column.children });
68
+ }
69
+ return parts;
70
+ }
71
+
72
+ // src/codec/syntax.ts
73
+ import { directive } from "micromark-extension-directive";
74
+ import { gfm } from "micromark-extension-gfm";
75
+ import { cjkFriendlyExtension } from "micromark-extension-cjk-friendly";
76
+ import { directiveFromMarkdown } from "mdast-util-directive";
77
+ import { gfmFromMarkdown, gfmToMarkdown } from "mdast-util-gfm";
78
+ function micromarkExtensions() {
79
+ const [container] = directive().flow[58];
80
+ const containerOnly = { flow: { 58: container } };
81
+ return [gfm(), cjkFriendlyExtension(), containerOnly];
82
+ }
83
+ function fromMarkdownExtensions() {
84
+ return [gfmFromMarkdown(), directiveFromMarkdown()];
85
+ }
86
+ var WIDE = /[\u1100-\u115F\u2E80-\u303E\u3041-\u33FF\u3400-\u4DBF\u4E00-\u9FFF\uA000-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFF60\uFFE0-\uFFE6\u{1F300}-\u{1F64F}\u{1F900}-\u{1F9FF}\u{20000}-\u{3FFFD}]/u;
87
+ function displayWidth(value) {
88
+ let width = 0;
89
+ for (const char of value) width += WIDE.test(char) ? 2 : 1;
90
+ return width;
91
+ }
92
+ function toMarkdownGfm() {
93
+ return gfmToMarkdown({ stringLength: displayWidth });
94
+ }
95
+
96
+ // src/codec/options.ts
97
+ function containerDirective(node, _, state, info) {
98
+ const tracker = state.createTracker(info);
99
+ const [first, ...rest] = node.children;
100
+ const hasLabel = first?.type === "paragraph" && first.data?.directiveLabel;
101
+ const label = hasLabel ? `[${state.containerPhrasing(first, { ...tracker.current(), before: "[", after: "]" })}]` : "";
102
+ const attributes = Object.entries(node.attributes ?? {}).map(
103
+ ([key, value]) => value === "" || value == null ? key : `${key}=${/^[\w.-]+$/.test(String(value)) ? value : JSON.stringify(value)}`
104
+ ).join(" ");
105
+ const opening = `${node.name}${label}${attributes ? `{${attributes}}` : ""}`;
106
+ const body = state.containerFlow(
107
+ { type: "root", children: hasLabel ? rest : node.children },
108
+ tracker.current()
109
+ );
110
+ const longest = Math.max(2, ...[...body.matchAll(/^[ \t>]*(:{3,})/gm)].map((m) => m[1].length));
111
+ const fence = ":".repeat(longest + 1);
112
+ return body ? `${fence}${opening}
113
+ ${body}
114
+ ${fence}` : `${fence}${opening}
115
+ ${fence}`;
116
+ }
117
+ function toMarkdownOptions() {
118
+ return {
119
+ bullet: "-",
120
+ bulletOther: "*",
121
+ emphasis: "*",
122
+ strong: "*",
123
+ fence: "`",
124
+ fences: true,
125
+ rule: "-",
126
+ listItemIndent: "one",
127
+ incrementListMarker: true,
128
+ setext: false,
129
+ closeAtx: false,
130
+ resourceLink: false,
131
+ extensions: [
132
+ toMarkdownGfm(),
133
+ { handlers: { containerDirective } }
134
+ ]
135
+ };
136
+ }
137
+
138
+ // src/codec/parse.ts
139
+ var WIKILINK = /!\[\[([^[\]\n]+)\]\]|\[\[([^[\]\n|#]+)(?:#([^[\]\n|]+))?(?:\|([^[\]\n]+))?\]\]/g;
140
+ function parseMdast(markdown) {
141
+ return fromMarkdown(markdown, {
142
+ extensions: micromarkExtensions(),
143
+ mdastExtensions: fromMarkdownExtensions()
144
+ });
145
+ }
146
+ function raw(node, inline2) {
147
+ const source = node.type === "html" ? node.value : toMarkdown(node, toMarkdownOptions()).replace(/\n+$/, "");
148
+ return { type: inline2 ? "rawInline" : "rawBlock", attrs: { source } };
149
+ }
150
+ function withMark(marks, mark) {
151
+ return [...marks, mark];
152
+ }
153
+ function text(value, marks) {
154
+ const out = [];
155
+ let last = 0;
156
+ for (const match of value.matchAll(WIKILINK)) {
157
+ const at = match.index;
158
+ if (at > last) out.push(leafText(value.slice(last, at), marks));
159
+ const [, embed, target, heading, label] = match;
160
+ out.push(
161
+ withMarks(
162
+ embed !== void 0 ? { type: "embedInline", attrs: { target: embed } } : {
163
+ type: "wikilink",
164
+ attrs: {
165
+ target: target.trim(),
166
+ heading: heading?.trim() ?? null,
167
+ label: label ?? null
168
+ }
169
+ },
170
+ marks
171
+ )
172
+ );
173
+ last = at + match[0].length;
174
+ }
175
+ if (last < value.length) out.push(leafText(value.slice(last), marks));
176
+ return out;
177
+ }
178
+ function withMarks(node, marks) {
179
+ return marks.length ? { ...node, marks } : node;
180
+ }
181
+ function leafText(value, marks) {
182
+ return marks.length ? { type: "text", text: value, marks } : { type: "text", text: value };
183
+ }
184
+ function inline(nodes, marks = []) {
185
+ return nodes.flatMap((node) => {
186
+ switch (node.type) {
187
+ case "text":
188
+ return text(node.value, marks);
189
+ case "emphasis":
190
+ return inline(node.children, withMark(marks, { type: "italic" }));
191
+ case "strong":
192
+ return inline(node.children, withMark(marks, { type: "bold" }));
193
+ case "delete":
194
+ return inline(node.children, withMark(marks, { type: "strike" }));
195
+ case "inlineCode":
196
+ return [leafText(node.value, withMark(marks, { type: "code" }))];
197
+ case "link":
198
+ return inline(
199
+ node.children,
200
+ withMark(marks, { type: "link", attrs: { href: node.url, title: node.title ?? null } })
201
+ );
202
+ case "image":
203
+ return [
204
+ withMarks(
205
+ {
206
+ type: "image",
207
+ attrs: { src: node.url, alt: node.alt ?? "", title: node.title ?? null }
208
+ },
209
+ marks
210
+ )
211
+ ];
212
+ case "break":
213
+ return [withMarks({ type: "hardBreak" }, marks)];
214
+ default:
215
+ return [withMarks(raw(node, true), marks)];
216
+ }
217
+ });
218
+ }
219
+ function paragraph(node) {
220
+ const content2 = inline(node.children);
221
+ if (content2.length === 1 && content2[0].type === "embedInline" && !content2[0].marks) {
222
+ return [{ type: "embed", attrs: { ...content2[0].attrs } }];
223
+ }
224
+ return [content2.length ? { type: "paragraph", content: content2 } : { type: "paragraph" }];
225
+ }
226
+ function blocks(nodes, depth) {
227
+ return nodes.flatMap((node) => block(node, depth));
228
+ }
229
+ function callout(node, depth) {
230
+ const parts = readCallout(node);
231
+ if (!parts) return null;
232
+ return {
233
+ type: "callout",
234
+ attrs: { kind: parts.kind, title: parts.title },
235
+ content: blocks(parts.body, depth)
236
+ };
237
+ }
238
+ var MARKER = /^\[![A-Za-z]+\][+-]?/;
239
+ function keepMarker(children) {
240
+ const [first, ...rest] = children;
241
+ if (first?.type !== "paragraph") return children;
242
+ const [head, ...tail] = first.children;
243
+ const marker = head?.type === "text" ? MARKER.exec(head.value)?.[0] : void 0;
244
+ if (!marker || head?.type !== "text") return children;
245
+ const remainder = head.value.slice(marker.length);
246
+ return [
247
+ {
248
+ ...first,
249
+ children: [
250
+ { type: "html", value: marker },
251
+ ...remainder ? [{ ...head, value: remainder }] : [],
252
+ ...tail
253
+ ]
254
+ },
255
+ ...rest
256
+ ];
257
+ }
258
+ function directiveBlock(node, depth) {
259
+ const toggle = readToggle(node);
260
+ if (toggle) {
261
+ const summary = inline(toggle.summary);
262
+ return {
263
+ type: "details",
264
+ content: [
265
+ summary.length ? { type: "detailsSummary", content: summary } : { type: "detailsSummary" },
266
+ { type: "detailsContent", content: nonEmpty(blocks(toggle.body, depth)) }
267
+ ]
268
+ };
269
+ }
270
+ const columns = readColumns(node, depth.columns);
271
+ if (columns) {
272
+ return {
273
+ type: "columns",
274
+ content: columns.map((column) => ({
275
+ type: "column",
276
+ attrs: { width: column.width },
277
+ content: nonEmpty(blocks(column.body, { columns: true }))
278
+ }))
279
+ };
280
+ }
281
+ return raw(node, false);
282
+ }
283
+ function nonEmpty(content2) {
284
+ return content2.length ? content2 : [{ type: "paragraph" }];
285
+ }
286
+ function block(node, depth) {
287
+ switch (node.type) {
288
+ case "paragraph":
289
+ return paragraph(node);
290
+ case "heading": {
291
+ const content2 = inline(node.children);
292
+ return [
293
+ { type: "heading", attrs: { level: node.depth }, ...content2.length ? { content: content2 } : {} }
294
+ ];
295
+ }
296
+ case "thematicBreak":
297
+ return [{ type: "horizontalRule" }];
298
+ case "code":
299
+ if (node.meta) return [raw(node, false)];
300
+ return [
301
+ {
302
+ type: "codeBlock",
303
+ attrs: { language: node.lang ?? null },
304
+ ...node.value ? { content: [{ type: "text", text: node.value }] } : {}
305
+ }
306
+ ];
307
+ case "blockquote":
308
+ return [
309
+ callout(node, depth) ?? {
310
+ type: "blockquote",
311
+ content: nonEmpty(blocks(keepMarker(node.children), depth))
312
+ }
313
+ ];
314
+ case "list": {
315
+ const tasks = node.children.map((item) => typeof item.checked === "boolean");
316
+ if (tasks.some(Boolean) && !tasks.every(Boolean)) return [raw(node, false)];
317
+ const isTask = tasks.length > 0 && tasks.every(Boolean);
318
+ const tight = !node.spread && node.children.every((item) => !item.spread);
319
+ const items = node.children.map((item) => ({
320
+ type: isTask ? "taskItem" : "listItem",
321
+ ...isTask ? { attrs: { checked: item.checked === true } } : {},
322
+ content: nonEmpty(blocks(item.children, depth))
323
+ }));
324
+ if (isTask) return [{ type: "taskList", attrs: { tight }, content: items }];
325
+ return node.ordered ? [{ type: "orderedList", attrs: { start: node.start ?? 1, tight }, content: items }] : [{ type: "bulletList", attrs: { tight }, content: items }];
326
+ }
327
+ case "table": {
328
+ const width = node.children[0]?.children.length ?? 0;
329
+ return [
330
+ {
331
+ type: "table",
332
+ content: node.children.map((row, r) => ({
333
+ type: "tableRow",
334
+ content: row.children.slice(0, width).map((cell, c) => {
335
+ const content2 = inline(cell.children);
336
+ return {
337
+ type: r === 0 ? "tableHeader" : "tableCell",
338
+ attrs: { align: node.align?.[c] ?? null },
339
+ content: [content2.length ? { type: "paragraph", content: content2 } : { type: "paragraph" }]
340
+ };
341
+ })
342
+ }))
343
+ }
344
+ ];
345
+ }
346
+ case "containerDirective":
347
+ return [directiveBlock(node, depth)];
348
+ default:
349
+ return [raw(node, false)];
350
+ }
351
+ }
352
+ function parse(markdown) {
353
+ const content2 = blocks(parseMdast(markdown.replace(/\r\n?/g, "\n")).children, { columns: false });
354
+ return { type: "doc", content: content2.length ? content2 : [{ type: "paragraph" }] };
355
+ }
356
+
357
+ // src/codec/serialize.ts
358
+ import { toMarkdown as toMarkdown2 } from "mdast-util-to-markdown";
359
+ var MARK_RANK = ["link", "bold", "italic", "strike"];
360
+ var hasMark = (node, type) => node.marks?.some((m) => m.type === type) ?? false;
361
+ var linkOf = (node) => node.marks?.find((m) => m.type === "link");
362
+ var sameLink = (a, b) => linkOf(a)?.attrs.href === linkOf(b)?.attrs.href && linkOf(a)?.attrs.title === linkOf(b)?.attrs.title;
363
+ function wikilinkSource(node, inTable) {
364
+ const a = node.attrs;
365
+ const pipe = inTable ? "\\|" : "|";
366
+ return `[[${a.target}${a.heading ? `#${a.heading}` : ""}${a.label ? `${pipe}${a.label}` : ""}]]`;
367
+ }
368
+ function leaf(node, inTable) {
369
+ switch (node.type) {
370
+ case "text":
371
+ return hasMark(node, "code") ? [{ type: "inlineCode", value: node.text ?? "" }] : [{ type: "text", value: node.text ?? "" }];
372
+ case "hardBreak":
373
+ return [{ type: "break" }];
374
+ case "image": {
375
+ const a = node.attrs;
376
+ return [{ type: "image", url: a.src, alt: a.alt ?? "", title: a.title ?? null }];
377
+ }
378
+ case "wikilink":
379
+ return [{ type: "html", value: wikilinkSource(node, inTable) }];
380
+ case "embedInline":
381
+ return [{ type: "html", value: `![[${node.attrs.target}]]` }];
382
+ case "rawInline":
383
+ return [{ type: "html", value: String(node.attrs.source) }];
384
+ default:
385
+ return [];
386
+ }
387
+ }
388
+ function phrasing(nodes, inTable = false, rank = 0) {
389
+ if (rank === MARK_RANK.length) return nodes.flatMap((n) => leaf(n, inTable));
390
+ const mark = MARK_RANK[rank];
391
+ const out = [];
392
+ let i = 0;
393
+ while (i < nodes.length) {
394
+ const start = nodes[i];
395
+ const marked = hasMark(start, mark);
396
+ let j = i + 1;
397
+ while (j < nodes.length && hasMark(nodes[j], mark) === marked && (!marked || mark !== "link" || sameLink(start, nodes[j]))) {
398
+ j++;
399
+ }
400
+ const inner = phrasing(nodes.slice(i, j), inTable, rank + 1);
401
+ if (!marked) out.push(...inner);
402
+ else if (mark === "link") {
403
+ const { href, title } = linkOf(start).attrs;
404
+ out.push({ type: "link", url: href, title, children: inner });
405
+ } else if (mark === "bold") out.push({ type: "strong", children: inner });
406
+ else if (mark === "italic") out.push({ type: "emphasis", children: inner });
407
+ else out.push({ type: "delete", children: inner });
408
+ i = j;
409
+ }
410
+ return out;
411
+ }
412
+ var content = (node) => node.content ?? [];
413
+ function paragraphOf(node) {
414
+ return { type: "paragraph", children: phrasing(content(node)) };
415
+ }
416
+ function flow(nodes) {
417
+ return nodes.flatMap(block2);
418
+ }
419
+ function block2(node) {
420
+ const attrs = node.attrs ?? {};
421
+ switch (node.type) {
422
+ case "paragraph":
423
+ return [paragraphOf(node)];
424
+ case "heading":
425
+ return [
426
+ {
427
+ type: "heading",
428
+ depth: Math.min(6, Math.max(1, Number(attrs.level) || 1)),
429
+ children: phrasing(content(node))
430
+ }
431
+ ];
432
+ case "horizontalRule":
433
+ return [{ type: "thematicBreak" }];
434
+ case "codeBlock":
435
+ return [
436
+ {
437
+ type: "code",
438
+ lang: attrs.language ?? null,
439
+ meta: null,
440
+ value: content(node).map((t) => t.text ?? "").join("")
441
+ }
442
+ ];
443
+ case "blockquote":
444
+ return [{ type: "blockquote", children: flow(content(node)) }];
445
+ case "callout": {
446
+ const marker = `[!${String(attrs.kind).toUpperCase()}]`;
447
+ const head = {
448
+ type: "paragraph",
449
+ children: [
450
+ { type: "html", value: marker },
451
+ ...attrs.title ? [{ type: "text", value: ` ${String(attrs.title)}` }] : []
452
+ ]
453
+ };
454
+ return [{ type: "blockquote", children: [head, ...flow(content(node))] }];
455
+ }
456
+ case "bulletList":
457
+ case "orderedList":
458
+ case "taskList": {
459
+ const tight = attrs.tight !== false;
460
+ return [
461
+ {
462
+ type: "list",
463
+ ordered: node.type === "orderedList",
464
+ start: node.type === "orderedList" ? Number(attrs.start ?? 1) : null,
465
+ spread: !tight,
466
+ children: content(node).map((item) => ({
467
+ type: "listItem",
468
+ spread: !tight,
469
+ checked: node.type === "taskList" ? item.attrs?.checked === true : null,
470
+ children: flow(content(item))
471
+ }))
472
+ }
473
+ ];
474
+ }
475
+ case "table": {
476
+ const rows = content(node);
477
+ const align = content(rows[0] ?? { type: "tableRow" }).map(
478
+ (cell) => cell.attrs?.align ?? null
479
+ );
480
+ return [
481
+ {
482
+ type: "table",
483
+ align,
484
+ children: rows.map((row) => ({
485
+ type: "tableRow",
486
+ children: content(row).map((cell) => ({
487
+ type: "tableCell",
488
+ children: phrasing(
489
+ content(cell).flatMap((p) => content(p)),
490
+ true
491
+ )
492
+ }))
493
+ }))
494
+ }
495
+ ];
496
+ }
497
+ case "details": {
498
+ const [summary, body] = [
499
+ content(node).find((n) => n.type === "detailsSummary"),
500
+ content(node).find((n) => n.type === "detailsContent")
501
+ ];
502
+ const label = summary && content(summary).length ? [
503
+ {
504
+ type: "paragraph",
505
+ data: { directiveLabel: true },
506
+ children: phrasing(content(summary))
507
+ }
508
+ ] : [];
509
+ return [
510
+ {
511
+ type: "containerDirective",
512
+ name: "toggle",
513
+ attributes: {},
514
+ children: [
515
+ ...label,
516
+ ...flow(content(body ?? { type: "detailsContent" }))
517
+ ]
518
+ }
519
+ ];
520
+ }
521
+ case "columns":
522
+ return [
523
+ {
524
+ type: "containerDirective",
525
+ name: "columns",
526
+ attributes: {},
527
+ children: content(node).map((column) => ({
528
+ type: "containerDirective",
529
+ name: "column",
530
+ attributes: column.attrs?.width != null ? { width: String(column.attrs.width) } : {},
531
+ children: flow(content(column))
532
+ }))
533
+ }
534
+ ];
535
+ case "embed":
536
+ return [{ type: "html", value: `![[${String(attrs.target)}]]` }];
537
+ case "rawBlock":
538
+ return [{ type: "html", value: String(attrs.source) }];
539
+ default:
540
+ return [];
541
+ }
542
+ }
543
+ function decodeAttentionReferences(markdown) {
544
+ const references = [...markdown.matchAll(/&#x([0-9A-F]+);/g)];
545
+ if (references.length === 0) return markdown;
546
+ const target = JSON.stringify(parse(markdown));
547
+ let out = markdown;
548
+ for (const reference of references.reverse()) {
549
+ const at = reference.index;
550
+ const candidate = out.slice(0, at) + String.fromCodePoint(Number.parseInt(reference[1], 16)) + out.slice(at + reference[0].length);
551
+ if (JSON.stringify(parse(candidate)) === target) out = candidate;
552
+ }
553
+ return out;
554
+ }
555
+ function serialize(doc) {
556
+ const root = { type: "root", children: flow(doc.content ?? []) };
557
+ const markdown = toMarkdown2(root, toMarkdownOptions());
558
+ if (markdown.trim() === "") return "";
559
+ const tidy = markdown.replace(/\n{3,}/g, "\n\n").replace(/^\n+/, "").replace(/\n+$/, "\n");
560
+ return decodeAttentionReferences(tidy);
561
+ }
562
+
563
+ // src/codec/index.ts
564
+ var CODEC_ID = "kit-md@1";
565
+ function normalize(markdown) {
566
+ return serialize(parse(markdown));
567
+ }
568
+ export {
569
+ CALLOUT_KINDS,
570
+ CODEC_ID,
571
+ COLUMNS_MAX,
572
+ COLUMNS_MIN,
573
+ normalize,
574
+ parse,
575
+ serialize
576
+ };
package/dist/index.d.ts CHANGED
@@ -65,6 +65,10 @@ declare function extendSchema(extension?: SchemaExtension): Options;
65
65
  */
66
66
  declare function safeUrl(url: string, key: string): string;
67
67
 
68
+ /** GitHub alert kinds, the only callouts v1 knows. Serialized uppercase. */
69
+ declare const CALLOUT_KINDS: readonly ["note", "tip", "important", "warning", "caution"];
70
+ type CalloutKind = (typeof CALLOUT_KINDS)[number];
71
+
68
72
  type PluggableList = NonNullable<Options$1["remarkPlugins"]>;
69
73
  /**
70
74
  * The stored body's format, and the renderer generation that produced its output. A consumer
@@ -73,7 +77,7 @@ type PluggableList = NonNullable<Options$1["remarkPlugins"]>;
73
77
  * differently — a schema change, a new default plugin — not on every release.
74
78
  */
75
79
  declare const MARKDOWN_FORMAT = "md";
76
- declare const RENDERER_VERSION = 3;
80
+ declare const RENDERER_VERSION = 4;
77
81
  type MarkdownProps = {
78
82
  /** The Markdown source. */
79
83
  children: string;
@@ -124,6 +128,11 @@ type MarkdownProps = {
124
128
  * Characters are user-perceived (grapheme clusters). Default 24; `false` stamps nothing.
125
129
  */
126
130
  shortCellLength?: number | false;
131
+ /**
132
+ * Visible callout titles per kind when a callout has none of its own (`> [!WARNING]`). English by
133
+ * default — this package carries no i18n; a host passes its localized strings.
134
+ */
135
+ calloutTitles?: Partial<Record<CalloutKind, string>>;
127
136
  };
128
137
  /**
129
138
  * THE ONE RENDERER (gh#1108). GFM — tables, task lists, fenced code, autolinks, footnotes,
@@ -135,7 +144,7 @@ type MarkdownProps = {
135
144
  * Renders bare elements; the host wraps it (`<Prose>` from `@godxjp/ui/data-display`), so this
136
145
  * package carries no UI-kit dependency.
137
146
  */
138
- declare function Markdown({ children, remarkPlugins, rehypePlugins, schema, components, resolveUrl, headingId, mermaid, allowedElements, disallowedElements, allowElement, unwrapDisallowed, shortCellLength, }: MarkdownProps): React.JSX.Element;
147
+ declare function Markdown({ children, remarkPlugins, rehypePlugins, schema, components, resolveUrl, headingId, mermaid, allowedElements, disallowedElements, allowElement, unwrapDisallowed, shortCellLength, calloutTitles, }: MarkdownProps): React.JSX.Element;
139
148
 
140
149
  type MermaidSvgCheck = {
141
150
  ok: true;
package/dist/index.js CHANGED
@@ -86,14 +86,14 @@ function checkMermaidSvg(svg) {
86
86
  const root = doc.documentElement;
87
87
  if (root.localName.toLowerCase() !== "svg") return { ok: false, reason: "root" };
88
88
  const elements = [root, ...Array.from(root.getElementsByTagName("*"))];
89
- for (const element of elements) {
90
- const name = element.localName.toLowerCase();
89
+ for (const element2 of elements) {
90
+ const name = element2.localName.toLowerCase();
91
91
  if (!ALLOWED_ELEMENTS.has(name)) return { ok: false, reason: `element ${name}` };
92
92
  if (name === "style") {
93
- const css = element.textContent ?? "";
93
+ const css = element2.textContent ?? "";
94
94
  if (CSS_HAZARD.test(css) || !urlsAreLocal(css)) return { ok: false, reason: "style" };
95
95
  }
96
- for (const attribute of Array.from(element.attributes)) {
96
+ for (const attribute of Array.from(element2.attributes)) {
97
97
  const attr = attribute.localName.toLowerCase();
98
98
  const value = attribute.value;
99
99
  if (attr.startsWith("on")) return { ok: false, reason: `handler ${attr}` };
@@ -203,7 +203,28 @@ var markdownSchema = {
203
203
  h3: [...defaultSchema.attributes?.h3 ?? [], "id"],
204
204
  h4: [...defaultSchema.attributes?.h4 ?? [], "id"],
205
205
  h5: [...defaultSchema.attributes?.h5 ?? [], "id"],
206
- h6: [...defaultSchema.attributes?.h6 ?? [], "id"]
206
+ h6: [...defaultSchema.attributes?.h6 ?? [], "id"],
207
+ // Grammar v1 (gh#1156): the renderer's own callout / toggle / column markup. Every value is
208
+ // pinned — a body cannot mint any other class, kind, role or inline style through these.
209
+ div: [
210
+ ...defaultSchema.attributes?.div ?? [],
211
+ [
212
+ "className",
213
+ "ui-prose-callout",
214
+ "ui-prose-columns",
215
+ "ui-prose-column",
216
+ "ui-prose-directive"
217
+ ],
218
+ ["dataKind", "note", "tip", "important", "warning", "caution"],
219
+ ["role", "note"],
220
+ ["style", /^--prose-column-grow:\d+(?:\.\d+)?$/]
221
+ ],
222
+ p: [...defaultSchema.attributes?.p ?? [], ["className", "ui-prose-callout-title"]],
223
+ details: [...defaultSchema.attributes?.details ?? [], ["className", "ui-prose-toggle"]],
224
+ summary: [
225
+ ...defaultSchema.attributes?.summary ?? [],
226
+ ["className", "ui-prose-toggle-summary"]
227
+ ]
207
228
  }
208
229
  };
209
230
  var ruleName = (rule) => Array.isArray(rule) ? rule[0] : rule;
@@ -241,10 +262,172 @@ function safeUrl(url, key) {
241
262
  return SAFE_HREF.test(probe) ? value : "";
242
263
  }
243
264
 
265
+ // src/codec/model.ts
266
+ var CALLOUT_KINDS = ["note", "tip", "important", "warning", "caution"];
267
+ var COLUMNS_MIN = 2;
268
+ var COLUMNS_MAX = 4;
269
+
270
+ // src/codec/readers.ts
271
+ var CALLOUT = /^\[!([A-Za-z]+)\](?:[ \t]+([^\n]*))?$/;
272
+ function readCallout(node) {
273
+ const [first, ...rest] = node.children;
274
+ if (first?.type !== "paragraph") return null;
275
+ const [head, ...tail] = first.children;
276
+ if (head?.type !== "text") return null;
277
+ const newline = head.value.indexOf("\n");
278
+ if (newline === -1 && tail.length > 0) return null;
279
+ const line = newline === -1 ? head.value : head.value.slice(0, newline);
280
+ const match = CALLOUT.exec(line);
281
+ const kind = match?.[1]?.toLowerCase();
282
+ if (!match || !kind || !CALLOUT_KINDS.includes(kind)) return null;
283
+ const remainder = [
284
+ ...newline === -1 ? [] : [{ ...head, value: head.value.slice(newline + 1) }],
285
+ ...tail
286
+ ].filter((n) => !(n.type === "text" && n.value === ""));
287
+ return {
288
+ kind,
289
+ title: match[2]?.trim() || null,
290
+ body: [
291
+ ...remainder.length ? [{ type: "paragraph", children: remainder }] : [],
292
+ ...rest
293
+ ]
294
+ };
295
+ }
296
+ function labelOf(node) {
297
+ const first = node.children[0];
298
+ return first?.type === "paragraph" && first.data?.directiveLabel ? first : null;
299
+ }
300
+ function readToggle(node) {
301
+ if (node.name !== "toggle" || Object.keys(node.attributes ?? {}).length > 0) return null;
302
+ const label = labelOf(node);
303
+ return {
304
+ summary: label?.children ?? [],
305
+ body: label ? node.children.slice(1) : node.children
306
+ };
307
+ }
308
+ function readColumns(node, nested) {
309
+ if (node.name !== "columns" || nested || labelOf(node) || Object.keys(node.attributes ?? {}).length) {
310
+ return null;
311
+ }
312
+ const columns = node.children;
313
+ if (columns.length < COLUMNS_MIN || columns.length > COLUMNS_MAX) return null;
314
+ const parts = [];
315
+ for (const column of columns) {
316
+ if (column.type !== "containerDirective" || column.name !== "column" || labelOf(column))
317
+ return null;
318
+ const attributes = Object.entries(column.attributes ?? {});
319
+ if (attributes.length > 1) return null;
320
+ let width = null;
321
+ if (attributes.length === 1) {
322
+ const [key, value] = attributes[0];
323
+ if (key !== "width" || typeof value !== "string" || !/^(?:[1-9]\d?|100)$/.test(value))
324
+ return null;
325
+ width = Number(value);
326
+ }
327
+ parts.push({ width, body: column.children });
328
+ }
329
+ return parts;
330
+ }
331
+ function columnShares(columns) {
332
+ const explicit = columns.reduce((sum, c) => sum + (c.width ?? 0), 0);
333
+ const open = columns.filter((c) => c.width == null).length;
334
+ const each = open ? Math.max(1, (100 - explicit) / open) : 0;
335
+ return columns.map((c) => c.width ?? Math.round(each * 100) / 100);
336
+ }
337
+
338
+ // src/codec/syntax.ts
339
+ import { directive } from "micromark-extension-directive";
340
+ import { gfm } from "micromark-extension-gfm";
341
+ import { cjkFriendlyExtension } from "micromark-extension-cjk-friendly";
342
+ import { directiveFromMarkdown } from "mdast-util-directive";
343
+ import { gfmFromMarkdown, gfmToMarkdown } from "mdast-util-gfm";
344
+ function micromarkExtensions() {
345
+ const [container] = directive().flow[58];
346
+ const containerOnly = { flow: { 58: container } };
347
+ return [gfm(), cjkFriendlyExtension(), containerOnly];
348
+ }
349
+ function fromMarkdownExtensions() {
350
+ return [gfmFromMarkdown(), directiveFromMarkdown()];
351
+ }
352
+
353
+ // src/directives.ts
354
+ var DEFAULT_CALLOUT_TITLES = {
355
+ note: "Note",
356
+ tip: "Tip",
357
+ important: "Important",
358
+ warning: "Warning",
359
+ caution: "Caution"
360
+ };
361
+ var element = (hName, hProperties = {}) => ({
362
+ hName,
363
+ hProperties
364
+ });
365
+ function remarkKitSyntax(options = {}) {
366
+ const titles = { ...DEFAULT_CALLOUT_TITLES, ...options.calloutTitles };
367
+ return function() {
368
+ const data = this.data();
369
+ (data.micromarkExtensions ??= []).push(...micromarkExtensions().slice(1));
370
+ (data.fromMarkdownExtensions ??= []).push(fromMarkdownExtensions()[1]);
371
+ const walk = (node, inColumn) => {
372
+ if (node.type === "blockquote") callout(node, titles);
373
+ else if (node.type === "containerDirective") directive2(node, inColumn);
374
+ if (!("children" in node)) return;
375
+ const isColumn = node.type === "containerDirective" && node.name === "column";
376
+ for (const child of node.children) walk(child, inColumn || isColumn);
377
+ };
378
+ return (tree) => walk(tree, false);
379
+ };
380
+ }
381
+ function callout(node, titles) {
382
+ const parts = readCallout(node);
383
+ if (!parts) return;
384
+ const title = {
385
+ type: "paragraph",
386
+ data: element("p", { className: ["ui-prose-callout-title"] }),
387
+ children: [{ type: "text", value: parts.title ?? titles[parts.kind] }]
388
+ };
389
+ node.data = element("div", {
390
+ className: ["ui-prose-callout"],
391
+ dataKind: parts.kind,
392
+ role: "note"
393
+ });
394
+ node.children = [title, ...parts.body];
395
+ }
396
+ function directive2(node, nested) {
397
+ const toggle = readToggle(node);
398
+ if (toggle) {
399
+ const summary = {
400
+ type: "paragraph",
401
+ data: element("summary", { className: ["ui-prose-toggle-summary"] }),
402
+ children: toggle.summary
403
+ };
404
+ node.data = element("details", { className: ["ui-prose-toggle"] });
405
+ node.children = [summary, ...toggle.body];
406
+ return;
407
+ }
408
+ const columns = readColumns(node, nested);
409
+ if (columns) {
410
+ const shares = columnShares(columns);
411
+ node.data = element("div", { className: ["ui-prose-columns"] });
412
+ node.children = columns.map((column, i) => ({
413
+ type: "containerDirective",
414
+ name: "column",
415
+ attributes: {},
416
+ data: element("div", {
417
+ className: ["ui-prose-column"],
418
+ style: `--prose-column-grow:${shares[i]}`
419
+ }),
420
+ children: column.body
421
+ }));
422
+ return;
423
+ }
424
+ if (!node.data?.hName) node.data = element("div", { className: ["ui-prose-directive"] });
425
+ }
426
+
244
427
  // src/markdown.tsx
245
428
  import { jsx as jsx3 } from "react/jsx-runtime";
246
429
  var MARKDOWN_FORMAT = "md";
247
- var RENDERER_VERSION = 3;
430
+ var RENDERER_VERSION = 4;
248
431
  var URL_ATTRIBUTES = {
249
432
  a: "href",
250
433
  img: "src",
@@ -312,9 +495,11 @@ function Markdown({
312
495
  disallowedElements,
313
496
  allowElement,
314
497
  unwrapDisallowed,
315
- shortCellLength = 24
498
+ shortCellLength = 24,
499
+ calloutTitles
316
500
  }) {
317
501
  const sanitizeSchema = React3.useMemo(() => extendSchema(schema), [schema]);
502
+ const kitSyntax = React3.useMemo(() => remarkKitSyntax({ calloutTitles }), [calloutTitles]);
318
503
  const merged = React3.useMemo(() => {
319
504
  const HostTable = components?.table;
320
505
  const table = (props) => /* @__PURE__ */ jsx3(TableScroll, { children: HostTable ? typeof HostTable === "string" ? React3.createElement(HostTable, props) : /* @__PURE__ */ jsx3(HostTable, { ...props }) : /* @__PURE__ */ jsx3("table", { ...omitNode(props) }) });
@@ -333,7 +518,7 @@ function Markdown({
333
518
  return /* @__PURE__ */ jsx3(
334
519
  ReactMarkdown,
335
520
  {
336
- remarkPlugins: [remarkGfm, ...remarkPlugins],
521
+ remarkPlugins: [remarkGfm, kitSyntax, ...remarkPlugins],
337
522
  rehypePlugins: [
338
523
  [rehypeResolveUrls, { resolve: resolveUrl }],
339
524
  [rehypeHeadingIds, { resolve: headingId }],
@@ -440,6 +625,37 @@ var MARKDOWN_FIXTURES = [
440
625
  markdown: "![x](data:image/svg+xml;base64,PHN2Zz4=) ![y](javascript:alert(1))",
441
626
  html: '<p><img alt="x"/> <img alt="y"/></p>',
442
627
  forbidden: ["data:", "javascript:"]
628
+ },
629
+ // Grammar v1 (gh#1156, renderer v4): the constructs the codec and @godxjp/block-editor model.
630
+ {
631
+ name: "CJK-friendly emphasis next to \u3001\u3002\u300C\u300D\uFF08\uFF09",
632
+ markdown: "\u306E**\u300C\u5F37\u8ABF\u300D**\u3067\u3059\u3002\uFF08*\u6CE8*\uFF09\u300C[\u30EA\u30F3\u30AF](https://example.com/)\u300D\u3001**\u592A\u5B57**\u3002",
633
+ html: '<p>\u306E<strong>\u300C\u5F37\u8ABF\u300D</strong>\u3067\u3059\u3002\uFF08<em>\u6CE8</em>\uFF09\u300C<a href="https://example.com/">\u30EA\u30F3\u30AF</a>\u300D\u3001<strong>\u592A\u5B57</strong>\u3002</p>'
634
+ },
635
+ {
636
+ name: "callout with a title, and one without",
637
+ markdown: "> [!WARNING] Careful\n> Body\n\n> [!tip]\n> No title",
638
+ html: '<div class="ui-prose-callout" data-kind="warning" role="note">\n<p class="ui-prose-callout-title">Careful</p>\n<p>Body</p>\n</div>\n<div class="ui-prose-callout" data-kind="tip" role="note">\n<p class="ui-prose-callout-title">Tip</p>\n<p>No title</p>\n</div>'
639
+ },
640
+ {
641
+ name: "a fold marker or unknown kind stays a quote",
642
+ markdown: "> [!NOTE]- folded\n\n> [!INFO] unknown",
643
+ html: "<blockquote>\n<p>[!NOTE]- folded</p>\n</blockquote>\n<blockquote>\n<p>[!INFO] unknown</p>\n</blockquote>"
644
+ },
645
+ {
646
+ name: "toggle with an inline summary",
647
+ markdown: ":::toggle[Read *more*]\nBody\n:::",
648
+ html: '<details class="ui-prose-toggle"><summary class="ui-prose-toggle-summary">Read <em>more</em></summary><p>Body</p></details>'
649
+ },
650
+ {
651
+ name: "columns with a width and an equal share",
652
+ markdown: "::::columns\n:::column{width=40}\nLeft\n:::\n\n:::column\nRight\n:::\n::::",
653
+ html: '<div class="ui-prose-columns"><div class="ui-prose-column" style="--prose-column-grow:40"><p>Left</p></div><div class="ui-prose-column" style="--prose-column-grow:60"><p>Right</p></div></div>'
654
+ },
655
+ {
656
+ name: "wikilinks, embeds and times stay text",
657
+ markdown: "See [[Page#Intro|intro]] at 10:30.\n\n![[Diagram]]",
658
+ html: "<p>See [[Page#Intro|intro]] at 10:30.</p>\n<p>![[Diagram]]</p>"
443
659
  }
444
660
  ];
445
661
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@godxjp/markdown",
3
- "version": "31.26.2",
3
+ "version": "31.27.0",
4
4
  "description": "The one Markdown renderer for GoDX apps: GFM + heading anchors, one sanitiser schema, a shared fixture corpus, and Mermaid drawn only after its SVG passes a fail-closed gate.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -11,6 +11,10 @@
11
11
  "types": "./dist/index.d.ts",
12
12
  "import": "./dist/index.js"
13
13
  },
14
+ "./codec": {
15
+ "types": "./dist/codec/index.d.ts",
16
+ "import": "./dist/codec/index.js"
17
+ },
14
18
  "./package.json": "./package.json"
15
19
  },
16
20
  "sideEffects": false,
@@ -30,6 +34,14 @@
30
34
  "license": "Apache-2.0",
31
35
  "dependencies": {
32
36
  "github-slugger": "^2.0.0",
37
+ "mdast-util-directive": "^3.1.1",
38
+ "mdast-util-from-markdown": "^2.1.0",
39
+ "mdast-util-gfm": "^3.1.0",
40
+ "mdast-util-to-markdown": "^2.2.0",
41
+ "micromark-extension-cjk-friendly": "^2.0.2",
42
+ "micromark-extension-directive": "^4.0.0",
43
+ "micromark-extension-gfm": "^3.0.0",
44
+ "micromark-util-types": "^2.0.2",
33
45
  "react-markdown": "^10.1.0",
34
46
  "rehype-sanitize": "^6.0.0",
35
47
  "remark-gfm": "^4.0.1",