Merge main into pr1-large-file-preview

The floating editor toolbar this branch was based on was removed on main
(the toolbar is always docked now); the file-view region resolves to
main's structure with this branch's virtualizer-bound ScrollableOverlay.
This commit is contained in:
Bohdan Triapitsyn
2026-08-26 02:26:20 +03:00
460 changed files with 26402 additions and 10976 deletions
+73 -172
View File
@@ -51,7 +51,7 @@ import { shouldAllowFileDraftSave, shouldScheduleFileAutosave } from '@/lib/file
import { getRuntimeUrlResolver } from '@/lib/runtime-url';
import { acquireRuntimeUrlAuthToken, refreshRuntimeUrlAuthToken, subscribeRuntimeUrlAuthToken } from '@/lib/runtime-auth';
import { getRuntimeApiBaseUrl, getRuntimeKey } from '@/lib/runtime-switch';
import { getOutsideFileGrant } from '@/lib/outsideFileGrants';
import { getOutsideFileGrant, resolveOutsideFileReadOptions } from '@/lib/outsideFileGrants';
import { subscribeToFileContentInvalidation } from '@/lib/fileContentInvalidation';
import { DiagramEditor } from '@/components/diagram';
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
@@ -62,7 +62,7 @@ import { useUIStore } from '@/stores/useUIStore';
import { useFilesViewTabsStore } from '@/stores/useFilesViewTabsStore';
import { useGitStatus } from '@/stores/useGitStore';
import { useConfigStore } from '@/stores/useConfigStore';
import { buildCodeMirrorCommentWidgets, normalizeLineRange, useInlineCommentController } from '@/components/comments';
import { buildCodeMirrorCommentWidgets, FilePreviewCommentMenu, normalizeLineRange, useInlineCommentController } from '@/components/comments';
import { opencodeClient } from '@/lib/opencode/client';
import { useDirectoryShowHidden } from '@/lib/directoryShowHidden';
import { useFilesViewShowGitignored } from '@/lib/filesViewShowGitignored';
@@ -769,8 +769,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
const [wrapLines, setWrapLines] = React.useState(true);
const [isFullscreen, setIsFullscreen] = React.useState(false);
const [isSearchOpen, setIsSearchOpen] = React.useState(false);
const [isFloatingToolbarOpen, setIsFloatingToolbarOpen] = React.useState(false);
const floatingToolbarRef = React.useRef<HTMLDivElement | null>(null);
const toolbarDropdownOpenCountRef = React.useRef(0);
const handleToolbarDropdownOpenChange = React.useCallback((open: boolean) => {
@@ -780,23 +778,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
);
}, []);
const isClickInsidePortalledMenu = React.useCallback((target: EventTarget | null) => {
if (!(target instanceof Element)) return false;
return target.closest('[data-slot="dropdown-menu-content"], [data-slot="dropdown-menu-item"]') !== null;
}, []);
React.useEffect(() => {
if (!isFloatingToolbarOpen) return;
const handler = (event: MouseEvent) => {
if (toolbarDropdownOpenCountRef.current > 0) return;
if (isClickInsidePortalledMenu(event.target)) return;
if (floatingToolbarRef.current && !floatingToolbarRef.current.contains(event.target as Node)) {
setIsFloatingToolbarOpen(false);
}
};
document.addEventListener('mousedown', handler);
return () => document.removeEventListener('mousedown', handler);
}, [isClickInsidePortalledMenu, isFloatingToolbarOpen]);
type TextViewMode = 'view' | 'edit';
type PreviewViewMode = 'preview' | 'edit';
@@ -861,6 +842,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
}, [openPaths, selectedPath]);
const selectedFile = React.useMemo(() => (effectiveSelectedPath ? toFileNode(effectiveSelectedPath) : null), [effectiveSelectedPath, toFileNode]);
const selectedFilePath = selectedFile?.path ?? '';
const [, setOutsideFileGrantRevision] = React.useState(0);
React.useEffect(() => {
if (!root || !selectedPath) return;
@@ -881,6 +863,14 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
}),
[mode, selectedFileIsOutsideWorkspace, selectedOutsideFileGrant, root],
);
const resolveFileReadOptions = React.useCallback(async (path: string) => {
const previousGrant = getOutsideFileGrant(path);
const readOptions = await resolveOutsideFileReadOptions(path, root, mode === 'editor-only');
if (readOptions.outsideFileGrant && readOptions.outsideFileGrant !== previousGrant) {
setOutsideFileGrantRevision((revision) => revision + 1);
}
return readOptions;
}, [mode, root]);
// Editor tabs horizontal scroll fades
const editorTabsScrollRef = React.useRef<HTMLDivElement>(null);
@@ -949,7 +939,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
const [confirmDiscardOpen, setConfirmDiscardOpen] = React.useState(false);
const pendingSelectFileRef = React.useRef<FileNode | null>(null);
const pendingTabRef = React.useRef<import('@/stores/useUIStore').MainTab | null>(null);
const pendingClosePathRef = React.useRef<string | null>(null);
const skipDirtyOnceRef = React.useRef(false);
const copiedContentTimeoutRef = React.useRef<number | null>(null);
@@ -1039,7 +1028,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
const [isDragging, setIsDragging] = React.useState(false);
// Session/config for sending comments
const setMainTabGuard = useUIStore((state) => state.setMainTabGuard);
const pendingFileNavigation = useUIStore((state) => state.pendingFileNavigation);
const setPendingFileNavigation = useUIStore((state) => state.setPendingFileNavigation);
const pendingFileFocusPath = useUIStore((state) => state.pendingFileFocusPath);
@@ -1048,7 +1036,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
const fileEditorKeymap = useUIStore((state) => state.fileEditorKeymap);
const settingsDefaultFileViewerPreview = useConfigStore((state) => state.settingsDefaultFileViewerPreview);
const showMessageTTSButtons = useConfigStore((state) => state.showMessageTTSButtons);
const settingsExpandedEditorToolbar = useUIStore((state) => state.expandedEditorToolbar);
// Global mouseup to end drag selection
React.useEffect(() => {
@@ -1081,6 +1068,8 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
return lines.slice(startLine - 1, endLine).join('\n');
}, []);
const markdownPreviewRef = React.useRef<HTMLDivElement | null>(null);
const fileCommentController = useInlineCommentController<SelectedLineRange>({
source: 'file',
fileLabel: selectedFile?.path ?? null,
@@ -1106,10 +1095,9 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
React.useEffect(() => {
setLineSelection(null);
reset();
setMainTabGuard(null);
setDraftContent('');
setIsSaving(false);
}, [selectedFile?.path, reset, setMainTabGuard]);
}, [selectedFile?.path, reset]);
React.useEffect(() => {
setCommentSelection(lineSelection);
@@ -1565,38 +1553,35 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
};
}, [currentDirectory, debouncedSearchQuery, searchFiles, showHidden, showGitignored]);
const readFile = React.useCallback(async (path: string, options?: { allowOutsideWorkspace?: boolean; outsideFileGrant?: string; optional?: boolean }): Promise<string> => {
const readFile = React.useCallback(async (path: string): Promise<string> => {
const options = await resolveFileReadOptions(path);
if (files.readFile) {
const result = await files.readFile(path, { ...(options ?? {}), directory: root || undefined });
const result = await files.readFile(path, { ...options, directory: root || undefined });
return result.content ?? '';
}
const params = new URLSearchParams({ path });
if (options?.allowOutsideWorkspace) {
if (options.allowOutsideWorkspace) {
params.set('allowOutsideWorkspace', 'true');
}
if (options?.outsideFileGrant) {
if (options.outsideFileGrant) {
params.set('outsideFileGrant', options.outsideFileGrant);
}
if (options?.optional) {
params.set('optional', 'true');
}
if (root) {
params.set('directory', root);
}
const response = await runtimeFetch(`/api/fs/read?${params.toString()}`, {
cache: options?.optional ? 'no-store' : 'default',
});
const response = await runtimeFetch(`/api/fs/read?${params.toString()}`);
if (!response.ok) {
const error = await response.json().catch(() => ({ error: response.statusText }));
throw new Error((error as { error?: string }).error || t('filesView.error.readFileFailed'));
}
return response.text();
}, [files, root, t]);
}, [files, resolveFileReadOptions, root, t]);
const readFileStat = React.useCallback(async (path: string, options?: { allowOutsideWorkspace?: boolean; outsideFileGrant?: string }): Promise<FileStatSnapshot | null> => {
const readFileStat = React.useCallback(async (path: string): Promise<FileStatSnapshot | null> => {
if (files.statFile) {
const result = await files.statFile(path, { ...(options ?? {}), directory: root || undefined });
const options = await resolveFileReadOptions(path);
const result = await files.statFile(path, { ...options, directory: root || undefined });
return {
path: result.path,
size: result.size,
@@ -1604,7 +1589,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
};
}
return null;
}, [files, root]);
}, [files, resolveFileReadOptions, root]);
React.useEffect(() => {
if (!root || !files.statFile || openPaths.length === 0) {
@@ -1616,7 +1601,8 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
void Promise.all(paths.map(async (path) => {
try {
const stat = await files.statFile?.(path, { directory: root || undefined });
const options = await resolveFileReadOptions(path);
const stat = await files.statFile?.(path, { ...options, directory: root || undefined });
if (!cancelled && stat && !stat.isFile) {
removeOpenPathsByPrefix(root, path);
}
@@ -1630,7 +1616,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
return () => {
cancelled = true;
};
}, [files, openPaths, removeOpenPathsByPrefix, root]);
}, [files, openPaths, removeOpenPathsByPrefix, resolveFileReadOptions, root]);
const displayedContent = React.useMemo(() =>
fileContent.length > MAX_VIEW_CHARS
@@ -1721,32 +1707,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
}
}, [contentDetectedBinary, draftContent, fileContent, fileLoading, files, isDirty, loadedFileLineEnding, loadedFilePath, readFileStat, root, selectedFile, t]);
React.useEffect(() => {
if (!isDirty) {
setMainTabGuard(null);
return;
}
const guard = (_nextTab: import('@/stores/useUIStore').MainTab) => {
if (skipDirtyOnceRef.current) {
skipDirtyOnceRef.current = false;
return true;
}
setConfirmDiscardOpen(true);
pendingTabRef.current = _nextTab;
return false;
};
setMainTabGuard(guard);
return () => {
const currentGuard = useUIStore.getState().mainTabGuard;
if (currentGuard === guard) {
setMainTabGuard(null);
}
};
}, [isDirty, setMainTabGuard]);
React.useEffect(() => {
if (autoSaveEnabled) {
return;
@@ -1843,6 +1803,13 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
setDesktopImageSrc('');
setLoadedFilePath(null);
setContentDetectedBinary(false);
setFileLoading(true);
// Prime asset URLs; read and stat resolve again immediately before their calls.
await resolveFileReadOptions(node.path);
if (!isCurrentLoad()) {
return;
}
const selectedIsImage = isImageFile(node.path);
const isSvg = isSvgFile(node.path);
@@ -1857,7 +1824,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
if (runtime.isDesktop && selectedIsImage && !isSvg) {
setFileContent('');
setDraftContent('');
setFileLoading(true);
return;
}
@@ -1888,15 +1854,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
return;
}
setFileLoading(true);
const outsideFileGrant = getOutsideFileGrant(node.path);
const readOptions = {
allowOutsideWorkspace: mode === 'editor-only' && Boolean(root) && !isPathWithinRoot(node.path, root),
outsideFileGrant,
};
await readFile(node.path, readOptions)
await readFile(node.path)
.then((content) => {
if (!isCurrentLoad()) {
return;
@@ -1917,7 +1875,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
? `${editorContent.slice(0, MAX_VIEW_CHARS)}\n\n… truncated …`
: editorContent);
setLoadedFilePath(node.path);
void readFileStat(node.path, readOptions)
void readFileStat(node.path)
.then((stat) => {
if (stat && isCurrentLoad()) {
lastLoadedFileStatRef.current = stat;
@@ -1982,7 +1940,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
setFileLoading(false);
}
});
}, [expandPaths, isMobile, loadDirectory, mode, readFile, readFileStat, removeOpenPathsByPrefix, root, runtime.isDesktop, searchQuery, setSelectedPath, t]);
}, [expandPaths, isMobile, loadDirectory, readFile, readFileStat, removeOpenPathsByPrefix, resolveFileReadOptions, root, runtime.isDesktop, searchQuery, setSelectedPath, t]);
const ensurePathVisible = React.useCallback(async (targetPath: string, includeTarget: boolean) => {
if (!root) {
@@ -2108,7 +2066,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
return;
}
void readFileStat(selectedFile.path, selectedFileReadOptions)
void readFileStat(selectedFile.path)
.then((latestStat) => {
if (cancelled || !latestStat) {
return;
@@ -2144,15 +2102,13 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
cancelled = true;
window.clearInterval(interval);
};
}, [loadedFilePath, readFileStat, selectedFile?.path, selectedFileReadOptions]);
}, [loadedFilePath, readFileStat, selectedFile?.path]);
const discardAndContinue = React.useCallback(() => {
const nextFile = pendingSelectFileRef.current;
const nextTab = pendingTabRef.current;
const closePath = pendingClosePathRef.current;
pendingSelectFileRef.current = null;
pendingTabRef.current = null;
pendingClosePathRef.current = null;
// Allow one guarded navigation (tab/file) without re-opening dialog.
@@ -2191,15 +2147,10 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
return;
}
if (nextTab) {
setMainTabGuard(null);
useUIStore.getState().setActiveMainTab(nextTab);
}
}, [displayedContent, handleSelectFile, isMobile, removeOpenPath, root, selectedFile?.path, setMainTabGuard, setSelectedPath]);
}, [displayedContent, handleSelectFile, isMobile, removeOpenPath, root, selectedFile?.path, setSelectedPath]);
const saveAndContinue = React.useCallback(async () => {
const nextFile = pendingSelectFileRef.current;
const nextTab = pendingTabRef.current;
const closePath = pendingClosePathRef.current;
const saved = await saveDraft();
@@ -2209,7 +2160,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
}
pendingSelectFileRef.current = null;
pendingTabRef.current = null;
pendingClosePathRef.current = null;
// We'll proceed after saving; suppress guard reopening.
@@ -2245,11 +2195,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
return;
}
if (nextTab) {
setMainTabGuard(null);
useUIStore.getState().setActiveMainTab(nextTab);
}
}, [handleSelectFile, isMobile, removeOpenPath, root, saveDraft, selectedFile?.path, setMainTabGuard, setSelectedPath]);
}, [handleSelectFile, isMobile, removeOpenPath, root, saveDraft, selectedFile?.path, setSelectedPath]);
const handleCloseFile = React.useCallback((path: string) => {
const isActive = selectedFile?.path === path;
@@ -2614,12 +2560,12 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
diagramXmlRef.current = xml;
diagramSavedXmlRef.current = xml;
setDraftContent(xml);
const stat = await readFileStat(path, selectedFileReadOptions).catch(() => null);
const stat = await readFileStat(path).catch(() => null);
if (stat) {
lastLoadedFileStatRef.current = stat;
}
return true;
}, [files, readFileStat, selectedFileReadOptions, t]);
}, [files, readFileStat, t]);
React.useEffect(() => {
return () => {
@@ -3046,7 +2992,9 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
[lightTheme.metadata.id, darkTheme.metadata.id],
);
const pdfAssetAuthKey = selectedFile?.path && isSelectedPdf
const pdfAssetAuthKey = selectedFile?.path
&& isSelectedPdf
&& (!selectedFileReadOptions.allowOutsideWorkspace || selectedFileReadOptions.outsideFileGrant)
? `${selectedFile.path}|${selectedFileReadOptions.allowOutsideWorkspace ? 'outside' : 'workspace'}|${selectedFileReadOptions.outsideFileGrant ?? ''}|${fileContentRevision}`
: '';
@@ -3069,7 +3017,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
: desktopImageSrc)
: '';
const pdfSrc = selectedFile?.path && isSelectedPdf && pdfAssetAuthReadyKey === pdfAssetAuthKey
const pdfSrc = selectedFile?.path && isSelectedPdf && pdfAssetAuthKey && pdfAssetAuthReadyKey === pdfAssetAuthKey
? getRuntimeUrlResolver().authenticatedAsset('/api/fs/raw', {
path: selectedFile.path,
allowOutsideWorkspace: selectedFileReadOptions.allowOutsideWorkspace ? 'true' : undefined,
@@ -3101,14 +3049,19 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
setFileError(null);
const readOptions = await resolveFileReadOptions(selectedFile.path);
if (cancelled) {
return;
}
const srcPromise = files.readFileBinary
? files.readFileBinary(selectedFile.path, selectedFileReadOptions).then((result) => result.dataUrl)
? files.readFileBinary(selectedFile.path, readOptions).then((result) => result.dataUrl)
: (async () => {
const response = await runtimeFetch('/api/fs/raw', {
query: {
path: selectedFile.path,
allowOutsideWorkspace: selectedFileReadOptions.allowOutsideWorkspace ? 'true' : undefined,
outsideFileGrant: selectedFileReadOptions.outsideFileGrant,
allowOutsideWorkspace: readOptions.allowOutsideWorkspace ? 'true' : undefined,
outsideFileGrant: readOptions.outsideFileGrant,
directory: root || undefined,
},
});
@@ -3154,7 +3107,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
URL.revokeObjectURL(objectUrl);
}
};
}, [fileContentRevision, files, isSelectedImage, isSelectedSvg, root, selectedFile?.path, selectedFileReadOptions, t]);
}, [fileContentRevision, files, isSelectedImage, isSelectedSvg, resolveFileReadOptions, root, selectedFile?.path, selectedFileReadOptions, t]);
const handleCloseDialog = React.useCallback(() => setActiveDialog(null), []);
@@ -3817,9 +3770,8 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
</div>
) : null}
{/* Row 2: Docked editor toolbar (expanded). Desktop opt-in; ALWAYS on
for mobile — floating hover controls don't work with touch. */}
{(settingsExpandedEditorToolbar || isMobile) && selectedFile ? (
{/* Row 2: Docked editor toolbar. */}
{selectedFile ? (
<div className="flex min-w-0 items-center gap-3 border-t border-border/40 bg-[var(--surface-subtle)] px-3 py-1">
{/* Mobile hosts already show the file name in their own header;
a truncated duplicate here just eats toolbar width. */}
@@ -3840,69 +3792,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
</div>
<div className="flex-1 min-h-0 min-w-0 relative">
{selectedFile && !isSearchOpen && !(settingsExpandedEditorToolbar || isMobile) && (
<div
ref={floatingToolbarRef}
className="absolute right-3 top-3 z-30"
onMouseLeave={() => {
if (toolbarDropdownOpenCountRef.current > 0) return;
setIsFloatingToolbarOpen(false);
}}
>
{isFloatingToolbarOpen ? (
renderFloatingFileControls()
) : (
<div className="flex items-center gap-1">
{isMarkdown ? (
<Tooltip>
<TooltipTrigger asChild>
<span className="inline-flex">
<Button
variant="ghost"
size="sm"
onClick={() => saveMdViewMode(getMdViewMode() === 'preview' ? 'edit' : 'preview')}
className={cn(
'size-8 rounded-lg border border-[var(--interactive-border)] bg-[var(--surface-elevated)] p-0 shadow-sm transition-colors',
getMdViewMode() === 'preview'
? 'bg-[var(--interactive-selection)] text-[var(--interactive-selection-foreground)] hover:bg-[var(--interactive-selection)]'
: 'text-muted-foreground hover:text-foreground'
)}
aria-label={t(getMdViewMode() === 'preview' ? 'filesView.editor.switchToEditMode' : 'filesView.editor.switchToPreviewMode')}
title={t(getMdViewMode() === 'preview' ? 'filesView.editor.switchToEditMode' : 'filesView.editor.switchToPreviewMode')}
>
<Icon name={getMdViewMode() === 'preview' ? 'eye' : 'eye-off'} className="size-4" />
</Button>
</span>
</TooltipTrigger>
<TooltipContent side="bottom" sideOffset={6}>
{t(getMdViewMode() === 'preview' ? 'filesView.editor.switchToEditMode' : 'filesView.editor.switchToPreviewMode')}
</TooltipContent>
</Tooltip>
) : null}
<Tooltip>
<TooltipTrigger asChild>
<span
className="inline-flex"
onMouseEnter={() => setIsFloatingToolbarOpen(true)}
>
<Button
variant="ghost"
size="sm"
onClick={() => setIsFloatingToolbarOpen(true)}
className="size-8 rounded-lg border border-[var(--interactive-border)] bg-[var(--surface-elevated)] p-0 text-muted-foreground shadow-sm hover:text-foreground"
aria-label={t('filesView.editor.showControlsAria')}
title={t('filesView.editor.controlsTitle')}
>
<Icon name="more-2-fill" className="size-4" />
</Button>
</span>
</TooltipTrigger>
<TooltipContent side="bottom" sideOffset={6}>{t('filesView.editor.controlsTitle')}</TooltipContent>
</Tooltip>
</div>
)}
</div>
)}
<ScrollableOverlay ref={mainViewVirtualizer.setScroller} outerClassName="h-full min-w-0" className={cn('h-full min-w-0', isLargeFile && '[overflow-anchor:none]')}>
{!selectedFile ? (
<div className="p-3 typography-ui text-muted-foreground">{t('filesView.editor.pickFileFromTree')}</div>
@@ -3980,7 +3869,12 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
</div>
</ErrorBoundary>
) : selectedFile && isMarkdown && getMdViewMode() === 'preview' ? (
<div className="h-full overflow-auto p-3">
<div className="oc-file-preview h-full overflow-auto p-3" ref={markdownPreviewRef}>
<FilePreviewCommentMenu
containerRef={markdownPreviewRef}
filePath={selectedFile.path}
fileContent={fileContent}
/>
{fileContent.length > 500 * 1024 && (
<div className="mb-3 rounded-md border border-status-warning/20 bg-status-warning/10 px-3 py-2 text-sm text-status-warning">
{t('filesView.warning.largeFilePreviewLimited', { sizeKb: Math.round(fileContent.length / 1024) })}
@@ -4346,7 +4240,14 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
) : null}
</div>
) : isMarkdown && getMdViewMode() === 'preview' ? (
<div className="h-full overflow-auto p-4">
<div className="oc-file-preview h-full overflow-auto p-4" ref={markdownPreviewRef}>
{selectedFile ? (
<FilePreviewCommentMenu
containerRef={markdownPreviewRef}
filePath={selectedFile.path}
fileContent={fileContent}
/>
) : null}
{fileContent.length > 500 * 1024 && (
<div className="mb-3 rounded-md border border-status-warning/20 bg-status-warning/10 px-3 py-2 text-sm text-status-warning">
{t('filesView.warning.largeFilePreviewLimited', { sizeKb: Math.round(fileContent.length / 1024) })}