2 changed files with 105 additions and 0 deletions
@ -0,0 +1,57 @@ |
|||
import { describe, expect, test } from "bun:test"; |
|||
import { |
|||
buildMarkdownExportFileName, |
|||
normalizeMarkdownImageUrls, |
|||
} from "./markdown-export"; |
|||
|
|||
describe("normalizeMarkdownImageUrls", () => { |
|||
test("converts /public/assets image links to absolute URLs", () => { |
|||
const markdown = ""; |
|||
const result = normalizeMarkdownImageUrls(markdown, "https://example.com"); |
|||
|
|||
expect(result).toBe(""); |
|||
}); |
|||
|
|||
test("keeps absolute http/https image links unchanged", () => { |
|||
const markdown = [ |
|||
"", |
|||
"", |
|||
].join("\n"); |
|||
|
|||
const result = normalizeMarkdownImageUrls(markdown, "https://example.com"); |
|||
|
|||
expect(result).toBe(markdown); |
|||
}); |
|||
|
|||
test("keeps protocol-relative and data URI image links unchanged", () => { |
|||
const markdown = [ |
|||
"", |
|||
"", |
|||
].join("\n"); |
|||
|
|||
const result = normalizeMarkdownImageUrls(markdown, "https://example.com"); |
|||
|
|||
expect(result).toBe(markdown); |
|||
}); |
|||
|
|||
test("does not change normal markdown links", () => { |
|||
const markdown = "[read more](/public/assets/posts/cover.png)"; |
|||
const result = normalizeMarkdownImageUrls(markdown, "https://example.com"); |
|||
|
|||
expect(result).toBe(markdown); |
|||
}); |
|||
}); |
|||
|
|||
describe("buildMarkdownExportFileName", () => { |
|||
test("prefers slug when slug is not empty", () => { |
|||
const result = buildMarkdownExportFileName({ slug: "hello-world", id: 42 }); |
|||
|
|||
expect(result).toBe("hello-world.md"); |
|||
}); |
|||
|
|||
test("falls back to post-<id>.md when slug is empty", () => { |
|||
const result = buildMarkdownExportFileName({ slug: "", id: 42 }); |
|||
|
|||
expect(result).toBe("post-42.md"); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,48 @@ |
|||
type MarkdownExportFileNameInput = { |
|||
slug: string; |
|||
id: number; |
|||
}; |
|||
|
|||
const PUBLIC_ASSETS_PREFIX = "/public/assets/"; |
|||
|
|||
function isAbsoluteOrSpecialUrl(url: string): boolean { |
|||
return /^(https?:)?\/\//i.test(url) || /^data:/i.test(url); |
|||
} |
|||
|
|||
export function normalizeMarkdownImageUrls(markdown: string, origin: string): string { |
|||
const normalizedOrigin = origin.replace(/\/+$/, ""); |
|||
|
|||
return markdown.replace(/!\[([^\]]*)\]\(([^)\s]+)([^)]*)\)/g, (full, alt, rawUrl, rest) => { |
|||
if (isAbsoluteOrSpecialUrl(rawUrl)) { |
|||
return full; |
|||
} |
|||
|
|||
if (!rawUrl.startsWith(PUBLIC_ASSETS_PREFIX)) { |
|||
return full; |
|||
} |
|||
|
|||
return ``; |
|||
}); |
|||
} |
|||
|
|||
export function buildMarkdownExportFileName(input: MarkdownExportFileNameInput): string { |
|||
const base = input.slug.trim() || `post-${input.id}`; |
|||
return `${base}.md`; |
|||
} |
|||
|
|||
export function downloadMarkdownFile(filename: string, content: string): void { |
|||
if (typeof window === "undefined" || typeof document === "undefined") { |
|||
return; |
|||
} |
|||
|
|||
const blob = new Blob([content], { type: "text/markdown;charset=utf-8" }); |
|||
const url = URL.createObjectURL(blob); |
|||
const anchor = document.createElement("a"); |
|||
anchor.href = url; |
|||
anchor.download = filename; |
|||
anchor.style.display = "none"; |
|||
document.body.appendChild(anchor); |
|||
anchor.click(); |
|||
anchor.remove(); |
|||
URL.revokeObjectURL(url); |
|||
} |
|||
Loading…
Reference in new issue