diff --git a/packages/baoyu-md/dist/index.cjs b/packages/baoyu-md/dist/index.cjs index 4af0516..cf3cb7b 100644 --- a/packages/baoyu-md/dist/index.cjs +++ b/packages/baoyu-md/dist/index.cjs @@ -72757,8 +72757,13 @@ async function resolveImagePath(imagePath, baseDir, tempDir, logLabel = "baoyu-m } return localPath; } - const resolved = import_node_path6.default.isAbsolute(imagePath) ? imagePath : import_node_path6.default.resolve(baseDir, imagePath); - return resolveLocalWithFallback(resolved, logLabel); + const decoded = safeDecodeImagePath(imagePath); + const resolved = resolveAgainstBaseDir(decoded, baseDir); + const resolvedWithFallback = resolveLocalWithFallback(resolved, logLabel); + if (decoded === imagePath || import_node_fs5.default.existsSync(resolvedWithFallback)) { + return resolvedWithFallback; + } + return resolveLocalWithFallback(resolveAgainstBaseDir(imagePath, baseDir), logLabel); } async function resolveContentImages(images, baseDir, tempDir, logLabel = "baoyu-md") { const resolved = []; @@ -72793,6 +72798,16 @@ function resolveLocalWithFallback(resolved, logLabel) { } return resolved; } +function safeDecodeImagePath(imagePath) { + try { + return decodeURIComponent(imagePath); + } catch { + return imagePath; + } +} +function resolveAgainstBaseDir(imagePath, baseDir) { + return import_node_path6.default.isAbsolute(imagePath) ? imagePath : import_node_path6.default.resolve(baseDir, imagePath); +} // src/mermaid-preprocess.ts var import_node_fs6 = __toESM(require("node:fs")); var import_node_path7 = __toESM(require("node:path")); diff --git a/packages/baoyu-md/dist/index.js b/packages/baoyu-md/dist/index.js index e5802fb..9ad278f 100644 --- a/packages/baoyu-md/dist/index.js +++ b/packages/baoyu-md/dist/index.js @@ -72683,8 +72683,13 @@ async function resolveImagePath(imagePath, baseDir, tempDir, logLabel = "baoyu-m } return localPath; } - const resolved = path5.isAbsolute(imagePath) ? imagePath : path5.resolve(baseDir, imagePath); - return resolveLocalWithFallback(resolved, logLabel); + const decoded = safeDecodeImagePath(imagePath); + const resolved = resolveAgainstBaseDir(decoded, baseDir); + const resolvedWithFallback = resolveLocalWithFallback(resolved, logLabel); + if (decoded === imagePath || fs5.existsSync(resolvedWithFallback)) { + return resolvedWithFallback; + } + return resolveLocalWithFallback(resolveAgainstBaseDir(imagePath, baseDir), logLabel); } async function resolveContentImages(images, baseDir, tempDir, logLabel = "baoyu-md") { const resolved = []; @@ -72719,6 +72724,16 @@ function resolveLocalWithFallback(resolved, logLabel) { } return resolved; } +function safeDecodeImagePath(imagePath) { + try { + return decodeURIComponent(imagePath); + } catch { + return imagePath; + } +} +function resolveAgainstBaseDir(imagePath, baseDir) { + return path5.isAbsolute(imagePath) ? imagePath : path5.resolve(baseDir, imagePath); +} // src/mermaid-preprocess.ts import fs6 from "node:fs"; import path6 from "node:path"; diff --git a/packages/baoyu-md/src/images.test.ts b/packages/baoyu-md/src/images.test.ts index f3eca2a..793cf4a 100644 --- a/packages/baoyu-md/src/images.test.ts +++ b/packages/baoyu-md/src/images.test.ts @@ -59,6 +59,24 @@ test("resolveImagePath decodes URL-encoded filenames with spaces", async (t) => assert.equal(resolved, path.join(baseDir, "Pasted image 20260524.png")); }); +test("resolveImagePath keeps literal percent filenames usable", async (t) => { + const root = await makeTempDir("baoyu-md-percent-"); + t.after(() => fs.rm(root, { recursive: true, force: true })); + + const baseDir = path.join(root, "article"); + const tempDir = path.join(root, "tmp"); + await fs.mkdir(baseDir, { recursive: true }); + await fs.mkdir(tempDir, { recursive: true }); + await fs.writeFile(path.join(baseDir, "100% complete.png"), "png"); + await fs.writeFile(path.join(baseDir, "diagram%23hash.png"), "png"); + + const malformedPercent = await resolveImagePath("100% complete.png", baseDir, tempDir, "test"); + assert.equal(malformedPercent, path.join(baseDir, "100% complete.png")); + + const literalEncodedPercent = await resolveImagePath("diagram%23hash.png", baseDir, tempDir, "test"); + assert.equal(literalEncodedPercent, path.join(baseDir, "diagram%23hash.png")); +}); + test("resolveContentImages resolves image placeholders against the content directory", async (t) => { const root = await makeTempDir("baoyu-md-content-images-"); t.after(() => fs.rm(root, { recursive: true, force: true })); diff --git a/packages/baoyu-md/src/images.ts b/packages/baoyu-md/src/images.ts index 667fa89..3bc43d0 100644 --- a/packages/baoyu-md/src/images.ts +++ b/packages/baoyu-md/src/images.ts @@ -103,11 +103,14 @@ export async function resolveImagePath( return localPath; } - const decoded = decodeURIComponent(imagePath); - const resolved = path.isAbsolute(decoded) - ? decoded - : path.resolve(baseDir, decoded); - return resolveLocalWithFallback(resolved, logLabel); + const decoded = safeDecodeImagePath(imagePath); + const resolved = resolveAgainstBaseDir(decoded, baseDir); + const resolvedWithFallback = resolveLocalWithFallback(resolved, logLabel); + if (decoded === imagePath || fs.existsSync(resolvedWithFallback)) { + return resolvedWithFallback; + } + + return resolveLocalWithFallback(resolveAgainstBaseDir(imagePath, baseDir), logLabel); } export async function resolveContentImages( @@ -155,3 +158,15 @@ function resolveLocalWithFallback(resolved: string, logLabel: string): string { return resolved; } + +function safeDecodeImagePath(imagePath: string): string { + try { + return decodeURIComponent(imagePath); + } catch { + return imagePath; + } +} + +function resolveAgainstBaseDir(imagePath: string, baseDir: string): string { + return path.isAbsolute(imagePath) ? imagePath : path.resolve(baseDir, imagePath); +} diff --git a/skills/baoyu-post-to-x/scripts/md-to-html.test.ts b/skills/baoyu-post-to-x/scripts/md-to-html.test.ts new file mode 100644 index 0000000..cfad63d --- /dev/null +++ b/skills/baoyu-post-to-x/scripts/md-to-html.test.ts @@ -0,0 +1,37 @@ +import assert from "node:assert/strict"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +import { parseMarkdown } from "./md-to-html.ts"; + +async function makeTempDir(prefix: string): Promise { + return fs.mkdtemp(path.join(os.tmpdir(), prefix)); +} + +test("parseMarkdown resolves encoded spaces and literal percent image paths", async (t) => { + const root = await makeTempDir("baoyu-post-to-x-images-"); + t.after(() => fs.rm(root, { recursive: true, force: true })); + + const articlePath = path.join(root, "article.md"); + const tempDir = path.join(root, "tmp"); + await fs.mkdir(tempDir, { recursive: true }); + await fs.writeFile(path.join(root, "Pasted image.png"), "png"); + await fs.writeFile(path.join(root, "100%.png"), "png"); + await fs.writeFile( + articlePath, + [ + "# Title", + "", + "![encoded](Pasted%20image.png)", + "", + "![literal](100%.png)", + ].join("\n"), + ); + + const result = await parseMarkdown(articlePath, { tempDir }); + + assert.equal(result.contentImages[0]?.localPath, path.join(root, "Pasted image.png")); + assert.equal(result.contentImages[1]?.localPath, path.join(root, "100%.png")); +}); diff --git a/skills/baoyu-post-to-x/scripts/md-to-html.ts b/skills/baoyu-post-to-x/scripts/md-to-html.ts index 9b2fbc9..545aaa7 100644 --- a/skills/baoyu-post-to-x/scripts/md-to-html.ts +++ b/skills/baoyu-post-to-x/scripts/md-to-html.ts @@ -181,12 +181,29 @@ async function resolveImagePath(imagePath: string, baseDir: string, tempDir: str return localPath; } - const decoded = decodeURIComponent(imagePath); - if (path.isAbsolute(decoded)) { - return decoded; + return resolveLocalImagePath(imagePath, baseDir); +} + +function resolveLocalImagePath(imagePath: string, baseDir: string): string { + const decoded = safeDecodeImagePath(imagePath); + const resolved = resolveAgainstBaseDir(decoded, baseDir); + if (decoded === imagePath || fs.existsSync(resolved)) { + return resolved; } - return path.resolve(baseDir, decoded); + return resolveAgainstBaseDir(imagePath, baseDir); +} + +function safeDecodeImagePath(imagePath: string): string { + try { + return decodeURIComponent(imagePath); + } catch { + return imagePath; + } +} + +function resolveAgainstBaseDir(imagePath: string, baseDir: string): string { + return path.isAbsolute(imagePath) ? imagePath : path.resolve(baseDir, imagePath); } function escapeHtml(text: string): string {