Skip to content

Commit c68ebd6

Browse files
165 support incomplete url parsing for links (#167)
* Replace the URLs of unterminated links to prevent clicks * Create full-mirrors-lead.md
1 parent 5cdde41 commit c68ebd6

File tree

3 files changed

+36
-7
lines changed

3 files changed

+36
-7
lines changed

.changeset/full-mirrors-lead.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"streamdown": patch
3+
---
4+
5+
Support incomplete URL parsing for links

packages/streamdown/__tests__/parse-incomplete-markdown.test.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -497,22 +497,20 @@ describe("parseIncompleteMarkdown", () => {
497497
);
498498
});
499499

500-
it("should handle partial link at chunk boundary", () => {
500+
it("should handle partial link at chunk boundary - #165", () => {
501501
expect(parseIncompleteMarkdown("Check out [this lin")).toBe(
502502
"Check out [this lin](streamdown:incomplete-link)"
503503
);
504-
// Links with partial URLs are kept as-is since they might be complete
504+
// Links with partial URLs should now be completed with placeholder
505505
expect(parseIncompleteMarkdown("Visit [our site](https://exa")).toBe(
506-
"Visit [our site](https://exa"
506+
"Visit [our site](streamdown:incomplete-link)"
507507
);
508508
});
509509

510510
it("should handle partial image at chunk boundary", () => {
511511
expect(parseIncompleteMarkdown("See ![the diag")).toBe("See ");
512-
// Images with partial URLs are kept as-is since they might be complete
513-
expect(parseIncompleteMarkdown("![logo](./assets/log")).toBe(
514-
"![logo](./assets/log"
515-
);
512+
// Images with partial URLs should be removed (images can't show skeleton)
513+
expect(parseIncompleteMarkdown("![logo](./assets/log")).toBe("");
516514
});
517515

518516
it("should handle nested formatting cut mid-stream", () => {

packages/streamdown/lib/parse-incomplete-markdown.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,32 @@ const hasCompleteCodeBlock = (text: string): boolean => {
1717

1818
// Handles incomplete links and images by preserving them with a special marker
1919
const handleIncompleteLinksAndImages = (text: string): string => {
20+
// First check for incomplete URLs: [text](partial-url or ![text](partial-url without closing )
21+
// Pattern: !?[text](url-without-closing-paren at end of string
22+
const incompleteLinkUrlPattern = /(!?)\[([^\]]+)\]\(([^)]+)$/;
23+
const incompleteLinkUrlMatch = text.match(incompleteLinkUrlPattern);
24+
25+
if (incompleteLinkUrlMatch) {
26+
const isImage = incompleteLinkUrlMatch[1] === "!";
27+
const linkText = incompleteLinkUrlMatch[2];
28+
const partialUrl = incompleteLinkUrlMatch[3];
29+
30+
// Find the start position of this link/image pattern
31+
const matchStart = text.lastIndexOf(
32+
`${isImage ? "!" : ""}[${linkText}](${partialUrl}`
33+
);
34+
const beforeLink = text.substring(0, matchStart);
35+
36+
if (isImage) {
37+
// For images with incomplete URLs, remove them entirely
38+
return beforeLink;
39+
}
40+
41+
// For links with incomplete URLs, replace the URL with placeholder and close it
42+
return `${beforeLink}[${linkText}](streamdown:incomplete-link)`;
43+
}
44+
45+
// Then check for incomplete link text: [partial-text without closing ]
2046
const linkMatch = text.match(linkImagePattern);
2147

2248
if (linkMatch) {

0 commit comments

Comments
 (0)