diff --git a/src/components/code-editor/view/CodeEditor.tsx b/src/components/code-editor/view/CodeEditor.tsx
index c542ae12..588d1ec1 100644
--- a/src/components/code-editor/view/CodeEditor.tsx
+++ b/src/components/code-editor/view/CodeEditor.tsx
@@ -1,8 +1,9 @@
import { EditorView } from '@codemirror/view';
import { unifiedMergeView } from '@codemirror/merge';
import type { Extension } from '@codemirror/state';
-import { useMemo, useState } from 'react';
+import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
+
import { usePaletteOps } from '../../../contexts/PaletteOpsContext';
import { useCodeEditorDocument } from '../hooks/useCodeEditorDocument';
import { useCodeEditorSettings } from '../hooks/useCodeEditorSettings';
@@ -11,6 +12,7 @@ import type { CodeEditorFile } from '../types/types';
import { createMinimapExtension, createScrollToFirstChunkExtension, getLanguageExtensions } from '../utils/editorExtensions';
import { getEditorStyles } from '../utils/editorStyles';
import { createEditorToolbarPanelExtension } from '../utils/editorToolbarPanel';
+
import CodeEditorFooter from './subcomponents/CodeEditorFooter';
import CodeEditorHeader from './subcomponents/CodeEditorHeader';
import CodeEditorLoadingState from './subcomponents/CodeEditorLoadingState';
@@ -73,6 +75,29 @@ export default function CodeEditor({
return extension === 'md' || extension === 'markdown';
}, [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(
() => (
createMinimapExtension({
@@ -224,10 +249,12 @@ export default function CodeEditor({
isSidebar={isSidebar}
isFullscreen={isFullscreen}
isMarkdownFile={isMarkdownFile}
+ isHtmlPreviewFile={isHtmlPreviewFile}
markdownPreview={markdownPreview}
saving={saving}
saveSuccess={saveSuccess}
onToggleMarkdownPreview={() => setMarkdownPreview((previous) => !previous)}
+ onOpenHtmlPreview={openHtmlPreview}
onOpenSettings={() => paletteOps.openSettings('appearance')}
onDownload={handleDownload}
onSave={handleSave}
@@ -237,6 +264,7 @@ export default function CodeEditor({
showingChanges: t('header.showingChanges'),
editMarkdown: t('actions.editMarkdown'),
previewMarkdown: t('actions.previewMarkdown'),
+ previewHtml: t('actions.previewHtml', 'Open HTML preview in new tab'),
settings: t('toolbar.settings'),
download: t('actions.download'),
save: t('actions.save'),
diff --git a/src/components/code-editor/view/subcomponents/CodeEditorHeader.tsx b/src/components/code-editor/view/subcomponents/CodeEditorHeader.tsx
index 7c429a63..d16f21f5 100644
--- a/src/components/code-editor/view/subcomponents/CodeEditorHeader.tsx
+++ b/src/components/code-editor/view/subcomponents/CodeEditorHeader.tsx
@@ -1,4 +1,5 @@
import { Code2, Download, Eye, Maximize2, Minimize2, Save, Settings as SettingsIcon, X } from 'lucide-react';
+
import type { CodeEditorFile } from '../../types/types';
type CodeEditorHeaderProps = {
@@ -6,10 +7,12 @@ type CodeEditorHeaderProps = {
isSidebar: boolean;
isFullscreen: boolean;
isMarkdownFile: boolean;
+ isHtmlPreviewFile: boolean;
markdownPreview: boolean;
saving: boolean;
saveSuccess: boolean;
onToggleMarkdownPreview: () => void;
+ onOpenHtmlPreview: () => void;
onOpenSettings: () => void;
onDownload: () => void;
onSave: () => void;
@@ -19,6 +22,7 @@ type CodeEditorHeaderProps = {
showingChanges: string;
editMarkdown: string;
previewMarkdown: string;
+ previewHtml: string;
settings: string;
download: string;
save: string;
@@ -35,10 +39,12 @@ export default function CodeEditorHeader({
isSidebar,
isFullscreen,
isMarkdownFile,
+ isHtmlPreviewFile,
markdownPreview,
saving,
saveSuccess,
onToggleMarkdownPreview,
+ onOpenHtmlPreview,
onOpenSettings,
onDownload,
onSave,
@@ -82,6 +88,17 @@ export default function CodeEditorHeader({
)}
+ {isHtmlPreviewFile && (
+
+ )}
+