feat: add markdown image gallery previews
This commit is contained in:
@@ -37,6 +37,7 @@ Keep `bridge.ts` as a thin orchestration layer that delegates message handling t
|
||||
- directory listing
|
||||
- file search
|
||||
- file read path safety checks
|
||||
- active-directory selection across multi-root workspaces
|
||||
- dropped-file parsing and attachment reading
|
||||
- models metadata fetch helper
|
||||
|
||||
|
||||
@@ -538,7 +538,13 @@ export const fetchModelsMetadata = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const getFsAccessRoot = (): string => vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || os.homedir();
|
||||
const getFsAccessRoot = (requestedRoot?: string): string => {
|
||||
const workspaceRoots = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath) ?? [];
|
||||
const requested = requestedRoot ? path.resolve(requestedRoot) : '';
|
||||
return workspaceRoots.find((root) => path.resolve(root) === requested)
|
||||
|| workspaceRoots[0]
|
||||
|| os.homedir();
|
||||
};
|
||||
|
||||
export const getFsMimeType = (filePath: string): string => {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
@@ -564,13 +570,13 @@ export type FsReadPathResolution =
|
||||
| { ok: true; resolvedPath: string }
|
||||
| { ok: false; status: number; error: string };
|
||||
|
||||
export const resolveFileReadPath = async (targetPath: string): Promise<FsReadPathResolution> => {
|
||||
export const resolveFileReadPath = async (targetPath: string, requestedRoot?: string): Promise<FsReadPathResolution> => {
|
||||
const trimmed = targetPath.trim();
|
||||
if (!trimmed) {
|
||||
return { ok: false, status: 400, error: 'Path is required' };
|
||||
}
|
||||
|
||||
const baseRoot = getFsAccessRoot();
|
||||
const baseRoot = getFsAccessRoot(requestedRoot);
|
||||
const resolved = resolveUserPath(trimmed, baseRoot);
|
||||
if (!resolved) {
|
||||
return { ok: false, status: 400, error: 'Path is required' };
|
||||
|
||||
@@ -1,32 +1,25 @@
|
||||
import { describe, expect, it, mock } from 'bun:test';
|
||||
|
||||
const existingFiles = new Set();
|
||||
const fsPromises = {
|
||||
realpath: mock(async (filePath) => {
|
||||
if (existingFiles.has(filePath)) return filePath;
|
||||
const error = new Error('missing');
|
||||
error.code = 'ENOENT';
|
||||
throw error;
|
||||
}),
|
||||
stat: mock(async (filePath) => {
|
||||
if (existingFiles.has(filePath)) return { isFile: () => true, size: 4, mtimeMs: 1 };
|
||||
const error = new Error('missing');
|
||||
error.code = 'ENOENT';
|
||||
throw error;
|
||||
}),
|
||||
readFile: mock(async () => Buffer.from('test')),
|
||||
};
|
||||
|
||||
mock.module('fs', () => ({
|
||||
promises: {
|
||||
realpath: mock(async () => {
|
||||
const error = new Error('missing');
|
||||
error.code = 'ENOENT';
|
||||
throw error;
|
||||
}),
|
||||
stat: mock(async () => {
|
||||
const error = new Error('missing');
|
||||
error.code = 'ENOENT';
|
||||
throw error;
|
||||
}),
|
||||
},
|
||||
default: {
|
||||
promises: {
|
||||
realpath: mock(async () => {
|
||||
const error = new Error('missing');
|
||||
error.code = 'ENOENT';
|
||||
throw error;
|
||||
}),
|
||||
stat: mock(async () => {
|
||||
const error = new Error('missing');
|
||||
error.code = 'ENOENT';
|
||||
throw error;
|
||||
}),
|
||||
},
|
||||
},
|
||||
promises: fsPromises,
|
||||
default: { promises: fsPromises },
|
||||
}));
|
||||
|
||||
mock.module('vscode', () => ({
|
||||
@@ -34,7 +27,10 @@ mock.module('vscode', () => ({
|
||||
file: (fsPath) => ({ fsPath }),
|
||||
},
|
||||
workspace: {
|
||||
workspaceFolders: [{ uri: { fsPath: '/workspace' } }],
|
||||
workspaceFolders: [
|
||||
{ uri: { fsPath: '/workspace' } },
|
||||
{ uri: { fsPath: '/workspace-two' } },
|
||||
],
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -56,4 +52,15 @@ describe('bridge local fs proxy', () => {
|
||||
|
||||
expect(response?.status).toBe(404);
|
||||
});
|
||||
|
||||
it('reads from the active directory when it is the second workspace root', async () => {
|
||||
existingFiles.add('/workspace-two/image.png');
|
||||
const response = await tryHandleLocalFsProxy(
|
||||
'GET',
|
||||
'/api/fs/raw?path=%2Fworkspace-two%2Fimage.png&directory=%2Fworkspace-two',
|
||||
);
|
||||
|
||||
expect(response?.status).toBe(200);
|
||||
expect(Buffer.from(response?.bodyBase64 ?? '', 'base64').toString()).toBe('test');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -66,7 +66,10 @@ export const tryHandleLocalFsProxy = async (method: string, requestPath: string)
|
||||
|
||||
const targetPath = parsed.searchParams.get('path') || '';
|
||||
const optional = parsed.searchParams.get('optional') === 'true';
|
||||
const resolution: FsReadPathResolution = await resolveFileReadPath(targetPath);
|
||||
const resolution: FsReadPathResolution = await resolveFileReadPath(
|
||||
targetPath,
|
||||
parsed.searchParams.get('directory') || undefined,
|
||||
);
|
||||
if (!resolution.ok) {
|
||||
if (fsProxyPath === '/api/fs/stat' && optional && resolution.status === 404) {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user