()
| 25 | const BAR_H = 32; |
| 26 | |
| 27 | const install = (): void => { |
| 28 | const root = document.documentElement; |
| 29 | if (!root) return; |
| 30 | |
| 31 | const host = document.createElement("div"); |
| 32 | host.style.cssText = `position:fixed;top:0;left:0;width:100%;height:${BAR_H}px;z-index:2147483647;pointer-events:none`; |
| 33 | const shadow = host.attachShadow({ mode: "closed" }); |
| 34 | |
| 35 | const bar = document.createElement("div"); |
| 36 | bar.style.cssText = [ |
| 37 | "display:flex", |
| 38 | "align-items:center", |
| 39 | "gap:8px", |
| 40 | `height:${BAR_H}px`, |
| 41 | "box-sizing:border-box", |
| 42 | "padding:0 12px", |
| 43 | "background:#161b22", |
| 44 | "border-bottom:1px solid #21262d", |
| 45 | "font:13px/1 ui-monospace,SFMono-Regular,Menlo,monospace", |
| 46 | "color:#c9d1d9", |
| 47 | "white-space:nowrap", |
| 48 | "overflow:hidden", |
| 49 | ].join(";"); |
| 50 | |
| 51 | const dot = (color: string): HTMLElement => { |
| 52 | const d = document.createElement("span"); |
| 53 | d.style.cssText = `width:11px;height:11px;border-radius:50%;flex:none;background:${color}`; |
| 54 | return d; |
| 55 | }; |
| 56 | const lights = document.createElement("span"); |
| 57 | lights.style.cssText = "display:inline-flex;gap:6px;margin-right:4px"; |
| 58 | lights.append(dot("#ff5f57"), dot("#febc2e"), dot("#28c840")); |
| 59 | |
| 60 | const lock = document.createElement("span"); |
| 61 | lock.textContent = "⌁"; // the viewer's URL-bar glyph |
| 62 | lock.style.cssText = "color:#8b949e;flex:none"; |
| 63 | |
| 64 | const url = document.createElement("span"); |
| 65 | url.style.cssText = "overflow:hidden;text-overflow:ellipsis"; |
| 66 | |
| 67 | bar.append(lights, lock, url); |
| 68 | shadow.append(bar); |
| 69 | root.appendChild(host); |
| 70 | |
| 71 | const render = (): void => { |
| 72 | const next = location.href.replace(/^https?:\/\//, "") || "about:blank"; |
| 73 | if (url.textContent !== next) url.textContent = next; |
| 74 | }; |
| 75 | render(); |
| 76 | |
| 77 | // SPA route changes don't reload the document, so re-read the URL on every |
| 78 | // history transition; the interval also re-attaches the bar if a framework |
| 79 | // re-render detached it, and is the catch-all for navigations we can't hook. |
| 80 | window.setInterval(() => { |
| 81 | if (!root.contains(host)) root.appendChild(host); |
| 82 | render(); |
| 83 | }, 250); |
| 84 | for (const ev of ["popstate", "hashchange"] as const) window.addEventListener(ev, render); |
no test coverage detected