mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-30 17:12:58 +08:00
feat(file-viewer): add html preview tab
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
import { EditorView } from '@codemirror/view';
|
import { EditorView } from '@codemirror/view';
|
||||||
import { unifiedMergeView } from '@codemirror/merge';
|
import { unifiedMergeView } from '@codemirror/merge';
|
||||||
import type { Extension } from '@codemirror/state';
|
import type { Extension } from '@codemirror/state';
|
||||||
import { useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { usePaletteOps } from '../../../contexts/PaletteOpsContext';
|
import { usePaletteOps } from '../../../contexts/PaletteOpsContext';
|
||||||
import { useCodeEditorDocument } from '../hooks/useCodeEditorDocument';
|
import { useCodeEditorDocument } from '../hooks/useCodeEditorDocument';
|
||||||
import { useCodeEditorSettings } from '../hooks/useCodeEditorSettings';
|
import { useCodeEditorSettings } from '../hooks/useCodeEditorSettings';
|
||||||
@@ -11,6 +12,7 @@ import type { CodeEditorFile } from '../types/types';
|
|||||||
import { createMinimapExtension, createScrollToFirstChunkExtension, getLanguageExtensions } from '../utils/editorExtensions';
|
import { createMinimapExtension, createScrollToFirstChunkExtension, getLanguageExtensions } from '../utils/editorExtensions';
|
||||||
import { getEditorStyles } from '../utils/editorStyles';
|
import { getEditorStyles } from '../utils/editorStyles';
|
||||||
import { createEditorToolbarPanelExtension } from '../utils/editorToolbarPanel';
|
import { createEditorToolbarPanelExtension } from '../utils/editorToolbarPanel';
|
||||||
|
|
||||||
import CodeEditorFooter from './subcomponents/CodeEditorFooter';
|
import CodeEditorFooter from './subcomponents/CodeEditorFooter';
|
||||||
import CodeEditorHeader from './subcomponents/CodeEditorHeader';
|
import CodeEditorHeader from './subcomponents/CodeEditorHeader';
|
||||||
import CodeEditorLoadingState from './subcomponents/CodeEditorLoadingState';
|
import CodeEditorLoadingState from './subcomponents/CodeEditorLoadingState';
|
||||||
@@ -73,6 +75,29 @@ export default function CodeEditor({
|
|||||||
return extension === 'md' || extension === 'markdown';
|
return extension === 'md' || extension === 'markdown';
|
||||||
}, [file.name]);
|
}, [file.name]);
|
||||||
|
|
||||||
|
const isHtmlPreviewFile = useMemo(() => {
|
||||||
|
const extension = file.name.split('.').pop()?.toLowerCase();
|
||||||
|
return extension === 'html' || extension === 'htm';
|
||||||
|
}, [file.name]);
|
||||||
|
|
||||||
|
const openHtmlPreview = useCallback(() => {
|
||||||
|
const previewWindow = window.open('', '_blank');
|
||||||
|
if (!previewWindow) return;
|
||||||
|
|
||||||
|
previewWindow.opener = null;
|
||||||
|
previewWindow.document.title = file.name;
|
||||||
|
previewWindow.document.body.style.margin = '0';
|
||||||
|
|
||||||
|
const iframe = previewWindow.document.createElement('iframe');
|
||||||
|
iframe.title = file.name;
|
||||||
|
iframe.sandbox.add('allow-forms', 'allow-modals', 'allow-popups', 'allow-scripts');
|
||||||
|
iframe.style.cssText = 'position:fixed;inset:0;width:100%;height:100%;border:0;background:white';
|
||||||
|
|
||||||
|
iframe.srcdoc = content;
|
||||||
|
|
||||||
|
previewWindow.document.body.appendChild(iframe);
|
||||||
|
}, [content, file.name]);
|
||||||
|
|
||||||
const minimapExtension = useMemo(
|
const minimapExtension = useMemo(
|
||||||
() => (
|
() => (
|
||||||
createMinimapExtension({
|
createMinimapExtension({
|
||||||
@@ -224,10 +249,12 @@ export default function CodeEditor({
|
|||||||
isSidebar={isSidebar}
|
isSidebar={isSidebar}
|
||||||
isFullscreen={isFullscreen}
|
isFullscreen={isFullscreen}
|
||||||
isMarkdownFile={isMarkdownFile}
|
isMarkdownFile={isMarkdownFile}
|
||||||
|
isHtmlPreviewFile={isHtmlPreviewFile}
|
||||||
markdownPreview={markdownPreview}
|
markdownPreview={markdownPreview}
|
||||||
saving={saving}
|
saving={saving}
|
||||||
saveSuccess={saveSuccess}
|
saveSuccess={saveSuccess}
|
||||||
onToggleMarkdownPreview={() => setMarkdownPreview((previous) => !previous)}
|
onToggleMarkdownPreview={() => setMarkdownPreview((previous) => !previous)}
|
||||||
|
onOpenHtmlPreview={openHtmlPreview}
|
||||||
onOpenSettings={() => paletteOps.openSettings('appearance')}
|
onOpenSettings={() => paletteOps.openSettings('appearance')}
|
||||||
onDownload={handleDownload}
|
onDownload={handleDownload}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
@@ -237,6 +264,7 @@ export default function CodeEditor({
|
|||||||
showingChanges: t('header.showingChanges'),
|
showingChanges: t('header.showingChanges'),
|
||||||
editMarkdown: t('actions.editMarkdown'),
|
editMarkdown: t('actions.editMarkdown'),
|
||||||
previewMarkdown: t('actions.previewMarkdown'),
|
previewMarkdown: t('actions.previewMarkdown'),
|
||||||
|
previewHtml: t('actions.previewHtml', 'Open HTML preview in new tab'),
|
||||||
settings: t('toolbar.settings'),
|
settings: t('toolbar.settings'),
|
||||||
download: t('actions.download'),
|
download: t('actions.download'),
|
||||||
save: t('actions.save'),
|
save: t('actions.save'),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Code2, Download, Eye, Maximize2, Minimize2, Save, Settings as SettingsIcon, X } from 'lucide-react';
|
import { Code2, Download, Eye, Maximize2, Minimize2, Save, Settings as SettingsIcon, X } from 'lucide-react';
|
||||||
|
|
||||||
import type { CodeEditorFile } from '../../types/types';
|
import type { CodeEditorFile } from '../../types/types';
|
||||||
|
|
||||||
type CodeEditorHeaderProps = {
|
type CodeEditorHeaderProps = {
|
||||||
@@ -6,10 +7,12 @@ type CodeEditorHeaderProps = {
|
|||||||
isSidebar: boolean;
|
isSidebar: boolean;
|
||||||
isFullscreen: boolean;
|
isFullscreen: boolean;
|
||||||
isMarkdownFile: boolean;
|
isMarkdownFile: boolean;
|
||||||
|
isHtmlPreviewFile: boolean;
|
||||||
markdownPreview: boolean;
|
markdownPreview: boolean;
|
||||||
saving: boolean;
|
saving: boolean;
|
||||||
saveSuccess: boolean;
|
saveSuccess: boolean;
|
||||||
onToggleMarkdownPreview: () => void;
|
onToggleMarkdownPreview: () => void;
|
||||||
|
onOpenHtmlPreview: () => void;
|
||||||
onOpenSettings: () => void;
|
onOpenSettings: () => void;
|
||||||
onDownload: () => void;
|
onDownload: () => void;
|
||||||
onSave: () => void;
|
onSave: () => void;
|
||||||
@@ -19,6 +22,7 @@ type CodeEditorHeaderProps = {
|
|||||||
showingChanges: string;
|
showingChanges: string;
|
||||||
editMarkdown: string;
|
editMarkdown: string;
|
||||||
previewMarkdown: string;
|
previewMarkdown: string;
|
||||||
|
previewHtml: string;
|
||||||
settings: string;
|
settings: string;
|
||||||
download: string;
|
download: string;
|
||||||
save: string;
|
save: string;
|
||||||
@@ -35,10 +39,12 @@ export default function CodeEditorHeader({
|
|||||||
isSidebar,
|
isSidebar,
|
||||||
isFullscreen,
|
isFullscreen,
|
||||||
isMarkdownFile,
|
isMarkdownFile,
|
||||||
|
isHtmlPreviewFile,
|
||||||
markdownPreview,
|
markdownPreview,
|
||||||
saving,
|
saving,
|
||||||
saveSuccess,
|
saveSuccess,
|
||||||
onToggleMarkdownPreview,
|
onToggleMarkdownPreview,
|
||||||
|
onOpenHtmlPreview,
|
||||||
onOpenSettings,
|
onOpenSettings,
|
||||||
onDownload,
|
onDownload,
|
||||||
onSave,
|
onSave,
|
||||||
@@ -82,6 +88,17 @@ export default function CodeEditorHeader({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isHtmlPreviewFile && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onOpenHtmlPreview}
|
||||||
|
className="flex items-center justify-center rounded-md p-1.5 text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
|
||||||
|
title={labels.previewHtml}
|
||||||
|
>
|
||||||
|
<Eye className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onOpenSettings}
|
onClick={onOpenSettings}
|
||||||
|
|||||||
Reference in New Issue
Block a user