testomatio-editor-blocks 0.4.29 → 0.4.30
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.
|
@@ -266,6 +266,16 @@ function serializeBlock(block, ctx, orderedIndex, stepIndex) {
|
|
|
266
266
|
}
|
|
267
267
|
return flattenWithBlankLine(lines, true);
|
|
268
268
|
}
|
|
269
|
+
case "file": {
|
|
270
|
+
const url = block.props.url || "";
|
|
271
|
+
const name = block.props.name || "";
|
|
272
|
+
const caption = block.props.caption || "";
|
|
273
|
+
if (url) {
|
|
274
|
+
const displayUrl = caption || url;
|
|
275
|
+
lines.push(`[](${url})`);
|
|
276
|
+
}
|
|
277
|
+
return flattenWithBlankLine(lines, true);
|
|
278
|
+
}
|
|
269
279
|
case "testStep":
|
|
270
280
|
case "snippet": {
|
|
271
281
|
const isSnippet = block.type === "snippet";
|
|
@@ -1214,6 +1224,20 @@ export function markdownToBlocks(markdown) {
|
|
|
1214
1224
|
index = nextIndex;
|
|
1215
1225
|
continue;
|
|
1216
1226
|
}
|
|
1227
|
+
const fileMatch = line.trim().match(/^\[!\[([^\]]*)\]\(([^)]*)\)\]\(([^)]+)\)$/);
|
|
1228
|
+
if (fileMatch) {
|
|
1229
|
+
blocks.push({
|
|
1230
|
+
type: "file",
|
|
1231
|
+
props: {
|
|
1232
|
+
name: fileMatch[1] || "",
|
|
1233
|
+
caption: fileMatch[2] || "",
|
|
1234
|
+
url: fileMatch[3],
|
|
1235
|
+
},
|
|
1236
|
+
children: [],
|
|
1237
|
+
});
|
|
1238
|
+
index += 1;
|
|
1239
|
+
continue;
|
|
1240
|
+
}
|
|
1217
1241
|
const imageMatch = line.trim().match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
|
|
1218
1242
|
if (imageMatch) {
|
|
1219
1243
|
blocks.push({
|
package/package.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -349,6 +349,9 @@ function App() {
|
|
|
349
349
|
const editor = useCreateBlockNote({
|
|
350
350
|
schema: customSchema,
|
|
351
351
|
pasteHandler: createMarkdownPasteHandler(markdownToBlocks),
|
|
352
|
+
uploadFile: async (file: File) => {
|
|
353
|
+
return `https://placehold.co/600x400?text=${encodeURIComponent(file.name)}`;
|
|
354
|
+
},
|
|
352
355
|
});
|
|
353
356
|
const [markdown, setMarkdown] = useState("");
|
|
354
357
|
const [conversionError, setConversionError] = useState<string | null>(null);
|
|
@@ -1513,3 +1513,114 @@ describe("markdownToBlocks", () => {
|
|
|
1513
1513
|
expect(roundTripMarkdown).not.toMatch(/\n!\s*$/);
|
|
1514
1514
|
});
|
|
1515
1515
|
});
|
|
1516
|
+
|
|
1517
|
+
describe("file block serialization", () => {
|
|
1518
|
+
it("serializes a file block with name, caption (display_url), and url", () => {
|
|
1519
|
+
const blocks: CustomEditorBlock[] = [
|
|
1520
|
+
{
|
|
1521
|
+
id: "1",
|
|
1522
|
+
type: "file",
|
|
1523
|
+
props: {
|
|
1524
|
+
...baseProps,
|
|
1525
|
+
url: "https://example.com/file.pdf",
|
|
1526
|
+
name: "report.pdf",
|
|
1527
|
+
caption: "/images/file-type-icons/pdf.svg",
|
|
1528
|
+
},
|
|
1529
|
+
content: undefined as any,
|
|
1530
|
+
children: [],
|
|
1531
|
+
},
|
|
1532
|
+
];
|
|
1533
|
+
const md = blocksToMarkdown(blocks);
|
|
1534
|
+
expect(md).toBe("[](https://example.com/file.pdf)");
|
|
1535
|
+
});
|
|
1536
|
+
|
|
1537
|
+
it("falls back to url when caption is empty", () => {
|
|
1538
|
+
const blocks: CustomEditorBlock[] = [
|
|
1539
|
+
{
|
|
1540
|
+
id: "1",
|
|
1541
|
+
type: "file",
|
|
1542
|
+
props: {
|
|
1543
|
+
...baseProps,
|
|
1544
|
+
url: "https://example.com/file.pdf",
|
|
1545
|
+
name: "file.pdf",
|
|
1546
|
+
caption: "",
|
|
1547
|
+
},
|
|
1548
|
+
content: undefined as any,
|
|
1549
|
+
children: [],
|
|
1550
|
+
},
|
|
1551
|
+
];
|
|
1552
|
+
const md = blocksToMarkdown(blocks);
|
|
1553
|
+
expect(md).toBe("[](https://example.com/file.pdf)");
|
|
1554
|
+
});
|
|
1555
|
+
|
|
1556
|
+
it("outputs nothing when url is empty", () => {
|
|
1557
|
+
const blocks: CustomEditorBlock[] = [
|
|
1558
|
+
{
|
|
1559
|
+
id: "1",
|
|
1560
|
+
type: "file",
|
|
1561
|
+
props: {
|
|
1562
|
+
...baseProps,
|
|
1563
|
+
url: "",
|
|
1564
|
+
name: "file.pdf",
|
|
1565
|
+
caption: "",
|
|
1566
|
+
},
|
|
1567
|
+
content: undefined as any,
|
|
1568
|
+
children: [],
|
|
1569
|
+
},
|
|
1570
|
+
];
|
|
1571
|
+
const md = blocksToMarkdown(blocks);
|
|
1572
|
+
expect(md.trim()).toBe("");
|
|
1573
|
+
});
|
|
1574
|
+
});
|
|
1575
|
+
|
|
1576
|
+
describe("file block parsing", () => {
|
|
1577
|
+
it("parses file markdown into a file block", () => {
|
|
1578
|
+
const markdown = "[](https://example.com/file.pdf)";
|
|
1579
|
+
const blocks = markdownToBlocks(markdown);
|
|
1580
|
+
expect(blocks).toHaveLength(1);
|
|
1581
|
+
expect(blocks[0].type).toBe("file");
|
|
1582
|
+
expect((blocks[0].props as any).name).toBe("report.pdf");
|
|
1583
|
+
expect((blocks[0].props as any).caption).toBe("/images/file-type-icons/pdf.svg");
|
|
1584
|
+
expect((blocks[0].props as any).url).toBe("https://example.com/file.pdf");
|
|
1585
|
+
});
|
|
1586
|
+
|
|
1587
|
+
it("parses file markdown with empty name", () => {
|
|
1588
|
+
const markdown = "[](https://example.com/url)";
|
|
1589
|
+
const blocks = markdownToBlocks(markdown);
|
|
1590
|
+
expect(blocks).toHaveLength(1);
|
|
1591
|
+
expect(blocks[0].type).toBe("file");
|
|
1592
|
+
expect((blocks[0].props as any).name).toBe("");
|
|
1593
|
+
expect((blocks[0].props as any).url).toBe("https://example.com/url");
|
|
1594
|
+
});
|
|
1595
|
+
|
|
1596
|
+
it("does not confuse file blocks with image blocks", () => {
|
|
1597
|
+
const markdown = "";
|
|
1598
|
+
const blocks = markdownToBlocks(markdown);
|
|
1599
|
+
expect(blocks).toHaveLength(1);
|
|
1600
|
+
expect(blocks[0].type).toBe("image");
|
|
1601
|
+
});
|
|
1602
|
+
|
|
1603
|
+
it("round-trips file blocks through serialize and parse", () => {
|
|
1604
|
+
const blocks: CustomEditorBlock[] = [
|
|
1605
|
+
{
|
|
1606
|
+
id: "1",
|
|
1607
|
+
type: "file",
|
|
1608
|
+
props: {
|
|
1609
|
+
...baseProps,
|
|
1610
|
+
url: "https://example.com/doc.xlsx",
|
|
1611
|
+
name: "doc.xlsx",
|
|
1612
|
+
caption: "/images/file-type-icons/xlsx.svg",
|
|
1613
|
+
},
|
|
1614
|
+
content: undefined as any,
|
|
1615
|
+
children: [],
|
|
1616
|
+
},
|
|
1617
|
+
];
|
|
1618
|
+
const md = blocksToMarkdown(blocks);
|
|
1619
|
+
const parsed = markdownToBlocks(md);
|
|
1620
|
+
expect(parsed).toHaveLength(1);
|
|
1621
|
+
expect(parsed[0].type).toBe("file");
|
|
1622
|
+
expect((parsed[0].props as any).url).toBe("https://example.com/doc.xlsx");
|
|
1623
|
+
expect((parsed[0].props as any).name).toBe("doc.xlsx");
|
|
1624
|
+
expect((parsed[0].props as any).caption).toBe("/images/file-type-icons/xlsx.svg");
|
|
1625
|
+
});
|
|
1626
|
+
});
|
|
@@ -344,6 +344,16 @@ function serializeBlock(
|
|
|
344
344
|
}
|
|
345
345
|
return flattenWithBlankLine(lines, true);
|
|
346
346
|
}
|
|
347
|
+
case "file": {
|
|
348
|
+
const url = (block.props as any).url || "";
|
|
349
|
+
const name = (block.props as any).name || "";
|
|
350
|
+
const caption = (block.props as any).caption || "";
|
|
351
|
+
if (url) {
|
|
352
|
+
const displayUrl = caption || url;
|
|
353
|
+
lines.push(`[](${url})`);
|
|
354
|
+
}
|
|
355
|
+
return flattenWithBlankLine(lines, true);
|
|
356
|
+
}
|
|
347
357
|
case "testStep":
|
|
348
358
|
case "snippet": {
|
|
349
359
|
const isSnippet = block.type === "snippet";
|
|
@@ -1456,6 +1466,21 @@ export function markdownToBlocks(markdown: string): CustomPartialBlock[] {
|
|
|
1456
1466
|
continue;
|
|
1457
1467
|
}
|
|
1458
1468
|
|
|
1469
|
+
const fileMatch = line.trim().match(/^\[!\[([^\]]*)\]\(([^)]*)\)\]\(([^)]+)\)$/);
|
|
1470
|
+
if (fileMatch) {
|
|
1471
|
+
blocks.push({
|
|
1472
|
+
type: "file",
|
|
1473
|
+
props: {
|
|
1474
|
+
name: fileMatch[1] || "",
|
|
1475
|
+
caption: fileMatch[2] || "",
|
|
1476
|
+
url: fileMatch[3],
|
|
1477
|
+
},
|
|
1478
|
+
children: [],
|
|
1479
|
+
} as CustomPartialBlock);
|
|
1480
|
+
index += 1;
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1459
1484
|
const imageMatch = line.trim().match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
|
|
1460
1485
|
if (imageMatch) {
|
|
1461
1486
|
blocks.push({
|