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);