Skip to content
254 linesCodeBlameRaw

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 work1// 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__";
Merge branch 'main' of github.com:flagon-io/g1t12 // 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 work19
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;
Merge branch 'main' of github.com:flagon-io/g1t93 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;
Merge branch 'main' of github.com:flagon-io/g1t97 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.
Merge branch 'main' of github.com:flagon-io/g1t102 // 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 work104 };
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 work110 // 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 work117 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 work134 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 work150 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 ─────────────────────────────────────────────────────
Merge branch 'main' of github.com:flagon-io/g1t157 // 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;
Merge branch 'main' of github.com:flagon-io/g1t163 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
Merge branch 'main' of github.com:flagon-io/g1t166 // 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 }
Merge branch 'main' of github.com:flagon-io/g1t175 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 }
Merge branch 'main' of github.com:flagon-io/g1t182 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 }
Merge branch 'main' of github.com:flagon-io/g1t185 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 }
Merge branch 'main' of github.com:flagon-io/g1t189 // 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
Merge branch 'main' of github.com:flagon-io/g1t208 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 };
Merge branch 'main' of github.com:flagon-io/g1t221 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
Merge branch 'main' of github.com:flagon-io/g1t228 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 });
Merge branch 'main' of github.com:flagon-io/g1t234 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
Merge branch 'main' of github.com:flagon-io/g1t245 // 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 work254})();

This file's history is long; its oldest lines are credited to the oldest commit read.