diff --git a/packages/ui/src/components/views/FilesView.tsx b/packages/ui/src/components/views/FilesView.tsx index ba3eee29..9b0ae0da 100644 --- a/packages/ui/src/components/views/FilesView.tsx +++ b/packages/ui/src/components/views/FilesView.tsx @@ -263,6 +263,12 @@ const isMarkdownFile = (path: string): boolean => { return ext === 'md' || ext === 'markdown'; }; +const isHtmlFile = (path: string): boolean => { + if (!path) return false; + const ext = path.toLowerCase().split('.').pop(); + return ext === 'html' || ext === 'htm'; +}; + interface FileRowProps { node: FileNode; isExpanded: boolean; @@ -466,6 +472,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => { const [isSearchOpen, setIsSearchOpen] = React.useState(false); const [textViewMode, setTextViewMode] = React.useState<'view' | 'edit'>('edit'); const [mdViewMode, setMdViewMode] = React.useState<'preview' | 'edit'>('edit'); + const [htmlViewMode, setHtmlViewMode] = React.useState<'preview' | 'edit'>('edit'); const lightTheme = React.useMemo( () => availableThemes.find((theme) => theme.metadata.id === lightThemeId) ?? getDefaultTheme(false), @@ -1632,8 +1639,9 @@ export const FilesView: React.FC = ({ mode = 'full' }) => { const canCopyPath = Boolean(selectedFile && displaySelectedPath.length > 0); const canEdit = Boolean(selectedFile && !isSelectedImage && files.writeFile && fileContent.length <= MAX_VIEW_CHARS); const isMarkdown = Boolean(selectedFile?.path && isMarkdownFile(selectedFile.path)); + const isHtml = Boolean(selectedFile?.path && isHtmlFile(selectedFile.path)); const isTextFile = Boolean(selectedFile && !isSelectedImage); - const canUseShikiFileView = isTextFile && !isMarkdown; + const canUseShikiFileView = isTextFile && !isMarkdown && !(isHtml && htmlViewMode === 'preview'); const staticLanguageExtension = React.useMemo( () => (selectedFilePath ? languageByExtension(selectedFilePath) : null), [selectedFilePath], @@ -1669,6 +1677,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => { React.useEffect(() => { setTextViewMode('edit'); + setHtmlViewMode('edit'); }, [selectedFile?.path]); const MD_VIEWER_MODE_KEY = 'openchamber:files:md-viewer-mode'; @@ -1699,6 +1708,34 @@ export const FilesView: React.FC = ({ mode = 'full' }) => { return mdViewMode; }, [mdViewMode]); + const HTML_VIEWER_MODE_KEY = 'openchamber:files:html-viewer-mode'; + + React.useEffect(() => { + try { + const stored = localStorage.getItem(HTML_VIEWER_MODE_KEY); + if (stored === 'preview') { + setHtmlViewMode('preview'); + } else if (stored === 'edit') { + setHtmlViewMode('edit'); + } + } catch { + // Ignore localStorage errors + } + }, []); + + const saveHtmlViewMode = React.useCallback((mode: 'preview' | 'edit') => { + setHtmlViewMode(mode); + try { + localStorage.setItem(HTML_VIEWER_MODE_KEY, mode); + } catch { + // Ignore localStorage errors + } + }, []); + + const getHtmlViewMode = React.useCallback((): 'preview' | 'edit' => { + return htmlViewMode; + }, [htmlViewMode]); + React.useEffect(() => { if (!pendingFileNavigation || !root) { return; @@ -2235,10 +2272,16 @@ export const FilesView: React.FC = ({ mode = 'full' }) => { )} - {isMarkdown && ( + {(isMarkdown || isHtmlFile(selectedFile?.path ?? '')) && ( saveMdViewMode(getMdViewMode() === 'preview' ? 'edit' : 'preview')} + currentMode={isMarkdown ? getMdViewMode() : getHtmlViewMode()} + onToggle={() => { + if (isHtmlFile(selectedFile?.path ?? '')) { + saveHtmlViewMode(getHtmlViewMode() === 'preview' ? 'edit' : 'preview'); + } else { + saveMdViewMode(getMdViewMode() === 'preview' ? 'edit' : 'preview'); + } + }} /> )} @@ -2561,6 +2604,21 @@ export const FilesView: React.FC = ({ mode = 'full' }) => { /> + ) : selectedFile && isHtml && htmlViewMode === 'preview' ? ( +
+