Add iOS Files vault selection

This commit is contained in:
Yuri Karamian
2026-07-24 02:19:00 +02:00
parent 51acd51cb2
commit d497ff8aaf
15 changed files with 846 additions and 39 deletions
+11 -2
View File
@@ -15,14 +15,23 @@ import type {
BackupEntry,
VersionEntry,
TaskItem,
ExternalVaultResult,
} from "./types";
export async function openVault(path: string): Promise<void> {
return invoke("open_vault", { path });
}
export async function removeVault(path: string): Promise<void> {
return invoke("remove_vault", { path });
export async function chooseExternalVault(): Promise<ExternalVaultResult | null> {
return invoke("choose_external_vault");
}
export async function restoreExternalVault(bookmarkId: string): Promise<ExternalVaultResult> {
return invoke("restore_external_vault", { bookmarkId });
}
export async function removeVault(path: string, bookmarkId?: string | null): Promise<void> {
return invoke("remove_vault", { path, bookmarkId: bookmarkId ?? null });
}
export async function getAppConfig(): Promise<AppConfig> {
+65 -8
View File
@@ -2,11 +2,13 @@
import { open } from '@tauri-apps/plugin-dialog';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { documentDir } from '@tauri-apps/api/path';
import { openVault, removeVault, getAppConfig } from '$lib/api';
import { chooseExternalVault, getAppConfig, openVault, removeVault, restoreExternalVault } from '$lib/api';
import { appConfig, vaultReady } from '$lib/stores/app';
import { isAndroid, isIOS, isMobile } from '$lib/platform';
import type { VaultConfig } from '$lib/types';
let { initialError = '' }: { initialError?: string } = $props();
const appWindow = getCurrentWindow();
let recentVaults: VaultConfig[] = $derived($appConfig?.vaults ?? []);
@@ -15,6 +17,10 @@
let vaultName = $state('HelixNotes');
let hasPermission = $state(!isAndroid);
let selectedLocation = $state('Documents');
$effect(() => {
if (initialError && !error) error = initialError;
});
let customPath = $state('');
let iosBasePath = $state('');
@@ -108,14 +114,57 @@
}
}
async function forgetVault(path: string) {
async function pickExternalFolder() {
loading = true;
error = '';
try {
await removeVault(path);
const selected = await chooseExternalVault();
if (!selected) return;
$appConfig = await getAppConfig();
$vaultReady = true;
} catch (e) {
error = String(e);
} finally {
loading = false;
}
}
async function openRecentVault(vault: VaultConfig) {
if (!isIOS || !vault.bookmark_id) {
await openSelectedVault(vault.path);
return;
}
loading = true;
error = '';
try {
await restoreExternalVault(vault.bookmark_id);
$appConfig = await getAppConfig();
$vaultReady = true;
} catch (e) {
error = String(e);
} finally {
loading = false;
}
}
async function forgetVault(vault: VaultConfig) {
try {
const wasActive = isActiveVault(vault);
await removeVault(vault.path, vault.bookmark_id);
$appConfig = await getAppConfig();
if (wasActive) $vaultReady = false;
} catch (e) {
error = String(e);
}
}
function isActiveVault(vault: VaultConfig): boolean {
if (vault.bookmark_id) {
return vault.bookmark_id === $appConfig?.active_bookmark_id;
}
return !$appConfig?.active_bookmark_id && vault.path === $appConfig?.active_vault;
}
</script>
<!-- svelte-ignore a11y_no_static_element_interactions -->
@@ -146,7 +195,7 @@
<p class="subtitle">Local markdown notes</p>
{#if isMobile}
{#if isIOS}
<p class="description">Your notes are saved in the app's folder and appear in the Files app under "On My iPhone &rarr; HelixNotes".</p>
<p class="description">Keep notes in HelixNotes, or use an existing folder from iCloud Drive or Files.</p>
{:else}
<p class="description">Choose where to store your notes. Sync with Syncthing, Nextcloud, or any file sync app.</p>
{/if}
@@ -197,6 +246,8 @@
<button class="btn-primary" onclick={pickFolder} disabled={loading}>
{#if loading}
Opening...
{:else if isIOS}
Use Local Folder
{:else if isMobile}
Get Started
{:else}
@@ -204,6 +255,12 @@
{/if}
</button>
{#if isIOS}
<button class="btn-back" onclick={pickExternalFolder} disabled={loading}>
Choose Folder in Files
</button>
{/if}
{#if $appConfig?.active_vault}
<button class="btn-back" onclick={() => ($vaultReady = true)}>
Back
@@ -215,11 +272,11 @@
<span class="recent-label">Recent</span>
{#each recentVaults as vault}
<div class="vault-row">
<button class="vault-item" class:current={vault.path === $appConfig?.active_vault} onclick={() => openSelectedVault(vault.path)}>
<span class="vault-name">{vault.name}{#if vault.path === $appConfig?.active_vault}<span class="vault-current-badge">Current</span>{/if}</span>
<span class="vault-path">{vault.path}</span>
<button class="vault-item" class:current={isActiveVault(vault)} onclick={() => openRecentVault(vault)}>
<span class="vault-name">{vault.name}{#if isActiveVault(vault)}<span class="vault-current-badge">Current</span>{/if}</span>
<span class="vault-path">{vault.bookmark_id ? `Files · ${vault.path}` : vault.path}</span>
</button>
<button class="vault-remove" title="Remove from list" aria-label="Remove from list" onclick={() => forgetVault(vault.path)}>
<button class="vault-remove" title="Remove from list" aria-label="Remove from list" onclick={() => forgetVault(vault)}>
<svg width="12" height="12" viewBox="0 0 10 10">
<line x1="1.5" y1="1.5" x2="8.5" y2="8.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" />
<line x1="8.5" y1="1.5" x2="1.5" y2="8.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" />
+4 -1
View File
@@ -18,7 +18,10 @@ export const appConfig = writable<AppConfig | null>(null);
// The active vault's config entry (where per-vault WebDAV sync settings live).
export function activeVaultConfig(c: AppConfig | null): VaultConfig | null {
if (!c?.active_vault) return null;
return c.vaults.find((v) => v.path === c.active_vault) ?? null;
if (c.active_bookmark_id) {
return c.vaults.find((v) => v.bookmark_id === c.active_bookmark_id) ?? null;
}
return c.vaults.find((v) => !v.bookmark_id && v.path === c.active_vault) ?? null;
}
export const vaultReady = writable(false);
+8
View File
@@ -44,6 +44,7 @@ export interface NoteContent {
export interface VaultConfig {
path: string;
name: string;
bookmark_id?: string | null;
// Per-vault WebDAV sync (moved off the global AppConfig).
sync_provider?: string | null;
webdav_url?: string | null;
@@ -55,6 +56,12 @@ export interface VaultConfig {
last_sync_time?: string | null;
}
export interface ExternalVaultResult {
bookmarkId: string;
path: string;
name: string;
}
export interface CustomThemeColors {
bg_primary: string;
bg_secondary: string;
@@ -77,6 +84,7 @@ export interface CustomTheme {
export interface AppConfig {
vaults: VaultConfig[];
active_vault: string | null;
active_bookmark_id?: string | null;
theme: string;
accent_color: string | null;
font_size: number | null;
+24 -6
View File
@@ -1,8 +1,8 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte';
import { appConfig, vaultReady, theme } from '$lib/stores/app';
import { getAppConfig, openVault, setFontSize } from '$lib/api';
import { darkThemes } from '$lib/platform';
import { getAppConfig, openVault, restoreExternalVault, setFontSize } from '$lib/api';
import { darkThemes, isIOS } from '$lib/platform';
import { getCurrentWebview } from '@tauri-apps/api/webview';
import VaultPicker from '$lib/components/VaultPicker.svelte';
import AppLayout from '$lib/components/AppLayout.svelte';
@@ -10,6 +10,7 @@
let loading = $state(true);
let noteWindowPath = $state<string | null>(null);
let startupVaultError = $state('');
let fontSizeSaveTimer: ReturnType<typeof setTimeout> | null = null;
let removeEditorZoomShortcuts: (() => void) | null = null;
@@ -204,14 +205,31 @@
// Auto-open last vault if available
if (config.active_vault) {
const activeVault = config.active_bookmark_id
? config.vaults.find((vault) => vault.bookmark_id === config.active_bookmark_id)
: config.vaults.find(
(vault) => !vault.bookmark_id && vault.path === config.active_vault
);
try {
// Note windows don't re-open the vault (already open in main process)
if (!noteWindowPath) {
await openVault(config.active_vault);
if (isIOS && activeVault?.bookmark_id) {
await restoreExternalVault(activeVault.bookmark_id);
$appConfig = await getAppConfig();
} else {
await openVault(config.active_vault);
}
}
$vaultReady = true;
} catch {
// Vault path might not exist anymore
} catch (e) {
if (isIOS && activeVault?.bookmark_id) {
startupVaultError = String(e);
$appConfig = {
...config,
active_vault: null,
active_bookmark_id: null
};
}
}
}
} catch {
@@ -235,7 +253,7 @@
{:else if $vaultReady}
<AppLayout />
{:else}
<VaultPicker />
<VaultPicker initialError={startupVaultError} />
{/if}
<style>