@haklex/rich-litexml 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,4 +1,1142 @@
1
- import { a as registerCustomWriters, c as registerCustomReaders, i as createDefaultRegistry, l as registerBuiltinReaders, n as deserializeNodesFromXml, o as registerBuiltinWriters, r as wrapWithFormatTags, s as LitexmlRegistry, t as deserializeFromXml } from "./deserializer-CfdlNh2e.js";
1
+ import { customAlphabet } from "nanoid";
2
+ import { parseHTML } from "linkedom";
3
+ //#region src/readers/builtin.ts
4
+ function extractBlockId$1(el) {
5
+ const id = el.getAttribute("id");
6
+ return id ? { $: { blockId: id } } : {};
7
+ }
8
+ var ELEMENT_DEFAULTS = {
9
+ direction: "ltr",
10
+ format: "",
11
+ indent: 0,
12
+ textFormat: 0,
13
+ textStyle: "",
14
+ version: 1
15
+ };
16
+ function registerBuiltinReaders(registry) {
17
+ registry.registerReader("p", (el, ctx) => ({
18
+ type: "paragraph",
19
+ ...extractBlockId$1(el),
20
+ children: ctx.parseChildren(el),
21
+ ...ELEMENT_DEFAULTS
22
+ }));
23
+ for (let level = 1; level <= 6; level++) registry.registerReader(`h${level}`, (el, ctx) => ({
24
+ type: "heading",
25
+ tag: `h${level}`,
26
+ ...extractBlockId$1(el),
27
+ children: ctx.parseChildren(el),
28
+ ...ELEMENT_DEFAULTS
29
+ }));
30
+ registry.registerReader("blockquote", (el, ctx) => {
31
+ const attribution = el.getAttribute("attribution");
32
+ const base = {
33
+ ...extractBlockId$1(el),
34
+ children: ctx.parseChildren(el),
35
+ ...ELEMENT_DEFAULTS
36
+ };
37
+ if (attribution !== null) return {
38
+ type: "rich-quote",
39
+ attribution,
40
+ ...base
41
+ };
42
+ return {
43
+ type: "quote",
44
+ ...base
45
+ };
46
+ });
47
+ registry.registerReader("hr", (el) => ({
48
+ type: "horizontalrule",
49
+ ...extractBlockId$1(el),
50
+ version: 1
51
+ }));
52
+ registry.registerReader("ul", (el, ctx) => {
53
+ return {
54
+ type: "list",
55
+ listType: el.getAttribute("type") === "check" ? "check" : "bullet",
56
+ tag: "ul",
57
+ start: 1,
58
+ ...extractBlockId$1(el),
59
+ children: ctx.parseChildren(el),
60
+ direction: "ltr",
61
+ format: "",
62
+ indent: 0,
63
+ version: 1
64
+ };
65
+ });
66
+ registry.registerReader("ol", (el, ctx) => ({
67
+ type: "list",
68
+ listType: "number",
69
+ tag: "ol",
70
+ start: Number(el.getAttribute("start") ?? 1),
71
+ ...extractBlockId$1(el),
72
+ children: ctx.parseChildren(el),
73
+ direction: "ltr",
74
+ format: "",
75
+ indent: 0,
76
+ version: 1
77
+ }));
78
+ registry.registerReader("li", (el, ctx) => {
79
+ const checked = el.getAttribute("checked");
80
+ return {
81
+ type: "listitem",
82
+ ...extractBlockId$1(el),
83
+ ...checked !== null ? { checked: checked === "true" } : {},
84
+ children: ctx.parseChildren(el),
85
+ ...ELEMENT_DEFAULTS,
86
+ value: 1
87
+ };
88
+ });
89
+ registry.registerReader("a", (el, ctx) => ({
90
+ type: "link",
91
+ url: el.getAttribute("href") ?? "",
92
+ target: el.getAttribute("target") ?? null,
93
+ title: el.getAttribute("title") ?? null,
94
+ rel: null,
95
+ children: ctx.parseChildren(el),
96
+ direction: "ltr",
97
+ format: "",
98
+ indent: 0,
99
+ version: 1
100
+ }));
101
+ registry.registerReader("table", (el, ctx) => ({
102
+ type: "table",
103
+ ...extractBlockId$1(el),
104
+ children: ctx.parseChildren(el),
105
+ direction: "ltr",
106
+ format: "",
107
+ indent: 0,
108
+ version: 1
109
+ }));
110
+ registry.registerReader("tr", (el, ctx) => ({
111
+ type: "tablerow",
112
+ children: ctx.parseChildren(el),
113
+ direction: "ltr",
114
+ format: "",
115
+ indent: 0,
116
+ version: 1
117
+ }));
118
+ registry.registerReader("th", (el, ctx) => ({
119
+ type: "tablecell",
120
+ headerState: 1,
121
+ children: ctx.parseChildren(el),
122
+ direction: "ltr",
123
+ format: "",
124
+ indent: 0,
125
+ version: 1
126
+ }));
127
+ registry.registerReader("td", (el, ctx) => ({
128
+ type: "tablecell",
129
+ headerState: 0,
130
+ children: ctx.parseChildren(el),
131
+ direction: "ltr",
132
+ format: "",
133
+ indent: 0,
134
+ version: 1
135
+ }));
136
+ }
137
+ //#endregion
138
+ //#region src/readers/custom.ts
139
+ var idAlphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
140
+ var makePollIdSuffix = customAlphabet(idAlphabet, 10);
141
+ var makeOptionIdSuffix = customAlphabet(idAlphabet, 6);
142
+ var makeChatParticipantId = customAlphabet(idAlphabet, 6);
143
+ var makeChatMessageId = customAlphabet(idAlphabet, 8);
144
+ function extractBlockId(el) {
145
+ const id = el.getAttribute("id");
146
+ return id ? { $: { blockId: id } } : {};
147
+ }
148
+ function numAttr(el, name) {
149
+ const v = el.getAttribute(name);
150
+ return v !== null ? Number(v) : void 0;
151
+ }
152
+ /**
153
+ * Extract CDATA text content from an element.
154
+ * linkedom (HTML parser) converts <![CDATA[...]]> to a comment node
155
+ * with value "[CDATA[...]]", so we detect that pattern.
156
+ */
157
+ function extractCdataText(el) {
158
+ for (const child of el.childNodes) if (child.nodeType === 8) {
159
+ const val = child.nodeValue ?? "";
160
+ if (val.startsWith("[CDATA[") && val.endsWith("]]")) return val.slice(7, -2);
161
+ }
162
+ return null;
163
+ }
164
+ function extractRawText(el) {
165
+ return extractCdataText(el) ?? el.textContent ?? "";
166
+ }
167
+ function registerCustomReaders(registry) {
168
+ registry.registerReader("img", (el) => {
169
+ if (el.parentElement?.tagName.toLowerCase() === "gallery") return false;
170
+ return {
171
+ type: "image",
172
+ ...extractBlockId(el),
173
+ src: el.getAttribute("src") ?? "",
174
+ altText: el.getAttribute("alt") ?? "",
175
+ width: numAttr(el, "width"),
176
+ height: numAttr(el, "height"),
177
+ caption: el.getAttribute("caption") ?? void 0,
178
+ thumbhash: el.getAttribute("thumbhash") ?? void 0,
179
+ accent: el.getAttribute("accent") ?? void 0,
180
+ version: 1
181
+ };
182
+ });
183
+ registry.registerReader("video", (el) => ({
184
+ type: "video",
185
+ ...extractBlockId(el),
186
+ src: el.getAttribute("src") ?? "",
187
+ poster: el.getAttribute("poster") ?? void 0,
188
+ width: numAttr(el, "width"),
189
+ height: numAttr(el, "height"),
190
+ version: 1
191
+ }));
192
+ registry.registerReader("link-card", (el) => ({
193
+ type: "link-card",
194
+ ...extractBlockId(el),
195
+ url: el.getAttribute("url") ?? "",
196
+ source: el.getAttribute("source") ?? void 0,
197
+ title: el.getAttribute("title") ?? void 0,
198
+ description: el.getAttribute("description") ?? void 0,
199
+ favicon: el.getAttribute("favicon") ?? void 0,
200
+ image: el.getAttribute("image") ?? void 0,
201
+ version: 1
202
+ }));
203
+ registry.registerReader("embed", (el) => ({
204
+ type: "embed",
205
+ ...extractBlockId(el),
206
+ url: el.getAttribute("url") ?? "",
207
+ source: el.getAttribute("source") ?? null,
208
+ version: 1
209
+ }));
210
+ registry.registerReader("codeblock", (el) => ({
211
+ type: "code-block",
212
+ ...extractBlockId(el),
213
+ code: extractRawText(el),
214
+ language: el.getAttribute("lang") ?? "",
215
+ version: 1
216
+ }));
217
+ registry.registerReader("mermaid", (el) => ({
218
+ type: "mermaid",
219
+ ...extractBlockId(el),
220
+ diagram: extractRawText(el),
221
+ version: 1
222
+ }));
223
+ registry.registerReader("math", (el) => {
224
+ if (el.getAttribute("display") === "block") return {
225
+ type: "katex-block",
226
+ ...extractBlockId(el),
227
+ equation: extractRawText(el),
228
+ version: 1
229
+ };
230
+ const color = el.getAttribute("color");
231
+ return {
232
+ type: "katex-inline",
233
+ equation: extractRawText(el),
234
+ ...color ? { color } : {},
235
+ version: 1
236
+ };
237
+ });
238
+ registry.registerReader("alert", (el, ctx) => {
239
+ const content = { root: {
240
+ type: "root",
241
+ children: ctx.parseChildren(el),
242
+ direction: "ltr",
243
+ format: "",
244
+ indent: 0,
245
+ version: 1
246
+ } };
247
+ return {
248
+ type: "alert-quote",
249
+ ...extractBlockId(el),
250
+ alertType: el.getAttribute("type") ?? "note",
251
+ content,
252
+ version: 1
253
+ };
254
+ });
255
+ registry.registerReader("banner", (el, ctx) => {
256
+ const content = { root: {
257
+ type: "root",
258
+ children: ctx.parseChildren(el),
259
+ direction: "ltr",
260
+ format: "",
261
+ indent: 0,
262
+ version: 1
263
+ } };
264
+ return {
265
+ type: "banner",
266
+ ...extractBlockId(el),
267
+ bannerType: el.getAttribute("type") ?? "note",
268
+ content,
269
+ version: 1
270
+ };
271
+ });
272
+ registry.registerReader("nested-doc", (el, ctx) => {
273
+ const content = { root: {
274
+ type: "root",
275
+ children: ctx.parseChildren(el),
276
+ direction: "ltr",
277
+ format: "",
278
+ indent: 0,
279
+ version: 1
280
+ } };
281
+ return {
282
+ type: "nested-doc",
283
+ ...extractBlockId(el),
284
+ content,
285
+ version: 1
286
+ };
287
+ });
288
+ registry.registerReader("details", (el, ctx) => ({
289
+ type: "details",
290
+ ...extractBlockId(el),
291
+ summary: el.getAttribute("summary") ?? "",
292
+ open: el.getAttribute("open") === "true",
293
+ children: ctx.parseChildren(el),
294
+ direction: "ltr",
295
+ format: "",
296
+ indent: 0,
297
+ version: 1
298
+ }));
299
+ registry.registerReader("spoiler", (el, ctx) => ({
300
+ type: "spoiler",
301
+ children: ctx.parseChildren(el),
302
+ direction: "ltr",
303
+ format: "",
304
+ indent: 0,
305
+ textFormat: 0,
306
+ textStyle: "",
307
+ version: 1
308
+ }));
309
+ registry.registerReader("ruby", (el, ctx) => ({
310
+ type: "ruby",
311
+ reading: el.getAttribute("rt") ?? "",
312
+ children: ctx.parseChildren(el),
313
+ direction: "ltr",
314
+ format: "",
315
+ indent: 0,
316
+ textFormat: 0,
317
+ textStyle: "",
318
+ version: 1
319
+ }));
320
+ registry.registerReader("mention", (el) => ({
321
+ type: "mention",
322
+ platform: el.getAttribute("platform") ?? "",
323
+ handle: el.getAttribute("handle") ?? "",
324
+ displayName: el.textContent || void 0,
325
+ version: 1
326
+ }));
327
+ registry.registerReader("tag", (el) => ({
328
+ type: "tag",
329
+ text: el.textContent ?? "",
330
+ format: 0,
331
+ detail: 0,
332
+ mode: "normal",
333
+ style: "",
334
+ version: 1
335
+ }));
336
+ registry.registerReader("comment", (el) => ({
337
+ type: "comment",
338
+ text: el.textContent ?? "",
339
+ format: 0,
340
+ detail: 0,
341
+ mode: "normal",
342
+ style: "",
343
+ version: 1
344
+ }));
345
+ registry.registerReader("footnote", (el) => ({
346
+ type: "footnote",
347
+ identifier: el.getAttribute("ref") ?? "",
348
+ version: 1
349
+ }));
350
+ registry.registerReader("footnote-section", (el) => {
351
+ const definitions = {};
352
+ for (const child of el.children) if (child.tagName.toLowerCase() === "def") {
353
+ const ref = child.getAttribute("ref") ?? "";
354
+ definitions[ref] = child.textContent ?? "";
355
+ }
356
+ return {
357
+ type: "footnote-section",
358
+ ...extractBlockId(el),
359
+ definitions,
360
+ version: 1
361
+ };
362
+ });
363
+ registry.registerReader("gallery", (el) => {
364
+ const images = [];
365
+ for (const child of el.children) if (child.tagName.toLowerCase() === "img") images.push({
366
+ src: child.getAttribute("src") ?? "",
367
+ alt: child.getAttribute("alt") ?? void 0
368
+ });
369
+ return {
370
+ type: "gallery",
371
+ ...extractBlockId(el),
372
+ images,
373
+ layout: el.getAttribute("layout") ?? "grid",
374
+ version: 1
375
+ };
376
+ });
377
+ registry.registerReader("excalidraw", (el) => ({
378
+ type: "excalidraw",
379
+ ...extractBlockId(el),
380
+ snapshot: extractCdataText(el) || el.getAttribute("snapshot") || "",
381
+ version: 1
382
+ }));
383
+ registry.registerReader("grid", (el, ctx) => {
384
+ const cells = [];
385
+ for (const child of el.children) if (child.tagName.toLowerCase() === "cell") {
386
+ const children = ctx.parseChildren(child);
387
+ cells.push({ root: {
388
+ type: "root",
389
+ children,
390
+ direction: "ltr",
391
+ format: "",
392
+ indent: 0,
393
+ version: 1
394
+ } });
395
+ }
396
+ return {
397
+ type: "grid-container",
398
+ ...extractBlockId(el),
399
+ cols: numAttr(el, "cols") ?? 2,
400
+ gap: el.getAttribute("gap") ?? "16px",
401
+ cells,
402
+ version: 1
403
+ };
404
+ });
405
+ registry.registerReader("agent-diff", (el) => ({
406
+ type: "agent-diff",
407
+ ...extractBlockId(el),
408
+ opType: el.getAttribute("op") ?? "insert",
409
+ diffEntryId: el.getAttribute("entry") ?? "",
410
+ version: 1
411
+ }));
412
+ registry.registerReader("code-snippet", (el) => {
413
+ const files = [];
414
+ for (const child of el.children) if (child.tagName.toLowerCase() === "file") files.push({
415
+ filename: child.getAttribute("name") ?? "",
416
+ code: extractRawText(child),
417
+ language: child.getAttribute("lang") ?? ""
418
+ });
419
+ return {
420
+ type: "code-snippet",
421
+ ...extractBlockId(el),
422
+ files,
423
+ version: 1
424
+ };
425
+ });
426
+ registry.registerReader("chat", (el) => {
427
+ const variant = el.getAttribute("variant") === "user-user" ? "user-user" : "user-agent";
428
+ const participants = [];
429
+ const messages = [];
430
+ for (const child of el.children) {
431
+ const tag = child.tagName.toLowerCase();
432
+ if (tag === "participants") for (const p of child.children) {
433
+ if (p.tagName.toLowerCase() !== "participant") continue;
434
+ const kind = p.getAttribute("kind") === "agent" ? "agent" : "user";
435
+ participants.push({
436
+ id: p.getAttribute("id") || `p_${makeChatParticipantId()}`,
437
+ kind,
438
+ name: p.getAttribute("name") || void 0,
439
+ avatar: p.getAttribute("avatar") || void 0
440
+ });
441
+ }
442
+ else if (tag === "messages") for (const m of child.children) {
443
+ if (m.tagName.toLowerCase() !== "message") continue;
444
+ messages.push({
445
+ id: m.getAttribute("id") || `m_${makeChatMessageId()}`,
446
+ participantId: m.getAttribute("participant") ?? "",
447
+ content: m.textContent ?? ""
448
+ });
449
+ }
450
+ }
451
+ return {
452
+ type: "chat",
453
+ ...extractBlockId(el),
454
+ variant,
455
+ participants,
456
+ messages,
457
+ version: 1
458
+ };
459
+ });
460
+ registry.registerReader("poll", (el) => {
461
+ let question = "";
462
+ const options = [];
463
+ for (const child of el.children) {
464
+ const tag = child.tagName.toLowerCase();
465
+ if (tag === "question") question = child.textContent ?? "";
466
+ else if (tag === "option") {
467
+ const id = child.getAttribute("id") || `o_${makeOptionIdSuffix()}`;
468
+ options.push({
469
+ id,
470
+ label: child.textContent ?? ""
471
+ });
472
+ }
473
+ }
474
+ const mode = el.getAttribute("mode") === "multiple" ? "multiple" : "single";
475
+ const closeAt = el.getAttribute("close-at");
476
+ const showResultsAttr = el.getAttribute("show-results");
477
+ const showResults = showResultsAttr === "always" || showResultsAttr === "after-vote" || showResultsAttr === "after-close" ? showResultsAttr : void 0;
478
+ return {
479
+ type: "poll",
480
+ ...extractBlockId(el),
481
+ pollId: el.getAttribute("poll-id") || `p_${makePollIdSuffix()}`,
482
+ question,
483
+ options,
484
+ mode,
485
+ ...closeAt ? { closeAt } : {},
486
+ ...showResults ? { showResults } : {},
487
+ version: 1
488
+ };
489
+ });
490
+ }
491
+ //#endregion
492
+ //#region src/registry.ts
493
+ var LitexmlRegistry = class {
494
+ constructor() {
495
+ this.writers = /* @__PURE__ */ new Map();
496
+ this.readers = /* @__PURE__ */ new Map();
497
+ }
498
+ registerWriter(nodeType, writer) {
499
+ this.writers.set(nodeType, writer);
500
+ }
501
+ registerReader(tagName, reader) {
502
+ this.readers.set(tagName.toLowerCase(), reader);
503
+ }
504
+ getWriter(nodeType) {
505
+ return this.writers.get(nodeType);
506
+ }
507
+ getReader(tagName) {
508
+ return this.readers.get(tagName.toLowerCase());
509
+ }
510
+ };
511
+ //#endregion
512
+ //#region src/writers/builtin.ts
513
+ function blockId$1(node) {
514
+ return node.$?.blockId ? { id: node.$.blockId } : {};
515
+ }
516
+ function registerBuiltinWriters(registry) {
517
+ registry.registerWriter("paragraph", (node, ctx) => {
518
+ const n = node;
519
+ return {
520
+ tag: "p",
521
+ attrs: blockId$1(n),
522
+ children: ctx.serializeChildren(n.children ?? [])
523
+ };
524
+ });
525
+ registry.registerWriter("heading", (node, ctx) => {
526
+ const n = node;
527
+ return {
528
+ tag: n.tag ?? "h1",
529
+ attrs: blockId$1(n),
530
+ children: ctx.serializeChildren(n.children ?? [])
531
+ };
532
+ });
533
+ const writeQuote = (node, ctx) => {
534
+ const n = node;
535
+ const attrs = { ...blockId$1(n) };
536
+ if (typeof n.attribution === "string" && n.attribution.trim() !== "") attrs.attribution = n.attribution;
537
+ return {
538
+ tag: "blockquote",
539
+ attrs,
540
+ children: ctx.serializeChildren(n.children ?? [])
541
+ };
542
+ };
543
+ registry.registerWriter("quote", writeQuote);
544
+ registry.registerWriter("rich-quote", writeQuote);
545
+ registry.registerWriter("horizontalrule", (node) => {
546
+ return {
547
+ tag: "hr",
548
+ attrs: blockId$1(node),
549
+ selfClosing: true
550
+ };
551
+ });
552
+ registry.registerWriter("list", (node, ctx) => {
553
+ const n = node;
554
+ const tag = n.listType === "number" ? "ol" : "ul";
555
+ const attrs = { ...blockId$1(n) };
556
+ if (n.listType === "check") attrs.type = "check";
557
+ return {
558
+ tag,
559
+ attrs,
560
+ children: ctx.serializeChildren(n.children ?? [])
561
+ };
562
+ });
563
+ registry.registerWriter("listitem", (node, ctx) => {
564
+ const n = node;
565
+ const attrs = { ...blockId$1(n) };
566
+ if (n.checked !== void 0) attrs.checked = String(n.checked);
567
+ return {
568
+ tag: "li",
569
+ attrs,
570
+ children: ctx.serializeChildren(n.children ?? [])
571
+ };
572
+ });
573
+ registry.registerWriter("link", (node, ctx) => {
574
+ const n = node;
575
+ const attrs = { href: n.url ?? "" };
576
+ if (n.target) attrs.target = n.target;
577
+ if (n.title) attrs.title = n.title;
578
+ return {
579
+ tag: "a",
580
+ attrs,
581
+ children: ctx.serializeChildren(n.children ?? [])
582
+ };
583
+ });
584
+ registry.registerWriter("autolink", (node, ctx) => {
585
+ const n = node;
586
+ return {
587
+ tag: "a",
588
+ attrs: { href: n.url ?? "" },
589
+ children: ctx.serializeChildren(n.children ?? [])
590
+ };
591
+ });
592
+ registry.registerWriter("table", (node, ctx) => {
593
+ const n = node;
594
+ return {
595
+ tag: "table",
596
+ attrs: blockId$1(n),
597
+ children: ctx.serializeChildren(n.children ?? [])
598
+ };
599
+ });
600
+ registry.registerWriter("tablerow", (node, ctx) => {
601
+ const n = node;
602
+ return {
603
+ tag: "tr",
604
+ children: ctx.serializeChildren(n.children ?? [])
605
+ };
606
+ });
607
+ registry.registerWriter("tablecell", (node, ctx) => {
608
+ const n = node;
609
+ return {
610
+ tag: n.headerState === 1 ? "th" : "td",
611
+ children: ctx.serializeChildren(n.children ?? [])
612
+ };
613
+ });
614
+ }
615
+ //#endregion
616
+ //#region src/writers/custom.ts
617
+ function blockId(node) {
618
+ return node.$?.blockId ? { id: node.$.blockId } : {};
619
+ }
620
+ function optAttr(attrs) {
621
+ const result = {};
622
+ for (const [k, v] of Object.entries(attrs)) if (v !== void 0 && v !== null && v !== "") result[k] = String(v);
623
+ return result;
624
+ }
625
+ function registerCustomWriters(registry) {
626
+ registry.registerWriter("image", (node) => {
627
+ const n = node;
628
+ return {
629
+ tag: "img",
630
+ attrs: optAttr({
631
+ ...blockId(n),
632
+ src: n.src,
633
+ alt: n.altText,
634
+ width: n.width != null ? String(n.width) : void 0,
635
+ height: n.height != null ? String(n.height) : void 0,
636
+ caption: n.caption,
637
+ thumbhash: n.thumbhash,
638
+ accent: n.accent
639
+ }),
640
+ selfClosing: true
641
+ };
642
+ });
643
+ registry.registerWriter("video", (node) => {
644
+ const n = node;
645
+ return {
646
+ tag: "video",
647
+ attrs: optAttr({
648
+ ...blockId(n),
649
+ src: n.src,
650
+ poster: n.poster
651
+ }),
652
+ selfClosing: true
653
+ };
654
+ });
655
+ registry.registerWriter("link-card", (node) => {
656
+ const n = node;
657
+ return {
658
+ tag: "link-card",
659
+ attrs: optAttr({
660
+ ...blockId(n),
661
+ url: n.url,
662
+ source: n.source,
663
+ title: n.title,
664
+ description: n.description,
665
+ favicon: n.favicon,
666
+ image: n.image
667
+ }),
668
+ selfClosing: true
669
+ };
670
+ });
671
+ registry.registerWriter("embed", (node) => {
672
+ const n = node;
673
+ return {
674
+ tag: "embed",
675
+ attrs: optAttr({
676
+ ...blockId(n),
677
+ url: n.url,
678
+ source: n.source
679
+ }),
680
+ selfClosing: true
681
+ };
682
+ });
683
+ registry.registerWriter("code-block", (node) => {
684
+ const n = node;
685
+ return {
686
+ tag: "codeblock",
687
+ attrs: optAttr({
688
+ ...blockId(n),
689
+ lang: n.language
690
+ }),
691
+ children: [n.code ?? ""]
692
+ };
693
+ });
694
+ registry.registerWriter("mermaid", (node) => {
695
+ const n = node;
696
+ return {
697
+ tag: "mermaid",
698
+ attrs: blockId(n),
699
+ children: [n.diagram ?? ""]
700
+ };
701
+ });
702
+ registry.registerWriter("katex-block", (node) => {
703
+ const n = node;
704
+ return {
705
+ tag: "math",
706
+ attrs: {
707
+ ...blockId(n),
708
+ display: "block"
709
+ },
710
+ children: [n.equation ?? ""]
711
+ };
712
+ });
713
+ registry.registerWriter("katex-inline", (node) => {
714
+ const n = node;
715
+ const attrs = {};
716
+ if (n.color) attrs.color = n.color;
717
+ return {
718
+ tag: "math",
719
+ attrs: Object.keys(attrs).length > 0 ? attrs : void 0,
720
+ children: [n.equation ?? ""]
721
+ };
722
+ });
723
+ registry.registerWriter("alert-quote", (node, ctx) => {
724
+ const n = node;
725
+ return {
726
+ tag: "alert",
727
+ attrs: optAttr({
728
+ ...blockId(n),
729
+ type: n.alertType
730
+ }),
731
+ children: ctx.serializeNestedState(n.content)
732
+ };
733
+ });
734
+ registry.registerWriter("banner", (node, ctx) => {
735
+ const n = node;
736
+ return {
737
+ tag: "banner",
738
+ attrs: optAttr({
739
+ ...blockId(n),
740
+ type: n.bannerType
741
+ }),
742
+ children: ctx.serializeNestedState(n.content)
743
+ };
744
+ });
745
+ registry.registerWriter("nested-doc", (node, ctx) => {
746
+ const n = node;
747
+ return {
748
+ tag: "nested-doc",
749
+ attrs: blockId(n),
750
+ children: ctx.serializeNestedState(n.content ?? n.contentState)
751
+ };
752
+ });
753
+ registry.registerWriter("excalidraw", (node) => {
754
+ const n = node;
755
+ if (!n.snapshot) return {
756
+ tag: "excalidraw",
757
+ attrs: optAttr(blockId(n)),
758
+ selfClosing: true
759
+ };
760
+ return {
761
+ tag: "excalidraw",
762
+ attrs: optAttr(blockId(n)),
763
+ children: [{ cdata: n.snapshot }]
764
+ };
765
+ });
766
+ registry.registerWriter("grid-container", (node, ctx) => {
767
+ const n = node;
768
+ const cells = (n.cells ?? []).map((cellState) => ({
769
+ tag: "cell",
770
+ children: ctx.serializeNestedState(cellState)
771
+ }));
772
+ return {
773
+ tag: "grid",
774
+ attrs: optAttr({
775
+ ...blockId(n),
776
+ cols: n.cols != null ? String(n.cols) : void 0,
777
+ gap: n.gap
778
+ }),
779
+ children: cells
780
+ };
781
+ });
782
+ registry.registerWriter("agent-diff", (node) => {
783
+ const n = node;
784
+ return {
785
+ tag: "agent-diff",
786
+ attrs: optAttr({
787
+ ...blockId(n),
788
+ op: n.opType,
789
+ entry: n.diffEntryId
790
+ }),
791
+ selfClosing: true
792
+ };
793
+ });
794
+ registry.registerWriter("details", (node, ctx) => {
795
+ const n = node;
796
+ return {
797
+ tag: "details",
798
+ attrs: optAttr({
799
+ ...blockId(n),
800
+ summary: n.summary,
801
+ open: n.open != null ? String(n.open) : void 0
802
+ }),
803
+ children: ctx.serializeChildren(n.children ?? [])
804
+ };
805
+ });
806
+ registry.registerWriter("spoiler", (node, ctx) => {
807
+ const n = node;
808
+ return {
809
+ tag: "spoiler",
810
+ children: ctx.serializeChildren(n.children ?? [])
811
+ };
812
+ });
813
+ registry.registerWriter("ruby", (node, ctx) => {
814
+ const n = node;
815
+ return {
816
+ tag: "ruby",
817
+ attrs: optAttr({ rt: n.reading }),
818
+ children: ctx.serializeChildren(n.children ?? [])
819
+ };
820
+ });
821
+ registry.registerWriter("mention", (node) => {
822
+ const n = node;
823
+ return {
824
+ tag: "mention",
825
+ attrs: optAttr({
826
+ platform: n.platform,
827
+ handle: n.handle
828
+ }),
829
+ children: [n.displayName ?? n.handle ?? ""]
830
+ };
831
+ });
832
+ registry.registerWriter("tag", (node) => {
833
+ return {
834
+ tag: "tag",
835
+ children: [node.text ?? ""]
836
+ };
837
+ });
838
+ registry.registerWriter("comment", (node) => {
839
+ return {
840
+ tag: "comment",
841
+ children: [node.text ?? ""]
842
+ };
843
+ });
844
+ registry.registerWriter("footnote", (node) => {
845
+ return {
846
+ tag: "footnote",
847
+ attrs: { ref: node.identifier ?? "" },
848
+ selfClosing: true
849
+ };
850
+ });
851
+ registry.registerWriter("footnote-section", (node) => {
852
+ const n = node;
853
+ const defs = n.definitions ?? {};
854
+ const children = Object.entries(defs).map(([ref, text]) => ({
855
+ tag: "def",
856
+ attrs: { ref },
857
+ children: [text]
858
+ }));
859
+ return {
860
+ tag: "footnote-section",
861
+ attrs: blockId(n),
862
+ children
863
+ };
864
+ });
865
+ registry.registerWriter("gallery", (node) => {
866
+ const n = node;
867
+ const images = (n.images ?? []).map((img) => ({
868
+ tag: "img",
869
+ attrs: optAttr({
870
+ src: img.src,
871
+ alt: img.alt
872
+ }),
873
+ selfClosing: true
874
+ }));
875
+ return {
876
+ tag: "gallery",
877
+ attrs: optAttr({
878
+ ...blockId(n),
879
+ layout: n.layout
880
+ }),
881
+ children: images
882
+ };
883
+ });
884
+ registry.registerWriter("code-snippet", (node) => {
885
+ const n = node;
886
+ const files = (n.files ?? []).map((f) => ({
887
+ tag: "file",
888
+ attrs: optAttr({
889
+ name: f.filename,
890
+ lang: f.language
891
+ }),
892
+ children: [f.code ?? ""]
893
+ }));
894
+ return {
895
+ tag: "code-snippet",
896
+ attrs: blockId(n),
897
+ children: files
898
+ };
899
+ });
900
+ registry.registerWriter("chat", (node) => {
901
+ const n = node;
902
+ const participantsEl = {
903
+ tag: "participants",
904
+ children: (n.participants ?? []).map((p) => ({
905
+ tag: "participant",
906
+ attrs: optAttr({
907
+ id: p.id,
908
+ kind: p.kind,
909
+ name: p.name,
910
+ avatar: p.avatar
911
+ }),
912
+ children: []
913
+ }))
914
+ };
915
+ const messagesEl = {
916
+ tag: "messages",
917
+ children: (n.messages ?? []).map((m) => ({
918
+ tag: "message",
919
+ attrs: optAttr({
920
+ id: m.id,
921
+ participant: m.participantId
922
+ }),
923
+ children: [m.content ?? ""]
924
+ }))
925
+ };
926
+ return {
927
+ tag: "chat",
928
+ attrs: optAttr({
929
+ ...blockId(n),
930
+ variant: n.variant
931
+ }),
932
+ children: [participantsEl, messagesEl]
933
+ };
934
+ });
935
+ registry.registerWriter("poll", (node) => {
936
+ const n = node;
937
+ const optionElements = (n.options ?? []).map((option) => ({
938
+ tag: "option",
939
+ attrs: optAttr({ id: option.id }),
940
+ children: [option.label ?? ""]
941
+ }));
942
+ return {
943
+ tag: "poll",
944
+ attrs: optAttr({
945
+ ...blockId(n),
946
+ "poll-id": n.pollId,
947
+ "mode": n.mode,
948
+ "close-at": n.closeAt,
949
+ "show-results": n.showResults
950
+ }),
951
+ children: [{
952
+ tag: "question",
953
+ children: [n.question ?? ""]
954
+ }, ...optionElements]
955
+ };
956
+ });
957
+ }
958
+ //#endregion
959
+ //#region src/default-registry.ts
960
+ function createDefaultRegistry() {
961
+ const registry = new LitexmlRegistry();
962
+ registerBuiltinWriters(registry);
963
+ registerBuiltinReaders(registry);
964
+ registerCustomWriters(registry);
965
+ registerCustomReaders(registry);
966
+ return registry;
967
+ }
968
+ //#endregion
969
+ //#region src/text-format.ts
970
+ /** Ordered list: bit value → XML tag. Order determines nesting (outer→inner). */
971
+ var FORMAT_BIT_TO_TAG = [
972
+ [1, "b"],
973
+ [2, "i"],
974
+ [4, "s"],
975
+ [8, "u"],
976
+ [16, "code"],
977
+ [32, "sub"],
978
+ [64, "sup"],
979
+ [128, "mark"]
980
+ ];
981
+ /** Reverse map: XML tag name → bit value (includes aliases) */
982
+ var FORMAT_TAG_TO_BIT = {
983
+ b: 1,
984
+ strong: 1,
985
+ i: 2,
986
+ em: 2,
987
+ s: 4,
988
+ del: 4,
989
+ strike: 4,
990
+ u: 8,
991
+ code: 16,
992
+ sub: 32,
993
+ sup: 64,
994
+ mark: 128
995
+ };
996
+ /** Wrap text content with nested format tags based on bitmask. */
997
+ function wrapWithFormatTags(text, format) {
998
+ if (format === 0) return [text];
999
+ let content = [text];
1000
+ for (let idx = FORMAT_BIT_TO_TAG.length - 1; idx >= 0; idx--) {
1001
+ const [bit, tag] = FORMAT_BIT_TO_TAG[idx];
1002
+ if (format & bit) content = [{
1003
+ tag,
1004
+ children: content
1005
+ }];
1006
+ }
1007
+ return content;
1008
+ }
1009
+ /** Check if a tag name is a known text format tag. */
1010
+ function isFormatTag(tagName) {
1011
+ return tagName.toLowerCase() in FORMAT_TAG_TO_BIT;
1012
+ }
1013
+ /** Get the format bit for a tag name. Returns 0 if not a format tag. */
1014
+ function getFormatBit(tagName) {
1015
+ return FORMAT_TAG_TO_BIT[tagName.toLowerCase()] ?? 0;
1016
+ }
1017
+ //#endregion
1018
+ //#region src/deserializer.ts
1019
+ function parseXml(xml) {
1020
+ const { document } = parseHTML(`<!DOCTYPE html><html><body>${xml}</body></html>`);
1021
+ return document;
1022
+ }
1023
+ function deserializeFromXml(xml, registry) {
1024
+ const doc = parseXml(xml);
1025
+ const docEl = doc.querySelector("doc") ?? doc.body;
1026
+ return { root: {
1027
+ type: "root",
1028
+ children: createReaderContext(registry).parseChildren(docEl),
1029
+ direction: "ltr",
1030
+ format: "",
1031
+ indent: 0,
1032
+ version: 1
1033
+ } };
1034
+ }
1035
+ function deserializeNodesFromXml(xml, registry) {
1036
+ const doc = parseXml(`<fragment>${xml}</fragment>`);
1037
+ const fragment = doc.querySelector("fragment") ?? doc.body;
1038
+ return createReaderContext(registry).parseChildren(fragment);
1039
+ }
1040
+ /** Structural containers whose whitespace-only text nodes are layout artifacts, not content */
1041
+ var BLOCK_TAGS = new Set([
1042
+ "doc",
1043
+ "fragment",
1044
+ "root",
1045
+ "ul",
1046
+ "ol",
1047
+ "table",
1048
+ "tr",
1049
+ "blockquote",
1050
+ "alert",
1051
+ "banner",
1052
+ "details",
1053
+ "nested-doc",
1054
+ "gallery",
1055
+ "codeblock",
1056
+ "code-snippet",
1057
+ "footnote-section",
1058
+ "grid",
1059
+ "cell",
1060
+ "excalidraw"
1061
+ ]);
1062
+ function isBlockContainer(element) {
1063
+ return BLOCK_TAGS.has(element.tagName.toLowerCase());
1064
+ }
1065
+ function createReaderContext(registry) {
1066
+ const ctx = {
1067
+ parseChildren(element) {
1068
+ const blockLevel = isBlockContainer(element);
1069
+ const nodes = [];
1070
+ for (const child of element.childNodes) if (child.nodeType === 3) {
1071
+ const text = child.textContent ?? "";
1072
+ if (blockLevel && text.trim() === "") continue;
1073
+ if (text === "") continue;
1074
+ nodes.push(makeTextNode(text, 0));
1075
+ } else if (child.nodeType === 1) {
1076
+ const parsed = parseElement(child, registry, ctx, 0);
1077
+ if (parsed) if (Array.isArray(parsed)) nodes.push(...parsed);
1078
+ else nodes.push(parsed);
1079
+ }
1080
+ return nodes;
1081
+ },
1082
+ parseNestedState(xml) {
1083
+ return deserializeFromXml(xml, registry);
1084
+ }
1085
+ };
1086
+ return ctx;
1087
+ }
1088
+ function parseElement(element, registry, ctx, inheritedFormat) {
1089
+ const tag = element.tagName.toLowerCase();
1090
+ if (isFormatTag(tag)) return parseInlineChildren(element, registry, ctx, inheritedFormat | getFormatBit(tag));
1091
+ if (tag === "br") return {
1092
+ type: "linebreak",
1093
+ version: 1
1094
+ };
1095
+ if (tag === "node") return parseFallbackNode(element);
1096
+ const reader = registry.getReader(tag);
1097
+ if (reader) {
1098
+ const result = reader(element, ctx);
1099
+ if (result !== false) return result;
1100
+ }
1101
+ return ctx.parseChildren(element);
1102
+ }
1103
+ function parseInlineChildren(element, registry, ctx, format) {
1104
+ const nodes = [];
1105
+ for (const child of element.childNodes) if (child.nodeType === 3) {
1106
+ const text = child.textContent ?? "";
1107
+ if (text === "") continue;
1108
+ nodes.push(makeTextNode(text, format));
1109
+ } else if (child.nodeType === 1) {
1110
+ const parsed = parseElement(child, registry, ctx, format);
1111
+ if (parsed) if (Array.isArray(parsed)) nodes.push(...parsed);
1112
+ else nodes.push(parsed);
1113
+ }
1114
+ return nodes;
1115
+ }
1116
+ function parseFallbackNode(element) {
1117
+ const type = element.getAttribute("type") ?? "unknown";
1118
+ const id = element.getAttribute("id");
1119
+ const dataStr = element.getAttribute("data");
1120
+ const data = dataStr ? JSON.parse(dataStr) : {};
1121
+ return {
1122
+ type,
1123
+ ...id ? { $: { blockId: id } } : {},
1124
+ ...data,
1125
+ version: 1
1126
+ };
1127
+ }
1128
+ function makeTextNode(text, format) {
1129
+ return {
1130
+ type: "text",
1131
+ text,
1132
+ format,
1133
+ detail: 0,
1134
+ mode: "normal",
1135
+ style: "",
1136
+ version: 1
1137
+ };
1138
+ }
1139
+ //#endregion
2
1140
  //#region src/xml-utils.ts
3
1141
  var ESCAPE_MAP = {
4
1142
  "&": "&amp;",