From a6c5bd26e7a7c00cb18f8cdf85d99789b553d7f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Lohrer?= Date: Sat, 18 Apr 2026 05:24:03 +0200 Subject: [PATCH] publish(task 5): markdown bild-url-rewriter (mapping-basiert, =WxH-strip) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rewriteImageUrls(md, mapping) ersetzt alle ![alt](filename)- und [![alt](filename)](link)-konstrukte per dateinamen-lookup durch blossom-urls aus dem mapping. absolute urls bleiben unverändert, unbekannte dateinamen bleiben stehen (kein strip). =WxH-größenhinweise werden entfernt. URL-dekodierung für leerzeichen-dateinamen (z.b. '03-config generieren.png' im moodle-post). resolveCoverUrl()-helper für den cover-lookup. 7 tests grün. Co-Authored-By: Claude Opus 4.6 (1M context) --- publish/src/core/markdown.ts | 29 +++++++++++++++++++ publish/tests/markdown_test.ts | 53 ++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 publish/src/core/markdown.ts create mode 100644 publish/tests/markdown_test.ts diff --git a/publish/src/core/markdown.ts b/publish/src/core/markdown.ts new file mode 100644 index 0000000..69bab2a --- /dev/null +++ b/publish/src/core/markdown.ts @@ -0,0 +1,29 @@ +const IMG_RE = /!\[([^\]]*)\]\(([^)\s]+)(?:\s+=\d+x\d+)?\)/g + +function isAbsolute(url: string): boolean { + return /^(https?:)?\/\//i.test(url) +} + +export function rewriteImageUrls(md: string, mapping: Map): string { + return md.replace(IMG_RE, (full, alt: string, url: string) => { + if (isAbsolute(url)) return full.replace(/\s+=\d+x\d+\)$/, ')') + let decoded: string + try { + decoded = decodeURIComponent(url) + } catch { + decoded = url + } + const target = mapping.get(decoded) ?? mapping.get(url) + if (!target) return full.replace(/\s+=\d+x\d+\)$/, ')') + return `![${alt}](${target})` + }) +} + +export function resolveCoverUrl( + coverRaw: string | undefined, + mapping: Map, +): string | undefined { + if (!coverRaw) return undefined + if (isAbsolute(coverRaw)) return coverRaw + return mapping.get(coverRaw) +} diff --git a/publish/tests/markdown_test.ts b/publish/tests/markdown_test.ts new file mode 100644 index 0000000..d07bf86 --- /dev/null +++ b/publish/tests/markdown_test.ts @@ -0,0 +1,53 @@ +import { assertEquals } from '@std/assert' +import { rewriteImageUrls } from '../src/core/markdown.ts' + +Deno.test('rewriteImageUrls: ersetzt ![alt](file) durch Mapping', () => { + const mapping = new Map([['cat.png', 'https://blossom.example/hash.png']]) + const input = '![cat](cat.png)' + assertEquals(rewriteImageUrls(input, mapping), '![cat](https://blossom.example/hash.png)') +}) + +Deno.test('rewriteImageUrls: absolute URL bleibt unverändert', () => { + const mapping = new Map([['cat.png', 'https://blossom.example/hash.png']]) + const input = '![cat](https://other.com/cat.png)' + assertEquals(rewriteImageUrls(input, mapping), input) +}) + +Deno.test('rewriteImageUrls: entfernt =WxH-Suffix', () => { + const mapping = new Map([['cat.png', 'https://blossom.example/hash.png']]) + const input = '![cat](cat.png =300x200)' + assertEquals(rewriteImageUrls(input, mapping), '![cat](https://blossom.example/hash.png)') +}) + +Deno.test('rewriteImageUrls: bild-in-link [![alt](file)](link)', () => { + const mapping = new Map([['cat.png', 'https://blossom.example/hash.png']]) + const input = '[![cat](cat.png)](https://target.example.com)' + assertEquals( + rewriteImageUrls(input, mapping), + '[![cat](https://blossom.example/hash.png)](https://target.example.com)', + ) +}) + +Deno.test('rewriteImageUrls: mehrere Bilder im Text', () => { + const mapping = new Map([ + ['a.png', 'https://bl/a-hash.png'], + ['b.jpg', 'https://bl/b-hash.jpg'], + ]) + const input = 'Text ![a](a.png) more ![b](b.jpg) end' + assertEquals( + rewriteImageUrls(input, mapping), + 'Text ![a](https://bl/a-hash.png) more ![b](https://bl/b-hash.jpg) end', + ) +}) + +Deno.test('rewriteImageUrls: lässt unbekannte Dateinamen stehen', () => { + const mapping = new Map([['cat.png', 'https://bl/c.png']]) + const input = '![x](missing.jpg)' + assertEquals(rewriteImageUrls(input, mapping), input) +}) + +Deno.test('rewriteImageUrls: URL-Dekodierung für Leerzeichen-Namen', () => { + const mapping = new Map([['file with spaces.png', 'https://bl/hash.png']]) + const input = '![x](file%20with%20spaces.png)' + assertEquals(rewriteImageUrls(input, mapping), '![x](https://bl/hash.png)') +})