window.cascadingStateInstanceReference = (dotNetReference) => { try { window.cascadingStateInstance = dotNetReference } catch (e) { console.error(e) } } window.logFromJs = (message, where) => { try { if (!window.cascadingStateInstance || typeof window.cascadingStateInstance.invokeMethodAsync != "function") return window.cascadingStateInstance.invokeMethodAsync('LogFromJs', message, where) } catch (e) { console.error(e) } } // The client once kept its error log in IndexedDB (DnetIndexedDb's database "data"). Nothing uses IndexedDB now, so a // browser that still holds that database drops it. try { window.indexedDB?.deleteDatabase("data") } catch (e) { console.error(e) } window.getLanguage = () => { try { return navigator.language.substr(0, 2) } catch (e) { console.error(e) window.logFromJs(e.message, "getLanguage") } } window.isOnline = async () => { try { const isOnline = navigator.onLine if (navigator.connection !== undefined && navigator.connection.effectiveType !== undefined && navigator.connection.type !== undefined) { if (!isOnline) return isOnline return navigator.connection.type === "cellular" ? navigator.connection.effectiveType.indexOf("2g") === -1 : true } else return isOnline } catch (e) { console.error(e) window.logFromJs(e.message, "isOnline") } } window.setAppBadge = (counter) => { try { navigator.setAppBadge(counter) } catch (e) { console.error(e) window.logFromJs(e.message, "setAppBadge") } } window.clearAppBadge = () => { try { navigator.clearAppBadge() } catch (e) { console.error(e) window.logFromJs(e.message, "clearAppBadge") } } // On a touch screen a tap on a post (not on its links, controls or menus, and not ending a text selection) opens its // thread, through the card's own thread link (Content.razor, data-thread-link), which Blazor's router takes. document.addEventListener("click", event => { try { if (window.matchMedia("(hover: hover)").matches || !(event.target instanceof Element)) return const card = event.target.closest("[data-thread]") if (!card || event.target.closest("a, button, input, textarea, select, label, summary, video, audio, [role=button], .dropdown, .modal, [data-sheet]")) return if (!window.getSelection()?.isCollapsed) return card.querySelector("a[data-thread-link]")?.click() } catch (e) { console.error(e) window.logFromJs(e.message, "openThread") } }) // The Back button (a popstate) as opposed to the app's own navigation: Blazor's location-changing handlers can't tell // them apart, and MainLayout lets Back close an open overlay but lets the app's navigation through. Registered for the // capture phase, so it runs before Blazor's own listener. let backAt = 0 window.addEventListener("popstate", () => backAt = Date.now(), { capture: true }) window.tookBack = () => { const back = Date.now() - backAt < 1000 backAt = 0 return back } window.canHover = () => { try { return window.matchMedia("(hover: hover)").matches } catch (e) { console.error(e) window.logFromJs(e.message, "canHover") return true } } // the selected tab of a sideways-scrolling strip, brought into its middle window.revealActive = strip => { try { strip?.querySelector("[aria-selected=true]")?.scrollIntoView({ inline: "center", block: "nearest" }) } catch (e) { console.error(e) window.logFromJs(e.message, "revealActive") } } // the reader asked for less motion: what would move by itself (a gifv) waits for a tap window.prefersReducedMotion = () => { try { return window.matchMedia("(prefers-reduced-motion: reduce)").matches } catch (e) { console.error(e) window.logFromJs(e.message, "prefersReducedMotion") return false } } // a phone: narrower than Tailwind's md, or held sideways (as short as the short: variant) window.isMobileMedia = () => { try { return window.matchMedia("(max-width: 767.98px), (max-height: 30rem)").matches } catch (e) { console.error(e) window.logFromJs(e.message, "isMobileMedia") } } // This device's position for located posts and the Nearby feed, asked only when the reader chooses one of them. It // goes to PrivaPub only, which keeps located posts on the server and rounds what it stores. window.feedPosition = () => new Promise(resolve => { try { if (!navigator.geolocation) return resolve({ error: "unsupported" }) navigator.geolocation.getCurrentPosition( position => resolve({ latitude: position.coords.latitude, longitude: position.coords.longitude, accuracy: position.coords.accuracy }), error => resolve({ error: error.message || String(error.code) }), { enableHighAccuracy: false, timeout: 15000, maximumAge: 600000 }) } catch (e) { console.error(e) window.logFromJs(e.message, "feedPosition") resolve({ error: e.message }) } })