feat(export): prefer native save dialog over browser blob fallback
DictationPage + FilesPage handleExport() now use Tauri save() + write_text_file_cmd when in the desktop runtime; browser blob path remains as fallback when hasTauriRuntime() is false. saveMarkdown.ts surfaces dialog errors via toasts. Adds txt to the extension map. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -637,17 +637,38 @@
|
||||
activeTemplate = "";
|
||||
}
|
||||
|
||||
function handleExport(format) {
|
||||
async function handleExport(format) {
|
||||
if (!transcript) return;
|
||||
showExportMenu = false;
|
||||
const content = exportTranscript(transcript, segments, format);
|
||||
const extMap = { vtt: "vtt", srt: "srt", md: "md", csv: "csv", html: "html" };
|
||||
const extMap = { txt: "txt", vtt: "vtt", srt: "srt", md: "md", csv: "csv", html: "html" };
|
||||
const ext = extMap[format] || "txt";
|
||||
const defaultPath = `magnotia-${new Date().toISOString().slice(0, 10)}.${ext}`;
|
||||
|
||||
if (hasTauriRuntime()) {
|
||||
try {
|
||||
const { save } = await import("@tauri-apps/plugin-dialog");
|
||||
const path = await save({
|
||||
title: `Export ${format.toUpperCase()} transcript`,
|
||||
defaultPath,
|
||||
filters: [{ name: format.toUpperCase(), extensions: [ext] }],
|
||||
});
|
||||
if (!path) return;
|
||||
await invoke("write_text_file_cmd", { path, contents: content });
|
||||
toasts.success(`Exported ${format.toUpperCase()} transcript`);
|
||||
return;
|
||||
} catch (err) {
|
||||
console.error("dictation export failed", err);
|
||||
error = `Export failed: ${typeof err === "string" ? err : err.message || err}`;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const blob = new Blob([content], { type: "text/plain" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `magnotia-${new Date().toISOString().slice(0, 10)}.${ext}`;
|
||||
a.download = defaultPath;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import Card from "$lib/components/Card.svelte";
|
||||
import EmptyState from "$lib/components/EmptyState.svelte";
|
||||
import { exportTranscript } from "$lib/utils/export.js";
|
||||
import { hasTauriRuntime } from "$lib/utils/runtime";
|
||||
import { Upload } from 'lucide-svelte';
|
||||
|
||||
let fileTranscript = $state("");
|
||||
@@ -132,17 +133,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
function handleExport(format) {
|
||||
async function handleExport(format) {
|
||||
if (!fileTranscript) return;
|
||||
showExportMenu = false;
|
||||
const content = exportTranscript(fileTranscript, segments, format);
|
||||
const extMap = { vtt: "vtt", srt: "srt", md: "md", csv: "csv", html: "html" };
|
||||
const extMap = { txt: "txt", vtt: "vtt", srt: "srt", md: "md", csv: "csv", html: "html" };
|
||||
const ext = extMap[format] || "txt";
|
||||
const defaultPath = `magnotia-file-${new Date().toISOString().slice(0, 10)}.${ext}`;
|
||||
|
||||
if (hasTauriRuntime()) {
|
||||
try {
|
||||
const { save } = await import("@tauri-apps/plugin-dialog");
|
||||
const path = await save({
|
||||
title: `Export ${format.toUpperCase()} transcript`,
|
||||
defaultPath,
|
||||
filters: [{ name: format.toUpperCase(), extensions: [ext] }],
|
||||
});
|
||||
if (!path) return;
|
||||
await invoke("write_text_file_cmd", { path, contents: content });
|
||||
return;
|
||||
} catch (err) {
|
||||
console.error("file transcript export failed", err);
|
||||
error = `Export failed: ${typeof err === "string" ? err : err.message || err}`;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const blob = new Blob([content], { type: "text/plain" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `magnotia-file-${new Date().toISOString().slice(0, 10)}.${ext}`;
|
||||
a.download = defaultPath;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
@@ -33,14 +33,24 @@ export function suggestedFilename(item: TranscriptEntry): string {
|
||||
export async function saveTranscriptAsMarkdown(
|
||||
item: TranscriptEntry,
|
||||
): Promise<string | null> {
|
||||
if (!hasTauriRuntime()) return null;
|
||||
if (!hasTauriRuntime()) {
|
||||
toasts.error("Export unavailable", "Native save dialogs are only available in the desktop app.");
|
||||
return null;
|
||||
}
|
||||
const md = buildMarkdown(item);
|
||||
const defaultPath = suggestedFilename(item);
|
||||
const path = await save({
|
||||
title: "Save transcript as Markdown",
|
||||
defaultPath,
|
||||
filters: [{ name: "Markdown", extensions: ["md"] }],
|
||||
});
|
||||
let path: string | null = null;
|
||||
try {
|
||||
path = await save({
|
||||
title: "Save transcript as Markdown",
|
||||
defaultPath,
|
||||
filters: [{ name: "Markdown", extensions: ["md"] }],
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("saveTranscriptAsMarkdown dialog failed", err);
|
||||
toasts.error("Couldn't open save dialog", String(err));
|
||||
return null;
|
||||
}
|
||||
if (!path) return null;
|
||||
try {
|
||||
await invoke("write_text_file_cmd", { path, contents: md });
|
||||
@@ -64,13 +74,24 @@ export async function saveTranscriptAsMarkdown(
|
||||
export async function exportTranscriptsToDir(
|
||||
items: TranscriptEntry[],
|
||||
): Promise<number> {
|
||||
if (!hasTauriRuntime() || items.length === 0) return 0;
|
||||
if (items.length === 0) return 0;
|
||||
if (!hasTauriRuntime()) {
|
||||
toasts.error("Export unavailable", "Native folder dialogs are only available in the desktop app.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const dir = await open({
|
||||
directory: true,
|
||||
multiple: false,
|
||||
title: "Choose export folder",
|
||||
});
|
||||
let dir: string | string[] | null = null;
|
||||
try {
|
||||
dir = await open({
|
||||
directory: true,
|
||||
multiple: false,
|
||||
title: "Choose export folder",
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("exportTranscriptsToDir dialog failed", err);
|
||||
toasts.error("Couldn't open export folder dialog", String(err));
|
||||
return 0;
|
||||
}
|
||||
if (!dir || typeof dir !== "string") return 0;
|
||||
|
||||
const used = new Set<string>();
|
||||
|
||||
Reference in New Issue
Block a user