testomatio-editor-blocks 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1038 @@
1
+ import { isLinkInlineContent, isStyledTextInlineContent, } from "@blocknote/core";
2
+ const BASE_BLOCK_PROPS = {
3
+ textAlignment: "left",
4
+ textColor: "default",
5
+ backgroundColor: "default",
6
+ };
7
+ const BASE_CELL_PROPS = {
8
+ backgroundColor: "default",
9
+ textColor: "default",
10
+ textAlignment: "left",
11
+ };
12
+ const TABLE_BLOCK_PROPS = {
13
+ textColor: "default",
14
+ };
15
+ const headingPrefixes = {
16
+ 1: "#",
17
+ 2: "##",
18
+ 3: "###",
19
+ 4: "####",
20
+ 5: "#####",
21
+ 6: "######",
22
+ };
23
+ const STEP_STATUSES = new Set(["draft", "ready", "blocked"]);
24
+ function normalizeStatus(value) {
25
+ if (value && STEP_STATUSES.has(value)) {
26
+ return value;
27
+ }
28
+ return "draft";
29
+ }
30
+ const SPECIAL_CHAR_REGEX = /([*_`~\[\]()<>\\])/g;
31
+ const HTML_SPAN_REGEX = /<\/?span[^>]*>/g;
32
+ const HTML_UNDERLINE_REGEX = /<\/?u>/g;
33
+ const EXPECTED_LABEL_REGEX = /^(?:[*_`]*\s*)?(expected(?:\s+result)?)\s*(?:[*_`]*\s*)?(?:\s*[:\-–—]?\s*)/i;
34
+ // Matches any non-empty line that falls between the step title and the expected result line.
35
+ const STEP_DATA_LINE_REGEX = /^(?!\s*(?:[*_`]*\s*)?(?:expected(?:\s+result)?)\b).+/i;
36
+ const NUMBERED_STEP_REGEX = /^\d+[.)]\s+/;
37
+ function escapeMarkdown(text) {
38
+ return text.replace(SPECIAL_CHAR_REGEX, "\\$1");
39
+ }
40
+ function stripHtmlWrappers(text) {
41
+ return text
42
+ .replace(HTML_SPAN_REGEX, "")
43
+ .replace(HTML_UNDERLINE_REGEX, "");
44
+ }
45
+ function stripExpectedPrefix(text) {
46
+ const match = text.match(EXPECTED_LABEL_REGEX);
47
+ if (!match) {
48
+ return text;
49
+ }
50
+ const label = match[0];
51
+ let remainder = text.slice(label.length).trimStart();
52
+ const cleanupLeading = (value) => {
53
+ let result = value.trimStart();
54
+ result = result.replace(/^\\+(?=[*_`~:[\]])/, "");
55
+ result = result.replace(/^(?:[*_`~]+)(?=\s|$)/, "");
56
+ return result.trimStart();
57
+ };
58
+ remainder = cleanupLeading(remainder);
59
+ remainder = stripLeadingFormatting(remainder);
60
+ return remainder.trimStart();
61
+ }
62
+ function stripLeadingFormatting(text) {
63
+ let result = text.trimStart();
64
+ let changed = true;
65
+ while (changed) {
66
+ changed = false;
67
+ // Remove escaped markers like \* or \_ at the start
68
+ if (/^\\+[*_`~]/.test(result)) {
69
+ result = result.replace(/^\\+/, "");
70
+ changed = true;
71
+ result = result.trimStart();
72
+ continue;
73
+ }
74
+ // Remove leading sequences of markdown emphasis markers
75
+ const leadingMarkers = result.match(/^([*_`~]{1,3})(\s+|$)/);
76
+ if (leadingMarkers) {
77
+ result = result.slice(leadingMarkers[1].length).trimStart();
78
+ changed = true;
79
+ continue;
80
+ }
81
+ }
82
+ return result;
83
+ }
84
+ function unescapeMarkdown(text) {
85
+ return stripHtmlWrappers(text).replace(/\\([*_`~\[\]()<>\\])/g, "$1");
86
+ }
87
+ function applyTextStyles(text, styles) {
88
+ var _a;
89
+ if (!styles) {
90
+ return text;
91
+ }
92
+ const hasCode = styles.code === true;
93
+ let result = text;
94
+ if (hasCode) {
95
+ result = "`" + result.replace(/`/g, "\\`") + "`";
96
+ // Code style supersedes other styles in Markdown.
97
+ return result;
98
+ }
99
+ const wrappers = [];
100
+ if (styles.bold) {
101
+ wrappers.push({ prefix: "**", suffix: "**" });
102
+ }
103
+ if (styles.italic) {
104
+ wrappers.push({ prefix: "*", suffix: "*" });
105
+ }
106
+ if (styles.strike) {
107
+ wrappers.push({ prefix: "~~", suffix: "~~" });
108
+ }
109
+ if (styles.underline) {
110
+ wrappers.push({ prefix: "<u>", suffix: "</u>" });
111
+ }
112
+ if (styles.textColor && styles.textColor !== "default") {
113
+ wrappers.push({
114
+ prefix: `<span style="color: ${styles.textColor}">`,
115
+ suffix: "</span>",
116
+ });
117
+ }
118
+ if (styles.backgroundColor && styles.backgroundColor !== "default") {
119
+ wrappers.push({
120
+ prefix: `<span style="background-color: ${styles.backgroundColor}">`,
121
+ suffix: "</span>",
122
+ });
123
+ }
124
+ for (const wrapper of wrappers) {
125
+ const suffix = (_a = wrapper.suffix) !== null && _a !== void 0 ? _a : wrapper.prefix;
126
+ result = `${wrapper.prefix}${result}${suffix}`;
127
+ }
128
+ return result;
129
+ }
130
+ function inlineToMarkdown(content) {
131
+ if (!content || !Array.isArray(content)) {
132
+ return "";
133
+ }
134
+ return content
135
+ .map((item) => {
136
+ if (isStyledTextInlineContent(item)) {
137
+ return applyTextStyles(escapeMarkdown(item.text), item.styles);
138
+ }
139
+ if (isLinkInlineContent(item)) {
140
+ const inner = inlineToMarkdown(item.content);
141
+ const safeHref = escapeMarkdown(item.href);
142
+ return `[${inner}](${safeHref})`;
143
+ }
144
+ if (Array.isArray(item.content)) {
145
+ return inlineToMarkdown(item.content);
146
+ }
147
+ return "";
148
+ })
149
+ .join("");
150
+ }
151
+ function serializeChildren(block, ctx) {
152
+ var _a;
153
+ if (!((_a = block.children) === null || _a === void 0 ? void 0 : _a.length)) {
154
+ return [];
155
+ }
156
+ const childCtx = { ...ctx, listDepth: ctx.listDepth + 1 };
157
+ return serializeBlocks(block.children, childCtx);
158
+ }
159
+ function flattenWithBlankLine(lines, appendBlank = false) {
160
+ if (appendBlank && (lines.length === 0 || lines.at(-1) !== "")) {
161
+ return [...lines, ""];
162
+ }
163
+ return lines;
164
+ }
165
+ function serializeBlock(block, ctx, orderedIndex) {
166
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
167
+ const lines = [];
168
+ const indent = ctx.listDepth > 0 ? " ".repeat(ctx.listDepth) : "";
169
+ switch (block.type) {
170
+ case "paragraph": {
171
+ const text = inlineToMarkdown(block.content);
172
+ if (text.length > 0) {
173
+ lines.push(ctx.insideQuote ? `> ${text}` : text);
174
+ }
175
+ return flattenWithBlankLine(lines, !ctx.insideQuote);
176
+ }
177
+ case "heading": {
178
+ const level = (_a = block.props.level) !== null && _a !== void 0 ? _a : 1;
179
+ const prefix = (_b = headingPrefixes[level]) !== null && _b !== void 0 ? _b : headingPrefixes[3];
180
+ const text = inlineToMarkdown(block.content);
181
+ lines.push(`${prefix} ${text}`.trimEnd());
182
+ return flattenWithBlankLine(lines, true);
183
+ }
184
+ case "quote": {
185
+ const quoteContent = serializeBlocks((_c = block.children) !== null && _c !== void 0 ? _c : [], {
186
+ ...ctx,
187
+ listDepth: ctx.listDepth,
188
+ insideQuote: true,
189
+ });
190
+ if ((_d = block.content) === null || _d === void 0 ? void 0 : _d.length) {
191
+ const quoteText = inlineToMarkdown(block.content)
192
+ .split(/\n/)
193
+ .map((fragment) => `> ${fragment}`);
194
+ lines.push(...quoteText);
195
+ }
196
+ lines.push(...quoteContent.map((line) => (line ? `> ${line}` : ">")));
197
+ return flattenWithBlankLine(lines, true);
198
+ }
199
+ case "codeBlock": {
200
+ const language = block.props.language || "";
201
+ const fence = "```" + language;
202
+ const body = inlineToMarkdown(block.content);
203
+ lines.push(fence);
204
+ if (body.length > 0) {
205
+ lines.push(body);
206
+ }
207
+ lines.push("```");
208
+ return flattenWithBlankLine(lines, true);
209
+ }
210
+ case "bulletListItem": {
211
+ const text = inlineToMarkdown(block.content);
212
+ lines.push(`${indent}- ${text}`.trimEnd());
213
+ lines.push(...serializeChildren(block, ctx));
214
+ return lines;
215
+ }
216
+ case "numberedListItem": {
217
+ const number = orderedIndex !== null && orderedIndex !== void 0 ? orderedIndex : (typeof block.props.start === "number"
218
+ ? block.props.start
219
+ : 1);
220
+ const text = inlineToMarkdown(block.content);
221
+ lines.push(`${indent}${number}. ${text}`.trimEnd());
222
+ lines.push(...serializeChildren(block, ctx));
223
+ return lines;
224
+ }
225
+ case "checkListItem": {
226
+ const checked = block.props.checked ? "x" : " ";
227
+ const text = inlineToMarkdown(block.content);
228
+ lines.push(`${indent}- [${checked}] ${text}`.trimEnd());
229
+ lines.push(...serializeChildren(block, ctx));
230
+ return lines;
231
+ }
232
+ case "testCase": {
233
+ const status = (_e = block.props.status) !== null && _e !== void 0 ? _e : "draft";
234
+ const reference = block.props.reference;
235
+ const attrs = [`status="${status}"`];
236
+ if (reference) {
237
+ attrs.push(`reference="${escapeMarkdown(reference)}"`);
238
+ }
239
+ lines.push(`:::test-case ${attrs.join(" ")}`.trimEnd());
240
+ const body = inlineToMarkdown(block.content);
241
+ if (body.length > 0) {
242
+ lines.push(body);
243
+ }
244
+ lines.push(":::");
245
+ return flattenWithBlankLine(lines, true);
246
+ }
247
+ case "testStep": {
248
+ const stepTitle = ((_f = block.props.stepTitle) !== null && _f !== void 0 ? _f : "").trim();
249
+ const stepData = ((_g = block.props.stepData) !== null && _g !== void 0 ? _g : "").trim();
250
+ const expectedResult = ((_h = block.props.expectedResult) !== null && _h !== void 0 ? _h : "").trim();
251
+ if (stepTitle.length > 0) {
252
+ const normalizedTitle = stepTitle
253
+ .split(/\r?\n/)
254
+ .map((segment) => segment.trim())
255
+ .filter((segment) => segment.length > 0)
256
+ .join(" ");
257
+ if (normalizedTitle.length > 0) {
258
+ lines.push(`* ${normalizedTitle}`);
259
+ }
260
+ }
261
+ if (stepData.length > 0) {
262
+ const dataLines = stepData.split(/\r?\n/);
263
+ dataLines.forEach((dataLine) => {
264
+ const trimmedLine = dataLine.trim();
265
+ if (trimmedLine.length > 0) {
266
+ lines.push(` ${trimmedLine}`);
267
+ }
268
+ else {
269
+ lines.push(" ");
270
+ }
271
+ });
272
+ }
273
+ const normalizedExpected = stripExpectedPrefix(expectedResult).trim();
274
+ if (normalizedExpected.length > 0) {
275
+ const expectedLines = normalizedExpected.split(/\r?\n/);
276
+ const label = "*Expected Result*";
277
+ expectedLines.forEach((expectedLine, index) => {
278
+ const trimmedLine = expectedLine.trim();
279
+ if (trimmedLine.length === 0) {
280
+ return;
281
+ }
282
+ if (index === 0) {
283
+ lines.push(` ${label}: ${trimmedLine}`);
284
+ }
285
+ else {
286
+ lines.push(` ${trimmedLine}`);
287
+ }
288
+ });
289
+ }
290
+ if (lines.length === 0) {
291
+ return lines;
292
+ }
293
+ return flattenWithBlankLine(lines, false);
294
+ }
295
+ case "table": {
296
+ const tableContent = block.content;
297
+ if (!tableContent || tableContent.type !== "tableContent") {
298
+ return flattenWithBlankLine(lines, true);
299
+ }
300
+ const rows = Array.isArray(tableContent.rows)
301
+ ? tableContent.rows
302
+ : [];
303
+ if (rows.length === 0) {
304
+ return flattenWithBlankLine(lines, true);
305
+ }
306
+ const columnCount = rows.reduce((max, row) => {
307
+ const length = Array.isArray(row.cells) ? row.cells.length : 0;
308
+ return Math.max(max, length);
309
+ }, 0);
310
+ if (columnCount === 0) {
311
+ return flattenWithBlankLine(lines, true);
312
+ }
313
+ const headerRowCount = rows.length
314
+ ? Math.min(rows.length, Math.max((_j = tableContent.headerRows) !== null && _j !== void 0 ? _j : 1, 1))
315
+ : 0;
316
+ const columnAlignments = new Array(columnCount).fill("left");
317
+ const getCellAlignment = (cell) => {
318
+ var _a;
319
+ if (cell && typeof cell === "object" && ((_a = cell.props) === null || _a === void 0 ? void 0 : _a.textAlignment)) {
320
+ return cell.props.textAlignment;
321
+ }
322
+ return "left";
323
+ };
324
+ const getCellText = (cell) => {
325
+ if (Array.isArray(cell)) {
326
+ return inlineToMarkdown(cell);
327
+ }
328
+ if (cell && typeof cell === "object" && Array.isArray(cell.content)) {
329
+ return inlineToMarkdown(cell.content);
330
+ }
331
+ return "";
332
+ };
333
+ rows.forEach((row) => {
334
+ if (!Array.isArray(row.cells)) {
335
+ return;
336
+ }
337
+ row.cells.forEach((cell, index) => {
338
+ const alignment = getCellAlignment(cell);
339
+ if (alignment !== "left") {
340
+ columnAlignments[index] = alignment;
341
+ }
342
+ });
343
+ });
344
+ const normalizeRow = (row) => {
345
+ const cells = Array.isArray(row.cells) ? row.cells : [];
346
+ const cellTexts = cells.map(getCellText);
347
+ while (cellTexts.length < columnCount) {
348
+ cellTexts.push("");
349
+ }
350
+ return cellTexts;
351
+ };
352
+ const formattedRows = rows.map(normalizeRow);
353
+ const formatCell = (value) => (value.length ? value : " ");
354
+ const toAlignmentToken = (alignment) => {
355
+ switch (alignment) {
356
+ case "center":
357
+ return ":---:";
358
+ case "right":
359
+ return "---:";
360
+ case "justify":
361
+ return ":---:";
362
+ default:
363
+ return "---";
364
+ }
365
+ };
366
+ const headerRow = formattedRows[0];
367
+ lines.push(`| ${headerRow.map((cell) => formatCell(cell)).join(" | ")} |`);
368
+ lines.push(`| ${columnAlignments
369
+ .map((alignment) => toAlignmentToken(alignment))
370
+ .join(" | ")} |`);
371
+ const bodyStartIndex = headerRowCount > 0 ? headerRowCount : 1;
372
+ formattedRows.slice(bodyStartIndex).forEach((row) => {
373
+ lines.push(`| ${row.map((cell) => formatCell(cell)).join(" | ")} |`);
374
+ });
375
+ return flattenWithBlankLine(lines, true);
376
+ }
377
+ }
378
+ const fallbackBlock = block;
379
+ if (fallbackBlock.content) {
380
+ const text = inlineToMarkdown(fallbackBlock.content);
381
+ if (text.length > 0) {
382
+ lines.push(text);
383
+ }
384
+ }
385
+ lines.push(...serializeChildren(fallbackBlock, ctx));
386
+ return flattenWithBlankLine(lines, false);
387
+ }
388
+ function serializeBlocks(blocks, ctx) {
389
+ const lines = [];
390
+ let orderedIndex = null;
391
+ for (const block of blocks) {
392
+ if (block.type === "numberedListItem") {
393
+ if (typeof block.props.start === "number") {
394
+ orderedIndex = block.props.start;
395
+ }
396
+ else if (orderedIndex === null) {
397
+ orderedIndex = 1;
398
+ }
399
+ lines.push(...serializeBlock(block, ctx, orderedIndex));
400
+ orderedIndex += 1;
401
+ continue;
402
+ }
403
+ orderedIndex = null;
404
+ lines.push(...serializeBlock(block, ctx));
405
+ }
406
+ return lines;
407
+ }
408
+ export function blocksToMarkdown(blocks) {
409
+ const lines = serializeBlocks(blocks, { listDepth: 0, insideQuote: false });
410
+ const cleaned = lines
411
+ // Collapse more than two blank lines into just two for readability.
412
+ .join("\n")
413
+ .replace(/\n{3,}/g, "\n\n")
414
+ .trimEnd();
415
+ return cleaned;
416
+ }
417
+ function parseInlineMarkdown(text) {
418
+ const cleaned = stripHtmlWrappers(text);
419
+ const result = [];
420
+ let buffer = "";
421
+ const pushPlain = () => {
422
+ if (buffer.length === 0) {
423
+ return;
424
+ }
425
+ result.push({ type: "text", text: unescapeMarkdown(buffer), styles: {} });
426
+ buffer = "";
427
+ };
428
+ let i = 0;
429
+ while (i < cleaned.length) {
430
+ if (cleaned.startsWith("**", i)) {
431
+ const end = cleaned.indexOf("**", i + 2);
432
+ if (end !== -1) {
433
+ pushPlain();
434
+ const inner = cleaned.slice(i + 2, end);
435
+ result.push({
436
+ type: "text",
437
+ text: unescapeMarkdown(inner),
438
+ styles: { bold: true },
439
+ });
440
+ i = end + 2;
441
+ continue;
442
+ }
443
+ }
444
+ if (cleaned.startsWith("~~", i)) {
445
+ const end = cleaned.indexOf("~~", i + 2);
446
+ if (end !== -1) {
447
+ pushPlain();
448
+ const inner = cleaned.slice(i + 2, end);
449
+ result.push({
450
+ type: "text",
451
+ text: unescapeMarkdown(inner),
452
+ styles: { strike: true },
453
+ });
454
+ i = end + 2;
455
+ continue;
456
+ }
457
+ }
458
+ if (cleaned.startsWith("`", i)) {
459
+ const end = cleaned.indexOf("`", i + 1);
460
+ if (end !== -1) {
461
+ pushPlain();
462
+ const inner = cleaned.slice(i + 1, end);
463
+ result.push({
464
+ type: "text",
465
+ text: unescapeMarkdown(inner),
466
+ styles: { code: true },
467
+ });
468
+ i = end + 1;
469
+ continue;
470
+ }
471
+ }
472
+ if (cleaned[i] === "[") {
473
+ const endLabel = cleaned.indexOf("]", i + 1);
474
+ const startLink = cleaned.indexOf("(", endLabel + 1);
475
+ const endLink = cleaned.indexOf(")", startLink + 1);
476
+ if (endLabel !== -1 && startLink === endLabel + 1 && endLink !== -1) {
477
+ pushPlain();
478
+ const label = cleaned.slice(i + 1, endLabel);
479
+ const href = cleaned.slice(startLink + 1, endLink);
480
+ result.push({
481
+ type: "link",
482
+ href: unescapeMarkdown(href),
483
+ content: parseInlineMarkdown(label),
484
+ });
485
+ i = endLink + 1;
486
+ continue;
487
+ }
488
+ }
489
+ if (cleaned.startsWith("*", i)) {
490
+ const end = cleaned.indexOf("*", i + 1);
491
+ if (end !== -1) {
492
+ pushPlain();
493
+ const inner = cleaned.slice(i + 1, end);
494
+ result.push({
495
+ type: "text",
496
+ text: unescapeMarkdown(inner),
497
+ styles: { italic: true },
498
+ });
499
+ i = end + 1;
500
+ continue;
501
+ }
502
+ }
503
+ buffer += cleaned[i];
504
+ i += 1;
505
+ }
506
+ pushPlain();
507
+ return result;
508
+ }
509
+ function createTextContent(text) {
510
+ const inline = parseInlineMarkdown(text.trim());
511
+ return inline.length === 0 ? undefined : inline;
512
+ }
513
+ function cloneBaseProps() {
514
+ return { ...BASE_BLOCK_PROPS };
515
+ }
516
+ function cloneCellProps() {
517
+ return { ...BASE_CELL_PROPS };
518
+ }
519
+ function detectListType(trimmed) {
520
+ if (/^[-*+]\s+\[[xX\s]\]\s+/.test(trimmed)) {
521
+ return "check";
522
+ }
523
+ if (/^\d+[.)]\s+/.test(trimmed)) {
524
+ return "numbered";
525
+ }
526
+ if (/^[-*+]\s+/.test(trimmed)) {
527
+ return "bullet";
528
+ }
529
+ return null;
530
+ }
531
+ function countIndent(line) {
532
+ const match = line.match(/^ */);
533
+ return match ? match[0].length : 0;
534
+ }
535
+ function parseList(lines, startIndex, listType, indentLevel) {
536
+ var _a, _b, _c, _d;
537
+ const items = [];
538
+ let index = startIndex;
539
+ while (index < lines.length) {
540
+ const rawLine = lines[index];
541
+ const trimmed = rawLine.trim();
542
+ if (!trimmed) {
543
+ index += 1;
544
+ continue;
545
+ }
546
+ let indent = countIndent(rawLine);
547
+ const baseIndent = indentLevel * 2;
548
+ if (indent > baseIndent && indent <= baseIndent + 1) {
549
+ indent = baseIndent;
550
+ }
551
+ if (indent < indentLevel * 2) {
552
+ break;
553
+ }
554
+ if (indent > indentLevel * 2) {
555
+ const lastItem = items.at(-1);
556
+ if (!lastItem) {
557
+ break;
558
+ }
559
+ const nestedType = detectListType(trimmed);
560
+ if (!nestedType) {
561
+ break;
562
+ }
563
+ const nested = parseList(lines, index, nestedType, indentLevel + 1);
564
+ lastItem.children = [...((_a = lastItem.children) !== null && _a !== void 0 ? _a : []), ...nested.items];
565
+ index = nested.nextIndex;
566
+ continue;
567
+ }
568
+ const detectedType = detectListType(trimmed);
569
+ if (detectedType !== listType) {
570
+ break;
571
+ }
572
+ if (listType === "check") {
573
+ const checkMatch = trimmed.match(/^[-*+]\s+\[([xX\s])\]\s+(.*)$/);
574
+ const checked = ((_b = checkMatch === null || checkMatch === void 0 ? void 0 : checkMatch[1]) !== null && _b !== void 0 ? _b : "").toLowerCase() === "x";
575
+ const text = (_c = checkMatch === null || checkMatch === void 0 ? void 0 : checkMatch[2]) !== null && _c !== void 0 ? _c : trimmed.slice(6);
576
+ items.push({
577
+ type: "checkListItem",
578
+ props: { ...cloneBaseProps(), checked },
579
+ content: createTextContent(unescapeMarkdown(text)),
580
+ children: [],
581
+ });
582
+ }
583
+ else if (listType === "numbered") {
584
+ const match = trimmed.match(/^(\d+)[.)]\s+(.*)$/);
585
+ const start = match ? Number(match[1]) : 1;
586
+ const text = match ? match[2] : trimmed;
587
+ items.push({
588
+ type: "numberedListItem",
589
+ props: { ...cloneBaseProps(), start },
590
+ content: createTextContent(unescapeMarkdown(text)),
591
+ children: [],
592
+ });
593
+ }
594
+ else {
595
+ const bulletMatch = trimmed.match(/^[-*+]\s+(.*)$/);
596
+ const text = (_d = bulletMatch === null || bulletMatch === void 0 ? void 0 : bulletMatch[1]) !== null && _d !== void 0 ? _d : trimmed.slice(2);
597
+ items.push({
598
+ type: "bulletListItem",
599
+ props: cloneBaseProps(),
600
+ content: createTextContent(unescapeMarkdown(text)),
601
+ children: [],
602
+ });
603
+ }
604
+ index += 1;
605
+ }
606
+ return { items, nextIndex: index };
607
+ }
608
+ function parseTestStep(lines, index) {
609
+ const current = lines[index];
610
+ const trimmed = current.trim();
611
+ if (!trimmed.startsWith("* ") && !trimmed.startsWith("- ")) {
612
+ return null;
613
+ }
614
+ const stepTitle = unescapeMarkdown(trimmed.slice(2)).trim();
615
+ const stepDataLines = [];
616
+ let expectedResult = "";
617
+ let next = index + 1;
618
+ let inExpectedResult = false;
619
+ while (next < lines.length) {
620
+ const line = lines[next];
621
+ const hasIndent = /^\s{2,}/.test(line);
622
+ const rawTrimmed = line.trim();
623
+ if (!rawTrimmed) {
624
+ if (stepDataLines.length > 0) {
625
+ stepDataLines.push("");
626
+ }
627
+ next += 1;
628
+ continue;
629
+ }
630
+ const isNumberedStep = NUMBERED_STEP_REGEX.test(rawTrimmed);
631
+ const isNewStep = (!hasIndent && (rawTrimmed.startsWith("* ") || rawTrimmed.startsWith("- "))) ||
632
+ (!hasIndent && isNumberedStep);
633
+ if (isNewStep) {
634
+ break;
635
+ }
636
+ if (rawTrimmed.startsWith("#") ||
637
+ rawTrimmed.startsWith(":::") ||
638
+ rawTrimmed.startsWith(">") ||
639
+ rawTrimmed.startsWith("|")) {
640
+ break;
641
+ }
642
+ if (rawTrimmed.match(EXPECTED_LABEL_REGEX)) {
643
+ inExpectedResult = true;
644
+ const withoutLabel = stripExpectedPrefix(rawTrimmed);
645
+ expectedResult = unescapeMarkdown(withoutLabel);
646
+ next += 1;
647
+ continue;
648
+ }
649
+ if (rawTrimmed.startsWith("```")) {
650
+ stepDataLines.push(unescapeMarkdown(rawTrimmed));
651
+ next += 1;
652
+ while (next < lines.length) {
653
+ const fenceLine = lines[next];
654
+ const fenceTrimmed = fenceLine.trim();
655
+ stepDataLines.push(unescapeMarkdown(fenceTrimmed));
656
+ next += 1;
657
+ if (fenceTrimmed.startsWith("```")) {
658
+ break;
659
+ }
660
+ }
661
+ continue;
662
+ }
663
+ if (inExpectedResult) {
664
+ const withoutLabel = stripExpectedPrefix(rawTrimmed);
665
+ expectedResult += "\n" + unescapeMarkdown(withoutLabel);
666
+ next += 1;
667
+ continue;
668
+ }
669
+ if (STEP_DATA_LINE_REGEX.test(rawTrimmed)) {
670
+ const content = unescapeMarkdown(rawTrimmed);
671
+ stepDataLines.push(content);
672
+ next += 1;
673
+ continue;
674
+ }
675
+ break;
676
+ }
677
+ const stepData = stepDataLines
678
+ .map((line) => line.trimEnd())
679
+ .join("\n")
680
+ .trim();
681
+ // Only parse as test step if there's expected result or data content
682
+ if (expectedResult || stepData) {
683
+ return {
684
+ block: {
685
+ type: "testStep",
686
+ props: {
687
+ stepTitle,
688
+ stepData,
689
+ expectedResult,
690
+ },
691
+ children: [],
692
+ },
693
+ nextIndex: next,
694
+ };
695
+ }
696
+ return null;
697
+ }
698
+ function parseTestCase(lines, index) {
699
+ var _a;
700
+ const trimmed = lines[index].trim();
701
+ if (!trimmed.startsWith(":::test-case")) {
702
+ return null;
703
+ }
704
+ const statusMatch = trimmed.match(/status="([^"]*)"/);
705
+ const referenceMatch = trimmed.match(/reference="([^"]*)"/);
706
+ let bodyLines = [];
707
+ let next = index + 1;
708
+ while (next < lines.length && lines[next].trim() !== ":::") {
709
+ bodyLines.push(lines[next]);
710
+ next += 1;
711
+ }
712
+ if (next < lines.length && lines[next].trim() === ":::") {
713
+ next += 1;
714
+ }
715
+ const contentText = bodyLines.join("\n").trim();
716
+ return {
717
+ block: {
718
+ type: "testCase",
719
+ props: {
720
+ ...cloneBaseProps(),
721
+ status: normalizeStatus(statusMatch === null || statusMatch === void 0 ? void 0 : statusMatch[1]),
722
+ reference: (_a = referenceMatch === null || referenceMatch === void 0 ? void 0 : referenceMatch[1]) !== null && _a !== void 0 ? _a : "",
723
+ },
724
+ content: contentText
725
+ ? [{ type: "text", text: unescapeMarkdown(contentText), styles: {} }]
726
+ : undefined,
727
+ children: [],
728
+ },
729
+ nextIndex: next,
730
+ };
731
+ }
732
+ function parseHeading(lines, index) {
733
+ const trimmed = lines[index].trim();
734
+ if (!trimmed.startsWith("#")) {
735
+ return null;
736
+ }
737
+ const match = trimmed.match(/^(#+)\s+(.*)$/);
738
+ if (!match) {
739
+ return null;
740
+ }
741
+ const rawLevel = Math.min(match[1].length, 3);
742
+ const level = (rawLevel === 1 || rawLevel === 2 ? rawLevel : 3);
743
+ const text = match[2];
744
+ return {
745
+ block: {
746
+ type: "heading",
747
+ props: { ...cloneBaseProps(), level },
748
+ content: createTextContent(unescapeMarkdown(text)),
749
+ children: [],
750
+ },
751
+ nextIndex: index + 1,
752
+ };
753
+ }
754
+ function parseCodeBlock(lines, index) {
755
+ const trimmed = lines[index].trim();
756
+ if (!trimmed.startsWith("```")) {
757
+ return null;
758
+ }
759
+ const language = trimmed.slice(3).trim();
760
+ const body = [];
761
+ let next = index + 1;
762
+ while (next < lines.length && !lines[next].startsWith("```")) {
763
+ body.push(lines[next]);
764
+ next += 1;
765
+ }
766
+ if (next < lines.length && lines[next].startsWith("```")) {
767
+ next += 1;
768
+ }
769
+ return {
770
+ block: {
771
+ type: "codeBlock",
772
+ props: { language },
773
+ content: body.length
774
+ ? [{ type: "text", text: body.join("\n"), styles: {} }]
775
+ : undefined,
776
+ children: [],
777
+ },
778
+ nextIndex: next,
779
+ };
780
+ }
781
+ function parseQuote(lines, index) {
782
+ if (!lines[index].trim().startsWith(">")) {
783
+ return null;
784
+ }
785
+ const collected = [];
786
+ let next = index;
787
+ while (next < lines.length) {
788
+ const trimmed = lines[next].trim();
789
+ if (!trimmed.startsWith(">")) {
790
+ break;
791
+ }
792
+ collected.push(trimmed.replace(/^>\s?/, ""));
793
+ next += 1;
794
+ }
795
+ return {
796
+ block: {
797
+ type: "quote",
798
+ props: cloneBaseProps(),
799
+ content: createTextContent(unescapeMarkdown(collected.join("\n"))),
800
+ children: [],
801
+ },
802
+ nextIndex: next,
803
+ };
804
+ }
805
+ function parseParagraph(lines, index) {
806
+ const buffer = [];
807
+ let next = index;
808
+ const isTermination = (line) => {
809
+ const trimmed = line.trim();
810
+ if (!trimmed) {
811
+ return true;
812
+ }
813
+ if (trimmed.startsWith(":::test-case")) {
814
+ return true;
815
+ }
816
+ if (trimmed.startsWith("* ")) {
817
+ return true;
818
+ }
819
+ if (trimmed.startsWith("#")) {
820
+ return true;
821
+ }
822
+ if (trimmed.startsWith(">")) {
823
+ return true;
824
+ }
825
+ if (trimmed.startsWith("```")) {
826
+ return true;
827
+ }
828
+ if (detectListType(trimmed)) {
829
+ return true;
830
+ }
831
+ return false;
832
+ };
833
+ while (next < lines.length) {
834
+ const line = lines[next];
835
+ if (isTableRowLine(line) &&
836
+ next + 1 < lines.length &&
837
+ isSeparatorRow(lines[next + 1])) {
838
+ break;
839
+ }
840
+ if (isTermination(line) && buffer.length > 0) {
841
+ break;
842
+ }
843
+ if (!line.trim()) {
844
+ next += 1;
845
+ break;
846
+ }
847
+ buffer.push(line.trim());
848
+ next += 1;
849
+ }
850
+ return {
851
+ block: {
852
+ type: "paragraph",
853
+ props: cloneBaseProps(),
854
+ content: createTextContent(unescapeMarkdown(buffer.join(" "))),
855
+ children: [],
856
+ },
857
+ nextIndex: next,
858
+ };
859
+ }
860
+ export function markdownToBlocks(markdown) {
861
+ const normalized = markdown.replace(/\r\n/g, "\n");
862
+ const lines = normalized.split("\n");
863
+ const blocks = [];
864
+ let index = 0;
865
+ while (index < lines.length) {
866
+ const line = lines[index];
867
+ if (!line.trim()) {
868
+ index += 1;
869
+ continue;
870
+ }
871
+ const testCase = parseTestCase(lines, index);
872
+ if (testCase) {
873
+ blocks.push(testCase.block);
874
+ index = testCase.nextIndex;
875
+ continue;
876
+ }
877
+ const testStep = parseTestStep(lines, index);
878
+ if (testStep) {
879
+ blocks.push(testStep.block);
880
+ index = testStep.nextIndex;
881
+ continue;
882
+ }
883
+ const table = parseTable(lines, index);
884
+ if (table) {
885
+ blocks.push(table.block);
886
+ index = table.nextIndex;
887
+ continue;
888
+ }
889
+ const heading = parseHeading(lines, index);
890
+ if (heading) {
891
+ blocks.push(heading.block);
892
+ index = heading.nextIndex;
893
+ continue;
894
+ }
895
+ const code = parseCodeBlock(lines, index);
896
+ if (code) {
897
+ blocks.push(code.block);
898
+ index = code.nextIndex;
899
+ continue;
900
+ }
901
+ const quote = parseQuote(lines, index);
902
+ if (quote) {
903
+ blocks.push(quote.block);
904
+ index = quote.nextIndex;
905
+ continue;
906
+ }
907
+ const listType = detectListType(line.trim());
908
+ if (listType) {
909
+ const { items, nextIndex } = parseList(lines, index, listType, 0);
910
+ blocks.push(...items);
911
+ index = nextIndex;
912
+ continue;
913
+ }
914
+ const paragraph = parseParagraph(lines, index);
915
+ blocks.push(paragraph.block);
916
+ index = paragraph.nextIndex;
917
+ }
918
+ return blocks;
919
+ }
920
+ function splitTableRow(line) {
921
+ let value = line.trim();
922
+ if (value.startsWith("|")) {
923
+ value = value.slice(1);
924
+ }
925
+ if (value.endsWith("|")) {
926
+ value = value.slice(0, -1);
927
+ }
928
+ const cells = [];
929
+ let current = "";
930
+ let i = 0;
931
+ while (i < value.length) {
932
+ const char = value[i];
933
+ if (char === "\\" && i + 1 < value.length) {
934
+ current += value[i + 1];
935
+ i += 2;
936
+ continue;
937
+ }
938
+ if (char === "|") {
939
+ cells.push(current.trim());
940
+ current = "";
941
+ i += 1;
942
+ continue;
943
+ }
944
+ current += char;
945
+ i += 1;
946
+ }
947
+ cells.push(current.trim());
948
+ return cells;
949
+ }
950
+ function isTableRowLine(line) {
951
+ const trimmed = line.trim();
952
+ if (!trimmed.startsWith("|")) {
953
+ return false;
954
+ }
955
+ const cells = splitTableRow(trimmed);
956
+ return cells.length >= 2;
957
+ }
958
+ function isSeparatorRow(line) {
959
+ const trimmed = line.trim();
960
+ if (!trimmed.startsWith("|")) {
961
+ return false;
962
+ }
963
+ const cells = splitTableRow(trimmed);
964
+ if (!cells.length) {
965
+ return false;
966
+ }
967
+ return cells.every((cell) => /^:?[-]{3,}:?$/.test(cell.replace(/\s+/g, "")));
968
+ }
969
+ function alignmentFromToken(token) {
970
+ const trimmed = token.trim();
971
+ if (trimmed.startsWith(":") && trimmed.endsWith(":")) {
972
+ return "center";
973
+ }
974
+ if (trimmed.endsWith(":")) {
975
+ return "right";
976
+ }
977
+ if (trimmed.startsWith(":")) {
978
+ return "left";
979
+ }
980
+ return "left";
981
+ }
982
+ function parseTable(lines, index) {
983
+ if (!isTableRowLine(lines[index])) {
984
+ return null;
985
+ }
986
+ if (index + 1 >= lines.length || !isSeparatorRow(lines[index + 1])) {
987
+ return null;
988
+ }
989
+ const headerCells = splitTableRow(lines[index]);
990
+ const alignmentCells = splitTableRow(lines[index + 1]);
991
+ const columnCount = Math.max(headerCells.length, alignmentCells.length);
992
+ const columnAlignments = new Array(columnCount).fill("left");
993
+ alignmentCells.forEach((token, idx) => {
994
+ columnAlignments[idx] = alignmentFromToken(token);
995
+ });
996
+ const allRows = [headerCells];
997
+ let next = index + 2;
998
+ while (next < lines.length && isTableRowLine(lines[next])) {
999
+ allRows.push(splitTableRow(lines[next]));
1000
+ next += 1;
1001
+ }
1002
+ const normalizedRows = allRows.map((row) => {
1003
+ const cells = [...row];
1004
+ while (cells.length < columnCount) {
1005
+ cells.push("");
1006
+ }
1007
+ return cells;
1008
+ });
1009
+ const tableRows = normalizedRows.map((row) => {
1010
+ const cells = row.map((cellText, columnIndex) => {
1011
+ var _a, _b;
1012
+ return ({
1013
+ type: "tableCell",
1014
+ props: {
1015
+ ...cloneCellProps(),
1016
+ textAlignment: (_a = columnAlignments[columnIndex]) !== null && _a !== void 0 ? _a : "left",
1017
+ },
1018
+ content: (_b = createTextContent(cellText)) !== null && _b !== void 0 ? _b : [],
1019
+ });
1020
+ });
1021
+ return { cells };
1022
+ });
1023
+ const tableBlock = {
1024
+ type: "table",
1025
+ props: { ...TABLE_BLOCK_PROPS },
1026
+ content: {
1027
+ type: "tableContent",
1028
+ columnWidths: new Array(columnCount).fill(undefined),
1029
+ headerRows: 1,
1030
+ rows: tableRows,
1031
+ },
1032
+ children: [],
1033
+ };
1034
+ return {
1035
+ block: tableBlock,
1036
+ nextIndex: next,
1037
+ };
1038
+ }