diff --git a/src/renderer/components/modals/ExportHtmlDialog.tsx b/src/renderer/components/modals/ExportHtmlDialog.tsx new file mode 100644 index 0000000..a9b53a7 --- /dev/null +++ b/src/renderer/components/modals/ExportHtmlDialog.tsx @@ -0,0 +1,79 @@ +import { useState } from 'react'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Label } from '@/components/ui/label'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { useAppStore } from '@/stores/app-store'; +import { useSettingsStore } from '@/stores/settings-store'; +import { useExportSource } from '@/hooks/use-export-source'; +import { ipc } from '@/lib/ipc'; +import { ExportDialogFooter } from './ExportDialogFooter'; + +export function ExportHtmlDialog({ sourcePath }: { sourcePath: string }) { + const closeModal = useAppStore((s) => s.closeModal); + const { htmlHighlightStyle, renderTablesAsAscii } = useSettingsStore(); + const source = useExportSource(); + const [standalone, setStandalone] = useState(true); + const [highlight, setHighlight] = useState(htmlHighlightStyle); + const [ascii, setAscii] = useState(renderTablesAsAscii); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + + const handleSubmit = async () => { + if (!source) { setError('No file open.'); return; } + setSubmitting(true); + setError(null); + const result = await ipc.export.html({ + inputPath: source.path, + outputPath: source.path.replace(/\.md$/, '.html'), + standalone, + highlightStyle: highlight, + renderTablesAsAscii: ascii, + } as any); + if (!result.ok) { + setError(result.error.message); + setSubmitting(false); + } else { + closeModal(); + } + }; + + return ( + !o && closeModal()}> + + + Export to HTML + {sourcePath} + +
+ +
+ + +
+ + {error && ( +
+ {error} +
+ )} +
+ +
+
+ ); +} \ No newline at end of file diff --git a/tests/component/modals/ExportHtmlDialog.test.tsx b/tests/component/modals/ExportHtmlDialog.test.tsx new file mode 100644 index 0000000..12c6131 --- /dev/null +++ b/tests/component/modals/ExportHtmlDialog.test.tsx @@ -0,0 +1,36 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { ExportHtmlDialog } from '@/components/modals/ExportHtmlDialog'; +import { useFileStore } from '@/stores/file-store'; +import { useEditorStore } from '@/stores/editor-store'; +import { useSettingsStore } from '@/stores/settings-store'; + +describe('ExportHtmlDialog', () => { + beforeEach(() => { + localStorage.clear(); + window.electronAPI = { + export: { html: vi.fn().mockResolvedValue({ ok: true, data: { outputPath: '/out.html' } }) }, + } as any; + useSettingsStore.setState(useSettingsStore.getInitialState()); + useFileStore.setState({ activeTabId: '/test.md', openTabs: [{ id: '/test.md', path: '/test.md', title: 'test.md', dirty: false }] } as any); + useEditorStore.setState({ buffers: new Map([['/test.md', { id: '/test.md', path: '/test.md', content: '# hi', dirty: false }]]) } as any); + }); + + it('renders with default github highlight style', () => { + render(); + expect(screen.getByText(/export to html/i)).toBeInTheDocument(); + }); + + it('toggles standalone and submits with chosen highlight', async () => { + render(); + await userEvent.click(screen.getByRole('checkbox', { name: /standalone/i })); + await userEvent.click(screen.getByRole('checkbox', { name: /standalone/i })); + await userEvent.click(screen.getByRole('combobox', { name: /highlight/i })); + await userEvent.click(screen.getByRole('option', { name: /monokai/i })); + await userEvent.click(screen.getByRole('button', { name: /^export$/i })); + const call = (window.electronAPI.export.html as any).mock.calls[0][0]; + expect(call.standalone).toBe(true); + expect(call.highlightStyle).toBe('monokai'); + }); +}); \ No newline at end of file