interface CopyLinkHook extends ViewHook { el: HTMLElement } export const CopyLink = { mounted(this: CopyLinkHook): void { this.el.addEventListener("click", () => { navigator.clipboard.writeText(window.location.href).then(() => { const orig = this.el.innerHTML this.el.innerHTML = 'Copied!' setTimeout(() => { this.el.innerHTML = orig }, 1500) }) }) } } interface LocateMeHook extends ViewHook { el: HTMLElement watchId: number | null lastSentAt: number startWatching(): void } export const LocateMe = { mounted(this: LocateMeHook): void { this.watchId = null this.lastSentAt = 0 this.el.addEventListener("click", () => this.startWatching()) // Also respond to server-pushed request (for source=gps URLs) this.handleEvent("request_gps", () => this.startWatching()) }, destroyed(this: LocateMeHook): void { if (this.watchId !== null) { navigator.geolocation.clearWatch(this.watchId) } }, startWatching(this: LocateMeHook): void { if (!navigator.geolocation) { alert("Geolocation is not supported by this browser") return } // Already watching — don't start another if (this.watchId !== null) return this.el.classList.add("loading", "loading-spinner") this.watchId = navigator.geolocation.watchPosition( (pos: GeolocationPosition) => { const now = Date.now() // Send immediately on first fix, then at most once per minute if (this.lastSentAt === 0 || now - this.lastSentAt >= 60000) { this.lastSentAt = now this.el.classList.remove("loading", "loading-spinner") this.pushEvent("gps_location", { lat: pos.coords.latitude, lon: pos.coords.longitude }) } }, (err: GeolocationPositionError) => { this.el.classList.remove("loading", "loading-spinner") navigator.geolocation.clearWatch(this.watchId!) this.watchId = null alert("Could not get location: " + err.message) }, { enableHighAccuracy: true, timeout: 10000 } ) } }