diff --git a/tools/blogeditor/blog-editor-0.1.12.vsix b/tools/blogeditor/blog-editor-0.1.12.vsix new file mode 100644 index 0000000..4a71c70 Binary files /dev/null and b/tools/blogeditor/blog-editor-0.1.12.vsix differ diff --git a/tools/blogeditor/editor.html b/tools/blogeditor/editor.html index 911316a..4bdb95e 100644 --- a/tools/blogeditor/editor.html +++ b/tools/blogeditor/editor.html @@ -424,6 +424,18 @@ /** uploadId -> resolve(relativePath) */ const _uploadResolvers = new Map(); + /** + * Splits markdown image target into src and optional title segment. + * @param {string} target + * @returns {{ src: string, suffix: string }} + */ + function splitImageTarget(target) { + const trimmed = target.trim(); + const m = trimmed.match(/^(\S+)(\s+["'][\s\S]*["'])?$/); + if (!m) return { src: trimmed, suffix: '' }; + return { src: m[1], suffix: m[2] ?? '' }; + } + /** * Uploads a blob URL to the extension to save to media/. * Returns a Promise that resolves once mediaSaved is received. @@ -552,9 +564,10 @@ * @returns {string} */ function cleanImagePaths(markdown) { - return markdown.replace(/(\!\[[^\]]*\]\()([^)]+)(\))/g, (_m, open, src, close) => { - const rel = blobToRelative.get(src) ?? webviewToRelative.get(src); - return rel ? `${open}${rel}${close}` : `${open}${src}${close}`; + return markdown.replace(/(\!\[[^\]]*\]\()([^)]+)(\))/g, (_m, open, target, close) => { + const { src, suffix } = splitImageTarget(target); + const rel = blobToRelative.get(src) ?? webviewToRelative.get(src); + return rel ? `${open}${rel}${suffix}${close}` : `${open}${src}${suffix}${close}`; }); } diff --git a/tools/blogeditor/extension.js b/tools/blogeditor/extension.js index fe1cc7d..c6b7677 100644 --- a/tools/blogeditor/extension.js +++ b/tools/blogeditor/extension.js @@ -343,6 +343,21 @@ function buildHtml(webview) { .replace('{{THEME_CSS}}', themeCss.toString()); } +/** + * Splits markdown image target into URL/path and optional trailing title segment. + * Examples: + * data/blog/media/a.png "Caption" -> { src: data/blog/media/a.png, suffix: "Caption" } + * https://x/y.png -> { src: https://x/y.png, suffix: '' } + * @param {string} target + * @returns {{ src: string, suffix: string }} + */ +function splitImageTarget(target) { + const trimmed = target.trim(); + const m = trimmed.match(/^(\S+)(\s+["'][\s\S]*["'])?$/); + if (!m) return { src: trimmed, suffix: '' }; + return { src: m[1], suffix: m[2] ?? '' }; +} + /** * Replaces relative image paths in markdown with webview-safe URIs. * @param {string} content @@ -350,9 +365,10 @@ function buildHtml(webview) { * @returns {string} */ function injectWebviewImageUris(content, websiteBaseUri) { - return content.replace(/(\!\[[^\]]*\]\()([^)]+)(\))/g, (match, open, src, close) => { + return content.replace(/(\!\[[^\]]*\]\()([^)]+)(\))/g, (match, open, target, close) => { + const { src, suffix } = splitImageTarget(target); if (/^https?:|^data:|^blob:/.test(src)) return match; - return `${open}${websiteBaseUri}/${src.replace(/^\//, '')}${close}`; + return `${open}${websiteBaseUri}/${src.replace(/^\//, '')}${suffix}${close}`; }); } @@ -366,7 +382,10 @@ function revertWebviewImageUris(content, websiteBaseUri) { const escaped = websiteBaseUri.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); return content.replace( new RegExp(`(\\!\\[[^\\]]*\\]\\()${escaped}/([^)]+)(\\))`, 'g'), - (_m, open, rel, close) => `${open}${rel}${close}` + (_m, open, target, close) => { + const { src, suffix } = splitImageTarget(target); + return `${open}${src}${suffix}${close}`; + } ); } @@ -399,7 +418,7 @@ function handleMessage(msg, blogDir, websiteDir) { const imgPattern = /!\[[^\]]*\]\(([^)]+)\)/g; let imgMatch; while ((imgMatch = imgPattern.exec(raw)) !== null) { - const src = imgMatch[1]; + const { src } = splitImageTarget(imgMatch[1]); if (/^https?:|^data:|^blob:/.test(src)) continue; const absPath = path.join(websiteDir, src.replace(/^\//, '')); if (fs.existsSync(absPath)) { diff --git a/tools/blogeditor/package.json b/tools/blogeditor/package.json index 7de2647..db94c35 100644 --- a/tools/blogeditor/package.json +++ b/tools/blogeditor/package.json @@ -2,7 +2,7 @@ "name": "blog-editor", "displayName": "Blog Editor", "description": "WYSIWYG Milkdown blog post editor for this workspace", - "version": "0.1.11", + "version": "0.1.12", "publisher": "local", "engines": { "vscode": "^1.80.0" }, "categories": ["Other"], diff --git a/website/data/blog/markdown-test.md b/website/data/blog/markdown-test.md index f3e348b..dc6b845 100644 --- a/website/data/blog/markdown-test.md +++ b/website/data/blog/markdown-test.md @@ -104,9 +104,7 @@ const obj = { ![1.00](data/blog/media/test.gif "Caption Test") -![0.26](blob:vscode-webview://1b6n822od8b3q8ir5r6it0k34211vkpefa12tlnhqbm9ukcvq8kt/69bf42f8-c939-432d-9cf8-d42ed55cf5b5 "A Bear Skull, Grok Generated") - -![1.00](blob:vscode-webview://1b6n822od8b3q8ir5r6it0k34211vkpefa12tlnhqbm9ukcvq8kt/669fa236-7079-427f-97ee-6c96f089c32f "AI Generated Goblin") +![1.00](data/blog/media/paste-1778405550240-ddf8h.png "Gobby") ## Mixed Content diff --git a/website/data/blog/media/paste-1778405550240-ddf8h.png b/website/data/blog/media/paste-1778405550240-ddf8h.png new file mode 100644 index 0000000..41a7bf5 Binary files /dev/null and b/website/data/blog/media/paste-1778405550240-ddf8h.png differ