diff --git a/src/App.tsx b/src/App.tsx index b6c8e84..b18f8fe 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -69,7 +69,7 @@ import { import { formatTable, toggleTaskCheckboxOnLine } from "./lib/cm-table-format"; import { collapseBlankLines } from "./lib/collapse-blanks"; import { sliceEmbed, splitEmbedTarget } from "./lib/embed-slice"; -import { exportHtml, exportPdfViaPrint } from "./lib/export"; +import { exportPdfViaPrint } from "./lib/export"; import { installFocusTypewriter } from "./lib/focus-typewriter"; import { wikilinkAtCursor } from "./lib/follow-wikilink"; import { @@ -131,6 +131,7 @@ import { refreshGitHubVault, openNewWindow, openVault, + pickHtmlSavePath, pickSavePath, pickVault, pushRecentFileNative, @@ -1215,19 +1216,38 @@ export function App() { }, []); async function handleExportHtml() { - if (!tab) return; - const baseName = (tab.name || "Untitled").replace(/\.[^.]+$/, ""); + // Read the live active tab from the store, not a captured render closure: + // the menu listener is registered once, so a closed-over `tab` would always + // be the first (welcome) document. + const state = useAppStore.getState(); + const t = state.activeTabId + ? state.tabs.find((x) => x.id === state.activeTabId) + : null; + if (!t) return; + const baseName = (t.name || "Untitled").replace(/\.[^.]+$/, ""); try { - await exportHtml(tab.content, baseName, exportTheme); + // Let the user pick where to save (instead of a silent Downloads dump). + const raw = await pickHtmlSavePath(`${baseName}.html`); + if (!raw) return; // cancelled + const target = /\.html?$/i.test(raw) ? raw : `${raw}.html`; + const html = await renderHtml(t.content, baseName, exportTheme); + await writeFile(target, html, null); + showToast(tr("toast.exportedHtml", target.split("/").pop() || target)); } catch (e) { console.error("exportHtml failed", e); + setActiveStatus("error", String(e)); } } async function handleExportPdf() { - if (!tab) return; - const title = tab.name || "Untitled"; + // Same stale-closure fix as handleExportHtml — read the live active tab. + const state = useAppStore.getState(); + const t = state.activeTabId + ? state.tabs.find((x) => x.id === state.activeTabId) + : null; + if (!t) return; + const title = t.name || "Untitled"; try { - await exportPdfViaPrint(tab.content, title, exportTheme); + await exportPdfViaPrint(t.content, title, exportTheme); } catch (e) { console.error("exportPdf failed", e); } diff --git a/src/lib/export.ts b/src/lib/export.ts index 561c5dd..85dac82 100644 --- a/src/lib/export.ts +++ b/src/lib/export.ts @@ -40,25 +40,3 @@ export async function exportPdfViaPrint( win.focus(); win.print(); } - -/** Trigger a download of a string as `filename` with given mime. */ -function downloadString(text: string, filename: string, mime: string) { - const blob = new Blob([text], { type: mime }); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = filename; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - setTimeout(() => URL.revokeObjectURL(url), 1000); -} - -export async function exportHtml( - content: string, - baseName: string, - theme: ExportTheme = "github", -) { - const html = await renderHtml(content, baseName, theme); - downloadString(html, `${baseName}.html`, "text/html"); -} diff --git a/src/lib/locales/en.ts b/src/lib/locales/en.ts index 84e975a..af02f56 100644 --- a/src/lib/locales/en.ts +++ b/src/lib/locales/en.ts @@ -112,6 +112,7 @@ export const en = { "toast.githubUpdated": "GitHub vault updated ({0})", "toast.githubUpToDate": "Already up to date", "toast.githubPullFailed": "Couldn't pull from GitHub", + "toast.exportedHtml": "Exported {0}", "github.confirmPullDirty": "{0} file(s) have local changes since the last sync. Pulling from GitHub will overwrite them. Continue?", "toast.renameNoFile": "No file to rename", diff --git a/src/lib/locales/zh.ts b/src/lib/locales/zh.ts index f54c4e7..4054b50 100644 --- a/src/lib/locales/zh.ts +++ b/src/lib/locales/zh.ts @@ -113,6 +113,7 @@ export const zh: Strings = { "toast.githubUpdated": "GitHub 仓库已更新({0})", "toast.githubUpToDate": "已是最新", "toast.githubPullFailed": "无法从 GitHub 拉取", + "toast.exportedHtml": "已导出 {0}", "github.confirmPullDirty": "有 {0} 个文件在上次同步后被本地修改过。从 GitHub 拉取会覆盖这些改动。是否继续?", "toast.renameNoFile": "没有可重命名的文件", diff --git a/src/lib/tauri.ts b/src/lib/tauri.ts index c450e22..53dc97d 100644 --- a/src/lib/tauri.ts +++ b/src/lib/tauri.ts @@ -24,6 +24,18 @@ export async function pickSavePath(defaultName: string): Promise return path ?? null; } +/** Save dialog for an exported HTML file (filters to .html). Authorizes the + * chosen path for writing, like pickSavePath. */ +export async function pickHtmlSavePath(defaultName: string): Promise { + const path = await saveDialog({ + title: "Export as HTML", + defaultPath: defaultName, + filters: [{ name: "HTML", extensions: ["html", "htm"] }], + }); + if (path) await authorizePaths([path]); + return path ?? null; +} + export async function openFileDialog(): Promise { return await invoke("open_file"); }