diff --git a/src-tauri/src/commands/system.rs b/src-tauri/src/commands/system.rs index c8d1ad6..f4230b3 100644 --- a/src-tauri/src/commands/system.rs +++ b/src-tauri/src/commands/system.rs @@ -58,12 +58,31 @@ pub fn exit_app(app: tauri::AppHandle) { app.exit(0); } +fn remove_dir_best_effort(path: &std::path::Path) { + if path.is_file() { + if let Err(e) = std::fs::remove_file(path) { + if e.raw_os_error() == Some(32) { + return; + } + } + } else if path.is_dir() { + if let Ok(entries) = std::fs::read_dir(path) { + for entry in entries.flatten() { + remove_dir_best_effort(&entry.path()); + } + } + let _ = std::fs::remove_dir(path); + } +} + #[tauri::command] -pub fn clear_moku_cache(app: tauri::AppHandle) -> Result<(), String> { - use tauri::Manager; +pub async fn clear_moku_cache(app: tauri::AppHandle) -> Result<(), String> { + let window = app.get_webview_window("main").ok_or("no main window")?; + window.clear_all_browsing_data().map_err(|e| e.to_string())?; + let cache_dir = app.path().app_cache_dir().map_err(|e| e.to_string())?; if cache_dir.exists() { - std::fs::remove_dir_all(&cache_dir).map_err(|e| e.to_string())?; + remove_dir_best_effort(&cache_dir); std::fs::create_dir_all(&cache_dir).map_err(|e| e.to_string())?; } Ok(()) diff --git a/src/api/queries/extensions.ts b/src/api/queries/extensions.ts index 446ec37..6faa53d 100644 --- a/src/api/queries/extensions.ts +++ b/src/api/queries/extensions.ts @@ -22,7 +22,7 @@ export const GET_SOURCES = ` sources { nodes { id name lang displayName iconUrl isNsfw - isConfigurable supportsLatest baseUrl + isConfigurable supportsLatest extension { pkgName } } } @@ -92,7 +92,7 @@ export const GET_MIGRATABLE_SOURCES = ` nodes { sourceId source { - id name lang displayName iconUrl isNsfw isConfigurable supportsLatest baseUrl + id name lang displayName iconUrl isNsfw isConfigurable supportsLatest } } } diff --git a/src/core/actions/selectPortal.ts b/src/core/actions/selectPortal.ts index 78dd499..d6086df 100644 --- a/src/core/actions/selectPortal.ts +++ b/src/core/actions/selectPortal.ts @@ -1,32 +1,30 @@ import type { Attachment } from "svelte/attachments"; -/** - * {@attach selectPortal(triggerEl)} - * - * Moves the decorated element to and positions it below `triggerEl`. - * The element stays reactive — Svelte still owns its DOM, we just re-parent it. - * - * The portalled menu element is stored on `triggerEl.__selectMenuEl` so that - * the outside-click guard in Settings.svelte can exclude it from dismissal. - */ export function selectPortal(triggerEl: HTMLElement & { __selectMenuEl?: HTMLElement | null }): Attachment { return (menuEl: HTMLElement) => { - // Position & move to body function position() { + const zoom = parseFloat(document.documentElement.style.zoom) / 100 || 1; const r = triggerEl.getBoundingClientRect(); + + const top = r.bottom / zoom + 4; + const right = r.right / zoom; + const width = menuEl.offsetWidth; + const left = Math.max(8, right - width); + menuEl.style.position = "fixed"; - menuEl.style.top = `${r.bottom + 4}px`; - menuEl.style.left = `${r.right - menuEl.offsetWidth}px`; - // clamp to viewport left edge - const left = parseFloat(menuEl.style.left); - if (left < 8) menuEl.style.left = "8px"; + menuEl.style.top = `${top}px`; + menuEl.style.left = `${left}px`; } + menuEl.style.visibility = "hidden"; document.body.appendChild(menuEl); triggerEl.__selectMenuEl = menuEl; - position(); - // Reposition on scroll / resize while open + requestAnimationFrame(() => { + position(); + menuEl.style.visibility = ""; + }); + window.addEventListener("scroll", position, true); window.addEventListener("resize", position);