Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| preparing for cloud work | 1 | // The desktop bridge: what the site finds at `window.g1tDesktop`, run at |
| 2 | // the start of every page the window loads (bridge.rs puts it there). The | |
| 3 | // site's lib/notify-client.ts describes the contract from its side; when | |
| 4 | // the bridge is there, notifications go to the system, the badge to the | |
| 5 | // icon, links out to the browser, and g1t:// links arrive as `onNavigate`. | |
| 6 | // Each call is a command in bridge.rs, which only answers the server's | |
| 7 | // pages; on any other page the calls fail quietly. | |
| 8 | (function () { | |
| 9 | if (window.g1tDesktop) return; | |
| 10 | var VERSION = "__VERSION__"; | |
| 11 | var PLATFORM = "__PLATFORM__"; | |
| The desktop app's title bar is a web view of its own, above the site's, so no page is ever under it. | 12 | // Whether g1t's own title bar is above this page ("custom"), or the |
| 13 | // system's ("system"). The bar is a page of its own (src/frame.rs); this | |
| 14 | // page only tells it what to show (`report`), and is never under it. | |
| The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again. | 15 | var FRAME = "__FRAME__"; |
| The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it. | 16 | // The site's pages for reading about g1t, not using it, which open in |
| 17 | // the browser (navigation.rs); one ending in "/" is every page under it. | |
| 18 | var OUTSIDE = "__OUTSIDE__"; | |
| preparing for cloud work | 19 | |
| 20 | function invoke(command, args) { | |
| 21 | var core = window.__TAURI__ && window.__TAURI__.core; | |
| 22 | if (core && typeof core.invoke === "function") return core.invoke(command, args || {}); | |
| 23 | var internals = window.__TAURI_INTERNALS__; | |
| 24 | if (internals && typeof internals.invoke === "function") return internals.invoke(command, args || {}); | |
| 25 | return Promise.reject(new Error("g1t desktop: this page cannot reach the app")); | |
| 26 | } | |
| 27 | ||
| 28 | function quietly(promise) { | |
| 29 | promise.catch(function (error) { | |
| 30 | console.warn("g1t desktop:", error && error.message ? error.message : error); | |
| 31 | }); | |
| 32 | } | |
| 33 | ||
| 34 | var listeners = []; | |
| 35 | var listening = false; | |
| 36 | function listen() { | |
| 37 | if (listening) return; | |
| 38 | listening = true; | |
| 39 | var events = window.__TAURI__ && window.__TAURI__.event; | |
| 40 | if (!events || typeof events.listen !== "function") return; | |
| 41 | events | |
| 42 | .listen("g1t-navigate", function (event) { | |
| 43 | var href = event && event.payload; | |
| 44 | if (typeof href !== "string") return; | |
| 45 | listeners.slice().forEach(function (listener) { | |
| 46 | try { | |
| 47 | listener(href); | |
| 48 | } catch (error) { | |
| 49 | console.warn("g1t desktop: a navigate listener failed", error); | |
| 50 | } | |
| 51 | }); | |
| 52 | }) | |
| 53 | .catch(function () {}); | |
| 54 | } | |
| 55 | ||
| 56 | var bridge = { | |
| 57 | version: VERSION, | |
| 58 | platform: PLATFORM, | |
| 59 | // A native notification, for a message to the person while the window is behind another. | |
| 60 | notify: function (notification) { | |
| 61 | quietly(invoke("notify", { title: String(notification.title || "g1t"), body: String(notification.body || "") })); | |
| 62 | }, | |
| 63 | // What needs the person, on the icon and the tray; 0 clears it. | |
| 64 | setBadge: function (count) { | |
| 65 | quietly(invoke("set_badge", { count: Math.max(0, Math.floor(Number(count) || 0)) })); | |
| 66 | }, | |
| 67 | // A link to somewhere else, in the browser the system uses. | |
| 68 | openUrl: function (url) { | |
| 69 | quietly(invoke("open_url", { url: String(url) })); | |
| 70 | }, | |
| 71 | // g1t:// links, as site paths such as "/acme/-/chat/dm/chn_1". | |
| 72 | onNavigate: function (listener) { | |
| 73 | listeners.push(listener); | |
| 74 | listen(); | |
| 75 | return function () { | |
| 76 | var at = listeners.indexOf(listener); | |
| 77 | if (at >= 0) listeners.splice(at, 1); | |
| 78 | }; | |
| 79 | }, | |
| 80 | // Looks for an update now: "current", "ready:<version>", or rejects with why. | |
| 81 | checkForUpdates: function () { | |
| 82 | return invoke("check_for_updates"); | |
| 83 | }, | |
| The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it. | 84 | // The app's own settings, in a window of their own. |
| 85 | openSettings: function () { | |
| 86 | quietly(invoke("open_settings")); | |
| 87 | }, | |
| The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again. | 88 | // The title bar's search, in its middle: the site hands it a label |
| 89 | // ("Search Flagon") and what opens its search. Returns what takes it away. | |
| 90 | setSearch: function (label, open) { | |
| 91 | var mine = typeof open === "function" ? { label: String(label || "Search"), open: open } : null; | |
| 92 | search = mine; | |
| The desktop app's title bar is a web view of its own, above the site's, so no page is ever under it. | 93 | report(); |
| The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again. | 94 | return function () { |
| 95 | if (search === mine) { | |
| 96 | search = null; | |
| The desktop app's title bar is a web view of its own, above the site's, so no page is ever under it. | 97 | report(); |
| The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again. | 98 | } |
| 99 | }; | |
| 100 | }, | |
| 101 | // Whether g1t draws the title bar, and how tall it is: 0 under the system's. | |
| The desktop app's title bar is a web view of its own, above the site's, so no page is ever under it. | 102 | // The page is below it, never under it: this says only that the bar is there. |
| 103 | titleBarHeight: FRAME === "custom" ? 36 : 0, | |
| preparing for cloud work | 104 | }; |
| 105 | Object.defineProperty(window, "g1tDesktop", { value: Object.freeze(bridge), writable: false, configurable: false }); | |
| 106 | ||
| The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it. | 107 | // Links out. A link to another site, or to one of the site's pages for |
| 108 | // reading about g1t, opens in the system's browser, and the window stays | |
| 109 | // where it is; a link that asks for a new tab, or a click | |
| preparing for cloud work | 110 | // with Ctrl or Cmd held, goes on in this window, which has no tabs. |
| The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it. | 111 | function outside(url) { |
| 112 | var path = url.pathname.length > 1 ? url.pathname.replace(/\/+$/, "") : url.pathname; | |
| 113 | return OUTSIDE.some(function (page) { | |
| 114 | return page.charAt(page.length - 1) === "/" ? path.indexOf(page) === 0 : path === page; | |
| 115 | }); | |
| 116 | } | |
| preparing for cloud work | 117 | function destination(href) { |
| 118 | try { | |
| 119 | var url = new URL(href, location.href); | |
| 120 | return /^https?:$/.test(url.protocol) ? url : null; | |
| 121 | } catch (error) { | |
| 122 | return null; | |
| 123 | } | |
| 124 | } | |
| 125 | document.addEventListener( | |
| 126 | "click", | |
| 127 | function (event) { | |
| 128 | if (event.defaultPrevented || event.button !== 0) return; | |
| 129 | var anchor = event.target && event.target.closest ? event.target.closest("a[href]") : null; | |
| 130 | if (!anchor || anchor.hasAttribute("download")) return; | |
| 131 | var url = destination(anchor.getAttribute("href")); | |
| 132 | if (!url) return; | |
| The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it. | 133 | if (url.origin !== location.origin || outside(url)) { |
| preparing for cloud work | 134 | event.preventDefault(); |
| 135 | event.stopImmediatePropagation(); | |
| 136 | bridge.openUrl(url.href); | |
| 137 | } else if (anchor.target === "_blank" || event.metaKey || event.ctrlKey) { | |
| 138 | event.preventDefault(); | |
| 139 | event.stopImmediatePropagation(); | |
| 140 | location.assign(url.href); | |
| 141 | } | |
| 142 | }, | |
| 143 | true, | |
| 144 | ); | |
| 145 | var open = window.open; | |
| 146 | window.open = function (href, target, features) { | |
| 147 | var url = destination(href); | |
| 148 | if (url) { | |
| The desktop app has settings of its own and a menu, and it is for using g1t, not browsing it. | 149 | if (url.origin === location.origin && !outside(url)) location.assign(url.href); |
| preparing for cloud work | 150 | else bridge.openUrl(url.href); |
| 151 | return null; | |
| 152 | } | |
| 153 | return typeof open === "function" ? open.call(window, href, target, features) : null; | |
| 154 | }; | |
| The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again. | 155 | |
| 156 | // ── The title bar ───────────────────────────────────────────────────── | |
| The desktop app's title bar is a web view of its own, above the site's, so no page is ever under it. | 157 | // What the bar above the page shows of it: the title, the search the |
| 158 | // site handed over (`setSearch`), whether there is a page to go back or | |
| 159 | // forward to, and the page's colours, so the bar is the colour of g1t's | |
| 160 | // rail, light or dark, or of whatever page is showing. Sent whenever any | |
| 161 | // of it changes; the bar's search comes back as an event. | |
| The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again. | 162 | var search = null; |
| The desktop app's title bar is a web view of its own, above the site's, so no page is ever under it. | 163 | var report = function () {}; |
| The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again. | 164 | if (FRAME !== "custom") return; |
| 165 | ||
| The desktop app's title bar is a web view of its own, above the site's, so no page is ever under it. | 166 | // A colour the page names, resolved: `var(--color-rail)` is light-dark(…) until it is drawn. |
| 167 | var probe = null; | |
| 168 | function resolve(value) { | |
| 169 | if (!document.body) return ""; | |
| 170 | if (!probe) { | |
| 171 | probe = document.createElement("span"); | |
| 172 | probe.setAttribute("aria-hidden", "true"); | |
| 173 | probe.style.cssText = "position:absolute;width:0;height:0;overflow:hidden;pointer-events:none;"; | |
| The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again. | 174 | } |
| The desktop app's title bar is a web view of its own, above the site's, so no page is ever under it. | 175 | if (probe.parentNode !== document.body) document.body.appendChild(probe); |
| 176 | probe.style.color = ""; | |
| 177 | probe.style.color = value; | |
| 178 | var color = getComputedStyle(probe).color; | |
| 179 | probe.remove(); | |
| 180 | return color; | |
| The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again. | 181 | } |
| The desktop app's title bar is a web view of its own, above the site's, so no page is ever under it. | 182 | function opaque(color) { |
| 183 | return !!color && color !== "transparent" && !/rgba\(.*,\s*0\)$/.test(color); | |
| The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again. | 184 | } |
| The desktop app's title bar is a web view of its own, above the site's, so no page is ever under it. | 185 | function luminance(color) { |
| 186 | var parts = (color.match(/[\d.]+/g) || []).map(Number); | |
| 187 | return parts.length < 3 ? 0 : (0.2126 * parts[0] + 0.7152 * parts[1] + 0.0722 * parts[2]) / 255; | |
| The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again. | 188 | } |
| The desktop app's title bar is a web view of its own, above the site's, so no page is ever under it. | 189 | // g1t's pages name their colours (apps/web/app/app.css); any other page has its own background and text. |
| 190 | function colors() { | |
| 191 | if (!document.body) return null; | |
| 192 | var root = getComputedStyle(document.documentElement); | |
| 193 | var ours = root.getPropertyValue("--color-rail").trim() !== ""; | |
| 194 | var body = getComputedStyle(document.body); | |
| 195 | var background = ours ? resolve("var(--color-rail)") : body.backgroundColor; | |
| 196 | if (!opaque(background)) background = root.backgroundColor; | |
| 197 | if (!opaque(background)) return null; | |
| 198 | var foreground = ours ? resolve("var(--color-fg)") : body.color; | |
| 199 | return { | |
| 200 | background: background, | |
| 201 | foreground: foreground, | |
| 202 | muted: ours ? resolve("var(--color-muted)") : foreground, | |
| 203 | accent: ours ? resolve("var(--color-accent)") : foreground, | |
| 204 | dark: luminance(background) < 0.5, | |
| The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again. | 205 | }; |
| 206 | } | |
| 207 | ||
| The desktop app's title bar is a web view of its own, above the site's, so no page is ever under it. | 208 | var sent = ""; |
| 209 | var waiting = 0; | |
| 210 | report = function () { | |
| 211 | clearTimeout(waiting); | |
| 212 | waiting = setTimeout(function () { | |
| 213 | var nav = window.navigation; | |
| 214 | var page = { | |
| 215 | title: document.title || "g1t", | |
| 216 | search: search ? search.label : null, | |
| 217 | canGoBack: nav && typeof nav.canGoBack === "boolean" ? nav.canGoBack : null, | |
| 218 | canGoForward: nav && typeof nav.canGoForward === "boolean" ? nav.canGoForward : null, | |
| 219 | colors: colors(), | |
| The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again. | 220 | }; |
| The desktop app's title bar is a web view of its own, above the site's, so no page is ever under it. | 221 | var text = JSON.stringify(page); |
| 222 | if (text === sent) return; | |
| 223 | sent = text; | |
| 224 | invoke("title_bar_page", { page: page }).catch(function () {}); | |
| 225 | }, 30); | |
| 226 | }; | |
| The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again. | 227 | |
| The desktop app's title bar is a web view of its own, above the site's, so no page is ever under it. | 228 | document.addEventListener("DOMContentLoaded", function () { |
| 229 | report(); | |
| 230 | if (document.head) new MutationObserver(report).observe(document.head, { subtree: true, childList: true, characterData: true }); | |
| 231 | // The site's Light, Dark or Auto (data-theme on <html>), and the system's own. | |
| 232 | new MutationObserver(report).observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme", "class", "style"] }); | |
| The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again. | 233 | }); |
| The desktop app's title bar is a web view of its own, above the site's, so no page is ever under it. | 234 | window.addEventListener("load", report); |
| 235 | window.addEventListener("pageshow", report); | |
| 236 | window.addEventListener("popstate", report); | |
| 237 | if (window.matchMedia) { | |
| 238 | var scheme = window.matchMedia("(prefers-color-scheme: dark)"); | |
| 239 | if (typeof scheme.addEventListener === "function") scheme.addEventListener("change", report); | |
| 240 | } | |
| 241 | if (window.navigation && typeof window.navigation.addEventListener === "function") { | |
| 242 | window.navigation.addEventListener("currententrychange", report); | |
| 243 | } | |
| The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again. | 244 | |
| The desktop app's title bar is a web view of its own, above the site's, so no page is ever under it. | 245 | // The bar's search was clicked: the site's search opens. |
| 246 | var events = window.__TAURI__ && window.__TAURI__.event; | |
| 247 | if (events && typeof events.listen === "function") { | |
| 248 | events | |
| 249 | .listen("g1t-search", function () { | |
| 250 | if (search) search.open(); | |
| 251 | }) | |
| 252 | .catch(function () {}); | |
| The desktop app draws its own title bar, as Slack and Discord do, and the site can call the app again. | 253 | } |
| preparing for cloud work | 254 | })(); |