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 ZonePicker from "$lib/components/ZonePicker.svelte";
|
||||||
import AccessibilityControls from "$lib/components/AccessibilityControls.svelte";
|
import AccessibilityControls from "$lib/components/AccessibilityControls.svelte";
|
||||||
import { getPreferences, updatePreferences } from "$lib/stores/preferences.svelte.js";
|
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";
|
import { open } from "@tauri-apps/plugin-dialog";
|
||||||
|
|
||||||
const prefs = getPreferences();
|
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 ---
|
// --- Profile management ---
|
||||||
function createProfile() {
|
function createProfile() {
|
||||||
if (!newProfileName.trim()) return;
|
if (!newProfileName.trim()) return;
|
||||||
@@ -621,6 +652,86 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</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 -->
|
<!-- Hotkey -->
|
||||||
<div class="border-b border-border-subtle">
|
<div class="border-b border-border-subtle">
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user