feat(tauri-app): expand tray menu + open_external_url + Profile/Search items

Tray menu now has:
  Show maarcadetweet
  Home
  Compose
  Profile
  Search
  ----
  Quit

The Home/Profile/Search items emit 'app://navigate' events
which the frontend's listenTrayEvents translates to view
switches. The compose and show events continue to be
separate event types ('app://compose', 'app://show').

open_external_url Tauri command takes a URL, validates it's
http(s), and uses tauri-plugin-shell to open it in the user's
default browser. The frontend's openExternalUrl falls back
to window.open in the browser preview (no Tauri runtime).

svelte-check error fix: tauriCall<T>(cmd, fallback, args?)
had the second call argument as 'undefined' instead of 'null'
on the call site for session.load(). The TypeScript compiler
correctly noted that the fallback type 'T' (here Session |
null) couldn't be undefined. Replaced with 'null' and
dropped the trailing null args argument (it's optional).
This commit is contained in:
tomdebone
2026-07-07 17:31:09 +02:00
parent 6a85f44bab
commit 550b89673d
4 changed files with 190 additions and 5 deletions
+29 -4
View File
@@ -51,7 +51,7 @@ function createSessionStore() {
return {
subscribe,
async load() {
const s = await tauriCall<Session | null>("current_session", undefined, null);
const s = await tauriCall<Session | null>("current_session", null);
set(s);
},
async login(handle: string, password: string) {
@@ -355,18 +355,43 @@ export async function showNotification(
/// The handler receives the event payload (empty object for these
/// cases). Returns an unsubscribe function.
export async function listenTrayEvents(
handler: (event: "show" | "compose") => void,
handler: (event: "show" | "home" | "compose" | "profile" | "search") => void,
): Promise<() => void> {
const { listen } = await import("@tauri-apps/api/event");
const unlisteners: Array<() => void> = [];
const u1 = await listen("app://show", () => handler("show"));
const u2 = await listen("app://compose", () => handler("compose"));
unlisteners.push(u1, u2);
const u2 = await listen("app://navigate", (e) => {
handler(e.payload as "home" | "profile" | "search");
});
const u3 = await listen("app://compose", () => handler("compose"));
unlisteners.push(u1, u2, u3);
return () => {
for (const u of unlisteners) u();
};
}
/// Open an external URL in the user's default browser. The Rust
/// `open_external_url` command enforces http(s) only; in the
/// browser preview where no Tauri runtime is present, fall back
/// to `window.open` and treat a popup-blocker denial as
/// "fine, user can copy the URL themselves".
export async function openExternalUrl(url: string): Promise<void> {
try {
if (isTauri()) {
await safeInvoke("open_external_url", { url });
return;
}
} catch (e) {
console.warn("open_external_url failed", e);
}
// Browser fallback (vite dev preview).
try {
window.open(url, "_blank", "noopener,noreferrer");
} catch (e) {
console.warn("window.open failed (popup blocker?)", e);
}
}
/// Subscribe to `app://notification` events emitted by the Rust
/// `show_notification` command. Used to focus the window and
/// navigate when the user clicks the notification.