Skip to content

Commit 1e219d6

Browse files
committed
bug: opening repo will now accurately load repo images also
1 parent 294994a commit 1e219d6

2 files changed

Lines changed: 107 additions & 3 deletions

File tree

src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3511,6 +3511,8 @@ const App = () => {
35113511
isHorizontal={isHorizontal}
35123512
initializeMermaid={initializeMermaid}
35133513
plainTextPreview={plainTextPreview}
3514+
currentFilePath={currentFilePath}
3515+
currentDirHandle={currentDirHandle}
35143516
/>
35153517
)}
35163518
</div>

src/components/PreviewComponent.tsx

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect } from 'react';
1+
import React, { useEffect, useState } from 'react';
22
import ReactMarkdown, { defaultUrlTransform } from 'react-markdown';
33
import remarkGfm from 'remark-gfm';
44
import remarkEmoji from 'remark-emoji';
@@ -17,6 +17,8 @@ interface PreviewComponentProps {
1717
isHorizontal: boolean;
1818
initializeMermaid: () => void;
1919
plainTextPreview?: boolean;
20+
currentFilePath?: string | null;
21+
currentDirHandle?: any;
2022
}
2123

2224
const PreviewComponent: React.FC<PreviewComponentProps> = React.memo(({
@@ -25,7 +27,9 @@ const PreviewComponent: React.FC<PreviewComponentProps> = React.memo(({
2527
isPreviewFull,
2628
isHorizontal,
2729
initializeMermaid,
28-
plainTextPreview
30+
plainTextPreview,
31+
currentFilePath,
32+
currentDirHandle
2933
}) => {
3034
// Custom remark plugin to preserve blank lines between list items
3135
const preserveListBreaks = () => {
@@ -153,7 +157,105 @@ const PreviewComponent: React.FC<PreviewComponentProps> = React.memo(({
153157
}}
154158
components={{
155159
img(props) {
156-
return <img {...props} style={{ maxWidth: '100%', ...(props.style as React.CSSProperties) }} />;
160+
const AsyncImage = ({ src, alt, style, ...rest }: any) => {
161+
const [resolvedSrc, setResolvedSrc] = useState(src);
162+
163+
useEffect(() => {
164+
let objectUrl = '';
165+
166+
const resolveImg = async () => {
167+
if (!src || src.startsWith('http') || src.startsWith('data:') || src.startsWith('blob:') || src.startsWith('tauri://') || src.startsWith('asset://')) {
168+
return;
169+
}
170+
171+
// Check if running in Tauri
172+
const isTauri = typeof window !== 'undefined' && ((window as any).__TAURI_INTERNALS__ || (window as any).__TAURI__);
173+
174+
if (isTauri && currentFilePath) {
175+
try {
176+
// Use plugin-fs to read file and create object URL, bypassing assetScopes
177+
const { readFile } = await import('@tauri-apps/plugin-fs');
178+
// currentFilePath is usually absolute. e.g. /home/user/repo/docs/README.md
179+
const dirPath = currentFilePath.replace(/\\/g, '/').split('/').slice(0, -1).join('/');
180+
181+
let cleanSrc = src;
182+
let absolutePath = '';
183+
184+
if (cleanSrc.startsWith('./')) {
185+
absolutePath = `${dirPath}/${cleanSrc.substring(2)}`;
186+
} else if (cleanSrc.startsWith('../')) {
187+
const parts = dirPath.split('/');
188+
const srcParts = cleanSrc.split('/');
189+
for(let p of srcParts) {
190+
if (p === '..') parts.pop();
191+
else if (p !== '.') parts.push(p);
192+
}
193+
absolutePath = parts.join('/');
194+
} else {
195+
absolutePath = `${dirPath}/${cleanSrc}`;
196+
}
197+
198+
const fileData = await readFile(absolutePath);
199+
const blob = new Blob([fileData]);
200+
objectUrl = URL.createObjectURL(blob);
201+
setResolvedSrc(objectUrl);
202+
} catch (e) {
203+
console.error('Failed to load Tauri fs core for image resolution:', e);
204+
}
205+
return;
206+
}
207+
208+
// Check Web with File System Access API
209+
if (!isTauri && currentDirHandle && currentFilePath) {
210+
try {
211+
// Attempt to traverse from currentDirHandle directly, assuming src is relative to current file
212+
const getFileHandleFromPath = async (dirHandle: any, path: string) => {
213+
const parts = path.split('/').filter(p => p && p !== '.');
214+
let currentHandle = dirHandle;
215+
for (let i = 0; i < parts.length - 1; i++) {
216+
if (parts[i] === '..') throw new Error("Parent traversal not fully supported without root handle.");
217+
currentHandle = await currentHandle.getDirectoryHandle(parts[i]);
218+
}
219+
const fileName = parts[parts.length - 1];
220+
return await currentHandle.getFileHandle(fileName);
221+
};
222+
223+
// We need the relative path from the dirHandle
224+
// If currentFilePath is a relative path starting from dirHandle's root
225+
const dirPath = currentFilePath.split(/[/\\]/).slice(0, -1).join('/');
226+
let fetchPath = dirPath ? `${dirPath}/${src}` : src;
227+
228+
// Normalize fetchPath relative to dirHandle
229+
const fetchParts = fetchPath.split('/');
230+
const normalizedParts = [];
231+
for (const p of fetchParts) {
232+
if (p === '..') normalizedParts.pop();
233+
else if (p !== '.' && p) normalizedParts.push(p);
234+
}
235+
fetchPath = normalizedParts.join('/');
236+
237+
const fileHandle = await getFileHandleFromPath(currentDirHandle, fetchPath);
238+
const file = await fileHandle.getFile();
239+
objectUrl = URL.createObjectURL(file);
240+
setResolvedSrc(objectUrl);
241+
242+
} catch(e) {
243+
console.warn('Failed to resolve web local image:', e);
244+
}
245+
}
246+
};
247+
248+
resolveImg();
249+
250+
return () => {
251+
if (objectUrl) URL.revokeObjectURL(objectUrl);
252+
};
253+
}, [src]);
254+
255+
return <img src={resolvedSrc} alt={alt} style={{ maxWidth: '100%', ...(style as React.CSSProperties) }} {...rest} />;
256+
};
257+
258+
return <AsyncImage {...props} />;
157259
},
158260
li({ children, className }) {
159261
return <li className={className}>{children}</li>;

0 commit comments

Comments
 (0)