/* Running as a desktop app. One bundle answers both a browser and an app window (Tauri — see src-tauri/), so nothing in `app/` is desktop code: it is the wiki asking at run time whether it happens to be sitting in a window. The answer comes from the shell, which marks the page it loads and marks no other — so the same dist-desktop/ files opened in an ordinary browser behave as a web page, and a web page that somehow met Tauri would only gain the shell's habits. What a window does differently, then, is small and all about being a window with one page and no browser chrome: a link leads out to the system's own browser rather than replacing the wiki in the only view there is, and the few bytes the app remembers of a person live where a window can be certain of keeping them. */ import type { Ref } from 'vue' import { watch } from 'vue' /** The handle every Tauri webview injects into its page — present before the * npm API is imported, and without it. Nothing else sets this name. */ export function isDesktop(): boolean { if (!import.meta.client) return false return (window as Window & { __TAURI_INTERNALS__?: unknown }).__TAURI_INTERNALS__ != null } /* A window is expected to have localStorage. A private browser may throw on being asked for it at all, and a setting that cannot be remembered is worth far less than a page that still draws — so the three doors below are the only place any of it is touched, and none of them lets a throw through. */ export function storeGet(key: string): string | null { try { return localStorage.getItem(key) } catch { return null } } export function storeSet(key: string, value: string): void { try { localStorage.setItem(key, value) } catch { /* nothing remembered; nothing else depends on it */ } } export function storeClear(key: string): void { try { localStorage.removeItem(key) } catch { /* as good as cleared, for as long as this page lives */ } } /** Whether an address leads outside this app — to a document on some other * site, or to a mail client, or to a phone dialler. The app's own pages are * the relative `/wiki/…` the router already knows; an absolute address that * happens to name the wiki's public site counts as outward too, and opens * there in the browser, which is the honest reading of a link written out in * full. */ export function leadsOutward(href: string): boolean { if (!import.meta.client || !href) return false let url: URL try { url = new URL(href, window.location.href) } catch { return false } if (url.protocol === 'mailto:' || url.protocol === 'tel:') return true return (url.protocol === 'http:' || url.protocol === 'https:') && url.origin !== window.location.origin } /** Hand a document to whatever machine-level application owns it: the browser * for a page, the mail client for `mailto:`. In a browser a link already does * this, so the fallback is the browser's own new tab; in a window there is no * other tab, and following the link in place would leave the wiki's page * gone from the only view there is. */ export async function openExternal(url: string): Promise { if (!import.meta.client || !url) return if (isDesktop()) { try { const { openUrl } = await import('@tauri-apps/plugin-opener') await openUrl(url) return } catch { /* no shell to hand it to, or this window is not allowed this one — the least worse thing left is the browser's way out */ } } window.open(url, '_blank', 'noopener') } /** Keep a session token where a window cannot lose it. On the web a cookie is the whole of it: the server reads it, and the browser keeps it as long as the API said the token lasts. A window's own address (`tauri://localhost`) is one a webview is not everywhere obliged to keep cookies for, and a session dying with the window would mean signing in again every time the app was opened — for a token the API hands out for a week. So here it is mirrored into localStorage and taken back from there whenever the cookie comes up empty, whichever of the two this webview chose to keep. The cookie still leads: it is the one already at hand, the one a web build would have read, and the one the sign-out line clears. A browser asked this question of the same files ignores all of it — there is nothing to mirror where cookies work — so the wiki's behavior there is untouched. */ export function keepTokenRemembered(token: Ref, key: string): void { if (!import.meta.client || !isDesktop()) return if (!token.value) { const kept = storeGet(key) if (kept) token.value = kept } // sync, so signing out clears the mirror in the same breath, not on a tick watch(token, (value) => (value ? storeSet(key, value) : storeClear(key)), { flush: 'sync' }) }