Fix Windows image previews (#1811)
* fix(images): preserve Windows preview paths Provider image markdown used escaped backslash paths on Windows, which markdown parsing could turn into encoded drive paths that the app treated as workspace-relative. Emit drive-letter paths as file URIs and normalize existing encoded local paths before file preview. * test(server): accept file URI image sources Windows provider image output now renders drive-letter paths as file URIs. Decode those markdown sources before filesystem assertions so the image rendering tests keep checking the materialized file. * fix(images): keep plain local paths literal Limit legacy URI decoding to markdown-encoded Windows drive paths so ordinary local paths with percent sequences remain unchanged. Document why provider image markdown matching still accepts old doubled-backslash history.
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createImageSourceCacheKey, parseDataUrl, parseImageDataUrl, pathToFileUri } from "./utils";
|
||||
import {
|
||||
createImageSourceCacheKey,
|
||||
fileUriToPath,
|
||||
localFileSourceToPath,
|
||||
parseDataUrl,
|
||||
parseImageDataUrl,
|
||||
pathToFileUri,
|
||||
} from "./utils";
|
||||
|
||||
describe("pathToFileUri", () => {
|
||||
it("converts POSIX absolute paths to file URIs", () => {
|
||||
@@ -23,6 +30,24 @@ describe("pathToFileUri", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("fileUriToPath", () => {
|
||||
it("converts Windows drive-letter file URIs back to paths", () => {
|
||||
expect(fileUriToPath("file:///C:/Users/file.txt")).toBe("C:/Users/file.txt");
|
||||
});
|
||||
});
|
||||
|
||||
describe("localFileSourceToPath", () => {
|
||||
it("decodes markdown-encoded Windows drive-letter paths", () => {
|
||||
expect(localFileSourceToPath("C:%5CUsers%5Cfile.txt")).toBe("C:/Users/file.txt");
|
||||
});
|
||||
|
||||
it("preserves literal percent sequences in plain local paths", () => {
|
||||
expect(localFileSourceToPath("/tmp/image%20with%20literal%20percent.png")).toBe(
|
||||
"/tmp/image%20with%20literal%20percent.png",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseDataUrl", () => {
|
||||
it("accepts base64 data URLs with media-type parameters", () => {
|
||||
expect(parseDataUrl("data:image/png;charset=utf-8;name=preview;base64,AAECAw==")).toEqual({
|
||||
|
||||
@@ -139,11 +139,41 @@ export function pathToFileUri(path: string): string {
|
||||
return `file:///${path.replace(/\\/g, "/")}`;
|
||||
}
|
||||
|
||||
function decodeFilePathSource(source: string): string {
|
||||
try {
|
||||
return decodeURIComponent(source);
|
||||
} catch {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeWindowsDrivePath(path: string): string {
|
||||
if (!/^[A-Za-z]:[\\/]/.test(path)) {
|
||||
return path;
|
||||
}
|
||||
return path.replace(/\\/g, "/");
|
||||
}
|
||||
|
||||
function isMarkdownEncodedWindowsDrivePath(source: string): boolean {
|
||||
return /^[A-Za-z]:(?:%5[Cc]|%2[Ff])/.test(source);
|
||||
}
|
||||
|
||||
export function fileUriToPath(uri: string): string {
|
||||
if (!uri.startsWith("file://")) {
|
||||
return uri;
|
||||
}
|
||||
return decodeURIComponent(uri.replace(/^file:\/\//, ""));
|
||||
const decodedPath = decodeFilePathSource(uri.replace(/^file:\/\//, ""));
|
||||
return normalizeWindowsDrivePath(decodedPath.replace(/^\/([A-Za-z]:[\\/])/, "$1"));
|
||||
}
|
||||
|
||||
export function localFileSourceToPath(source: string): string {
|
||||
let path = source;
|
||||
if (source.startsWith("file://")) {
|
||||
path = fileUriToPath(source);
|
||||
} else if (isMarkdownEncodedWindowsDrivePath(source)) {
|
||||
path = decodeFilePathSource(source);
|
||||
}
|
||||
return normalizeWindowsDrivePath(path);
|
||||
}
|
||||
|
||||
export function getFileExtensionFromName(fileName: string | null | undefined): string {
|
||||
|
||||
Reference in New Issue
Block a user