feat(ui): add export section to SettingsPage with directory picker and format selection
Export Your Data section with three buttons: Export Transcriptions (MD/TXT), Export Tasks (JSON/CSV), and Export to Obsidian. Each opens a directory picker and shows status on completion. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
import ZonePicker from "$lib/components/ZonePicker.svelte";
|
||||
import AccessibilityControls from "$lib/components/AccessibilityControls.svelte";
|
||||
import { getPreferences, updatePreferences } from "$lib/stores/preferences.svelte.js";
|
||||
import { Check, ChevronRight } from "lucide-svelte";
|
||||
import { Check, ChevronRight, Download } from "lucide-svelte";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
|
||||
const prefs = getPreferences();
|
||||
@@ -173,6 +173,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
// --- Export state ---
|
||||
let exportTranscriptFormat = $state("md");
|
||||
let exportTaskFormat = $state("json");
|
||||
let exportStatus = $state("");
|
||||
let exporting = $state("");
|
||||
|
||||
async function doExport(type) {
|
||||
try {
|
||||
const folder = await open({ directory: true, title: "Select export folder" });
|
||||
if (!folder) return;
|
||||
|
||||
exporting = type;
|
||||
exportStatus = "";
|
||||
|
||||
if (type === "transcriptions") {
|
||||
const count = await invoke("export_transcriptions", { directory: folder, format: exportTranscriptFormat });
|
||||
exportStatus = count === 0 ? "No transcriptions yet" : `Exported ${count} transcription${count === 1 ? "" : "s"} to ${folder}`;
|
||||
} else if (type === "tasks") {
|
||||
const path = await invoke("export_tasks", { directory: folder, format: exportTaskFormat });
|
||||
exportStatus = `Tasks exported to ${path}`;
|
||||
} else if (type === "obsidian") {
|
||||
const count = await invoke("export_to_obsidian", { directory: folder });
|
||||
exportStatus = count === 0 ? "Nothing to export yet" : `Exported ${count} file${count === 1 ? "" : "s"} to ${folder}`;
|
||||
}
|
||||
} catch (err) {
|
||||
exportStatus = typeof err === "string" ? err : "Export failed";
|
||||
} finally {
|
||||
exporting = "";
|
||||
}
|
||||
}
|
||||
|
||||
// --- Profile management ---
|
||||
function createProfile() {
|
||||
if (!newProfileName.trim()) return;
|
||||
@@ -621,6 +652,86 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Export -->
|
||||
<div class="border-b border-border-subtle">
|
||||
<button
|
||||
class="flex items-center justify-between w-full py-4 px-5 text-left"
|
||||
onclick={() => openSection = openSection === 'export' ? null : 'export'}
|
||||
>
|
||||
<h3 class="font-display text-[18px] italic text-text">Export Your Data</h3>
|
||||
<span class="text-text-tertiary text-[16px] leading-none">{openSection === 'export' ? '−' : '+'}</span>
|
||||
</button>
|
||||
{#if openSection === 'export'}
|
||||
<div class="px-5 pb-5 animate-fade-in">
|
||||
<p class="text-[11px] text-text-tertiary mb-4">All your data stays yours. Export everything as plain text files.</p>
|
||||
|
||||
<div class="space-y-3">
|
||||
<!-- Export Transcriptions -->
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[12px] font-medium
|
||||
bg-accent/10 text-accent hover:bg-accent/20 disabled:opacity-50"
|
||||
disabled={exporting === "transcriptions"}
|
||||
onclick={() => doExport("transcriptions")}
|
||||
>
|
||||
<Download class="w-3.5 h-3.5" />
|
||||
{exporting === "transcriptions" ? "Exporting..." : "Export Transcriptions"}
|
||||
</button>
|
||||
<select
|
||||
class="bg-bg-input border border-border-subtle rounded-lg px-2 py-1 text-[11px] text-text-secondary
|
||||
focus:outline-none focus:border-accent appearance-none cursor-pointer"
|
||||
bind:value={exportTranscriptFormat}
|
||||
>
|
||||
<option value="md">Markdown</option>
|
||||
<option value="txt">Plain Text</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Export Tasks -->
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[12px] font-medium
|
||||
bg-accent/10 text-accent hover:bg-accent/20 disabled:opacity-50"
|
||||
disabled={exporting === "tasks"}
|
||||
onclick={() => doExport("tasks")}
|
||||
>
|
||||
<Download class="w-3.5 h-3.5" />
|
||||
{exporting === "tasks" ? "Exporting..." : "Export Tasks"}
|
||||
</button>
|
||||
<select
|
||||
class="bg-bg-input border border-border-subtle rounded-lg px-2 py-1 text-[11px] text-text-secondary
|
||||
focus:outline-none focus:border-accent appearance-none cursor-pointer"
|
||||
bind:value={exportTaskFormat}
|
||||
>
|
||||
<option value="json">JSON</option>
|
||||
<option value="csv">CSV</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Export to Obsidian -->
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[12px] font-medium
|
||||
bg-accent/10 text-accent hover:bg-accent/20 disabled:opacity-50"
|
||||
disabled={exporting === "obsidian"}
|
||||
onclick={() => doExport("obsidian")}
|
||||
>
|
||||
<Download class="w-3.5 h-3.5" />
|
||||
{exporting === "obsidian" ? "Exporting..." : "Export to Obsidian"}
|
||||
</button>
|
||||
<span class="text-[10px] text-text-tertiary">Markdown + wikilinks, both transcriptions and tasks</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if exportStatus}
|
||||
<p class="text-[11px] text-text-secondary mt-3 bg-bg-input rounded-lg px-3 py-2 border border-border-subtle">
|
||||
{exportStatus}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Hotkey -->
|
||||
<div class="border-b border-border-subtle">
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user