diff --git a/packages/app/src/file-pane/pane.tsx b/packages/app/src/file-pane/pane.tsx index c185cb613e..8e43931677 100644 --- a/packages/app/src/file-pane/pane.tsx +++ b/packages/app/src/file-pane/pane.tsx @@ -7,7 +7,7 @@ import { StyleSheet, UnistylesRuntime, withUnistyles } from "react-native-unisty import { useTranslation } from "react-i18next"; import { useIsCompactFormFactor } from "@/constants/layout"; import { useSessionStore, type ExplorerFile } from "@/stores/session-store"; -import { FilePanelPreview, resolveFilePreviewPanel } from "@/file-panels"; +import { FilePanelPreview, resolveFilePreviewPanel, type FilePanel } from "@/file-panels"; import { useAttachmentPreviewUrl } from "@/attachments/use-attachment-preview-url"; import { getFileNameFromPath } from "@/attachments/utils"; import { resolveFilePreviewReadTarget } from "@/file-explorer/preview-target"; @@ -37,6 +37,7 @@ const foregroundMutedColorMapping = (theme: Theme) => ({ }); interface FilePreviewBodyProps { + panel: FilePanel | null; preview: ExplorerFile | null; mode?: "preview" | "source"; isLoading: boolean; @@ -121,6 +122,7 @@ function TooLargeSource({ size }: { size?: number }) { } function FilePreviewBody({ + panel, preview, mode, isLoading, @@ -134,7 +136,6 @@ function FilePreviewBody({ }: FilePreviewBodyProps) { const { t } = useTranslation(); const filePath = location.path; - const { settings } = useAppSettings(); if (isLoading && !preview) { return ( @@ -153,11 +154,6 @@ function FilePreviewBody({ ); } - const panel = resolveFilePreviewPanel({ - preview, - path: filePath, - associations: settings.filePanelAssociations, - }); const showSource = preview.kind === "text" && (Boolean(location.lineStart) || mode === "source"); if (panel && !showSource) { return ( @@ -274,6 +270,7 @@ export function FilePane({ return ( ) : null} ) : ( { }); describe("built-in file panels", () => { - it("keeps image classification independent of extension and associations", () => { - const resolution = resolveFilePanel({ - file: { path: "/capture", kind: "image", size: 512, hasBytes: true }, - candidates: builtinFilePanels, - associations: { "ext:": "builtin:markdown" }, - }); - expect(resolution.panel?.id).toBe("builtin:image"); - expect( - resolveFilePanel({ - file: { path: "capture.png", kind: "binary", size: 512, hasBytes: true }, + it.each(["png", "jpg", "jpeg", "gif", "webp", "svg"])( + "registers the image extension %s", + (extension) => { + const resolution = resolveFilePanel({ + file: { + path: `/capture.${extension.toUpperCase()}`, + kind: "image", + size: 512, + hasBytes: true, + }, candidates: builtinFilePanels, - associations: {}, - }).panel, - ).toBeNull(); - }); + associations: { [`ext:${extension}`]: "builtin:markdown" }, + }); + expect(resolution.panel?.id).toBe("builtin:image"); + expect( + resolveFilePanel({ + file: { path: "capture.png", kind: "binary", size: 512, hasBytes: false }, + candidates: builtinFilePanels, + associations: {}, + }).panel, + ).toBeNull(); + }, + ); it("does not impose a new preview size limit on built-in text panels", () => { expect( diff --git a/packages/app/src/file-panels/catalog.ts b/packages/app/src/file-panels/catalog.ts index 0c0b0510ef..fd350ec1ff 100644 --- a/packages/app/src/file-panels/catalog.ts +++ b/packages/app/src/file-panels/catalog.ts @@ -1,12 +1,14 @@ import { filePanelExtension, type FilePanelCandidate } from "./resolve"; +const markdownPanel = { + id: "builtin:markdown", + files: { extensions: ["md", "markdown"] }, + document: "text", + maxBytes: Infinity, +} as const satisfies FilePanelCandidate; + export const builtinFilePanels = [ - { - id: "builtin:markdown", - files: { extensions: ["md", "markdown"] }, - document: "text", - maxBytes: Infinity, - }, + markdownPanel, { id: "builtin:html", files: { extensions: ["html", "htm"] }, @@ -15,14 +17,12 @@ export const builtinFilePanels = [ }, { id: "builtin:image", - files: { extensions: [], kind: "image" }, + files: { extensions: ["png", "jpg", "jpeg", "gif", "webp", "svg"] }, document: "bytes", maxBytes: Infinity, }, ] as const satisfies readonly FilePanelCandidate[]; export function isRenderedMarkdownFile(path: string): boolean { - return builtinFilePanels[0].files.extensions.some( - (extension) => extension === filePanelExtension(path), - ); + return markdownPanel.files.extensions.some((extension) => extension === filePanelExtension(path)); } diff --git a/packages/app/src/file-panels/index.ts b/packages/app/src/file-panels/index.ts index 2f19121a30..497e3aba2e 100644 --- a/packages/app/src/file-panels/index.ts +++ b/packages/app/src/file-panels/index.ts @@ -6,6 +6,8 @@ export { resolveFilePanel, type FilePanelCandidate, type FilePanelFile } from ". export { builtinFilePanels, isRenderedMarkdownFile } from "./catalog"; export { FilePanelPreview } from "./builtin"; +export type FilePanel = (typeof builtinFilePanels)[number]; + export function resolveFilePreviewPanel(input: { preview: ExplorerFile | null; path: string; diff --git a/packages/app/src/file-panels/resolve.ts b/packages/app/src/file-panels/resolve.ts index 7d8bd416f1..3ede7993de 100644 --- a/packages/app/src/file-panels/resolve.ts +++ b/packages/app/src/file-panels/resolve.ts @@ -8,8 +8,6 @@ export interface FilePanelFile { export interface FilePanelSelector { extensions: readonly string[]; filenames?: readonly string[]; - /** Image classification comes from the file read, independent of its extension. */ - kind?: "image"; } export interface FilePanelCandidate { @@ -36,7 +34,6 @@ function filePanelBasename(path: string): string { } function matchesSelector(file: FilePanelFile, selector: FilePanelSelector): boolean { - if (selector.kind) return selector.kind === file.kind; if (selector.filenames?.includes(filePanelBasename(file.path))) return true; const extension = filePanelExtension(file.path); return selector.extensions.some((entry) => entry.toLowerCase() === extension);