Skip to repository content385 lines · 10.7 KB · javascript
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T00:29:46.148Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
plugins.js
1function detectOS() {
2 var userAgent = navigator.userAgent;
3
4 var platform = navigator.platform;
5 var macosPlatforms = ["Macintosh", "MacIntel", "MacPPC", "Mac68K"];
6 var windowsPlatforms = ["Win32", "Win64", "Windows", "WinCE"];
7 var iosPlatforms = ["iPhone", "iPad", "iPod"];
8
9 if (macosPlatforms.indexOf(platform) !== -1) {
10 return "Mac";
11 } else if (iosPlatforms.indexOf(platform) !== -1) {
12 return "iOS";
13 } else if (windowsPlatforms.indexOf(platform) !== -1) {
14 return "Windows";
15 } else if (/Android/.test(userAgent)) {
16 return "Android";
17 } else if (/Linux/.test(platform)) {
18 return "Linux";
19 }
20
21 return "Unknown";
22}
23
24var os = detectOS();
25console.log("Operating System:", os);
26
27// Defer keybinding processing to avoid blocking initial render
28function updateKeybindings() {
29 const os = detectOS();
30 const isMac = os === "Mac" || os === "iOS";
31
32 function processKeybinding(element) {
33 const [macKeybinding, linuxKeybinding] = element.textContent.split("|");
34 element.textContent = isMac ? macKeybinding : linuxKeybinding;
35 element.classList.add("keybinding");
36 }
37
38 // Process all kbd elements at once (more efficient than walking entire DOM)
39 const kbdElements = document.querySelectorAll("kbd");
40 kbdElements.forEach(processKeybinding);
41}
42
43// Use requestIdleCallback if available, otherwise requestAnimationFrame
44if (typeof requestIdleCallback === "function") {
45 requestIdleCallback(updateKeybindings);
46} else {
47 requestAnimationFrame(updateKeybindings);
48}
49
50function darkModeToggle() {
51 var html = document.documentElement;
52
53 function setTheme(theme) {
54 html.setAttribute("data-theme", theme);
55 html.setAttribute("data-color-scheme", theme);
56 html.className = theme;
57 localStorage.setItem("mdbook-theme", theme);
58 }
59
60 // Set initial theme
61 var currentTheme = localStorage.getItem("mdbook-theme");
62 if (currentTheme) {
63 setTheme(currentTheme);
64 } else {
65 // If no theme is set, use the system's preference
66 var systemPreference = window.matchMedia("(prefers-color-scheme: dark)")
67 .matches
68 ? "dark"
69 : "light";
70 setTheme(systemPreference);
71 }
72
73 // Listen for system's preference changes
74 const darkModeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
75 darkModeMediaQuery.addEventListener("change", function (e) {
76 if (!localStorage.getItem("mdbook-theme")) {
77 setTheme(e.matches ? "dark" : "light");
78 }
79 });
80}
81
82const copyPageActions = () => {
83 const headerCopyMarkdownButton = document.getElementById(
84 "copy-markdown-toggle",
85 );
86
87 const actionButtons = new Map();
88 let isLoading = false;
89
90 const showToast = (message, isSuccess = true) => {
91 const existingToast = document.getElementById("copy-toast");
92 existingToast?.remove();
93
94 const toast = document.createElement("div");
95 toast.id = "copy-toast";
96 toast.className = `copy-toast ${isSuccess ? "success" : "error"}`;
97 toast.textContent = message;
98
99 document.body.appendChild(toast);
100
101 setTimeout(() => {
102 toast.classList.add("show");
103 }, 10);
104
105 setTimeout(() => {
106 toast.classList.remove("show");
107 setTimeout(() => {
108 toast.parentNode?.removeChild(toast);
109 }, 300);
110 }, 2000);
111 };
112
113 const registerButton = (button, originalIconClass) => {
114 if (!button) return;
115 actionButtons.set(button, {
116 originalIconClass,
117 iconTimeoutId: null,
118 });
119 };
120
121 registerButton(headerCopyMarkdownButton, "fa fa-copy");
122
123 const changeButtonIcon = (button, iconClass, duration = 1000) => {
124 const state = actionButtons.get(button);
125 if (!state) return;
126
127 const icon = button.querySelector("i");
128 if (!icon) return;
129
130 if (state.iconTimeoutId) {
131 clearTimeout(state.iconTimeoutId);
132 state.iconTimeoutId = null;
133 }
134
135 icon.className = iconClass;
136
137 if (duration > 0) {
138 state.iconTimeoutId = setTimeout(() => {
139 icon.className = state.originalIconClass;
140 state.iconTimeoutId = null;
141 }, duration);
142 }
143 };
144
145 const copyText = async (text) => {
146 if (!navigator.clipboard?.writeText) {
147 throw new Error("Clipboard API not supported in this browser");
148 }
149
150 await navigator.clipboard.writeText(text);
151 };
152
153 const getContentRoot = () =>
154 document.querySelector("#content main .content-wrap") ||
155 document.querySelector("#content > main");
156
157 const markdownAlternateHref = () =>
158 document
159 .querySelector('link[rel="alternate"][type="text/markdown"]')
160 ?.getAttribute("href");
161
162 const insertPageActionButtons = () => {
163 const contentRoot = getContentRoot();
164 if (
165 !contentRoot ||
166 contentRoot.querySelector(".page-actions") ||
167 !headerCopyMarkdownButton ||
168 !markdownAlternateHref()
169 ) {
170 return;
171 }
172
173 const firstHeading = contentRoot.querySelector("h1");
174 if (!firstHeading) return;
175
176 const header = document.createElement("div");
177 header.className = "page-header";
178
179 const actions = document.createElement("div");
180 actions.className = "page-actions";
181
182 firstHeading.insertAdjacentElement("beforebegin", header);
183 headerCopyMarkdownButton.classList.remove(
184 "icon-button",
185 "ib-hidden-mobile",
186 );
187 headerCopyMarkdownButton.classList.add("page-action", "page-action-icon");
188 actions.append(headerCopyMarkdownButton);
189 header.append(firstHeading, actions);
190 };
191
192 const markdownUrl = () => {
193 const alternateHref = markdownAlternateHref();
194 if (!alternateHref) return null;
195
196 const url = new URL(alternateHref, window.location.href);
197 if (
198 url.origin === window.location.origin &&
199 url.pathname.startsWith("/docs/") &&
200 !window.location.pathname.startsWith("/docs/")
201 ) {
202 return url.pathname.replace(/^\/docs/, "") || "/";
203 }
204
205 if (url.origin === window.location.origin) {
206 return `${url.pathname}${url.search}${url.hash}`;
207 }
208
209 return url.pathname;
210 };
211
212 const fetchAndCopyMarkdown = async (button) => {
213 // Prevent multiple simultaneous requests
214 if (isLoading) return;
215
216 try {
217 isLoading = true;
218 changeButtonIcon(button, "fa fa-spinner fa-spin", 0); // Don't auto-restore spinner
219
220 const url = markdownUrl();
221 if (!url) {
222 throw new Error("Markdown alternate link not found");
223 }
224
225 const response = await fetch(url);
226 if (!response.ok) {
227 throw new Error(
228 `Failed to fetch markdown: ${response.status} ${response.statusText}`,
229 );
230 }
231
232 const markdownContent = await response.text();
233
234 await copyText(markdownContent);
235
236 changeButtonIcon(button, "fa fa-check", 1000);
237 showToast("Markdown copied to clipboard!");
238 } catch (error) {
239 console.error("Error copying markdown:", error);
240 changeButtonIcon(button, "fa fa-exclamation-triangle", 2000);
241 showToast("Failed to copy markdown. Please try again.", false);
242 } finally {
243 isLoading = false;
244 }
245 };
246
247 headerCopyMarkdownButton?.addEventListener("click", () =>
248 fetchAndCopyMarkdown(headerCopyMarkdownButton),
249 );
250
251 insertPageActionButtons();
252};
253
254const initializePlugins = () => {
255 darkModeToggle();
256 requestAnimationFrame(copyPageActions);
257};
258
259if (document.readyState === "loading") {
260 document.addEventListener("DOMContentLoaded", initializePlugins);
261} else {
262 initializePlugins();
263}
264
265// Collapsible sidebar navigation for entire sections
266// Note: Initial collapsed state is applied in index.hbs to prevent flicker
267function initCollapsibleSidebar() {
268 var sidebar = document.getElementById("sidebar");
269 if (!sidebar) return;
270
271 var chapterList = sidebar.querySelector("ol.chapter");
272 if (!chapterList) return;
273
274 var partTitles = Array.from(chapterList.querySelectorAll("li.part-title"));
275
276 partTitles.forEach(function (partTitle) {
277 // Get all sibling elements that belong to this section
278 var sectionItems = getSectionItems(partTitle);
279
280 if (sectionItems.length > 0) {
281 setupCollapsibleSection(partTitle, sectionItems);
282 }
283 });
284}
285
286// Saves the list of collapsed section names to sessionStorage
287// This gets reset when the tab is closed and opened again
288function saveCollapsedSections() {
289 var collapsedSections = [];
290 var partTitles = document.querySelectorAll(
291 "#sidebar li.part-title.collapsible",
292 );
293
294 partTitles.forEach(function (partTitle) {
295 if (!partTitle.classList.contains("expanded")) {
296 collapsedSections.push(partTitle._sectionName);
297 }
298 });
299
300 try {
301 sessionStorage.setItem(
302 "sidebar-collapsed-sections",
303 JSON.stringify(collapsedSections),
304 );
305 } catch (e) {
306 // sessionStorage might not be available
307 }
308}
309
310function getSectionItems(partTitle) {
311 var items = [];
312 var sibling = partTitle.nextElementSibling;
313
314 while (sibling) {
315 // Stop when we hit another part-title
316 if (sibling.classList.contains("part-title")) {
317 break;
318 }
319 items.push(sibling);
320 sibling = sibling.nextElementSibling;
321 }
322
323 return items;
324}
325
326function setupCollapsibleSection(partTitle, sectionItems) {
327 partTitle.classList.add("collapsible");
328 partTitle.setAttribute("role", "button");
329 partTitle.setAttribute("tabindex", "0");
330 partTitle._sectionItems = sectionItems;
331
332 var isCurrentlyCollapsed = partTitle._isCollapsed;
333 if (isCurrentlyCollapsed) {
334 partTitle.setAttribute("aria-expanded", "false");
335 } else {
336 partTitle.classList.add("expanded");
337 partTitle.setAttribute("aria-expanded", "true");
338 }
339
340 partTitle.addEventListener("click", function (e) {
341 e.preventDefault();
342 toggleSection(partTitle);
343 });
344
345 // a11y: Add keyboard support (Enter and Space)
346 partTitle.addEventListener("keydown", function (e) {
347 if (e.key === "Enter" || e.key === " ") {
348 e.preventDefault();
349 toggleSection(partTitle);
350 }
351 });
352}
353
354function toggleSection(partTitle) {
355 var isExpanded = partTitle.classList.contains("expanded");
356 var sectionItems = partTitle._sectionItems;
357 var spacerAfter = partTitle._spacerAfter;
358
359 if (isExpanded) {
360 partTitle.classList.remove("expanded");
361 partTitle.setAttribute("aria-expanded", "false");
362 sectionItems.forEach(function (item) {
363 item.classList.add("section-hidden");
364 });
365 if (spacerAfter) {
366 spacerAfter.classList.add("section-hidden");
367 }
368 } else {
369 partTitle.classList.add("expanded");
370 partTitle.setAttribute("aria-expanded", "true");
371 sectionItems.forEach(function (item) {
372 item.classList.remove("section-hidden");
373 });
374 if (spacerAfter) {
375 spacerAfter.classList.remove("section-hidden");
376 }
377 }
378
379 saveCollapsedSections();
380}
381
382document.addEventListener("DOMContentLoaded", function () {
383 initCollapsibleSidebar();
384});
385