@brett_lamy/docstream 1.2.4 → 1.4.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.
Files changed (216) hide show
  1. package/README.md +76 -14
  2. package/dist/assets.d.ts +3 -0
  3. package/dist/assets.d.ts.map +1 -0
  4. package/dist/assets.js +20 -0
  5. package/dist/assets.js.map +1 -0
  6. package/dist/demo/DemoViewer.d.ts +64 -0
  7. package/dist/demo/DemoViewer.d.ts.map +1 -0
  8. package/dist/demo/DemoViewer.js +426 -0
  9. package/dist/demo/DemoViewer.js.map +1 -0
  10. package/dist/demo/context.d.ts +14 -0
  11. package/dist/demo/context.d.ts.map +1 -0
  12. package/dist/demo/context.js +25 -0
  13. package/dist/demo/context.js.map +1 -0
  14. package/dist/demo/glob.d.ts +60 -0
  15. package/dist/demo/glob.d.ts.map +1 -0
  16. package/dist/demo/glob.js +158 -0
  17. package/dist/demo/glob.js.map +1 -0
  18. package/dist/demo/index.d.ts +10 -0
  19. package/dist/demo/index.d.ts.map +1 -0
  20. package/dist/demo/index.js +6 -0
  21. package/dist/demo/index.js.map +1 -0
  22. package/dist/demo/markdown.d.ts +60 -0
  23. package/dist/demo/markdown.d.ts.map +1 -0
  24. package/dist/demo/markdown.js +181 -0
  25. package/dist/demo/markdown.js.map +1 -0
  26. package/dist/demo/surface.d.ts +11 -0
  27. package/dist/demo/surface.d.ts.map +1 -0
  28. package/dist/demo/surface.js +69 -0
  29. package/dist/demo/surface.js.map +1 -0
  30. package/dist/demo/types.d.ts +91 -0
  31. package/dist/demo/types.d.ts.map +1 -0
  32. package/dist/demo/types.js +2 -0
  33. package/dist/demo/types.js.map +1 -0
  34. package/dist/docs/CollapsibleCode.d.ts +14 -0
  35. package/dist/docs/CollapsibleCode.d.ts.map +1 -0
  36. package/dist/docs/CollapsibleCode.js +19 -0
  37. package/dist/docs/CollapsibleCode.js.map +1 -0
  38. package/dist/docs/DocsRenderer.d.ts +49 -0
  39. package/dist/docs/DocsRenderer.d.ts.map +1 -0
  40. package/dist/docs/DocsRenderer.js +277 -0
  41. package/dist/docs/DocsRenderer.js.map +1 -0
  42. package/dist/docs/HighlightedCode.d.ts +6 -0
  43. package/dist/docs/HighlightedCode.d.ts.map +1 -0
  44. package/dist/docs/HighlightedCode.js +40 -0
  45. package/dist/docs/HighlightedCode.js.map +1 -0
  46. package/dist/docs/Mermaid.d.ts +7 -0
  47. package/dist/docs/Mermaid.d.ts.map +1 -0
  48. package/dist/docs/Mermaid.js +39 -0
  49. package/dist/docs/Mermaid.js.map +1 -0
  50. package/dist/docs/PageActions.d.ts +33 -0
  51. package/dist/docs/PageActions.d.ts.map +1 -0
  52. package/dist/docs/PageActions.js +115 -0
  53. package/dist/docs/PageActions.js.map +1 -0
  54. package/dist/docs/controls.d.ts +29 -0
  55. package/dist/docs/controls.d.ts.map +1 -0
  56. package/dist/docs/controls.js +46 -0
  57. package/dist/docs/controls.js.map +1 -0
  58. package/dist/docs/copy.d.ts +24 -0
  59. package/dist/docs/copy.d.ts.map +1 -0
  60. package/dist/docs/copy.js +62 -0
  61. package/dist/docs/copy.js.map +1 -0
  62. package/dist/docs/math.d.ts +3 -0
  63. package/dist/docs/math.d.ts.map +1 -0
  64. package/dist/docs/math.js +12 -0
  65. package/dist/docs/math.js.map +1 -0
  66. package/dist/docs/reference.d.ts +27 -0
  67. package/dist/docs/reference.d.ts.map +1 -0
  68. package/dist/docs/reference.js +73 -0
  69. package/dist/docs/reference.js.map +1 -0
  70. package/dist/docs/tabs-sync.d.ts +5 -0
  71. package/dist/docs/tabs-sync.d.ts.map +1 -0
  72. package/dist/docs/tabs-sync.js +58 -0
  73. package/dist/docs/tabs-sync.js.map +1 -0
  74. package/dist/gitbook/ast.d.ts +506 -0
  75. package/dist/gitbook/ast.d.ts.map +1 -0
  76. package/dist/gitbook/ast.js +8 -0
  77. package/dist/gitbook/ast.js.map +1 -0
  78. package/dist/gitbook/attrs.d.ts +14 -0
  79. package/dist/gitbook/attrs.d.ts.map +1 -0
  80. package/dist/gitbook/attrs.js +36 -0
  81. package/dist/gitbook/attrs.js.map +1 -0
  82. package/dist/gitbook/flatten.d.ts +14 -0
  83. package/dist/gitbook/flatten.d.ts.map +1 -0
  84. package/dist/gitbook/flatten.js +45 -0
  85. package/dist/gitbook/flatten.js.map +1 -0
  86. package/dist/gitbook/index.d.ts +10 -0
  87. package/dist/gitbook/index.d.ts.map +1 -0
  88. package/dist/gitbook/index.js +8 -0
  89. package/dist/gitbook/index.js.map +1 -0
  90. package/dist/gitbook/inline.d.ts +28 -0
  91. package/dist/gitbook/inline.d.ts.map +1 -0
  92. package/dist/gitbook/inline.js +956 -0
  93. package/dist/gitbook/inline.js.map +1 -0
  94. package/dist/gitbook/outline.d.ts +16 -0
  95. package/dist/gitbook/outline.d.ts.map +1 -0
  96. package/dist/gitbook/outline.js +44 -0
  97. package/dist/gitbook/outline.js.map +1 -0
  98. package/dist/gitbook/package-managers.d.ts +12 -0
  99. package/dist/gitbook/package-managers.d.ts.map +1 -0
  100. package/dist/gitbook/package-managers.js +93 -0
  101. package/dist/gitbook/package-managers.js.map +1 -0
  102. package/dist/gitbook/parse.d.ts +44 -0
  103. package/dist/gitbook/parse.d.ts.map +1 -0
  104. package/dist/gitbook/parse.js +1113 -0
  105. package/dist/gitbook/parse.js.map +1 -0
  106. package/dist/gitbook/serialize.d.ts +23 -0
  107. package/dist/gitbook/serialize.d.ts.map +1 -0
  108. package/dist/gitbook/serialize.js +498 -0
  109. package/dist/gitbook/serialize.js.map +1 -0
  110. package/dist/index.d.ts +34 -0
  111. package/dist/index.d.ts.map +1 -0
  112. package/dist/index.js +24 -0
  113. package/dist/index.js.map +1 -0
  114. package/dist/openapi/OpenApiOperation.d.ts +6 -0
  115. package/dist/openapi/OpenApiOperation.d.ts.map +1 -0
  116. package/dist/openapi/OpenApiOperation.js +112 -0
  117. package/dist/openapi/OpenApiOperation.js.map +1 -0
  118. package/dist/playground/InlineDemoPreview.d.ts +24 -0
  119. package/dist/playground/InlineDemoPreview.d.ts.map +1 -0
  120. package/dist/playground/InlineDemoPreview.js +48 -0
  121. package/dist/playground/InlineDemoPreview.js.map +1 -0
  122. package/dist/playground/PlaygroundStreamdown.d.ts +16 -0
  123. package/dist/playground/PlaygroundStreamdown.d.ts.map +1 -0
  124. package/dist/playground/PlaygroundStreamdown.js +22 -0
  125. package/dist/playground/PlaygroundStreamdown.js.map +1 -0
  126. package/dist/playground/ReactCodePreview.d.ts +64 -0
  127. package/dist/playground/ReactCodePreview.d.ts.map +1 -0
  128. package/dist/playground/ReactCodePreview.js +108 -0
  129. package/dist/playground/ReactCodePreview.js.map +1 -0
  130. package/dist/playground/filesystem.d.ts +27 -0
  131. package/dist/playground/filesystem.d.ts.map +1 -0
  132. package/dist/playground/filesystem.js +65 -0
  133. package/dist/playground/filesystem.js.map +1 -0
  134. package/dist/playground/index.d.ts +9 -0
  135. package/dist/playground/index.d.ts.map +1 -0
  136. package/dist/playground/index.js +7 -0
  137. package/dist/playground/index.js.map +1 -0
  138. package/dist/playground/project.d.ts +57 -0
  139. package/dist/playground/project.d.ts.map +1 -0
  140. package/dist/playground/project.js +137 -0
  141. package/dist/playground/project.js.map +1 -0
  142. package/dist/replay/NativeReplay.d.ts +9 -0
  143. package/dist/replay/NativeReplay.d.ts.map +1 -0
  144. package/dist/replay/NativeReplay.js +173 -0
  145. package/dist/replay/NativeReplay.js.map +1 -0
  146. package/dist/replay/ReplayPreview.d.ts +33 -0
  147. package/dist/replay/ReplayPreview.d.ts.map +1 -0
  148. package/dist/replay/ReplayPreview.js +91 -0
  149. package/dist/replay/ReplayPreview.js.map +1 -0
  150. package/dist/replay/index.d.ts +5 -0
  151. package/dist/replay/index.d.ts.map +1 -0
  152. package/dist/replay/index.js +4 -0
  153. package/dist/replay/index.js.map +1 -0
  154. package/dist/replay/sanitize.d.ts +4 -0
  155. package/dist/replay/sanitize.d.ts.map +1 -0
  156. package/dist/replay/sanitize.js +86 -0
  157. package/dist/replay/sanitize.js.map +1 -0
  158. package/dist/replay/url.d.ts +8 -0
  159. package/dist/replay/url.d.ts.map +1 -0
  160. package/dist/replay/url.js +41 -0
  161. package/dist/replay/url.js.map +1 -0
  162. package/dist/source/SourcePreview.d.ts +13 -0
  163. package/dist/source/SourcePreview.d.ts.map +1 -0
  164. package/dist/source/SourcePreview.js +47 -0
  165. package/dist/source/SourcePreview.js.map +1 -0
  166. package/dist/source/client.d.ts +7 -0
  167. package/dist/source/client.d.ts.map +1 -0
  168. package/dist/source/client.js +52 -0
  169. package/dist/source/client.js.map +1 -0
  170. package/dist/source/index.d.ts +6 -0
  171. package/dist/source/index.d.ts.map +1 -0
  172. package/dist/source/index.js +3 -0
  173. package/dist/source/index.js.map +1 -0
  174. package/dist/source/types.d.ts +33 -0
  175. package/dist/source/types.d.ts.map +1 -0
  176. package/dist/source/types.js +2 -0
  177. package/dist/source/types.js.map +1 -0
  178. package/dist/streamdown.d.ts +19 -0
  179. package/dist/streamdown.d.ts.map +1 -0
  180. package/dist/streamdown.js +15 -0
  181. package/dist/streamdown.js.map +1 -0
  182. package/dist/styles.css +2441 -0
  183. package/dist/video/VideoEmbed.d.ts +17 -0
  184. package/dist/video/VideoEmbed.d.ts.map +1 -0
  185. package/dist/video/VideoEmbed.js +12 -0
  186. package/dist/video/VideoEmbed.js.map +1 -0
  187. package/dist/video/index.d.ts +3 -0
  188. package/dist/video/index.d.ts.map +1 -0
  189. package/dist/video/index.js +2 -0
  190. package/dist/video/index.js.map +1 -0
  191. package/dist/vite/index.d.ts +21 -0
  192. package/dist/vite/index.d.ts.map +1 -0
  193. package/dist/vite/index.js +137 -0
  194. package/dist/vite/index.js.map +1 -0
  195. package/dist/viz/VizEmbed.d.ts +18 -0
  196. package/dist/viz/VizEmbed.d.ts.map +1 -0
  197. package/dist/viz/VizEmbed.js +12 -0
  198. package/dist/viz/VizEmbed.js.map +1 -0
  199. package/dist/viz/index.d.ts +4 -0
  200. package/dist/viz/index.d.ts.map +1 -0
  201. package/dist/viz/index.js +3 -0
  202. package/dist/viz/index.js.map +1 -0
  203. package/package.json +70 -34
  204. package/src/demo/types.ts +2 -2
  205. package/src/docs/DocsRenderer.tsx +31 -7
  206. package/src/docs/Mermaid.tsx +5 -3
  207. package/src/docs/math.ts +3 -0
  208. package/src/index.ts +10 -28
  209. package/src/playground/PlaygroundStreamdown.tsx +28 -61
  210. package/src/playground/filesystem.ts +10 -190
  211. package/src/playground/index.ts +4 -0
  212. package/src/playground/project.ts +194 -0
  213. package/src/replay/NativeReplay.tsx +236 -0
  214. package/src/replay/ReplayPreview.tsx +7 -226
  215. package/src/styles.css +91 -1
  216. package/src/replay/styles.css +0 -87
@@ -0,0 +1,956 @@
1
+ // Reference-style link definitions ([ref]: url), populated by parseMarkdown
2
+ // and consumed here for [text][ref] / [text][] / [text] forms. Keys are normalized labels.
3
+ export const refDefinitions = new Map();
4
+ // Footnote citation definitions ([^id]: url "Label"), populated by
5
+ // parseMarkdown and consumed here to resolve [^id] markers.
6
+ export const footnoteDefinitions = new Map();
7
+ /** A reference label as matched against definitions: case-insensitive, inner whitespace collapsed. */
8
+ export function normalizeLabel(label) {
9
+ return label.trim().replace(/\s+/g, " ").toLowerCase();
10
+ }
11
+ // @mention / #tag / $codebase body: letter/underscore start, then word chars, dots,
12
+ // dashes. Codebase ids may also carry `/` (`$org/repo`). A letter start keeps prices
13
+ // like $5 plain text.
14
+ const REFERENCE_BODY_RE = /^[@#]([A-Za-z_][\w.-]*)/;
15
+ const CODEBASE_BODY_RE = /^\$([A-Za-z_][\w.\/-]*)/;
16
+ const SIGIL_KIND = { "@": "mention", "#": "tag", $: "codebase" };
17
+ const KIND_SIGIL = { mention: "@", tag: "#", codebase: "$" };
18
+ // Chips are only recognized at start-of-input or after whitespace, open brackets or an emphasis marker,
19
+ // so brett@replay.io and C# stay plain text.
20
+ const isReferenceBoundary = (prev) => prev === undefined || /[\s([{*_~]/.test(prev);
21
+ function imgAttrs(attrStr) {
22
+ const attr = (name) => attrStr.match(new RegExp(`(?:^|\\s)${name}="([^"]*)"`, "i"))?.[1];
23
+ const out = { src: attr("src") ?? "" };
24
+ const alt = attr("alt");
25
+ const width = attr("width");
26
+ const height = attr("height");
27
+ if (alt)
28
+ out.alt = alt;
29
+ if (width)
30
+ out.width = width;
31
+ if (height)
32
+ out.height = height;
33
+ return out;
34
+ }
35
+ const LINKED_IMG_RE = /^<a\s[^>]*href="([^"]*)"[^>]*>\s*<img\s([^>]*?)\/?>\s*<\/a>/i;
36
+ const HTML_IMG_RE = /^<img\s([^>]*?)\/?>/i;
37
+ /** What an image's recorded HTML says — `null` when it isn't an (optionally linked) `<img>`. */
38
+ function htmlImage(html) {
39
+ const linked = html.match(LINKED_IMG_RE);
40
+ if (linked && linked[0] === html)
41
+ return { ...imgAttrs(linked[2]), link: linked[1] };
42
+ const bare = html.match(HTML_IMG_RE);
43
+ if (bare && bare[0] === html)
44
+ return imgAttrs(bare[1]);
45
+ return null;
46
+ }
47
+ const MARK_OF = {
48
+ "**": "bold",
49
+ __: "bold",
50
+ "*": "italic",
51
+ _: "italic",
52
+ "~~": "strike",
53
+ link: "link",
54
+ "<>": "link",
55
+ url: "link",
56
+ ref: "link",
57
+ collapsed: "link",
58
+ shortcut: "link",
59
+ html: "link",
60
+ };
61
+ const isLinkForm = (d) => MARK_OF[d] === "link";
62
+ /** Link forms whose text sits between `[` and `]`. */
63
+ const BRACKETED = new Set(["link", "ref", "collapsed", "shortcut"]);
64
+ const ASCII_PUNCT = /[!-/:-@[-`{-~]/;
65
+ const isSpace = (c) => c === undefined || /\s/.test(c);
66
+ const isPunct = (c) => c !== undefined && /[\p{P}\p{S}]/u.test(c);
67
+ /** CommonMark left-/right-flanking for a delimiter run between `prev` and `next` (undefined = line edge). */
68
+ function flanking(prev, next) {
69
+ return {
70
+ left: !isSpace(next) && (!isPunct(next) || isSpace(prev) || isPunct(prev)),
71
+ right: !isSpace(prev) && (!isPunct(prev) || isSpace(next) || isPunct(next)),
72
+ };
73
+ }
74
+ /** Can a run of `ch` between `prev` and `next` open / close emphasis? */
75
+ function openClose(ch, prev, next) {
76
+ const { left, right } = flanking(prev, next);
77
+ if (ch !== "_")
78
+ return { open: left, close: right };
79
+ // `_` never opens or closes intraword: snake_case stays literal.
80
+ return { open: left && (!right || isPunct(prev)), close: right && (!left || isPunct(next)) };
81
+ }
82
+ /** Start of the next backtick run of exactly `n` at or after `from`, or -1. */
83
+ function closingBackticks(src, from, n) {
84
+ let j = from;
85
+ while (j < src.length) {
86
+ const k = src.indexOf("`", j);
87
+ if (k === -1)
88
+ return -1;
89
+ let m = 1;
90
+ while (src[k + m] === "`")
91
+ m++;
92
+ if (m === n)
93
+ return k;
94
+ j = k + m;
95
+ }
96
+ return -1;
97
+ }
98
+ /** Index of the `]` closing the `[` at `start` — brackets nest, escapes and code spans are skipped — or -1. */
99
+ function closingBracket(src, start) {
100
+ let depth = 0;
101
+ for (let j = start; j < src.length; j++) {
102
+ const c = src[j];
103
+ if (c === "\\") {
104
+ j++;
105
+ continue;
106
+ }
107
+ if (c === "`") {
108
+ let n = 1;
109
+ while (src[j + n] === "`")
110
+ n++;
111
+ const end = closingBackticks(src, j + n, n);
112
+ j = (end === -1 ? j + n : end + n) - 1;
113
+ continue;
114
+ }
115
+ if (c === "[")
116
+ depth++;
117
+ else if (c === "]" && --depth === 0)
118
+ return j;
119
+ }
120
+ return -1;
121
+ }
122
+ /** `(url)` or `(url "title")` at the start of `rest`. */
123
+ const LINK_TAIL_RE = /^\(([^)\s]+)(?:[ \t]+"((?:\\.|[^"\\])*)")?\)/;
124
+ const HTML_LINK_RE = /^(<a\s[^>]*?href="([^"]*)"[^>]*>)(.*?)(<\/a\s*>)/i;
125
+ /** `inLink`: tokenizing link text, where CommonMark allows no further links (or autolinks). */
126
+ function tokenize(src, ctx, inLink = false) {
127
+ const toks = [];
128
+ let buf = "";
129
+ const flush = () => {
130
+ if (buf)
131
+ toks.push({ k: "text", text: buf });
132
+ buf = "";
133
+ };
134
+ const push = (t) => {
135
+ flush();
136
+ toks.push(t);
137
+ };
138
+ const wrap = (delim, link, inner) => ({
139
+ k: "wrap",
140
+ delim,
141
+ link,
142
+ children: typeof inner === "string" ? pairEmphasis(tokenize(inner, ctx, true)) : inner,
143
+ });
144
+ let i = 0;
145
+ while (i < src.length) {
146
+ const c = src[i];
147
+ const rest = src.slice(i);
148
+ // Backslash escapes: ASCII punctuation only; `C:\path` keeps its backslash. Escaped characters are their
149
+ // own run, so the author's backslashes come back even where they aren't needed.
150
+ if (c === "\\" && i + 1 < src.length && ASCII_PUNCT.test(src[i + 1])) {
151
+ flush();
152
+ const last = toks[toks.length - 1];
153
+ if (last?.k === "esc")
154
+ last.text += src[i + 1];
155
+ else
156
+ toks.push({ k: "esc", text: src[i + 1] });
157
+ i += 2;
158
+ continue;
159
+ }
160
+ // Code span: a backtick run closed by the next run of the same length. Content is kept verbatim.
161
+ if (c === "`") {
162
+ let n = 1;
163
+ while (src[i + n] === "`")
164
+ n++;
165
+ const end = closingBackticks(src, i + n, n);
166
+ if (end !== -1) {
167
+ push({ k: "code", text: src.slice(i + n, end), ...(n > 1 ? { ticks: n } : {}) });
168
+ i = end + n;
169
+ }
170
+ else {
171
+ buf += src.slice(i, i + n);
172
+ i += n;
173
+ }
174
+ continue;
175
+ }
176
+ if (c === "<") {
177
+ // <a href="…"><img …></a> — linked image (GitHub badge style)
178
+ const linkedImg = !inLink && rest.match(LINKED_IMG_RE);
179
+ if (linkedImg) {
180
+ push({ k: "atom", node: { type: "image", ...imgAttrs(linkedImg[2]), link: linkedImg[1], html: linkedImg[0] } });
181
+ i += linkedImg[0].length;
182
+ continue;
183
+ }
184
+ // <img …> — bare inline image
185
+ const htmlImg = rest.match(HTML_IMG_RE);
186
+ if (htmlImg) {
187
+ push({ k: "atom", node: { type: "image", ...imgAttrs(htmlImg[1]), html: htmlImg[0] } });
188
+ i += htmlImg[0].length;
189
+ continue;
190
+ }
191
+ // <a href="…">text</a> — html link, its opening tag kept as written
192
+ const htmlLink = !inLink && rest.match(HTML_LINK_RE);
193
+ if (htmlLink) {
194
+ push(wrap("html", { url: htmlLink[2], tag: htmlLink[1], ...(htmlLink[4] !== "</a>" ? { close: htmlLink[4] } : {}) }, htmlLink[3]));
195
+ i += htmlLink[0].length;
196
+ continue;
197
+ }
198
+ // <https://…> — angle-bracket autolink
199
+ const angleLink = !inLink && rest.match(/^<(https?:\/\/[^>\s]+)>/i);
200
+ if (angleLink) {
201
+ push(wrap("<>", { url: angleLink[1] }, [{ k: "text", text: angleLink[1] }]));
202
+ i += angleLink[0].length;
203
+ continue;
204
+ }
205
+ }
206
+ // ![alt](src "title") — markdown inline image; ![alt][ref] / ![alt][] / ![alt] through a definition
207
+ if (c === "!" && src[i + 1] === "[") {
208
+ const close = closingBracket(src, i + 1);
209
+ const tail = close !== -1 ? src.slice(close + 1).match(LINK_TAIL_RE) : null;
210
+ const alt = close !== -1 ? src.slice(i + 2, close) : "";
211
+ if (close !== -1 && !tail) {
212
+ const ref = src.slice(close + 1).match(/^\[((?:\\.|[^\]\\])*)\]/);
213
+ const label = ref ? ref[1] || alt : alt;
214
+ const url = label.trim() ? ctx.defs.get(normalizeLabel(label)) : undefined;
215
+ if (url !== undefined) {
216
+ const srcForm = ref ? (ref[1] ? "ref" : "collapsed") : "shortcut";
217
+ push({
218
+ k: "atom",
219
+ node: { type: "image", src: url, ...(alt ? { alt } : {}), syntax: "markdown", srcForm, srcRef: label },
220
+ });
221
+ i = close + 1 + (ref ? ref[0].length : 0);
222
+ continue;
223
+ }
224
+ }
225
+ if (tail) {
226
+ push({
227
+ k: "atom",
228
+ node: {
229
+ type: "image",
230
+ src: tail[1],
231
+ ...(alt ? { alt } : {}),
232
+ ...(tail[2] !== undefined ? { title: tail[2] } : {}),
233
+ syntax: "markdown",
234
+ },
235
+ });
236
+ i = close + 1 + tail[0].length;
237
+ continue;
238
+ }
239
+ }
240
+ if (c === "[") {
241
+ // [^id] — footnote citation marker
242
+ const cite = rest.match(/^\[\^([^\]\s]+)\]/);
243
+ if (cite) {
244
+ const def = footnoteDefinitions.get(cite[1]);
245
+ push({ k: "atom", node: { type: "reference", kind: "citation", id: cite[1], ...def } });
246
+ i += cite[0].length;
247
+ continue;
248
+ }
249
+ const close = inLink ? -1 : closingBracket(src, i);
250
+ if (close !== -1) {
251
+ const text = src.slice(i + 1, close);
252
+ const after = src.slice(close + 1);
253
+ // [text](url "title") — link text is its own emphasis scope, as in CommonMark
254
+ const tail = after.match(LINK_TAIL_RE);
255
+ if (tail) {
256
+ push(wrap("link", { url: tail[1], ...(tail[2] !== undefined ? { title: tail[2] } : {}) }, text));
257
+ i = close + 1 + tail[0].length;
258
+ continue;
259
+ }
260
+ // [text][ref] / [text][] — reference-style links
261
+ const ref = after.match(/^\[((?:\\.|[^\]\\])*)\]/);
262
+ const refUrl = ref ? ctx.defs.get(normalizeLabel(ref[1] || text)) : undefined;
263
+ if (ref && refUrl !== undefined) {
264
+ push(wrap(ref[1] ? "ref" : "collapsed", { url: refUrl, ref: ref[1] || text }, text));
265
+ i = close + 1 + ref[0].length;
266
+ continue;
267
+ }
268
+ // [ref] — shortcut reference link
269
+ const shortcut = text.trim() ? ctx.defs.get(normalizeLabel(text)) : undefined;
270
+ if (shortcut !== undefined) {
271
+ push(wrap("shortcut", { url: shortcut, ref: text }, text));
272
+ i = close + 1;
273
+ continue;
274
+ }
275
+ }
276
+ }
277
+ // @mention / #tag / $codebase chips, only at a word boundary
278
+ if ((c === "@" || c === "#" || c === "$") && isReferenceBoundary(src[i - 1])) {
279
+ const m = rest.match(c === "$" ? CODEBASE_BODY_RE : REFERENCE_BODY_RE);
280
+ if (m) {
281
+ const id = m[1].replace(/[./-]+$/, "");
282
+ if (id) {
283
+ push({ k: "atom", node: { type: "reference", kind: SIGIL_KIND[c], id } });
284
+ i += 1 + id.length;
285
+ continue;
286
+ }
287
+ }
288
+ }
289
+ // bare URL autolink (GFM) — at start, after whitespace or an emphasis marker
290
+ if (!inLink && (c === "h" || c === "H") && /^https?:\/\//i.test(rest) && (i === 0 || /[\s*_~]/.test(src[i - 1]))) {
291
+ const m = rest.match(/^https?:\/\/[^\s<>"')\]]+/i);
292
+ const url = m[0].replace(/[.,;:!?*_~]+$/, "");
293
+ push(wrap("url", { url }, [{ k: "text", text: url }]));
294
+ i += url.length;
295
+ continue;
296
+ }
297
+ // Delimiter runs: any run of `*` / `_`, exactly two `~`.
298
+ if (c === "*" || c === "_" || (c === "~" && src[i + 1] === "~")) {
299
+ let n = 1;
300
+ while (src[i + n] === c)
301
+ n++;
302
+ if (c === "~" && n !== 2) {
303
+ buf += src.slice(i, i + n);
304
+ i += n;
305
+ continue;
306
+ }
307
+ push({ k: "delim", ch: c, n, orig: n, ...openClose(c, src[i - 1], src[i + n]) });
308
+ i += n;
309
+ continue;
310
+ }
311
+ buf += c;
312
+ i++;
313
+ }
314
+ flush();
315
+ return toks;
316
+ }
317
+ /** Leftover delimiter runs become literal text; adjacent text merges. */
318
+ function settle(toks) {
319
+ const out = [];
320
+ for (const t of toks) {
321
+ const tree = t.k === "delim" ? { k: "text", text: t.ch.repeat(t.n) } : t;
322
+ const last = out[out.length - 1];
323
+ if (tree.k === "text" && last?.k === "text")
324
+ out[out.length - 1] = { k: "text", text: last.text + tree.text };
325
+ else if (tree.k !== "text" || tree.text)
326
+ out.push(tree);
327
+ }
328
+ return out;
329
+ }
330
+ /** CommonMark "process emphasis": pair closers with the nearest eligible opener, innermost first. */
331
+ function pairEmphasis(toks) {
332
+ let ci = 0;
333
+ while (ci < toks.length) {
334
+ const closer = toks[ci];
335
+ if (closer.k !== "delim" || !closer.close || closer.n === 0) {
336
+ ci++;
337
+ continue;
338
+ }
339
+ let oi = ci - 1;
340
+ for (; oi >= 0; oi--) {
341
+ const o = toks[oi];
342
+ if (o.k !== "delim" || o.ch !== closer.ch || !o.open || o.n === 0)
343
+ continue;
344
+ if (closer.ch === "~") {
345
+ if (o.n === 2 && closer.n === 2)
346
+ break;
347
+ continue;
348
+ }
349
+ // Rule of 3: a run that can both open and close can't pair with one whose lengths sum to a multiple of 3.
350
+ const both = o.close || closer.open;
351
+ if (both && (o.orig + closer.orig) % 3 === 0 && !(o.orig % 3 === 0 && closer.orig % 3 === 0))
352
+ continue;
353
+ break;
354
+ }
355
+ if (oi < 0) {
356
+ ci++;
357
+ continue;
358
+ }
359
+ const opener = toks[oi];
360
+ const use = closer.ch === "~" ? 2 : opener.n >= 2 && closer.n >= 2 ? 2 : 1;
361
+ const delim = (closer.ch === "~" ? "~~" : closer.ch.repeat(use));
362
+ const node = { k: "wrap", delim, children: settle(toks.slice(oi + 1, ci)) };
363
+ opener.n -= use;
364
+ closer.n -= use;
365
+ const replaced = [...(opener.n > 0 ? [opener] : []), node, ...(closer.n > 0 ? [closer] : [])];
366
+ toks.splice(oi, ci - oi + 1, ...replaced);
367
+ // Continue with the closer's remainder (it may close an outer opener too), or what follows it.
368
+ ci = oi + replaced.length - (closer.n > 0 ? 1 : 0);
369
+ }
370
+ return settle(toks);
371
+ }
372
+ /** Is the inline's `link` a link mark (rather than the HTML anchor of an `<a href><img></a>` image)? */
373
+ function linkIsMark(n) {
374
+ if (!n.link)
375
+ return false;
376
+ return n.type !== "image" || !!n.delims?.some(isLinkForm);
377
+ }
378
+ /** The spelling an inline gets when nothing says otherwise — what serialization produced before `delims`. */
379
+ function defaultDelims(n) {
380
+ const emphasis = [];
381
+ if (n.strike)
382
+ emphasis.push("~~");
383
+ if (n.italic)
384
+ emphasis.push("_");
385
+ if (n.bold)
386
+ emphasis.push("**");
387
+ // An image's link is its HTML anchor unless the spelling says otherwise.
388
+ if (!n.link || n.type === "image")
389
+ return emphasis;
390
+ const inner = !!n.linkInner && emphasis.length > 0;
391
+ const form = n.type === "text" && (inner || !emphasis.length) && n.text === n.link && !n.code && !n.escaped ? "url" : "link";
392
+ return inner ? [...emphasis, form] : [form, ...emphasis];
393
+ }
394
+ const sameList = (a, b) => a.length === b.length && a.every((x, k) => x === b[k]);
395
+ function applyPath(n, path, state) {
396
+ let emphasis = false;
397
+ for (const e of path) {
398
+ const mark = MARK_OF[e.delim];
399
+ if (mark === "link") {
400
+ const link = e.link;
401
+ n.link = link.url;
402
+ if (link.title !== undefined)
403
+ n.linkTitle = link.title;
404
+ if (link.ref !== undefined)
405
+ n.linkRef = link.ref;
406
+ if (link.tag !== undefined)
407
+ n.linkTag = link.tag;
408
+ if (link.close !== undefined)
409
+ n.linkTagEnd = link.close;
410
+ if (emphasis)
411
+ n.linkInner = true;
412
+ state.linkSeq.set(n, e.seq);
413
+ }
414
+ else {
415
+ n[mark] = true;
416
+ emphasis = true;
417
+ }
418
+ }
419
+ const delims = path.map((e) => e.delim);
420
+ if (!sameList(delims, defaultDelims(n)))
421
+ n.delims = delims;
422
+ }
423
+ function flatten(trees, path, base, out, state) {
424
+ for (const t of trees) {
425
+ if (t.k === "wrap") {
426
+ const entry = { delim: t.delim, ...(t.link ? { link: t.link, seq: ++state.seq } : {}) };
427
+ const before = out.length;
428
+ flatten(t.children, [...path, entry], base, out, state);
429
+ // An empty link (`[](url)`) is an empty run, so it isn't lost.
430
+ if (t.link && out.length === before) {
431
+ const n = { type: "text", text: "", ...base };
432
+ applyPath(n, [...path, entry], state);
433
+ out.push(n);
434
+ }
435
+ continue;
436
+ }
437
+ if (t.k === "atom") {
438
+ const node = { ...t.node };
439
+ applyPath(node, path, state);
440
+ out.push(node);
441
+ continue;
442
+ }
443
+ const n = { type: "text", text: t.text, ...base };
444
+ if (t.k === "code") {
445
+ n.code = true;
446
+ if (t.ticks && t.ticks !== codeSpanTicks(t.text))
447
+ n.ticks = t.ticks;
448
+ }
449
+ if (t.k === "esc")
450
+ n.escaped = true;
451
+ applyPath(n, path, state);
452
+ out.push(n);
453
+ }
454
+ }
455
+ /** What makes two link runs the same link to the serializer (everything but identity). */
456
+ function linkKey(n) {
457
+ const form = inlineDelims(n).find(isLinkForm) ?? "";
458
+ return JSON.stringify([n.link, form, n.linkTitle ?? null, n.linkRef ?? null, n.linkTag ?? null, n.linkTagEnd ?? null]);
459
+ }
460
+ /** Adjacent links to the same target stay distinct: the later one gets a `linkId`. */
461
+ function markLinkIdentity(out, state) {
462
+ let next = 1;
463
+ const ids = new Map();
464
+ for (let k = 1; k < out.length; k++) {
465
+ const a = out[k - 1];
466
+ const b = out[k];
467
+ const sa = state.linkSeq.get(a);
468
+ const sb = state.linkSeq.get(b);
469
+ if (sa === undefined || sb === undefined || sa === sb || !linkIsMark(a) || !linkIsMark(b))
470
+ continue;
471
+ if (a.linkId !== undefined || linkKey(a) !== linkKey(b))
472
+ continue;
473
+ if (!ids.has(sb))
474
+ ids.set(sb, next++);
475
+ }
476
+ if (!ids.size)
477
+ return;
478
+ for (const n of out) {
479
+ const id = ids.get(state.linkSeq.get(n) ?? -1);
480
+ if (id !== undefined)
481
+ n.linkId = id;
482
+ }
483
+ }
484
+ function parseWith(src, ctx, marks = {}) {
485
+ const out = [];
486
+ const state = { seq: 0, linkSeq: new Map() };
487
+ flatten(pairEmphasis(tokenize(src, ctx)), [], marks, out, state);
488
+ markLinkIdentity(out, state);
489
+ return out;
490
+ }
491
+ // Parses GitBook/GFM inline markdown into flat inlines with marks.
492
+ // Supported: **bold** / __bold__, _italic_ / *italic*, ***both***, ~~strike~~, `code`, [text](url "title"),
493
+ // [text][ref] / [text][] / [ref], <url> and bare URLs, @mention / #tag / $codebase / [^n] chips,
494
+ // ![alt](src "title"), backslash escapes, plus GitHub-style inline HTML: <img …>, <a href><img …></a>,
495
+ // <a href>text</a>.
496
+ export function parseInline(src, marks = {}) {
497
+ return parseWith(src, { defs: refDefinitions }, marks);
498
+ }
499
+ // ─── Serializing ─────────────────────────────────────────────────────────────────────────────────────────
500
+ /**
501
+ * Definitions of the document being serialized (normalized label → url), so reference-style links whose
502
+ * target no longer matches their definition fall back to inline links. Set by `serializeMarkdown`.
503
+ */
504
+ let documentDefs = null;
505
+ /** Run `fn` with the document's reference definitions in scope for `serializeInline`. */
506
+ export function withDocumentDefinitions(defs, fn) {
507
+ const saved = documentDefs;
508
+ documentDefs = defs;
509
+ try {
510
+ return fn();
511
+ }
512
+ finally {
513
+ documentDefs = saved;
514
+ }
515
+ }
516
+ export function serializeReference(n) {
517
+ if (n.kind === "citation")
518
+ return `[^${n.id}]`;
519
+ return `${KIND_SIGIL[n.kind]}${n.id}`;
520
+ }
521
+ function serializeImage(n, atomicLink) {
522
+ const link = atomicLink ? n.link : undefined;
523
+ const alt = n.alt ?? "";
524
+ if (n.syntax === "markdown" && !link && !alt.includes("\n") && closingBracket(`[${alt}]`, 0) === alt.length + 1) {
525
+ // Through its definition, while that still gives this source.
526
+ if (n.srcForm && n.srcRef !== undefined && (!documentDefs || documentDefs.get(normalizeLabel(n.srcRef)) === n.src)) {
527
+ if (n.srcForm === "ref")
528
+ return `![${alt}][${n.srcRef}]`;
529
+ const same = normalizeLabel(unescapeLabel(n.srcRef)) === normalizeLabel(unescapeLabel(alt));
530
+ return !same ? `![${alt}][${n.srcRef}]` : n.srcForm === "collapsed" ? `![${alt}][]` : `![${alt}]`;
531
+ }
532
+ return `![${alt}](${n.src}${n.title !== undefined ? ` "${n.title}"` : ""})`;
533
+ }
534
+ if (n.html) {
535
+ const said = htmlImage(n.html);
536
+ if (said &&
537
+ said.src === n.src &&
538
+ (said.alt ?? "") === (n.alt ?? "") &&
539
+ (said.width ?? "") === (n.width ?? "") &&
540
+ (said.height ?? "") === (n.height ?? "") &&
541
+ (said.link ?? "") === (link ?? "")) {
542
+ return n.html;
543
+ }
544
+ }
545
+ const attrs = [
546
+ `src="${n.src}"`,
547
+ n.alt ? `alt="${n.alt}"` : "",
548
+ n.width ? `width="${n.width}"` : "",
549
+ n.height ? `height="${n.height}"` : "",
550
+ ]
551
+ .filter(Boolean)
552
+ .join(" ");
553
+ const img = `<img ${attrs} />`;
554
+ return link ? `<a href="${link}">${img}</a>` : img;
555
+ }
556
+ /** The inline's formatting, outermost → innermost: its `delims` hint reconciled with its actual marks. */
557
+ function entriesFor(n) {
558
+ const fallback = defaultDelims(n);
559
+ let delims = fallback;
560
+ if (n.delims?.length) {
561
+ const has = (m) => (m === "link" ? linkIsMark(n) : !!n[m]);
562
+ delims = [];
563
+ for (const d of n.delims) {
564
+ const m = MARK_OF[d];
565
+ if (m && has(m) && !(m === "link" && delims.some(isLinkForm)))
566
+ delims.push(d);
567
+ }
568
+ // Marks without a spelling: emphasis innermost (inside a trailing link), the link where it defaults to.
569
+ for (const d of fallback) {
570
+ const m = MARK_OF[d];
571
+ if (delims.some((x) => MARK_OF[x] === m))
572
+ continue;
573
+ if (m === "link") {
574
+ if (n.linkInner)
575
+ delims.push(d);
576
+ else
577
+ delims.unshift(d);
578
+ }
579
+ else {
580
+ const last = delims[delims.length - 1];
581
+ delims.splice(last && isLinkForm(last) ? delims.length - 1 : delims.length, 0, d);
582
+ }
583
+ }
584
+ delims = delims.map((d, k) => validForm(n, d, k === delims.length - 1));
585
+ }
586
+ return delims.map((delim) => {
587
+ if (!isLinkForm(delim))
588
+ return { mark: MARK_OF[delim], delim };
589
+ const e = { mark: "link", delim, url: n.link };
590
+ if (delim === "link" && n.linkTitle !== undefined)
591
+ e.title = n.linkTitle;
592
+ if ((delim === "ref" || delim === "collapsed" || delim === "shortcut") && n.linkRef !== undefined)
593
+ e.ref = n.linkRef;
594
+ if (delim === "html") {
595
+ e.tag = n.linkTag;
596
+ if (n.linkTagEnd && /^<\/a\s*>$/i.test(n.linkTagEnd))
597
+ e.tagEnd = n.linkTagEnd;
598
+ }
599
+ if (n.linkId !== undefined)
600
+ e.id = n.linkId;
601
+ return e;
602
+ });
603
+ }
604
+ /** A link form the inline can still be written in, or `"link"`. */
605
+ function validForm(n, d, innermost) {
606
+ switch (d) {
607
+ // An autolink shows its URL as its text: only valid innermost, around an unchanged URL.
608
+ case "url":
609
+ case "<>":
610
+ return innermost && n.type === "text" && n.text === n.link && !n.code && !n.escaped ? d : "link";
611
+ case "ref":
612
+ case "collapsed":
613
+ case "shortcut": {
614
+ if (n.linkRef === undefined || (d === "ref" && !n.linkRef))
615
+ return "link";
616
+ // The definition must still say this target.
617
+ const defined = documentDefs?.get(normalizeLabel(n.linkRef));
618
+ return documentDefs && defined !== n.link ? "link" : d;
619
+ }
620
+ case "html":
621
+ return n.linkTag ? d : "link";
622
+ default:
623
+ return d;
624
+ }
625
+ }
626
+ /**
627
+ * The spelling `serializeInline` gives an inline, outermost → innermost: its `delims` hint reconciled with
628
+ * its marks. Editors can store it per mark and hand it back as `delims`.
629
+ */
630
+ export function inlineDelims(n) {
631
+ return entriesFor(n).map((e) => e.delim);
632
+ }
633
+ /** The spelling an inline gets without a `delims` hint; a hint equal to it can be dropped. */
634
+ export function defaultInlineDelims(n) {
635
+ return defaultDelims(n);
636
+ }
637
+ const sameEntry = (a, b) => a.delim === b.delim && a.url === b.url && a.title === b.title && a.ref === b.ref && a.tag === b.tag && a.tagEnd === b.tagEnd && a.id === b.id;
638
+ /** An HTML link's opening tag, its `href` updated if the link target changed. */
639
+ function htmlTag(e) {
640
+ const tag = e.tag;
641
+ const href = tag.match(/\shref="([^"]*)"/i);
642
+ if (!href || href[1] === e.url)
643
+ return tag;
644
+ return tag.replace(/(\shref=")[^"]*(")/i, `$1${e.url}$2`);
645
+ }
646
+ function opener(e) {
647
+ if (e.mark !== "link")
648
+ return e.delim;
649
+ if (BRACKETED.has(e.delim))
650
+ return "[";
651
+ if (e.delim === "<>")
652
+ return "<";
653
+ if (e.delim === "html")
654
+ return htmlTag(e);
655
+ return "";
656
+ }
657
+ /** `text`: the link text as written (unescaped), for the collapsed / shortcut forms whose label it is. */
658
+ function closer(e, text) {
659
+ if (e.mark !== "link")
660
+ return e.delim;
661
+ switch (e.delim) {
662
+ case "link":
663
+ return `](${e.url}${e.title !== undefined ? ` "${e.title}"` : ""})`;
664
+ case "ref":
665
+ return `][${e.ref}]`;
666
+ case "collapsed":
667
+ case "shortcut": {
668
+ // The text is the label: if it was edited, name the label explicitly.
669
+ const same = normalizeLabel(unescapeLabel(e.ref ?? "")) === normalizeLabel(unescapeLabel(text));
670
+ return !same ? `][${e.ref}]` : e.delim === "collapsed" ? "][]" : "]";
671
+ }
672
+ case "<>":
673
+ return ">";
674
+ case "html":
675
+ return e.tagEnd ?? "</a>";
676
+ default:
677
+ return "";
678
+ }
679
+ }
680
+ const unescapeLabel = (s) => s.replace(/\\([!-/:-@[-`{-~])/g, "$1");
681
+ /** The fewest backticks that can fence `t`. */
682
+ function codeSpanTicks(t) {
683
+ const runs = new Set((t.match(/`+/g) ?? []).map((r) => r.length));
684
+ let n = 1;
685
+ while (runs.has(n))
686
+ n++;
687
+ return n;
688
+ }
689
+ /** `t` as a code span: `ticks` backticks when given and usable, else the fewest that work. */
690
+ function codeSpan(t, ticks) {
691
+ const runs = new Set((t.match(/`+/g) ?? []).map((r) => r.length));
692
+ const n = ticks && ticks > 0 && !runs.has(ticks) ? ticks : codeSpanTicks(t);
693
+ const fence = "`".repeat(n);
694
+ const pad = t.startsWith("`") || t.endsWith("`") ? " " : "";
695
+ return `${fence}${pad}${t}${pad}${fence}`;
696
+ }
697
+ /**
698
+ * Does literal `raw[i]` need a backslash? `minimal` escapes only what would otherwise parse as syntax —
699
+ * judged against the whole rendered line, so snake_case, `2 * 3`, `~5 min` and `a [b] c` stay as written.
700
+ */
701
+ function needsEscape(r, i, minimal, defs) {
702
+ const { raw, text, bracket } = r;
703
+ const c = raw[i];
704
+ switch (c) {
705
+ case "\\":
706
+ // A backslash ending the line (a hard break) stays as it is.
707
+ return !minimal || (i + 1 < raw.length && ASCII_PUNCT.test(raw[i + 1]));
708
+ case "`":
709
+ return true;
710
+ case "[": {
711
+ if (!minimal || bracket[i] === 1 || raw[i + 1] === "^")
712
+ return true;
713
+ if (/^\[(?:\\.|[^\]\\])*\][([]/.test(raw.slice(i)))
714
+ return true;
715
+ // `[label]` that a definition would turn into a shortcut link
716
+ const close = closingBracket(raw, i);
717
+ return close !== -1 && defs.has(normalizeLabel(raw.slice(i + 1, close)));
718
+ }
719
+ case "]":
720
+ return !minimal || bracket[i] === 1;
721
+ case "@":
722
+ case "#":
723
+ case "$":
724
+ // Only at a chip boundary, so emails / C# / $5 survive untouched — and not before an escaped character.
725
+ return isReferenceBoundary(raw[i - 1]) && /[A-Za-z_]/.test(raw[i + 1] ?? "") && !r.forced[i + 1];
726
+ case "*":
727
+ case "_":
728
+ case "~": {
729
+ if (!minimal)
730
+ return true;
731
+ let s = i;
732
+ let e = i + 1;
733
+ while (s > 0 && raw[s - 1] === c)
734
+ s--;
735
+ while (e < raw.length && raw[e] === c)
736
+ e++;
737
+ for (let k = s; k < e; k++)
738
+ if (!text[k])
739
+ return true; // touches a marker: always escape
740
+ if (c === "~")
741
+ return e - s >= 2;
742
+ const { open, close } = openClose(c, raw[s - 1], raw[e]);
743
+ return open || close;
744
+ }
745
+ }
746
+ return false;
747
+ }
748
+ function layout(pieces) {
749
+ const raw = pieces.map((p) => p.s).join("");
750
+ const text = new Uint8Array(raw.length);
751
+ const bracket = new Uint8Array(raw.length);
752
+ const forced = new Uint8Array(raw.length);
753
+ const bare = new Uint8Array(raw.length);
754
+ let at = 0;
755
+ // Brackets in link text must balance, or the link text ends early: escape the ones that don't.
756
+ let linkOpen = null;
757
+ for (const p of pieces) {
758
+ if (p.linkStart)
759
+ linkOpen = [];
760
+ if (p.text)
761
+ text.fill(1, at, at + p.s.length);
762
+ if (p.forced) {
763
+ for (let k = 0; k < p.s.length; k++)
764
+ if (ASCII_PUNCT.test(p.s[k]))
765
+ forced[at + k] = 1;
766
+ }
767
+ for (const k of p.barePipes ?? [])
768
+ bare[at + k] = 1;
769
+ if (linkOpen && p.text && !p.forced) {
770
+ for (let k = 0; k < p.s.length; k++) {
771
+ if (p.s[k] === "[")
772
+ linkOpen.push(at + k);
773
+ else if (p.s[k] === "]") {
774
+ if (linkOpen.length)
775
+ linkOpen.pop();
776
+ else
777
+ bracket[at + k] = 1;
778
+ }
779
+ }
780
+ }
781
+ if (p.linkEnd && linkOpen) {
782
+ for (const k of linkOpen)
783
+ bracket[k] = 1;
784
+ linkOpen = null;
785
+ }
786
+ at += p.s.length;
787
+ }
788
+ return { raw, text, bracket, forced, bare };
789
+ }
790
+ function render(input, minimal, defs) {
791
+ const { raw, text, forced } = input;
792
+ const escape = new Uint8Array(raw.length);
793
+ for (let i = 0; i < raw.length; i++) {
794
+ if (forced[i])
795
+ escape[i] = 1;
796
+ else if (text[i] && /[\\`*_~[\]@#$]/.test(raw[i]) && needsEscape(input, i, minimal, defs))
797
+ escape[i] = 1;
798
+ }
799
+ return { ...input, escape };
800
+ }
801
+ /** The written line; `table`: inside a GFM table cell, where pipes are escaped unless written bare. */
802
+ const written = ({ raw, escape, bare }, table = false) => {
803
+ let out = "";
804
+ for (let i = 0; i < raw.length; i++) {
805
+ if (escape[i])
806
+ out += `\\${raw[i]}`;
807
+ else if (table && raw[i] === "|" && !bare[i])
808
+ out += "\\|";
809
+ else
810
+ out += raw[i];
811
+ }
812
+ return out;
813
+ };
814
+ /**
815
+ * Drop backslashes the line can do without — `footnote*`, a lone backtick, `[x]` before a `(` that isn't a
816
+ * link. An escaped run is dropped only if the line still means `want` with every escaped run of the same
817
+ * character unescaped too, so paired escapes (`\*x\*`, `` \`x\` ``) keep both backslashes. The source's own
818
+ * escapes are never dropped.
819
+ */
820
+ function relax(r, want, ctx) {
821
+ const groups = [];
822
+ for (let i = 0; i < r.raw.length; i++) {
823
+ const ch = r.raw[i];
824
+ if (!r.escape[i] || r.forced[i] || !/[`*_~[]/.test(ch))
825
+ continue;
826
+ let end = i + 1;
827
+ if (ch !== "`" && ch !== "[")
828
+ while (r.escape[end] && !r.forced[end] && r.raw[end] === ch)
829
+ end++;
830
+ groups.push({ start: i, end, ch });
831
+ i = end - 1;
832
+ }
833
+ if (!groups.length || groups.length > 32)
834
+ return r;
835
+ const without = (drop) => {
836
+ const escape = r.escape.slice();
837
+ for (const g of groups)
838
+ if (drop(g))
839
+ escape.fill(0, g.start, g.end);
840
+ return { ...r, escape };
841
+ };
842
+ const droppable = new Set(groups.filter((g) => meaning(parseWith(written(without((o) => o.ch === g.ch)), ctx)) === want));
843
+ if (!droppable.size)
844
+ return r;
845
+ const relaxed = without((g) => droppable.has(g));
846
+ return meaning(parseWith(written(relaxed), ctx)) === want ? relaxed : r;
847
+ }
848
+ const marksKey = (n) => [!!n.bold, !!n.italic, !!n.strike, linkIsMark(n) ? n.link : ""];
849
+ /** What a line means, ignoring spelling: merged text runs with their marks, plus atoms with theirs. */
850
+ function meaning(nodes) {
851
+ const parts = [];
852
+ let last;
853
+ for (const n of nodes) {
854
+ if (n.type !== "text") {
855
+ last = undefined;
856
+ parts.push(n.type === "image"
857
+ ? ["img", n.src, linkIsMark(n) ? "" : (n.link ?? ""), ...marksKey(n)]
858
+ : ["ref", n.kind, n.id, ...marksKey(n)]);
859
+ continue;
860
+ }
861
+ if (!n.text)
862
+ continue;
863
+ const key = JSON.stringify([...marksKey(n), !!n.code]);
864
+ if (last && last.key === key && !n.code)
865
+ last.text += n.text;
866
+ else
867
+ parts.push((last = { key, text: n.text }));
868
+ }
869
+ return JSON.stringify(parts);
870
+ }
871
+ // Serializes inline nodes back to markdown, as written: each inline's `delims` restores the author's markers,
872
+ // nesting and link form, and formatting shared by adjacent inlines (chips and images included) is opened once
873
+ // — `**bold [link](url) inside**`, `**ask @brett**` and `[**a** b](url)` come back whole instead of per run.
874
+ // Text is escaped minimally (plus every escape the source wrote); if the minimal form would parse back
875
+ // differently, everything escapable is escaped instead.
876
+ export function serializeInline(nodes, options = {}) {
877
+ const pieces = [];
878
+ const open = [];
879
+ // Reference forms resolve through definitions: the document's, else the ones the links themselves imply.
880
+ const defs = new Map(documentDefs ?? []);
881
+ const closeTo = (depth) => {
882
+ while (open.length > depth) {
883
+ const e = open.pop();
884
+ const text = pieces.slice(e.start + 1).map((p) => p.s).join("");
885
+ pieces.push({ s: closer(e, text), ...(BRACKETED.has(e.delim) ? { linkEnd: true } : {}) });
886
+ }
887
+ };
888
+ for (const n of nodes) {
889
+ if (n.type === "text" && !n.text && !linkIsMark(n))
890
+ continue;
891
+ const wanted = entriesFor(n);
892
+ for (const e of wanted) {
893
+ if (e.ref !== undefined && e.url !== undefined && !defs.has(normalizeLabel(e.ref)))
894
+ defs.set(normalizeLabel(e.ref), e.url);
895
+ }
896
+ if (n.type === "image" && n.srcRef !== undefined && !defs.has(normalizeLabel(n.srcRef)))
897
+ defs.set(normalizeLabel(n.srcRef), n.src);
898
+ // Keep the open formatting this inline still wants (outermost first), close the rest, open what's missing.
899
+ const missing = [...wanted];
900
+ let keep = 0;
901
+ for (; keep < open.length; keep++) {
902
+ const k = missing.findIndex((w) => sameEntry(w, open[keep]));
903
+ if (k < 0)
904
+ break;
905
+ missing.splice(k, 1);
906
+ }
907
+ closeTo(keep);
908
+ for (const w of missing) {
909
+ open.push({ ...w, start: pieces.length });
910
+ pieces.push({ s: opener(w), ...(BRACKETED.has(w.delim) ? { linkStart: true } : {}) });
911
+ }
912
+ const inLink = open.some((e) => BRACKETED.has(e.delim));
913
+ const innermost = open[open.length - 1];
914
+ if (n.type === "image")
915
+ pieces.push({ s: serializeImage(n, !linkIsMark(n)) });
916
+ else if (n.type === "reference")
917
+ pieces.push({ s: serializeReference(n) });
918
+ else if (n.code) {
919
+ const s = codeSpan(n.text, n.ticks);
920
+ const pad = s.indexOf(n.text);
921
+ pieces.push({ s, ...(n.barePipes?.length ? { barePipes: n.barePipes.map((k) => k + pad) } : {}) });
922
+ }
923
+ else if (n.escaped)
924
+ pieces.push({ s: n.text, text: true, forced: true, inLink });
925
+ else if (innermost?.mark === "link" && (innermost.delim === "url" || innermost.delim === "<>"))
926
+ pieces.push({ s: n.text }); // autolink
927
+ else
928
+ pieces.push({ s: n.text, text: true, inLink });
929
+ }
930
+ closeTo(0);
931
+ const ctx = { defs };
932
+ const table = !!options.tableCell;
933
+ const input = layout(pieces);
934
+ const minimal = render(input, true, defs);
935
+ if (!pieces.some((p) => p.text))
936
+ return written(minimal, table);
937
+ const want = meaning(nodes);
938
+ if (meaning(parseWith(written(minimal), ctx)) === want)
939
+ return written(relax(minimal, want, ctx), table);
940
+ const full = render(input, false, defs);
941
+ return meaning(parseWith(written(full), ctx)) === want ? written(full, table) : written(minimal, table);
942
+ }
943
+ export function plainText(nodes) {
944
+ return nodes
945
+ .map((n) => {
946
+ if (n.type === "text")
947
+ return n.text;
948
+ if (n.type === "reference") {
949
+ // Citation superscripts are visual noise in plain text (titles etc.).
950
+ return n.kind === "citation" ? "" : serializeReference(n);
951
+ }
952
+ return n.alt ?? "";
953
+ })
954
+ .join("");
955
+ }
956
+ //# sourceMappingURL=inline.js.map