feat(files): expose markdown preview toggle (#2152)

* fix(files): expose markdown preview toggle

* fix(files): sync markdown preview icon state
This commit is contained in:
Greg Haynes
2026-07-13 01:46:03 +03:00
committed by GitHub
parent 9624d4b6f6
commit d4b75b268a
11 changed files with 110 additions and 42 deletions
+90 -42
View File
@@ -3302,18 +3302,35 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
/>
)}
{(isMarkdown || isHtmlFile(selectedFile?.path ?? '')) && (
<PreviewToggleButton
currentMode={isMarkdown ? getMdViewMode() : getHtmlViewMode()}
onToggle={() => {
if (isHtmlFile(selectedFile?.path ?? '')) {
saveHtmlViewMode(getHtmlViewMode() === 'preview' ? 'edit' : 'preview');
} else {
saveMdViewMode(getMdViewMode() === 'preview' ? 'edit' : 'preview');
}
}}
/>
)}
{isMarkdown && (
withTooltip(
t(getMdViewMode() === 'preview' ? 'filesView.editor.switchToEditMode' : 'filesView.editor.switchToPreviewMode'),
<Button
variant="ghost"
size="sm"
onClick={() => saveMdViewMode(getMdViewMode() === 'preview' ? 'edit' : 'preview')}
className={cn(
'size-6 p-0 transition-colors hover:bg-[var(--interactive-hover)] focus-visible:bg-[var(--interactive-hover)] active:bg-[var(--interactive-hover)]',
getMdViewMode() === 'preview'
? 'bg-[var(--interactive-selection)] text-[var(--interactive-selection-foreground)] hover:bg-[var(--interactive-selection)] focus-visible:bg-[var(--interactive-selection)] active:bg-[var(--interactive-selection)]'
: 'text-muted-foreground opacity-65 hover:opacity-100'
)}
title={t(getMdViewMode() === 'preview' ? 'filesView.editor.switchToEditMode' : 'filesView.editor.switchToPreviewMode')}
aria-label={t(getMdViewMode() === 'preview' ? 'filesView.editor.switchToEditMode' : 'filesView.editor.switchToPreviewMode')}
>
<Icon name={getMdViewMode() === 'preview' ? 'eye' : 'eye-off'} className="size-4" />
</Button>
)
)}
{isHtmlFile(selectedFile?.path ?? '') && (
<PreviewToggleButton
currentMode={getHtmlViewMode()}
onToggle={() => {
saveHtmlViewMode(getHtmlViewMode() === 'preview' ? 'edit' : 'preview');
}}
/>
)}
{isMarkdown && getMdViewMode() === 'preview' && showMessageTTSButtons && (
<Tooltip>
@@ -3713,38 +3730,69 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
<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"
onMouseEnter={() => setIsFloatingToolbarOpen(true)}
onMouseLeave={() => {
if (toolbarDropdownOpenCountRef.current > 0) return;
setIsFloatingToolbarOpen(false);
<div
ref={floatingToolbarRef}
className="absolute right-3 top-3 z-30"
onMouseEnter={() => {
if (isMarkdown) return;
setIsFloatingToolbarOpen(true);
}}
onMouseLeave={() => {
if (toolbarDropdownOpenCountRef.current > 0) return;
setIsFloatingToolbarOpen(false);
}}
>
{isFloatingToolbarOpen ? (
renderFloatingFileControls()
) : (
<Tooltip>
<TooltipTrigger asChild>
<span className="inline-flex">
<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>
)}
{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">
<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 outerClassName="h-full min-w-0" className="h-full min-w-0">
{!selectedFile ? (
<div className="p-3 typography-ui text-muted-foreground">{t('filesView.editor.pickFileFromTree')}</div>
+2
View File
@@ -1166,6 +1166,8 @@ export const dict = {
'filesView.editor.enableLineWrap': 'Enable line wrap',
'filesView.editor.findInFile': 'Find in file',
'filesView.editor.goToLine': 'Go to line',
'filesView.editor.switchToEditMode': 'Switch to edit mode',
'filesView.editor.switchToPreviewMode': 'Switch to preview mode',
'filesView.editor.switchToTextView': 'Switch to Text View',
'filesView.editor.switchToTreeView': 'Switch to Tree View',
'filesView.toast.copyFailed': 'Copy failed',
+2
View File
@@ -1132,6 +1132,8 @@ export const dict: Record<I18nKey, string> = {
"filesView.editor.enableLineWrap": "Activar ajuste de línea",
"filesView.editor.findInFile": "Buscar en el archivo",
"filesView.editor.goToLine": "Ir a línea",
"filesView.editor.switchToEditMode": "Cambiar al modo de edición",
"filesView.editor.switchToPreviewMode": "Cambiar al modo de vista previa",
"filesView.editor.switchToTextView": "Cambiar a vista de texto",
"filesView.editor.switchToTreeView": "Cambiar a vista de árbol",
"filesView.toast.copyFailed": "No se pudo copiar",
+2
View File
@@ -995,6 +995,8 @@ export const dict = {
'filesView.editor.enableLineWrap': 'Activer le retour à la ligne',
'filesView.editor.findInFile': 'Rechercher dans le fichier',
'filesView.editor.goToLine': 'Aller à la ligne',
'filesView.editor.switchToEditMode': 'Passer en mode édition',
'filesView.editor.switchToPreviewMode': 'Passer en mode aperçu',
'filesView.editor.switchToTextView': 'Passer à l\'affichage texte',
'filesView.editor.switchToTreeView': 'Passer à l\'arborescence',
'filesView.toast.copyFailed': 'Échec de la copie',
+2
View File
@@ -1162,6 +1162,8 @@ export const dict: Record<I18nKey, string> = {
'filesView.editor.enableLineWrap': '行の折り返しを有効にする',
'filesView.editor.findInFile': 'ファイル内を検索',
'filesView.editor.goToLine': '指定行に移動',
'filesView.editor.switchToEditMode': '編集モードに切り替え',
'filesView.editor.switchToPreviewMode': 'プレビューモードに切り替え',
'filesView.editor.switchToTextView': 'テキストビューに切り替え',
'filesView.editor.switchToTreeView': 'ツリービューに切り替え',
'filesView.toast.copyFailed': 'コピーに失敗しました',
+2
View File
@@ -1169,6 +1169,8 @@ export const dict: Record<I18nKey, string> = {
'filesView.editor.enableLineWrap': '줄 바꿈 켜기',
'filesView.editor.findInFile': '파일에서 찾기',
'filesView.editor.goToLine': '줄로 이동',
'filesView.editor.switchToEditMode': '편집 모드로 전환',
'filesView.editor.switchToPreviewMode': '미리보기 모드로 전환',
'filesView.editor.switchToTextView': '텍스트 보기로 전환',
'filesView.editor.switchToTreeView': '트리 보기로 전환',
'filesView.toast.copyFailed': '복사 실패',
+2
View File
@@ -1653,6 +1653,8 @@ export const dict: Record<I18nKey, string> = {
'filesView.editor.fullscreen': 'Pełny ekran',
'filesView.editor.goToLine': 'Przejdź do linii',
'filesView.editor.htmlPreviewTitle': 'Podgląd HTML',
'filesView.editor.switchToEditMode': 'Przełącz na tryb edycji',
'filesView.editor.switchToPreviewMode': 'Przełącz na tryb podglądu',
'filesView.diagram.closeDiagramView': 'Zamknij widok diagramu',
'filesView.diagram.saveDiagram': 'Zapisz diagram',
'filesView.editor.imageAltFallback': 'Obraz',
@@ -1132,6 +1132,8 @@ export const dict: Record<I18nKey, string> = {
"filesView.editor.enableLineWrap": "Ativar ajuste de linha",
"filesView.editor.findInFile": "Buscar no arquivo",
"filesView.editor.goToLine": "Ir para linha",
"filesView.editor.switchToEditMode": "Alternar para o modo de edição",
"filesView.editor.switchToPreviewMode": "Alternar para o modo de visualização",
"filesView.editor.switchToTextView": "Alternar para visualização de texto",
"filesView.editor.switchToTreeView": "Alternar para visualização em árvore",
"filesView.toast.copyFailed": "Não foi possível copiar",
+2
View File
@@ -1132,6 +1132,8 @@ export const dict: Record<I18nKey, string> = {
"filesView.editor.enableLineWrap": "Увімкнути перенос рядків",
"filesView.editor.findInFile": "Знайти у файлі",
"filesView.editor.goToLine": "Перейти до рядка",
"filesView.editor.switchToEditMode": "Перемкнутися в режим редагування",
"filesView.editor.switchToPreviewMode": "Перемкнутися в режим попереднього перегляду",
"filesView.editor.switchToTextView": "Перемкнутися на текстовий перегляд",
"filesView.editor.switchToTreeView": "Перейти до перегляду дерева",
"filesView.toast.copyFailed": "Помилка копіювання",
@@ -1132,6 +1132,8 @@ export const dict: Record<I18nKey, string> = {
'filesView.editor.enableLineWrap': '开启自动换行',
'filesView.editor.findInFile': '文件内查找',
'filesView.editor.goToLine': '跳转到行',
'filesView.editor.switchToEditMode': '切换到编辑模式',
'filesView.editor.switchToPreviewMode': '切换到预览模式',
'filesView.editor.switchToTextView': '切换到文本视图',
'filesView.editor.switchToTreeView': '切换到树视图',
'filesView.toast.copyFailed': '复制失败',
@@ -1143,6 +1143,8 @@ export const dict: Record<I18nKey, string> = {
'filesView.editor.enableLineWrap': '開啟自動換行',
'filesView.editor.findInFile': '檔案內尋找',
'filesView.editor.goToLine': '跳轉到行',
'filesView.editor.switchToEditMode': '切換到編輯模式',
'filesView.editor.switchToPreviewMode': '切換到預覽模式',
'filesView.editor.switchToTextView': '切換到文字檢視',
'filesView.editor.switchToTreeView': '切換到樹狀檢視',
'filesView.toast.copyFailed': '複製失敗',