@xenosystem/blocks 0.20.0 → 0.21.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.
@@ -0,0 +1,600 @@
1
+ // src/docs/document/format.ts
2
+ var XDOC_FORMAT = "xeno.xdoc";
3
+ var XDOC_VERSION = 1;
4
+ var XDOC_EXTENSION = ".xdoc";
5
+ var DOCUMENT_NODE_TYPES = Object.freeze([
6
+ "doc",
7
+ "paragraph",
8
+ "text",
9
+ "heading",
10
+ "blockquote",
11
+ "bulletList",
12
+ "orderedList",
13
+ "listItem",
14
+ "taskList",
15
+ "taskItem",
16
+ "codeBlock",
17
+ "horizontalRule",
18
+ "hardBreak",
19
+ "table",
20
+ "tableRow",
21
+ "tableCell",
22
+ "tableHeader",
23
+ "image",
24
+ "callout",
25
+ "toggleBlock",
26
+ "pageBreak"
27
+ ]);
28
+ var DOCUMENT_MARK_TYPES = Object.freeze([
29
+ "bold",
30
+ "italic",
31
+ "strike",
32
+ "code",
33
+ "underline",
34
+ "link",
35
+ "subscript",
36
+ "superscript",
37
+ "highlight",
38
+ "wikiLink"
39
+ ]);
40
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
41
+ var SAFE_LINK = /^(https?:\/\/|mailto:)/i;
42
+ var SAFE_IMAGE = /^(https:\/\/|data:image\/(png|jpe?g|gif|webp);base64,)/i;
43
+ var CALLOUT_TYPES = /* @__PURE__ */ new Set(["info", "warning", "error", "success", "tip"]);
44
+ var ALIGN = /* @__PURE__ */ new Set(["left", "center", "right", "justify"]);
45
+ var COLOUR_VALUE = /^(#[0-9a-fA-F]{3,8}|[a-zA-Z]{3,20}|var\(--[a-zA-Z0-9-]{1,60}\))$/;
46
+ var LANGUAGE = /^[a-zA-Z0-9_+#.-]{0,40}$/;
47
+ var LIMITS = Object.freeze({ depth: 64, nodes: 5e4, text: 2e6 });
48
+ var isNull = (v) => v === null || v === void 0;
49
+ var optString = (max) => (v) => isNull(v) || typeof v === "string" && v.length <= max;
50
+ var align = (v) => isNull(v) || typeof v === "string" && ALIGN.has(v);
51
+ var int = (lo, hi) => (v) => isNull(v) || Number.isInteger(v) && v >= lo && v <= hi;
52
+ var bool = (v) => isNull(v) || typeof v === "boolean";
53
+ var colwidth = (v) => isNull(v) || Array.isArray(v) && v.length <= 64 && v.every((n) => Number.isInteger(n) && n >= 0 && n <= 1e4);
54
+ var dims = (v) => isNull(v) || typeof v === "number" && v >= 0 && v <= 1e5 || typeof v === "string" && /^\d{1,6}(px|%)?$/.test(v);
55
+ var NODE_ATTRS = {
56
+ paragraph: { textAlign: align },
57
+ heading: { level: int(1, 6), textAlign: align },
58
+ orderedList: { start: int(0, 1e6), type: optString(8) },
59
+ taskItem: { checked: bool },
60
+ codeBlock: { language: (v) => isNull(v) || typeof v === "string" && LANGUAGE.test(v) },
61
+ tableCell: { colspan: int(1, 1e3), rowspan: int(1, 1e3), colwidth },
62
+ tableHeader: { colspan: int(1, 1e3), rowspan: int(1, 1e3), colwidth },
63
+ image: { src: (v) => typeof v === "string" && v.length <= 8e6 && SAFE_IMAGE.test(v), alt: optString(1e3), title: optString(1e3), width: dims, height: dims },
64
+ callout: { type: (v) => isNull(v) || typeof v === "string" && CALLOUT_TYPES.has(v) },
65
+ toggleBlock: { open: bool, summary: optString(500) }
66
+ };
67
+ var MARK_ATTRS = {
68
+ link: {
69
+ href: (v) => typeof v === "string" && v.length <= 4e3 && SAFE_LINK.test(v.trim()),
70
+ target: (v) => isNull(v) || v === "_blank",
71
+ rel: optString(200),
72
+ class: (v) => isNull(v)
73
+ },
74
+ highlight: { color: (v) => isNull(v) || typeof v === "string" && COLOUR_VALUE.test(v) },
75
+ wikiLink: { pageId: (v) => isNull(v) || typeof v === "string" && (UUID.test(v) || /^[A-Za-z0-9_-]{1,128}$/.test(v)), pageTitle: optString(500) }
76
+ };
77
+ function validateContent(content, options = {}) {
78
+ const nodes = /* @__PURE__ */ new Set([...DOCUMENT_NODE_TYPES, ...options.extraNodes ?? []]);
79
+ const marks = /* @__PURE__ */ new Set([...DOCUMENT_MARK_TYPES, ...options.extraMarks ?? []]);
80
+ const extraN = new Set(options.extraNodes ?? []);
81
+ const extraM = new Set(options.extraMarks ?? []);
82
+ const problems = [];
83
+ let count = 0;
84
+ let text = 0;
85
+ const isObj = (v) => !!v && typeof v === "object" && !Array.isArray(v);
86
+ const attrs = (where, kind, value, rules) => {
87
+ if (isNull(value)) return;
88
+ if (!isObj(value)) {
89
+ problems.push({ path: where, code: "bad_shape", detail: `${kind}.attrs is not an object` });
90
+ return;
91
+ }
92
+ for (const [k, v] of Object.entries(value)) {
93
+ const rule = rules?.[k];
94
+ if (!rule) {
95
+ if (!isNull(v)) problems.push({ path: where, code: "bad_attr", detail: `${kind} does not take "${k}"` });
96
+ continue;
97
+ }
98
+ if (!rule(v)) problems.push({ path: where, code: "bad_attr", detail: `${kind}.${k} is not allowed` });
99
+ }
100
+ };
101
+ const walk = (n, where, depth) => {
102
+ if (problems.length > 50) return;
103
+ if (depth > LIMITS.depth) {
104
+ problems.push({ path: where, code: "too_deep", detail: `deeper than ${LIMITS.depth}` });
105
+ return;
106
+ }
107
+ if (++count > LIMITS.nodes) {
108
+ if (count === LIMITS.nodes + 1) problems.push({ path: where, code: "too_large", detail: `more than ${LIMITS.nodes} nodes` });
109
+ return;
110
+ }
111
+ if (!isObj(n) || typeof n.type !== "string") {
112
+ problems.push({ path: where, code: "bad_shape", detail: "a node needs a string type" });
113
+ return;
114
+ }
115
+ if (!nodes.has(n.type)) {
116
+ problems.push({ path: where, code: "unknown_node", detail: n.type });
117
+ return;
118
+ }
119
+ if (!extraN.has(n.type)) attrs(where, n.type, n.attrs, NODE_ATTRS[n.type]);
120
+ if (n.type === "text") {
121
+ if (typeof n.text !== "string" || !n.text) problems.push({ path: where, code: "bad_shape", detail: "a text node needs text" });
122
+ else if ((text += n.text.length) > LIMITS.text) problems.push({ path: where, code: "too_large", detail: `more than ${LIMITS.text} characters` });
123
+ } else if (n.text !== void 0) problems.push({ path: where, code: "bad_shape", detail: `${n.type} cannot carry text` });
124
+ if (n.marks !== void 0) {
125
+ if (!Array.isArray(n.marks)) problems.push({ path: where, code: "bad_shape", detail: "marks is not a list" });
126
+ else n.marks.forEach((m, i) => {
127
+ if (!isObj(m) || typeof m.type !== "string") problems.push({ path: `${where}.marks[${i}]`, code: "bad_shape", detail: "a mark needs a string type" });
128
+ else if (!marks.has(m.type)) problems.push({ path: `${where}.marks[${i}]`, code: "unknown_mark", detail: m.type });
129
+ else if (!extraM.has(m.type)) attrs(`${where}.marks[${i}]`, m.type, m.attrs, MARK_ATTRS[m.type]);
130
+ });
131
+ }
132
+ if (n.content !== void 0) {
133
+ if (!Array.isArray(n.content)) problems.push({ path: where, code: "bad_shape", detail: "content is not a list" });
134
+ else n.content.forEach((c, i) => walk(c, `${where}.content[${i}]`, depth + 1));
135
+ }
136
+ };
137
+ if (!isObj(content) || content.type !== "doc") return [{ path: "content", code: "bad_shape", detail: "the root must be a doc node" }];
138
+ walk(content, "content", 0);
139
+ return problems;
140
+ }
141
+ var XdocError = class extends Error {
142
+ problems;
143
+ constructor(message, problems = []) {
144
+ super(message);
145
+ this.name = "XdocError";
146
+ this.problems = problems;
147
+ }
148
+ };
149
+ var emptyContent = () => ({ type: "doc", content: [{ type: "paragraph" }] });
150
+ function parseXdoc(input, options = {}) {
151
+ let raw = input;
152
+ if (typeof input === "string") {
153
+ try {
154
+ raw = JSON.parse(input);
155
+ } catch {
156
+ throw new XdocError("Not a XENO document: the file is not JSON");
157
+ }
158
+ }
159
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new XdocError("Not a XENO document");
160
+ const r = raw;
161
+ if (r.format !== void 0 && r.format !== XDOC_FORMAT) throw new XdocError(`Not a XENO document (format "${String(r.format)}")`);
162
+ if (r.version !== void 0 && (!Number.isInteger(r.version) || r.version > XDOC_VERSION)) {
163
+ throw new XdocError(`This document was written by a newer XENO Docs (version ${String(r.version)})`);
164
+ }
165
+ const problems = validateContent(r.content, options);
166
+ if (problems.length) throw new XdocError(`The document contains content this editor cannot show (${problems[0].detail})`, problems);
167
+ const now = Date.now();
168
+ const num = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
169
+ const content = r.content;
170
+ return {
171
+ format: XDOC_FORMAT,
172
+ version: XDOC_VERSION,
173
+ ...typeof r.id === "string" ? { id: r.id } : {},
174
+ title: typeof r.title === "string" ? r.title : "",
175
+ content,
176
+ created: num(r.created, now),
177
+ lastModified: num(r.lastModified, now),
178
+ wordCount: num(r.wordCount, wordCount(content)),
179
+ ...r.commentThreads && typeof r.commentThreads === "object" ? { commentThreads: r.commentThreads } : {}
180
+ };
181
+ }
182
+ function serializeXdoc(doc, options = {}) {
183
+ const problems = validateContent(doc.content, options);
184
+ if (problems.length) throw new XdocError(`The document contains content that cannot be saved (${problems[0].detail})`, problems);
185
+ const file = {
186
+ format: XDOC_FORMAT,
187
+ version: XDOC_VERSION,
188
+ ...doc.id ? { id: doc.id } : {},
189
+ title: doc.title,
190
+ content: doc.content,
191
+ created: doc.created,
192
+ lastModified: doc.lastModified,
193
+ wordCount: doc.wordCount ?? wordCount(doc.content),
194
+ ...doc.commentThreads && typeof doc.commentThreads === "object" ? { commentThreads: doc.commentThreads } : {}
195
+ };
196
+ return JSON.stringify(file, null, options.pretty === false ? void 0 : 2);
197
+ }
198
+ var BLOCKS = /* @__PURE__ */ new Set(["paragraph", "heading", "blockquote", "listItem", "taskItem", "codeBlock", "tableCell", "tableHeader", "callout", "toggleBlock"]);
199
+ function plainText(content) {
200
+ const out = [];
201
+ const walk = (n) => {
202
+ if (!n) return;
203
+ if (n.type === "text" && typeof n.text === "string") out.push(n.text);
204
+ else if (n.type === "hardBreak") out.push(" ");
205
+ if (n.type === "toggleBlock" && typeof n.attrs?.summary === "string") out.push(` ${n.attrs.summary} `);
206
+ for (const c of n.content ?? []) walk(c);
207
+ if (BLOCKS.has(n.type)) out.push(" ");
208
+ };
209
+ walk(content);
210
+ return out.join("").replace(/\s+/g, " ").trim();
211
+ }
212
+ function wordCount(content) {
213
+ const t = plainText(content);
214
+ return t ? t.split(" ").length : 0;
215
+ }
216
+ function pageLinksIn(content) {
217
+ const seen = /* @__PURE__ */ new Set();
218
+ const walk = (n) => {
219
+ if (!n) return;
220
+ for (const m of n.marks ?? []) {
221
+ const id = m.type === "wikiLink" ? m.attrs?.pageId : null;
222
+ if (typeof id === "string" && id) seen.add(id.toLowerCase());
223
+ }
224
+ for (const c of n.content ?? []) walk(c);
225
+ };
226
+ walk(content);
227
+ return [...seen];
228
+ }
229
+ function nodeTypesIn(content) {
230
+ const seen = /* @__PURE__ */ new Set();
231
+ const walk = (n) => {
232
+ if (!n) return;
233
+ seen.add(n.type);
234
+ for (const m of n.marks ?? []) seen.add(`mark:${m.type}`);
235
+ for (const c of n.content ?? []) walk(c);
236
+ };
237
+ walk(content);
238
+ return seen;
239
+ }
240
+
241
+ // src/docs/document/markdown.ts
242
+ var PAGE_LINK = /^xeno-doc:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
243
+ function documentToMarkdown(doc) {
244
+ if (!doc.content) return "";
245
+ return doc.content.map((node) => nodeToMarkdown(node, 0)).join("\n\n");
246
+ }
247
+ function nodeToMarkdown(node, depth) {
248
+ switch (node.type) {
249
+ case "heading":
250
+ return `${"#".repeat(node.attrs?.level || 1)} ${inlineToMarkdown(node.content)}`;
251
+ case "paragraph":
252
+ return inlineToMarkdown(node.content);
253
+ case "bulletList":
254
+ return (node.content || []).map((item) => listItemToMarkdown(item, depth, "-")).join("\n");
255
+ case "orderedList":
256
+ return (node.content || []).map((item, i) => listItemToMarkdown(item, depth, `${i + 1}.`)).join("\n");
257
+ case "taskList":
258
+ return (node.content || []).map((item) => {
259
+ const checked = item.attrs?.checked ? "x" : " ";
260
+ const content = (item.content || []).map((c) => nodeToMarkdown(c, depth + 1)).join("\n");
261
+ return `${" ".repeat(depth)}- [${checked}] ${content}`;
262
+ }).join("\n");
263
+ case "blockquote":
264
+ return (node.content || []).map((c) => `> ${nodeToMarkdown(c, depth)}`).join("\n");
265
+ case "codeBlock": {
266
+ const lang = node.attrs?.language || "";
267
+ const code = node.content?.map((c) => c.text || "").join("") || "";
268
+ return `\`\`\`${lang}
269
+ ${code}
270
+ \`\`\``;
271
+ }
272
+ case "horizontalRule":
273
+ return "---";
274
+ case "image": {
275
+ const alt = node.attrs?.alt || "";
276
+ const src = node.attrs?.src || "";
277
+ const title = node.attrs?.title ? ` "${node.attrs.title}"` : "";
278
+ return `![${alt}](${src}${title})`;
279
+ }
280
+ case "table":
281
+ return tableToMarkdown(node);
282
+ case "callout": {
283
+ const type = node.attrs?.type || "info";
284
+ const content = (node.content || []).map((c) => nodeToMarkdown(c, depth)).join("\n");
285
+ return `> **${type.charAt(0).toUpperCase() + type.slice(1)}:** ${content}`;
286
+ }
287
+ case "mathBlock":
288
+ return `$$
289
+ ${node.attrs?.content || ""}
290
+ $$`;
291
+ case "toggleBlock": {
292
+ const summary = node.attrs?.summary || "Toggle";
293
+ const content = (node.content || []).map((c) => nodeToMarkdown(c, depth)).join("\n");
294
+ return `<details>
295
+ <summary>${summary}</summary>
296
+
297
+ ${content}
298
+ </details>`;
299
+ }
300
+ case "multiColumn": {
301
+ return (node.content || []).map((col, i) => {
302
+ const colContent = (col.content || []).map((c) => nodeToMarkdown(c, depth)).join("\n\n");
303
+ return `<!-- Column ${i + 1} -->
304
+ ${colContent}`;
305
+ }).join("\n\n");
306
+ }
307
+ default:
308
+ return inlineToMarkdown(node.content);
309
+ }
310
+ }
311
+ function listItemToMarkdown(item, depth, bullet) {
312
+ const content = (item.content || []).map((c, i) => {
313
+ const text = nodeToMarkdown(c, depth + 1);
314
+ if (i === 0) return text;
315
+ return " ".repeat(depth + 1) + text;
316
+ }).join("\n");
317
+ return `${" ".repeat(depth)}${bullet} ${content}`;
318
+ }
319
+ function inlineToMarkdown(content) {
320
+ if (!content) return "";
321
+ return content.map((node) => {
322
+ let text = node.text || "";
323
+ if (node.type === "hardBreak") return " \n";
324
+ if (node.type === "image") {
325
+ return `![${node.attrs?.alt || ""}](${node.attrs?.src || ""})`;
326
+ }
327
+ if (!node.marks) return text;
328
+ for (const mark of node.marks) {
329
+ switch (mark.type) {
330
+ case "bold":
331
+ text = `**${text}**`;
332
+ break;
333
+ case "italic":
334
+ text = `*${text}*`;
335
+ break;
336
+ case "strike":
337
+ text = `~~${text}~~`;
338
+ break;
339
+ case "code":
340
+ text = `\`${text}\``;
341
+ break;
342
+ case "link":
343
+ text = `[${text}](${mark.attrs?.href || ""})`;
344
+ break;
345
+ case "wikiLink":
346
+ text = `[${text}](xeno-doc:${mark.attrs?.pageId || ""})`;
347
+ break;
348
+ case "underline":
349
+ text = `<u>${text}</u>`;
350
+ break;
351
+ case "subscript":
352
+ text = `<sub>${text}</sub>`;
353
+ break;
354
+ case "superscript":
355
+ text = `<sup>${text}</sup>`;
356
+ break;
357
+ }
358
+ }
359
+ return text;
360
+ }).join("");
361
+ }
362
+ function tableToMarkdown(node) {
363
+ if (!node.content) return "";
364
+ const rows = node.content.filter((r) => r.type === "tableRow");
365
+ if (rows.length === 0) return "";
366
+ const lines = [];
367
+ for (let i = 0; i < rows.length; i++) {
368
+ const cells = rows[i].content || [];
369
+ const line = cells.map((cell) => {
370
+ const text = (cell.content || []).map((c) => inlineToMarkdown(c.content)).join(" ");
371
+ return ` ${text} `;
372
+ }).join("|");
373
+ lines.push(`|${line}|`);
374
+ if (i === 0) {
375
+ const sep = cells.map(() => " --- ").join("|");
376
+ lines.push(`|${sep}|`);
377
+ }
378
+ }
379
+ return lines.join("\n");
380
+ }
381
+ function markdownToDocument(md) {
382
+ let input = md;
383
+ const frontmatterMatch = input.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
384
+ if (frontmatterMatch) {
385
+ input = input.slice(frontmatterMatch[0].length);
386
+ }
387
+ const lines = input.split("\n");
388
+ const content = [];
389
+ let i = 0;
390
+ while (i < lines.length) {
391
+ const line = lines[i];
392
+ if (line.trim() === "") {
393
+ i++;
394
+ continue;
395
+ }
396
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
397
+ if (headingMatch) {
398
+ content.push({
399
+ type: "heading",
400
+ attrs: { level: headingMatch[1].length },
401
+ content: parseInlineMarkdown(headingMatch[2])
402
+ });
403
+ i++;
404
+ continue;
405
+ }
406
+ if (/^(---|\*\*\*|___)/.test(line.trim())) {
407
+ content.push({ type: "horizontalRule" });
408
+ i++;
409
+ continue;
410
+ }
411
+ if (line.trim().startsWith("```")) {
412
+ const lang = line.trim().slice(3).trim();
413
+ const codeLines = [];
414
+ i++;
415
+ while (i < lines.length && !lines[i].trim().startsWith("```")) {
416
+ codeLines.push(lines[i]);
417
+ i++;
418
+ }
419
+ i++;
420
+ content.push({
421
+ type: "codeBlock",
422
+ attrs: { language: lang || null },
423
+ content: [{ type: "text", text: codeLines.join("\n") }]
424
+ });
425
+ continue;
426
+ }
427
+ if (line.trim() === "$$") {
428
+ const mathLines = [];
429
+ i++;
430
+ while (i < lines.length && lines[i].trim() !== "$$") {
431
+ mathLines.push(lines[i]);
432
+ i++;
433
+ }
434
+ i++;
435
+ content.push({
436
+ type: "mathBlock",
437
+ attrs: { content: mathLines.join("\n") }
438
+ });
439
+ continue;
440
+ }
441
+ if (line.startsWith(">")) {
442
+ const quoteLines = [];
443
+ while (i < lines.length && lines[i].startsWith(">")) {
444
+ quoteLines.push(lines[i].replace(/^>\s?/, ""));
445
+ i++;
446
+ }
447
+ content.push({
448
+ type: "blockquote",
449
+ content: quoteLines.map((ql) => ({
450
+ type: "paragraph",
451
+ content: parseInlineMarkdown(ql)
452
+ }))
453
+ });
454
+ continue;
455
+ }
456
+ if (/^\s*[-*+]\s/.test(line)) {
457
+ const listItems = [];
458
+ while (i < lines.length && /^\s*[-*+]\s/.test(lines[i])) {
459
+ const itemText = lines[i].replace(/^\s*[-*+]\s/, "");
460
+ const taskMatch = itemText.match(/^\[([ xX])\]\s*(.*)$/);
461
+ if (taskMatch) {
462
+ listItems.push({
463
+ type: "taskItem",
464
+ attrs: { checked: taskMatch[1] !== " " },
465
+ content: [{ type: "paragraph", content: parseInlineMarkdown(taskMatch[2]) }]
466
+ });
467
+ } else {
468
+ listItems.push({
469
+ type: "listItem",
470
+ content: [{ type: "paragraph", content: parseInlineMarkdown(itemText) }]
471
+ });
472
+ }
473
+ i++;
474
+ }
475
+ if (listItems[0]?.type === "taskItem") {
476
+ content.push({ type: "taskList", content: listItems });
477
+ } else {
478
+ content.push({ type: "bulletList", content: listItems });
479
+ }
480
+ continue;
481
+ }
482
+ if (/^\s*\d+\.\s/.test(line)) {
483
+ const listItems = [];
484
+ while (i < lines.length && /^\s*\d+\.\s/.test(lines[i])) {
485
+ const itemText = lines[i].replace(/^\s*\d+\.\s/, "");
486
+ listItems.push({
487
+ type: "listItem",
488
+ content: [{ type: "paragraph", content: parseInlineMarkdown(itemText) }]
489
+ });
490
+ i++;
491
+ }
492
+ content.push({ type: "orderedList", content: listItems });
493
+ continue;
494
+ }
495
+ if (line.includes("|") && line.trim().startsWith("|")) {
496
+ const tableRows = [];
497
+ let isFirst = true;
498
+ while (i < lines.length && lines[i].includes("|")) {
499
+ const trimmed = lines[i].trim();
500
+ if (/^\|[\s\-:|]+\|$/.test(trimmed)) {
501
+ i++;
502
+ continue;
503
+ }
504
+ const cells = trimmed.split("|").filter((_, idx, arr) => idx > 0 && idx < arr.length - 1).map((cell) => cell.trim());
505
+ const cellType = isFirst ? "tableHeader" : "tableCell";
506
+ tableRows.push({
507
+ type: "tableRow",
508
+ content: cells.map((cellText) => ({
509
+ type: cellType,
510
+ content: [{ type: "paragraph", content: parseInlineMarkdown(cellText) }]
511
+ }))
512
+ });
513
+ isFirst = false;
514
+ i++;
515
+ }
516
+ content.push({ type: "table", content: tableRows });
517
+ continue;
518
+ }
519
+ const imgMatch = line.match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
520
+ if (imgMatch) {
521
+ content.push({
522
+ type: "image",
523
+ attrs: { src: imgMatch[2], alt: imgMatch[1] }
524
+ });
525
+ i++;
526
+ continue;
527
+ }
528
+ const paraLines = [line];
529
+ i++;
530
+ while (i < lines.length && lines[i].trim() !== "" && !/^(#{1,6}\s|```|>|\s*[-*+]\s|\s*\d+\.\s|---|\*\*\*|___|!\[|\|)/.test(lines[i])) {
531
+ paraLines.push(lines[i]);
532
+ i++;
533
+ }
534
+ content.push({
535
+ type: "paragraph",
536
+ content: parseInlineMarkdown(paraLines.join(" "))
537
+ });
538
+ }
539
+ return { type: "doc", content: content.length > 0 ? content : [{ type: "paragraph" }] };
540
+ }
541
+ function parseInlineMarkdown(text) {
542
+ if (!text) return [];
543
+ const result = [];
544
+ const inlineRegex = /(\*\*(.+?)\*\*|\*(.+?)\*|~~(.+?)~~|`([^`]+)`|\[([^\]]+)\]\(([^)]+)\)|<u>(.+?)<\/u>|<sub>(.+?)<\/sub>|<sup>(.+?)<\/sup>)/g;
545
+ let lastIndex = 0;
546
+ let match;
547
+ while ((match = inlineRegex.exec(text)) !== null) {
548
+ if (match.index > lastIndex) {
549
+ result.push({ type: "text", text: text.slice(lastIndex, match.index) });
550
+ }
551
+ if (match[2]) {
552
+ result.push({ type: "text", text: match[2], marks: [{ type: "bold" }] });
553
+ } else if (match[3]) {
554
+ result.push({ type: "text", text: match[3], marks: [{ type: "italic" }] });
555
+ } else if (match[4]) {
556
+ result.push({ type: "text", text: match[4], marks: [{ type: "strike" }] });
557
+ } else if (match[5]) {
558
+ result.push({ type: "text", text: match[5], marks: [{ type: "code" }] });
559
+ } else if (match[6] && match[7]) {
560
+ const page = PAGE_LINK.exec(match[7]);
561
+ if (page) result.push({ type: "text", text: match[6], marks: [{ type: "wikiLink", attrs: { pageId: page[1].toLowerCase(), pageTitle: match[6] } }] });
562
+ else if (SAFE_LINK.test(match[7].trim())) result.push({ type: "text", text: match[6], marks: [{ type: "link", attrs: { href: match[7] } }] });
563
+ else result.push({ type: "text", text: match[6] });
564
+ } else if (match[8]) {
565
+ result.push({ type: "text", text: match[8], marks: [{ type: "underline" }] });
566
+ } else if (match[9]) {
567
+ result.push({ type: "text", text: match[9], marks: [{ type: "subscript" }] });
568
+ } else if (match[10]) {
569
+ result.push({ type: "text", text: match[10], marks: [{ type: "superscript" }] });
570
+ }
571
+ lastIndex = match.index + match[0].length;
572
+ }
573
+ if (lastIndex < text.length) {
574
+ result.push({ type: "text", text: text.slice(lastIndex) });
575
+ }
576
+ const kept = result.filter((n) => n.type !== "text" || (n.text ?? "") !== "");
577
+ return kept.length > 0 ? kept : text ? [{ type: "text", text }] : [];
578
+ }
579
+
580
+ export {
581
+ XDOC_FORMAT,
582
+ XDOC_VERSION,
583
+ XDOC_EXTENSION,
584
+ DOCUMENT_NODE_TYPES,
585
+ DOCUMENT_MARK_TYPES,
586
+ SAFE_LINK,
587
+ SAFE_IMAGE,
588
+ LIMITS,
589
+ validateContent,
590
+ XdocError,
591
+ emptyContent,
592
+ parseXdoc,
593
+ serializeXdoc,
594
+ plainText,
595
+ wordCount,
596
+ pageLinksIn,
597
+ nodeTypesIn,
598
+ documentToMarkdown,
599
+ markdownToDocument
600
+ };