mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-24 18:22:38 +00:00
fix(app): show local images in Markdown file previews
Carry forward document-relative image loading and preserve the host home directory when resolving images beside home-relative Markdown files. Decode file URIs once, and require decoded images in the file-pane regression. Co-authored-by: Ouyang Xingyuan <galaxyroaming@qq.com>
This commit is contained in:
co-authored by
Ouyang Xingyuan
parent
301d8618cd
commit
f518e18a72
@@ -71,6 +71,14 @@ async function selectFileView(page: Page, view: "Preview" | "Source"): Promise<v
|
||||
await expect(option).toHaveAttribute("aria-selected", "true");
|
||||
}
|
||||
|
||||
async function expectMarkdownImageLoaded(page: Page, name: string): Promise<void> {
|
||||
const image = page.getByRole("img", { name, exact: true });
|
||||
await expect(image).toBeVisible();
|
||||
await expect
|
||||
.poll(() => image.locator("img").evaluate((element: HTMLImageElement) => element.naturalWidth))
|
||||
.toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
function watchRequestsTo(page: Page, origin: string): string[] {
|
||||
const requests: string[] = [];
|
||||
page.on("request", (request) => {
|
||||
@@ -700,20 +708,18 @@ test.describe("CodeMirror workspace file editing", () => {
|
||||
"utf8",
|
||||
);
|
||||
await workspace.navigateTo();
|
||||
const tree = page.getByTestId("file-explorer-tree-scroll");
|
||||
if (!(await tree.isVisible())) await openFileExplorer(page);
|
||||
await openFileExplorer(page);
|
||||
await expandFolder(page, "docs");
|
||||
await openFileFromExplorer(page, "guide.md");
|
||||
await expectFileTabOpen(page, "guide.md");
|
||||
await expectFileTabOpen(page, "docs/guide.md");
|
||||
|
||||
const visibleFilePane = page.getByTestId("workspace-file-pane").filter({ visible: true });
|
||||
await expect(visibleFilePane.getByText("Guide", { exact: true })).toBeVisible();
|
||||
const previewImages = visibleFilePane.getByTestId("markdown-preview-image");
|
||||
await expect(previewImages).toHaveCount(2);
|
||||
for (const image of await previewImages.locator("img").all()) {
|
||||
await expect(image).toBeVisible();
|
||||
await expect.poll(async () => (await image.getAttribute("src")) ?? "").not.toBe("");
|
||||
}
|
||||
await expectMarkdownImageLoaded(page, "sibling");
|
||||
await expectMarkdownImageLoaded(page, "hero");
|
||||
await visibleFilePane.screenshot({
|
||||
path: test.info().outputPath("markdown-relative-images.png"),
|
||||
});
|
||||
});
|
||||
|
||||
test("previews and refreshes an HTML plan while preserving source access", async ({
|
||||
|
||||
@@ -21,13 +21,13 @@ describe("resolveMarkdownFileDirectory", () => {
|
||||
).toBe("/Users/test/project/docs/星谷云");
|
||||
});
|
||||
|
||||
it("returns null for home-relative markdown paths", () => {
|
||||
it("preserves the host home directory for home-relative markdown paths", () => {
|
||||
expect(
|
||||
resolveMarkdownFileDirectory({
|
||||
markdownPath: "~/.paseo/plans/note.md",
|
||||
workspaceRoot: "/Users/test/project",
|
||||
}),
|
||||
).toBeNull();
|
||||
).toBe("~/.paseo/plans");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -96,14 +96,34 @@ describe("resolveMarkdownPreviewImageSource", () => {
|
||||
).toBe("/tmp/paseo-preview.png");
|
||||
});
|
||||
|
||||
it("falls back to the raw relative path when the markdown directory is unknown", () => {
|
||||
it("resolves images beside a home-relative document without using the workspace root", () => {
|
||||
expect(
|
||||
resolveMarkdownPreviewImageSource({
|
||||
source: "screenshots/output.png",
|
||||
markdownPath: "~/.paseo/plans/note.md",
|
||||
workspaceRoot: "/Users/test/project",
|
||||
}),
|
||||
).toBe("screenshots/output.png");
|
||||
).toBe("~/.paseo/plans/screenshots/output.png");
|
||||
});
|
||||
|
||||
it("decodes a file URI exactly once", () => {
|
||||
expect(
|
||||
resolveMarkdownPreviewImageSource({
|
||||
source: "file:///tmp/hero%2520image.png",
|
||||
markdownPath: "docs/guide.md",
|
||||
workspaceRoot: "/workspace",
|
||||
}),
|
||||
).toBe("/tmp/hero%20image.png");
|
||||
});
|
||||
|
||||
it("resolves parent segments from a home-relative document", () => {
|
||||
expect(
|
||||
resolveMarkdownPreviewImageSource({
|
||||
source: "../images/hero.png",
|
||||
markdownPath: "~/docs/guide.md",
|
||||
workspaceRoot: "/workspace",
|
||||
}),
|
||||
).toBe("~/images/hero.png");
|
||||
});
|
||||
|
||||
it("resolves Windows relative images against a drive-rooted markdown file", () => {
|
||||
|
||||
@@ -27,6 +27,9 @@ function normalizePathSegments(value: string): string {
|
||||
|
||||
function normalizeAbsolutePath(value: string): string | null {
|
||||
const normalizedInput = trimTrailingSeparators(value.replace(/\\/g, "/"));
|
||||
if (isHomeRelativePath(normalizedInput)) {
|
||||
return `~/${normalizePathSegments(normalizedInput.slice(2))}`;
|
||||
}
|
||||
if (!isAbsolutePath(normalizedInput)) {
|
||||
return null;
|
||||
}
|
||||
@@ -91,11 +94,11 @@ export function resolveMarkdownFileDirectory(input: {
|
||||
workspaceRoot?: string;
|
||||
}): string | null {
|
||||
const markdownPath = input.markdownPath.trim().replace(/\\/g, "/");
|
||||
if (!markdownPath || isHomeRelativePath(markdownPath)) {
|
||||
if (!markdownPath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isAbsolutePath(markdownPath)) {
|
||||
if (isAbsolutePath(markdownPath) || isHomeRelativePath(markdownPath)) {
|
||||
const absolute = normalizeAbsolutePath(markdownPath);
|
||||
return absolute ? parentDirectory(absolute) : null;
|
||||
}
|
||||
@@ -123,7 +126,9 @@ export function resolveMarkdownPreviewImageSource(input: {
|
||||
return source;
|
||||
}
|
||||
|
||||
const path = decodeRelativeSource(localFileSourceToPath(source));
|
||||
const path = localFileSourceToPath(
|
||||
source.startsWith("file://") ? source : decodeRelativeSource(source),
|
||||
);
|
||||
if (!path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
MarkdownRenderer,
|
||||
type MarkdownStyles,
|
||||
} from "@/components/markdown/renderer";
|
||||
import { MAX_CONTENT_WIDTH } from "@/constants/layout";
|
||||
import { parseMarkdownPreviewDocument } from "./document";
|
||||
import { resolveMarkdownPreviewImageSource } from "./image-source";
|
||||
import { FileMarkdownPreviewImage } from "./preview-image";
|
||||
|
||||
Reference in New Issue
Block a user