import { ensureNavId, peekNavId } from "./history"; import { anyBlockingLayer } from "./layerStack"; import { createStore } from "./store"; // Downloading a link is an action you start anywhere: the Files page's link box, a link pasted onto // any page, the command palette. They all hand the link to ONE app-wide download dialog // (features/downloads/DownloadIntake), so none of them mounts a dialog of its own. const pending = createStore(null); /** An http(s) link, alone — what a paste or the link box may download; anything else is not one. */ export function asDownloadLink(text: string | null | undefined): string | null { const s = text?.trim() ?? ""; return /^https?:\/\/\S+$/i.test(s) ? s : null; } /** Whether a paste here is the page's to take: not typed into a field, and nothing on top that owns * the keyboard — a dialog, a player, an open menu (the reader is busy with that, and a link pasted * at it is not a request to download). The layer stack knows them all; a player marks no * `aria-modal`, so the markup alone would miss it. */ export function pasteIsForThePage(target: EventTarget | null): boolean { const el = target instanceof Element ? target : null; if (el?.closest("input, textarea, select, [contenteditable]:not([contenteditable='false'])")) return false; return !anyBlockingLayer() && !document.querySelector("[aria-modal='true']"); } /** Ask to download `url`: the app's download dialog opens on it. */ export const requestDownload = (url: string): void => pending.set(url); /** The link the dialog is open on (null = closed). DownloadIntake only. */ export const usePendingDownload = (): string | null => pending.useValue(); /** The dialog closed, downloading or not. */ export const clearPendingDownload = (): void => pending.set(null); // The palette's "Download from link…" takes the reader to the Files page's link box. A token for the // history entry the navigation made (as lib/homeIntent's): Files is a lazy module, often not mounted // when it is asked, and a request that outlived its entry must not grab the focus on a later visit. const linkBox = createStore(null); /** Ask the Files page to focus its link box on THIS entry — `setPage("files", requestLinkBox)`. */ export const requestLinkBox = (): void => linkBox.set(ensureNavId()); /** The pending request (null = none) — the render subscription that wakes the link box. */ export const useLinkBoxRequest = (): number | null => linkBox.useValue(); /** Take the request, clearing it; true only on the entry it was made for. */ export const takeLinkBox = (): boolean => { const entry = linkBox.get(); if (entry === null) return false; linkBox.set(null); return entry === peekNavId(); };