Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/af.json
Original file line number Diff line number Diff line change
Expand Up @@ -3912,6 +3912,9 @@
"copyHtml": "Kopieer HTML",
"copyMarkdown": "Kopieer Markdown",
"exportHtml": "Voer HTML uit",
"fileNameLabel": "Lêernaam",
"fileNamePlaceholder": "export",
"exportSuccess": "{name} gestoor",
"copied": "Gekopieer",
"charCount": "{count, plural, one {# karakter} other {# karakters}}",
"emptyHint": "Begin Markdown tik om die lewendige voorskou en HTML-uitvoer te sien.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -3901,6 +3901,9 @@
"copyHtml": "نسخ HTML",
"copyMarkdown": "نسخ Markdown",
"exportHtml": "تصدير HTML",
"fileNameLabel": "اسم الملف",
"fileNamePlaceholder": "export",
"exportSuccess": "تم حفظ {name}",
"copied": "تم النسخ",
"charCount": "{count, plural, one {# حرف} other {# أحرف}}",
"emptyHint": "ابدأ بكتابة Markdown لرؤية المعاينة المباشرة وإخراج HTML.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -3901,6 +3901,9 @@
"copyHtml": "Copia HTML",
"copyMarkdown": "Copia Markdown",
"exportHtml": "Exporta HTML",
"fileNameLabel": "Nom del fitxer",
"fileNamePlaceholder": "export",
"exportSuccess": "S'ha desat {name}",
"copied": "Copiat",
"charCount": "{count, plural, one {# caràcter} other {# caràcters}}",
"emptyHint": "Comenceu a escriure Markdown per veure la previsualització i la sortida HTML.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -3901,6 +3901,9 @@
"copyHtml": "Kopírovat HTML",
"copyMarkdown": "Kopírovat Markdown",
"exportHtml": "Exportovat HTML",
"fileNameLabel": "Název souboru",
"fileNamePlaceholder": "export",
"exportSuccess": "Uloženo {name}",
"copied": "Zkopírováno",
"charCount": "{count, plural, one {# znak} few {# znaky} other {# znaků}}",
"emptyHint": "Začněte psát Markdown pro zobrazení živého náhledu a HTML výstupu.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/da.json
Original file line number Diff line number Diff line change
Expand Up @@ -3901,6 +3901,9 @@
"copyHtml": "Kopiér HTML",
"copyMarkdown": "Kopiér Markdown",
"exportHtml": "Eksportér HTML",
"fileNameLabel": "Filnavn",
"fileNamePlaceholder": "export",
"exportSuccess": "Gemte {name}",
"copied": "Kopieret",
"charCount": "{count, plural, one {# tegn} other {# tegn}}",
"emptyHint": "Begynd at skrive Markdown for at se live-forhåndsvisning og HTML-output.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -3901,6 +3901,9 @@
"copyHtml": "HTML kopieren",
"copyMarkdown": "Markdown kopieren",
"exportHtml": "HTML exportieren",
"fileNameLabel": "Dateiname",
"fileNamePlaceholder": "export",
"exportSuccess": "{name} gespeichert",
"copied": "Kopiert",
"charCount": "{count, plural, one {# Zeichen} other {# Zeichen}}",
"emptyHint": "Beginnen Sie mit der Eingabe von Markdown, um die Live-Vorschau und HTML-Ausgabe zu sehen.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/el.json
Original file line number Diff line number Diff line change
Expand Up @@ -3901,6 +3901,9 @@
"copyHtml": "Αντιγραφή HTML",
"copyMarkdown": "Αντιγραφή Markdown",
"exportHtml": "Εξαγωγή HTML",
"fileNameLabel": "Όνομα αρχείου",
"fileNamePlaceholder": "export",
"exportSuccess": "Αποθηκεύτηκε το {name}",
"copied": "Αντιγράφηκε",
"charCount": "{count, plural, one {# χαρακτήρας} other {# χαρακτήρες}}",
"emptyHint": "Ξεκινήστε να πληκτρολογείτε Markdown για να δείτε την προεπισκόπηση και την έξοδο HTML.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3955,6 +3955,9 @@
"copyHtml": "Copy HTML",
"copyMarkdown": "Copy Markdown",
"exportHtml": "Export HTML",
"fileNameLabel": "File name",
"fileNamePlaceholder": "export",
"exportSuccess": "Saved {name}",
"copied": "Copied",
"charCount": "{count, plural, one {# character} other {# characters}}",
"emptyHint": "Start typing Markdown to see the live preview and HTML output.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -3912,6 +3912,9 @@
"copyHtml": "Copiar HTML",
"copyMarkdown": "Copiar Markdown",
"exportHtml": "Exportar HTML",
"fileNameLabel": "Nombre del archivo",
"fileNamePlaceholder": "export",
"exportSuccess": "Se guardó {name}",
"copied": "Copiado",
"charCount": "{count, plural, one {# carácter} other {# caracteres}}",
"emptyHint": "Empieza a escribir Markdown para ver la vista previa y la salida HTML.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/fa.json
Original file line number Diff line number Diff line change
Expand Up @@ -3912,6 +3912,9 @@
"copyHtml": "کپی HTML",
"copyMarkdown": "کپی Markdown",
"exportHtml": "خروجی HTML",
"fileNameLabel": "نام فایل",
"fileNamePlaceholder": "export",
"exportSuccess": "{name} ذخیره شد",
"copied": "کپی شد",
"charCount": "{count, plural, other {# کاراکتر}}",
"emptyHint": "برای دیدن پیش‌نمایش زنده و خروجی HTML، Markdown تایپ کنید.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -3912,6 +3912,9 @@
"copyHtml": "Copier le HTML",
"copyMarkdown": "Copier le Markdown",
"exportHtml": "Exporter le HTML",
"fileNameLabel": "Nom du fichier",
"fileNamePlaceholder": "export",
"exportSuccess": "{name} enregistré",
"copied": "Copié",
"charCount": "{count, plural, one {# caractère} other {# caractères}}",
"emptyHint": "Commencez à taper du Markdown pour voir l'aperçu et la sortie HTML.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/id.json
Original file line number Diff line number Diff line change
Expand Up @@ -3901,6 +3901,9 @@
"copyHtml": "Salin HTML",
"copyMarkdown": "Salin Markdown",
"exportHtml": "Ekspor HTML",
"fileNameLabel": "Nama file",
"fileNamePlaceholder": "export",
"exportSuccess": "{name} disimpan",
"copied": "Disalin",
"charCount": "{count, plural, other {# karakter}}",
"emptyHint": "Mulai mengetik Markdown untuk melihat pratinjau langsung dan output HTML.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -3912,6 +3912,9 @@
"copyHtml": "Copia HTML",
"copyMarkdown": "Copia Markdown",
"exportHtml": "Esporta HTML",
"fileNameLabel": "Nome file",
"fileNamePlaceholder": "export",
"exportSuccess": "{name} salvato",
"copied": "Copiato",
"charCount": "{count, plural, one {# carattere} other {# caratteri}}",
"emptyHint": "Inizia a digitare Markdown per vedere l'anteprima live e l'output HTML.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -3901,6 +3901,9 @@
"copyHtml": "HTMLをコピー",
"copyMarkdown": "Markdownをコピー",
"exportHtml": "HTMLをエクスポート",
"fileNameLabel": "ファイル名",
"fileNamePlaceholder": "export",
"exportSuccess": "{name} を保存しました",
"copied": "コピーしました",
"charCount": "{count, plural, other {# 文字}}",
"emptyHint": "Markdownの入力を開始すると、ライブプレビューとHTML出力が表示されます。",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -3901,6 +3901,9 @@
"copyHtml": "HTML 복사",
"copyMarkdown": "Markdown 복사",
"exportHtml": "HTML 내보내기",
"fileNameLabel": "파일 이름",
"fileNamePlaceholder": "export",
"exportSuccess": "{name} 저장됨",
"copied": "복사됨",
"charCount": "{count, plural, other {# 자}}",
"emptyHint": "Markdown을 입력하면 실시간 미리보기와 HTML 출력을 볼 수 있습니다.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/ms.json
Original file line number Diff line number Diff line change
Expand Up @@ -3912,6 +3912,9 @@
"copyHtml": "Salin HTML",
"copyMarkdown": "Salin Markdown",
"exportHtml": "Eksport HTML",
"fileNameLabel": "Nama fail",
"fileNamePlaceholder": "export",
"exportSuccess": "{name} disimpan",
"copied": "Disalin",
"charCount": "{count, plural, other {# aksara}}",
"emptyHint": "Mulakan menaip Markdown untuk melihat pratonton langsung dan output HTML.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -3901,6 +3901,9 @@
"copyHtml": "Kopier HTML",
"copyMarkdown": "Kopier Markdown",
"exportHtml": "Eksporter HTML",
"fileNameLabel": "Filnavn",
"fileNamePlaceholder": "export",
"exportSuccess": "Lagret {name}",
"copied": "Kopiert",
"charCount": "{count, plural, one {# tegn} other {# tegn}}",
"emptyHint": "Begynn å skrive Markdown for å se live-forhåndsvisning og HTML-utdata.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -3901,6 +3901,9 @@
"copyHtml": "HTML kopiëren",
"copyMarkdown": "Markdown kopiëren",
"exportHtml": "HTML exporteren",
"fileNameLabel": "Bestandsnaam",
"fileNamePlaceholder": "export",
"exportSuccess": "{name} opgeslagen",
"copied": "Gekopieerd",
"charCount": "{count, plural, one {# teken} other {# tekens}}",
"emptyHint": "Begin met het typen van Markdown om de live preview en HTML-uitvoer te zien.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -3901,6 +3901,9 @@
"copyHtml": "Kopiuj HTML",
"copyMarkdown": "Kopiuj Markdown",
"exportHtml": "Eksportuj HTML",
"fileNameLabel": "Nazwa pliku",
"fileNamePlaceholder": "export",
"exportSuccess": "Zapisano {name}",
"copied": "Skopiowano",
"charCount": "{count, plural, one {# znak} few {# znaki} other {# znaków}}",
"emptyHint": "Zacznij wpisywać Markdown, aby zobaczyć podgląd na żywo i wyjście HTML.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -3901,6 +3901,9 @@
"copyHtml": "Copiar HTML",
"copyMarkdown": "Copiar Markdown",
"exportHtml": "Exportar HTML",
"fileNameLabel": "Nome do arquivo",
"fileNamePlaceholder": "export",
"exportSuccess": "{name} salvo",
"copied": "Copiado",
"charCount": "{count, plural, one {# caractere} other {# caracteres}}",
"emptyHint": "Comece a digitar Markdown para ver a pré-visualização ao vivo e a saída HTML.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -3901,6 +3901,9 @@
"copyHtml": "Copiar HTML",
"copyMarkdown": "Copiar Markdown",
"exportHtml": "Exportar HTML",
"fileNameLabel": "Nome do ficheiro",
"fileNamePlaceholder": "export",
"exportSuccess": "{name} guardado",
"copied": "Copiado",
"charCount": "{count, plural, one {# carácter} other {# caracteres}}",
"emptyHint": "Comece a escrever Markdown para ver a pré-visualização em tempo real e a saída HTML.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -3901,6 +3901,9 @@
"copyHtml": "Копировать HTML",
"copyMarkdown": "Копировать Markdown",
"exportHtml": "Экспорт HTML",
"fileNameLabel": "Имя файла",
"fileNamePlaceholder": "export",
"exportSuccess": "Сохранено: {name}",
"copied": "Скопировано",
"charCount": "{count, plural, one {# символ} few {# символа} other {# символов}}",
"emptyHint": "Начните вводить Markdown, чтобы увидеть предпросмотр и HTML вывод.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/sv.json
Original file line number Diff line number Diff line change
Expand Up @@ -3912,6 +3912,9 @@
"copyHtml": "Kopiera HTML",
"copyMarkdown": "Kopiera Markdown",
"exportHtml": "Exportera HTML",
"fileNameLabel": "Filnamn",
"fileNamePlaceholder": "export",
"exportSuccess": "Sparade {name}",
"copied": "Kopierat",
"charCount": "{count, plural, one {# tecken} other {# tecken}}",
"emptyHint": "Börja skriva Markdown för att se live-förhandsgranskning och HTML-utdata.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/tr.json
Original file line number Diff line number Diff line change
Expand Up @@ -3912,6 +3912,9 @@
"copyHtml": "HTML'yi Kopyala",
"copyMarkdown": "Markdown'ı Kopyala",
"exportHtml": "HTML'yi Dışa Aktar",
"fileNameLabel": "Dosya adı",
"fileNamePlaceholder": "export",
"exportSuccess": "{name} kaydedildi",
"copied": "Kopyalandı",
"charCount": "{count, plural, one {# karakter} other {# karakter}}",
"emptyHint": "Canlı önizleme ve HTML çıktısını görmek için Markdown yazmaya başlayın.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/uk.json
Original file line number Diff line number Diff line change
Expand Up @@ -3912,6 +3912,9 @@
"copyHtml": "Копіювати HTML",
"copyMarkdown": "Копіювати Markdown",
"exportHtml": "Експортувати HTML",
"fileNameLabel": "Назва файлу",
"fileNamePlaceholder": "export",
"exportSuccess": "Збережено: {name}",
"copied": "Скопійовано",
"charCount": "{count, plural, one {# символ} few {# символи} other {# символів}}",
"emptyHint": "Почніть вводити Markdown, щоб побачити попередній перегляд і HTML вивід.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3901,6 +3901,9 @@
"copyHtml": "Sao chép HTML",
"copyMarkdown": "Sao chép Markdown",
"exportHtml": "Xuất HTML",
"fileNameLabel": "Tên tệp",
"fileNamePlaceholder": "export",
"exportSuccess": "Đã lưu {name}",
"copied": "Đã sao chép",
"charCount": "{count, plural, other {# ký tự}}",
"emptyHint": "Bắt đầu nhập Markdown để xem bản xem trước trực tiếp và đầu ra HTML.",
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop-ui/messages/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -3901,6 +3901,9 @@
"copyHtml": "复制 HTML",
"copyMarkdown": "复制 Markdown",
"exportHtml": "导出 HTML",
"fileNameLabel": "文件名",
"fileNamePlaceholder": "export",
"exportSuccess": "已保存 {name}",
"copied": "已复制",
"charCount": "{count, plural, other {# 个字符}}",
"emptyHint": "开始输入 Markdown 以查看实时预览和 HTML 输出。",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard';
import { useTranslations } from 'next-intl';
import { marked } from 'marked';
import TurndownService from 'turndown';
import { toast } from 'sonner';
import { safeFileName } from '@/lib/markdown-preview-html';
import { Card } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Button } from '@/components/ui/button';
Expand Down Expand Up @@ -76,6 +79,7 @@ export function MarkdownPreviewLayout() {
// Markdown → HTML tab
const [markdown, setMarkdown] = useState('');
const [outputTab, setOutputTab] = useState<'preview' | 'html'>('preview');
const [fileName, setFileName] = useState('export');
const { isCopied: copiedHtml, copyToClipboard: copyHtml } = useCopyToClipboard();

// HTML → Markdown tab
Expand All @@ -92,14 +96,16 @@ export function MarkdownPreviewLayout() {

const handleExportHtml = useCallback(() => {
if (!renderedHtml) return;
const name = `${safeFileName(fileName)}.html`;
const blob = new Blob([buildFullHtml(renderedHtml)], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'export.html';
a.download = name;
a.click();
URL.revokeObjectURL(url);
}, [renderedHtml]);
toast.success(t('exportSuccess', { name }));
}, [renderedHtml, fileName, t]);

const handleCopyMd = useCallback(() => {
if (!convertedMarkdown) return;
Expand Down Expand Up @@ -153,7 +159,17 @@ export function MarkdownPreviewLayout() {
</TabsTrigger>
</TabsList>
</Tabs>
<div className="flex gap-2">
<div className="flex items-center gap-2">
<div className="flex items-center rounded-md border bg-background pr-2 focus-within:ring-1 focus-within:ring-ring">
<Input
value={fileName}
onChange={(e) => setFileName(e.target.value)}
aria-label={t('fileNameLabel')}
placeholder={t('fileNamePlaceholder')}
className="h-8 w-28 border-0 bg-transparent px-2 text-xs shadow-none focus-visible:ring-0"
/>
<span className="text-xs text-muted-foreground">.html</span>
</div>
<Button size="sm" variant="secondary" disabled={!renderedHtml} onClick={handleCopyHtml}>
{copiedHtml ? <Check className="mr-1.5 h-4 w-4 text-emerald-600" /> : <Copy className="mr-1.5 h-4 w-4" />}
{copiedHtml ? t('copied') : t('copyHtml')}
Expand Down
25 changes: 25 additions & 0 deletions apps/desktop-ui/src/lib/__tests__/markdown-preview-html.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { safeFileName } from '@/lib/markdown-preview-html';

describe('safeFileName', () => {
it('keeps plain names, spaces and dashes', () => {
expect(safeFileName('my notes-v2')).toBe('my notes-v2');
});

it('drops a trailing .html/.htm so export does not double it', () => {
expect(safeFileName('report.html')).toBe('report');
expect(safeFileName('report.HTM')).toBe('report');
});

it('strips path separators and illegal chars', () => {
expect(safeFileName('../../etc/pass:wd')).toBe('etcpasswd');
});

it('falls back to export when nothing usable is left', () => {
expect(safeFileName(' ')).toBe('export');
expect(safeFileName('///')).toBe('export');
});

it('caps length', () => {
expect(safeFileName('a'.repeat(300))).toHaveLength(100);
});
});
10 changes: 10 additions & 0 deletions apps/desktop-ui/src/lib/markdown-preview-html.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** Strip path separators and illegal chars, drop a trailing .html, cap length. */
export function safeFileName(raw: string): string {
const cleaned = raw
.replace(/\.html?$/i, '')
.replace(/[\\/:*?"<>|]/g, '')
.replace(/^[.]+/, '')
.trim()
.slice(0, 100);
return cleaned || 'export';
}
Loading