mirror of
https://gitlab.com/ArkHost/HelixNotes.git
synced 2026-09-19 17:37:29 +02:00
Fix iOS Files vault transitions
This commit is contained in:
+98
-59
@@ -143,7 +143,12 @@ fn open_vault_path(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn open_vault(app: AppHandle, state: State<'_, AppState>, path: String) -> Result<(), String> {
|
pub async fn open_vault(
|
||||||
|
app: AppHandle,
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
path: String,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let _transition = state.vault_transition.lock().await;
|
||||||
open_vault_path(app.clone(), &state, path, None)?;
|
open_vault_path(app.clone(), &state, path, None)?;
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
app.ios_vault_access().release_active()?;
|
app.ios_vault_access().release_active()?;
|
||||||
@@ -155,6 +160,7 @@ pub async fn choose_external_vault(
|
|||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Option<ExternalVaultResult>, String> {
|
) -> Result<Option<ExternalVaultResult>, String> {
|
||||||
|
let _transition = state.vault_transition.lock().await;
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
{
|
{
|
||||||
let picker_app = app.clone();
|
let picker_app = app.clone();
|
||||||
@@ -194,7 +200,7 @@ pub async fn choose_external_vault(
|
|||||||
}
|
}
|
||||||
#[cfg(not(target_os = "ios"))]
|
#[cfg(not(target_os = "ios"))]
|
||||||
{
|
{
|
||||||
let _ = (app, state);
|
let _ = (&app, &state);
|
||||||
Err("Files folders are only available on iOS.".to_string())
|
Err("Files folders are only available on iOS.".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,6 +211,7 @@ pub async fn restore_external_vault(
|
|||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
bookmark_id: String,
|
bookmark_id: String,
|
||||||
) -> Result<ExternalVaultResult, String> {
|
) -> Result<ExternalVaultResult, String> {
|
||||||
|
let _transition = state.vault_transition.lock().await;
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
{
|
{
|
||||||
let resolver_app = app.clone();
|
let resolver_app = app.clone();
|
||||||
@@ -228,7 +235,7 @@ pub async fn restore_external_vault(
|
|||||||
}
|
}
|
||||||
#[cfg(not(target_os = "ios"))]
|
#[cfg(not(target_os = "ios"))]
|
||||||
{
|
{
|
||||||
let _ = (app, state, bookmark_id);
|
let _ = (&app, &state, &bookmark_id);
|
||||||
Err("Files folders are only available on iOS.".to_string())
|
Err("Files folders are only available on iOS.".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,39 +247,34 @@ pub async fn remove_vault(
|
|||||||
path: String,
|
path: String,
|
||||||
bookmark_id: Option<String>,
|
bookmark_id: Option<String>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let target = {
|
let _transition = state.vault_transition.lock().await;
|
||||||
let config = state.config.lock().map_err(|error| error.to_string())?;
|
let mut config = state.config.lock().map_err(|error| error.to_string())?;
|
||||||
let vault = if let Some(bookmark_id) = bookmark_id.as_deref() {
|
let target = if let Some(bookmark_id) = bookmark_id.as_deref() {
|
||||||
config
|
config
|
||||||
.vaults
|
.vaults
|
||||||
.iter()
|
.iter()
|
||||||
.find(|vault| vault.bookmark_id.as_deref() == Some(bookmark_id))
|
.find(|vault| vault.bookmark_id.as_deref() == Some(bookmark_id))
|
||||||
|
} else {
|
||||||
|
config
|
||||||
|
.vaults
|
||||||
|
.iter()
|
||||||
|
.find(|vault| vault.bookmark_id.is_none() && vault.path == path)
|
||||||
|
}
|
||||||
|
.map(|vault| {
|
||||||
|
let is_active = if let Some(bookmark_id) = vault.bookmark_id.as_deref() {
|
||||||
|
config.active_bookmark_id.as_deref() == Some(bookmark_id)
|
||||||
} else {
|
} else {
|
||||||
config
|
config.active_bookmark_id.is_none()
|
||||||
.vaults
|
&& config.active_vault.as_deref() == Some(vault.path.as_str())
|
||||||
.iter()
|
|
||||||
.find(|vault| vault.bookmark_id.is_none() && vault.path == path)
|
|
||||||
};
|
};
|
||||||
vault.map(|vault| {
|
(vault.path.clone(), vault.bookmark_id.clone(), is_active)
|
||||||
let is_active = if let Some(bookmark_id) = vault.bookmark_id.as_deref() {
|
});
|
||||||
config.active_bookmark_id.as_deref() == Some(bookmark_id)
|
|
||||||
} else {
|
|
||||||
config.active_bookmark_id.is_none()
|
|
||||||
&& config.active_vault.as_deref() == Some(vault.path.as_str())
|
|
||||||
};
|
|
||||||
(vault.path.clone(), vault.bookmark_id.clone(), is_active)
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some((target_path, target_bookmark, is_active)) = target else {
|
let Some((target_path, target_bookmark, is_active)) = target else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let old = state
|
let old = config.clone();
|
||||||
.config
|
|
||||||
.lock()
|
|
||||||
.map_err(|error| error.to_string())?
|
|
||||||
.clone();
|
|
||||||
let mut next = old.clone();
|
let mut next = old.clone();
|
||||||
if let Some(id) = target_bookmark.as_deref() {
|
if let Some(id) = target_bookmark.as_deref() {
|
||||||
next.vaults
|
next.vaults
|
||||||
@@ -289,20 +291,7 @@ pub async fn remove_vault(
|
|||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
if let Some(id) = target_bookmark.as_deref() {
|
if let Some(id) = target_bookmark.as_deref() {
|
||||||
let forget_app = app.clone();
|
if let Err(error) = app.ios_vault_access().forget_bookmark(id) {
|
||||||
let id_to_forget = id.to_string();
|
|
||||||
let forget_result = match tauri::async_runtime::spawn_blocking(move || {
|
|
||||||
forget_app
|
|
||||||
.ios_vault_access()
|
|
||||||
.forget_bookmark(&id_to_forget)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(result) => result,
|
|
||||||
Err(error) => Err(error.to_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(error) = forget_result {
|
|
||||||
return match save_app_config(&old) {
|
return match save_app_config(&old) {
|
||||||
Ok(()) => Err(error),
|
Ok(()) => Err(error),
|
||||||
Err(rollback_error) => Err(format!(
|
Err(rollback_error) => Err(format!(
|
||||||
@@ -317,7 +306,7 @@ pub async fn remove_vault(
|
|||||||
if is_active {
|
if is_active {
|
||||||
clear_vault_runtime(&state)?;
|
clear_vault_runtime(&state)?;
|
||||||
}
|
}
|
||||||
*state.config.lock().map_err(|error| error.to_string())? = next;
|
*config = next;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -2005,15 +1994,63 @@ pub fn test_ai_connection(app: AppHandle) -> Result<(), String> {
|
|||||||
|
|
||||||
// ── Sync (WebDAV) ──
|
// ── Sync (WebDAV) ──
|
||||||
|
|
||||||
fn active_vault_config(config: &AppConfig) -> Result<&VaultConfig, String> {
|
fn vault_matches_identity(
|
||||||
|
vault: &VaultConfig,
|
||||||
|
path: &str,
|
||||||
|
bookmark_id: Option<&str>,
|
||||||
|
) -> bool {
|
||||||
|
if let Some(bookmark_id) = bookmark_id {
|
||||||
|
vault.bookmark_id.as_deref() == Some(bookmark_id)
|
||||||
|
} else {
|
||||||
|
vault.bookmark_id.is_none() && vault.path == path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn active_vault_index(config: &AppConfig) -> Result<usize, String> {
|
||||||
let active = config.active_vault.as_deref().ok_or("No active vault")?;
|
let active = config.active_vault.as_deref().ok_or("No active vault")?;
|
||||||
config
|
config
|
||||||
.vaults
|
.vaults
|
||||||
.iter()
|
.iter()
|
||||||
.find(|v| v.path == active)
|
.position(|vault| {
|
||||||
|
vault_matches_identity(vault, active, config.active_bookmark_id.as_deref())
|
||||||
|
})
|
||||||
.ok_or_else(|| "Active vault not found in config".to_string())
|
.ok_or_else(|| "Active vault not found in config".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn active_vault_config(config: &AppConfig) -> Result<&VaultConfig, String> {
|
||||||
|
Ok(&config.vaults[active_vault_index(config)?])
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod vault_identity_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bookmark_identity_disambiguates_vaults_with_the_same_path() {
|
||||||
|
let mut config = AppConfig::default();
|
||||||
|
config.active_vault = Some("/same/path".to_string());
|
||||||
|
config.vaults = vec![
|
||||||
|
VaultConfig {
|
||||||
|
path: "/same/path".to_string(),
|
||||||
|
name: "Local".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
VaultConfig {
|
||||||
|
path: "/same/path".to_string(),
|
||||||
|
name: "Files".to_string(),
|
||||||
|
bookmark_id: Some("bookmark".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
config.active_bookmark_id = Some("bookmark".to_string());
|
||||||
|
assert_eq!(active_vault_config(&config).unwrap().name, "Files");
|
||||||
|
|
||||||
|
config.active_bookmark_id = None;
|
||||||
|
assert_eq!(active_vault_config(&config).unwrap().name, "Local");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn sync_config_from(config: &AppConfig) -> Result<crate::sync::WebdavConfig, String> {
|
fn sync_config_from(config: &AppConfig) -> Result<crate::sync::WebdavConfig, String> {
|
||||||
let v = active_vault_config(config)?;
|
let v = active_vault_config(config)?;
|
||||||
if v.sync_provider.as_deref() != Some("webdav") {
|
if v.sync_provider.as_deref() != Some("webdav") {
|
||||||
@@ -2043,12 +2080,8 @@ pub fn set_sync_settings(
|
|||||||
sync_interval_minutes: u32,
|
sync_interval_minutes: u32,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let mut config = state.config.lock().map_err(|e| e.to_string())?;
|
let mut config = state.config.lock().map_err(|e| e.to_string())?;
|
||||||
let active = config.active_vault.clone().ok_or("No active vault")?;
|
let active_index = active_vault_index(&config)?;
|
||||||
let v = config
|
let v = &mut config.vaults[active_index];
|
||||||
.vaults
|
|
||||||
.iter_mut()
|
|
||||||
.find(|v| v.path == active)
|
|
||||||
.ok_or_else(|| "Active vault not found in config".to_string())?;
|
|
||||||
v.sync_provider = provider.filter(|p| !p.is_empty());
|
v.sync_provider = provider.filter(|p| !p.is_empty());
|
||||||
v.webdav_url = url.filter(|u| !u.trim().is_empty());
|
v.webdav_url = url.filter(|u| !u.trim().is_empty());
|
||||||
v.webdav_username = username.filter(|u| !u.is_empty());
|
v.webdav_username = username.filter(|u| !u.is_empty());
|
||||||
@@ -2094,7 +2127,7 @@ pub fn sync_now(app: AppHandle) -> Result<(), String> {
|
|||||||
if app.state::<AppState>().syncing.swap(true, Ordering::SeqCst) {
|
if app.state::<AppState>().syncing.swap(true, Ordering::SeqCst) {
|
||||||
return Ok(()); // a sync is already running
|
return Ok(()); // a sync is already running
|
||||||
}
|
}
|
||||||
let (vault, cfg) = {
|
let (vault, bookmark_id, cfg) = {
|
||||||
let state = app.state::<AppState>();
|
let state = app.state::<AppState>();
|
||||||
let config = match state.config.lock() {
|
let config = match state.config.lock() {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
@@ -2107,9 +2140,13 @@ pub fn sync_now(app: AppHandle) -> Result<(), String> {
|
|||||||
.active_vault
|
.active_vault
|
||||||
.clone()
|
.clone()
|
||||||
.ok_or_else(|| "No active vault".to_string())
|
.ok_or_else(|| "No active vault".to_string())
|
||||||
.and_then(|v| sync_config_from(&config).map(|c| (v, c)));
|
.and_then(|vault| {
|
||||||
|
sync_config_from(&config).map(|cfg| {
|
||||||
|
(vault, config.active_bookmark_id.clone(), cfg)
|
||||||
|
})
|
||||||
|
});
|
||||||
match gathered {
|
match gathered {
|
||||||
Ok(vc) => vc,
|
Ok(vault_config) => vault_config,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
drop(config);
|
drop(config);
|
||||||
state.syncing.store(false, Ordering::SeqCst);
|
state.syncing.store(false, Ordering::SeqCst);
|
||||||
@@ -2125,8 +2162,10 @@ pub fn sync_now(app: AppHandle) -> Result<(), String> {
|
|||||||
Ok(summary) => {
|
Ok(summary) => {
|
||||||
let ts = chrono::Utc::now().to_rfc3339();
|
let ts = chrono::Utc::now().to_rfc3339();
|
||||||
if let Ok(mut config) = app.state::<AppState>().config.lock() {
|
if let Ok(mut config) = app.state::<AppState>().config.lock() {
|
||||||
if let Some(v) = config.vaults.iter_mut().find(|v| v.path == vault) {
|
if let Some(vault_config) = config.vaults.iter_mut().find(|candidate| {
|
||||||
v.last_sync_time = Some(ts.clone());
|
vault_matches_identity(candidate, &vault, bookmark_id.as_deref())
|
||||||
|
}) {
|
||||||
|
vault_config.last_sync_time = Some(ts.clone());
|
||||||
}
|
}
|
||||||
let _ = save_app_config(&config);
|
let _ = save_app_config(&config);
|
||||||
}
|
}
|
||||||
@@ -2265,7 +2304,7 @@ fn migrate_global_sync_to_vault(config: &mut AppConfig) -> bool {
|
|||||||
if config.sync_provider.is_none() && config.webdav_url.is_none() {
|
if config.sync_provider.is_none() && config.webdav_url.is_none() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let Some(active) = config.active_vault.clone() else { return false; };
|
let Ok(active_index) = active_vault_index(config) else { return false; };
|
||||||
let g_provider = config.sync_provider.clone();
|
let g_provider = config.sync_provider.clone();
|
||||||
let g_url = config.webdav_url.clone();
|
let g_url = config.webdav_url.clone();
|
||||||
let g_user = config.webdav_username.clone();
|
let g_user = config.webdav_username.clone();
|
||||||
@@ -2274,7 +2313,7 @@ fn migrate_global_sync_to_vault(config: &mut AppConfig) -> bool {
|
|||||||
let g_on_change = config.sync_on_change;
|
let g_on_change = config.sync_on_change;
|
||||||
let g_interval = config.sync_interval_minutes;
|
let g_interval = config.sync_interval_minutes;
|
||||||
let g_last = config.last_sync_time.clone();
|
let g_last = config.last_sync_time.clone();
|
||||||
let Some(v) = config.vaults.iter_mut().find(|v| v.path == active) else { return false; };
|
let v = &mut config.vaults[active_index];
|
||||||
if v.sync_provider.is_some() || v.webdav_url.is_some() {
|
if v.sync_provider.is_some() || v.webdav_url.is_some() {
|
||||||
return false; // already migrated / has its own config
|
return false; // already migrated / has its own config
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ pub struct AppState {
|
|||||||
pub config: Mutex<AppConfig>,
|
pub config: Mutex<AppConfig>,
|
||||||
pub search_index: Mutex<Option<Arc<SearchIndex>>>,
|
pub search_index: Mutex<Option<Arc<SearchIndex>>>,
|
||||||
pub watcher: Mutex<Option<RecommendedWatcher>>,
|
pub watcher: Mutex<Option<RecommendedWatcher>>,
|
||||||
|
pub vault_transition: tokio::sync::Mutex<()>,
|
||||||
pub importing: AtomicBool,
|
pub importing: AtomicBool,
|
||||||
pub syncing: AtomicBool,
|
pub syncing: AtomicBool,
|
||||||
pub pending_open_file: Mutex<Option<String>>,
|
pub pending_open_file: Mutex<Option<String>>,
|
||||||
@@ -20,6 +21,7 @@ impl AppState {
|
|||||||
config: Mutex::new(config),
|
config: Mutex::new(config),
|
||||||
search_index: Mutex::new(None),
|
search_index: Mutex::new(None),
|
||||||
watcher: Mutex::new(None),
|
watcher: Mutex::new(None),
|
||||||
|
vault_transition: tokio::sync::Mutex::new(()),
|
||||||
importing: AtomicBool::new(false),
|
importing: AtomicBool::new(false),
|
||||||
syncing: AtomicBool::new(false),
|
syncing: AtomicBool::new(false),
|
||||||
pending_open_file: Mutex::new(None),
|
pending_open_file: Mutex::new(None),
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
const appWindow = getCurrentWindow();
|
const appWindow = getCurrentWindow();
|
||||||
const isMac = navigator.platform.startsWith('Mac');
|
const isMac = navigator.platform.startsWith('Mac');
|
||||||
const isMobile = $derived($platformIsMobile);
|
const isMobile = $derived($platformIsMobile);
|
||||||
import { loadVaultState, saveVaultState, readNote, createDailyNote, createBackup, getPendingOpenFile, addQuickAccess, removeQuickAccess, getQuickAccess, setTheme, syncNow, setTaskDone, setTaskPriority, setTaskDue, findOrphanedAttachments, trashOrphanedAttachments } from '$lib/api';
|
import { loadVaultState, saveVaultState, readNote, createDailyNote, createBackup, getPendingOpenFile, addQuickAccess, removeQuickAccess, getQuickAccess, setTheme, syncNow, getAppConfig, setTaskDone, setTaskPriority, setTaskDue, findOrphanedAttachments, trashOrphanedAttachments } from '$lib/api';
|
||||||
import { darkThemes, isAndroid } from '$lib/platform';
|
import { darkThemes, isAndroid } from '$lib/platform';
|
||||||
import { debounce } from '$lib/utils/debounce';
|
import { debounce } from '$lib/utils/debounce';
|
||||||
import { openNoteWindow } from '$lib/utils/window';
|
import { openNoteWindow } from '$lib/utils/window';
|
||||||
@@ -729,11 +729,11 @@
|
|||||||
|
|
||||||
// ── WebDAV sync: global status + auto-sync triggers ──
|
// ── WebDAV sync: global status + auto-sync triggers ──
|
||||||
unlistenSync.push(await listen('sync-progress', () => syncState.set({ running: true, error: null })));
|
unlistenSync.push(await listen('sync-progress', () => syncState.set({ running: true, error: null })));
|
||||||
unlistenSync.push(await listen('sync-done', (event: any) => {
|
unlistenSync.push(await listen('sync-done', async () => {
|
||||||
syncState.set({ running: false, error: null });
|
syncState.set({ running: false, error: null });
|
||||||
const cur = get(appConfig);
|
try {
|
||||||
const ts = event.payload?.last_sync_time;
|
appConfig.set(await getAppConfig());
|
||||||
if (cur && ts) appConfig.set({ ...cur, vaults: cur.vaults.map((v) => v.path === cur.active_vault ? { ...v, last_sync_time: ts } : v) });
|
} catch {}
|
||||||
}));
|
}));
|
||||||
unlistenSync.push(await listen('sync-error', (event: any) => syncState.set({ running: false, error: event.payload?.error ?? 'Sync failed' })));
|
unlistenSync.push(await listen('sync-error', (event: any) => syncState.set({ running: false, error: event.payload?.error ?? 'Sync failed' })));
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { showSettings, theme, appConfig, activeVaultConfig, updateAvailable as globalUpdateAvailable, updateObj as globalUpdateObj, installType, settingsTab, vaultReady, androidApkUrl, checkForUpdateMobile, notebookSortMode, isManagedInstall, customThemes } from '$lib/stores/app';
|
import { showSettings, theme, appConfig, activeVaultConfig, updateAvailable as globalUpdateAvailable, updateObj as globalUpdateObj, installType, settingsTab, vaultReady, androidApkUrl, checkForUpdateMobile, notebookSortMode, isManagedInstall, customThemes } from '$lib/stores/app';
|
||||||
import { setTheme, setAccentColor, setFontSize, setFontFamily, setLineHeight, setUiScale, setContentWidth, setGeneralSettings, importObsidian, createBackup, listBackups, restoreBackup, deleteBackup, setBackupSettings, setAiSettings, testAiConnection, setSyncSettings, testSyncConnection, syncNow, saveCustomTheme, deleteCustomTheme, exportCustomTheme, importCustomThemes } from '$lib/api';
|
import { setTheme, setAccentColor, setFontSize, setFontFamily, setLineHeight, setUiScale, setContentWidth, setGeneralSettings, importObsidian, createBackup, listBackups, restoreBackup, deleteBackup, setBackupSettings, setAiSettings, testAiConnection, setSyncSettings, testSyncConnection, syncNow, getAppConfig, saveCustomTheme, deleteCustomTheme, exportCustomTheme, importCustomThemes } from '$lib/api';
|
||||||
import { darkThemes, isMobile, isAndroid } from '$lib/platform';
|
import { darkThemes, isMobile, isAndroid } from '$lib/platform';
|
||||||
import { open as openDialog, save as saveDialog } from '@tauri-apps/plugin-dialog';
|
import { open as openDialog, save as saveDialog } from '@tauri-apps/plugin-dialog';
|
||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
@@ -318,9 +318,13 @@
|
|||||||
// Re-seed the sync form when the active vault changes, so Settings reflects the current vault.
|
// Re-seed the sync form when the active vault changes, so Settings reflects the current vault.
|
||||||
let lastSyncVault: string | null = null;
|
let lastSyncVault: string | null = null;
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const path = $appConfig?.active_vault ?? null;
|
const identity = $appConfig?.active_bookmark_id
|
||||||
if (path === lastSyncVault) return;
|
? `bookmark:${$appConfig.active_bookmark_id}`
|
||||||
lastSyncVault = path;
|
: $appConfig?.active_vault
|
||||||
|
? `path:${$appConfig.active_vault}`
|
||||||
|
: null;
|
||||||
|
if (identity === lastSyncVault) return;
|
||||||
|
lastSyncVault = identity;
|
||||||
const vc = activeVaultConfig($appConfig);
|
const vc = activeVaultConfig($appConfig);
|
||||||
syncProvider = vc?.sync_provider ?? null;
|
syncProvider = vc?.sync_provider ?? null;
|
||||||
syncUrl = vc?.webdav_url ?? '';
|
syncUrl = vc?.webdav_url ?? '';
|
||||||
@@ -337,10 +341,11 @@
|
|||||||
// triggers update live, without an app restart.
|
// triggers update live, without an app restart.
|
||||||
if ($appConfig) {
|
if ($appConfig) {
|
||||||
const cur = $appConfig;
|
const cur = $appConfig;
|
||||||
|
const active = activeVaultConfig(cur);
|
||||||
$appConfig = {
|
$appConfig = {
|
||||||
...cur,
|
...cur,
|
||||||
vaults: cur.vaults.map((v) => v.path === cur.active_vault ? {
|
vaults: cur.vaults.map((vault) => vault === active ? {
|
||||||
...v,
|
...vault,
|
||||||
sync_provider: syncProvider,
|
sync_provider: syncProvider,
|
||||||
webdav_url: syncUrl || null,
|
webdav_url: syncUrl || null,
|
||||||
webdav_username: syncUsername || null,
|
webdav_username: syncUsername || null,
|
||||||
@@ -348,7 +353,7 @@
|
|||||||
sync_on_open: syncOnOpen,
|
sync_on_open: syncOnOpen,
|
||||||
sync_on_change: syncOnChange,
|
sync_on_change: syncOnChange,
|
||||||
sync_interval_minutes: syncIntervalMinutes,
|
sync_interval_minutes: syncIntervalMinutes,
|
||||||
} : v),
|
} : vault),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -379,7 +384,7 @@
|
|||||||
syncMessage = null;
|
syncMessage = null;
|
||||||
const unlisteners: Array<() => void> = [];
|
const unlisteners: Array<() => void> = [];
|
||||||
const cleanup = () => { unlisteners.forEach((u) => u()); };
|
const cleanup = () => { unlisteners.forEach((u) => u()); };
|
||||||
unlisteners.push(await listen<{ success: boolean; summary?: { uploaded?: number; downloaded?: number; deleted_local?: number; deleted_remote?: number; conflicts?: number }; last_sync_time?: string }>('sync-done', (event) => {
|
unlisteners.push(await listen<{ success: boolean; summary?: { uploaded?: number; downloaded?: number; deleted_local?: number; deleted_remote?: number; conflicts?: number }; last_sync_time?: string }>('sync-done', async (event) => {
|
||||||
const s = event.payload.summary ?? {};
|
const s = event.payload.summary ?? {};
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
if (s.uploaded) parts.push(`${s.uploaded} uploaded`);
|
if (s.uploaded) parts.push(`${s.uploaded} uploaded`);
|
||||||
@@ -389,10 +394,10 @@
|
|||||||
if (s.conflicts) parts.push(`${s.conflicts} conflict copies`);
|
if (s.conflicts) parts.push(`${s.conflicts} conflict copies`);
|
||||||
syncMessage = { type: 'success', text: parts.length ? `Synced: ${parts.join(', ')}.` : 'Already up to date.' };
|
syncMessage = { type: 'success', text: parts.length ? `Synced: ${parts.join(', ')}.` : 'Already up to date.' };
|
||||||
syncRunning = false;
|
syncRunning = false;
|
||||||
if ($appConfig && event.payload.last_sync_time) {
|
if (event.payload.last_sync_time) {
|
||||||
const cur = $appConfig;
|
try {
|
||||||
const ts = event.payload.last_sync_time;
|
$appConfig = await getAppConfig();
|
||||||
$appConfig = { ...cur, vaults: cur.vaults.map((v) => v.path === cur.active_vault ? { ...v, last_sync_time: ts } : v) };
|
} catch {}
|
||||||
}
|
}
|
||||||
cleanup();
|
cleanup();
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -130,6 +130,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function openRecentVault(vault: VaultConfig) {
|
async function openRecentVault(vault: VaultConfig) {
|
||||||
|
if (loading) return;
|
||||||
if (!isIOS || !vault.bookmark_id) {
|
if (!isIOS || !vault.bookmark_id) {
|
||||||
await openSelectedVault(vault.path);
|
await openSelectedVault(vault.path);
|
||||||
return;
|
return;
|
||||||
@@ -149,6 +150,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function forgetVault(vault: VaultConfig) {
|
async function forgetVault(vault: VaultConfig) {
|
||||||
|
if (loading) return;
|
||||||
|
loading = true;
|
||||||
|
error = '';
|
||||||
try {
|
try {
|
||||||
const wasActive = isActiveVault(vault);
|
const wasActive = isActiveVault(vault);
|
||||||
await removeVault(vault.path, vault.bookmark_id);
|
await removeVault(vault.path, vault.bookmark_id);
|
||||||
@@ -156,6 +160,8 @@
|
|||||||
if (wasActive) $vaultReady = false;
|
if (wasActive) $vaultReady = false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = String(e);
|
error = String(e);
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,11 +278,11 @@
|
|||||||
<span class="recent-label">Recent</span>
|
<span class="recent-label">Recent</span>
|
||||||
{#each recentVaults as vault}
|
{#each recentVaults as vault}
|
||||||
<div class="vault-row">
|
<div class="vault-row">
|
||||||
<button class="vault-item" class:current={isActiveVault(vault)} onclick={() => openRecentVault(vault)}>
|
<button class="vault-item" class:current={isActiveVault(vault)} onclick={() => openRecentVault(vault)} disabled={loading}>
|
||||||
<span class="vault-name">{vault.name}{#if isActiveVault(vault)}<span class="vault-current-badge">Current</span>{/if}</span>
|
<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>
|
<span class="vault-path">{vault.bookmark_id ? `Files · ${vault.path}` : vault.path}</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="vault-remove" title="Remove from list" aria-label="Remove from list" onclick={() => forgetVault(vault)}>
|
<button class="vault-remove" title="Remove from list" aria-label="Remove from list" onclick={() => forgetVault(vault)} disabled={loading}>
|
||||||
<svg width="12" height="12" viewBox="0 0 10 10">
|
<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="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" />
|
<line x1="8.5" y1="1.5" x2="1.5" y2="8.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" />
|
||||||
|
|||||||
Reference in New Issue
Block a user