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(function updateKeybindings() {
28 const os = detectOS();
29 const isMac = os === "Mac" || os === "iOS";
30
31 function processKeybinding(element) {
32 const [macKeybinding, linuxKeybinding] = element.textContent.split("|");
33 element.textContent = isMac ? macKeybinding : linuxKeybinding;
34 element.classList.add("keybinding");
35 }
36
37 function walkDOM(node) {
38 if (node.nodeType === Node.ELEMENT_NODE) {
39 if (node.tagName.toLowerCase() === "kbd") {
40 processKeybinding(node);
41 } else {
42 Array.from(node.children).forEach(walkDOM);
43 }
44 }
45 }
46
47 // Start the process from the body
48 walkDOM(document.body);
49})();
50
51function darkModeToggle() {
52 var html = document.documentElement;
53 var themeToggleButton = document.getElementById("theme-toggle");
54 var themePopup = document.getElementById("theme-list");
55 var themePopupButtons = themePopup.querySelectorAll("button");
56
57 function setTheme(theme) {
58 html.setAttribute("data-theme", theme);
59 html.setAttribute("data-color-scheme", theme);
60 html.className = theme;
61 localStorage.setItem("mdbook-theme", theme);
62
63 // Force a repaint to ensure the changes take effect in the client immediately
64 document.body.style.display = "none";
65 document.body.offsetHeight;
66 document.body.style.display = "";
67 }
68
69 themeToggleButton.addEventListener("click", function (event) {
70 event.preventDefault();
71 themePopup.style.display =
72 themePopup.style.display === "block" ? "none" : "block";
73 });
74
75 themePopupButtons.forEach(function (button) {
76 button.addEventListener("click", function () {
77 setTheme(this.id);
78 themePopup.style.display = "none";
79 });
80 });
81
82 document.addEventListener("click", function (event) {
83 if (
84 !themePopup.contains(event.target) &&
85 !themeToggleButton.contains(event.target)
86 ) {
87 themePopup.style.display = "none";
88 }
89 });
90
91 // Set initial theme
92 var currentTheme = localStorage.getItem("mdbook-theme");
93 if (currentTheme) {
94 setTheme(currentTheme);
95 } else {
96 // If no theme is set, use the system's preference
97 var systemPreference = window.matchMedia("(prefers-color-scheme: dark)")
98 .matches
99 ? "dark"
100 : "light";
101 setTheme(systemPreference);
102 }
103
104 // Listen for system's preference changes
105 const darkModeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
106 darkModeMediaQuery.addEventListener("change", function (e) {
107 if (!localStorage.getItem("mdbook-theme")) {
108 setTheme(e.matches ? "dark" : "light");
109 }
110 });
111}
112
113const copyMarkdown = () => {
114 const copyButton = document.getElementById("copy-markdown-toggle");
115 if (!copyButton) return;
116
117 // Store the original icon class, loading state, and timeout reference
118 const originalIconClass = "fa fa-copy";
119 let isLoading = false;
120 let iconTimeoutId = null;
121
122 const getCurrentPagePath = () => {
123 const pathname = window.location.pathname;
124
125 // Handle root docs path
126 if (pathname === "/docs/" || pathname === "/docs") {
127 return "getting-started.md";
128 }
129
130 // Remove /docs/ prefix and .html suffix, then add .md
131 const cleanPath = pathname
132 .replace(/^\/docs\//, "")
133 .replace(/\.html$/, "")
134 .replace(/\/$/, "");
135
136 return cleanPath ? cleanPath + ".md" : "getting-started.md";
137 };
138
139 const showToast = (message, isSuccess = true) => {
140 // Remove existing toast if any
141 const existingToast = document.getElementById("copy-toast");
142 existingToast?.remove();
143
144 const toast = document.createElement("div");
145 toast.id = "copy-toast";
146 toast.className = `copy-toast ${isSuccess ? "success" : "error"}`;
147 toast.textContent = message;
148
149 document.body.appendChild(toast);
150
151 // Show toast with animation
152 setTimeout(() => {
153 toast.classList.add("show");
154 }, 10);
155
156 // Hide and remove toast after 2 seconds
157 setTimeout(() => {
158 toast.classList.remove("show");
159 setTimeout(() => {
160 toast.parentNode?.removeChild(toast);
161 }, 300);
162 }, 2000);
163 };
164
165 const changeButtonIcon = (iconClass, duration = 1000) => {
166 const icon = copyButton.querySelector("i");
167 if (!icon) return;
168
169 // Clear any existing timeout
170 if (iconTimeoutId) {
171 clearTimeout(iconTimeoutId);
172 iconTimeoutId = null;
173 }
174
175 icon.className = iconClass;
176
177 if (duration > 0) {
178 iconTimeoutId = setTimeout(() => {
179 icon.className = originalIconClass;
180 iconTimeoutId = null;
181 }, duration);
182 }
183 };
184
185 const fetchAndCopyMarkdown = async () => {
186 // Prevent multiple simultaneous requests
187 if (isLoading) return;
188
189 try {
190 isLoading = true;
191 changeButtonIcon("fa fa-spinner fa-spin", 0); // Don't auto-restore spinner
192
193 const pagePath = getCurrentPagePath();
194 const rawUrl = `https://raw.githubusercontent.com/zed-industries/zed/main/docs/src/${pagePath}`;
195
196 const response = await fetch(rawUrl);
197 if (!response.ok) {
198 throw new Error(
199 `Failed to fetch markdown: ${response.status} ${response.statusText}`,
200 );
201 }
202
203 const markdownContent = await response.text();
204
205 // Copy to clipboard using modern API
206 if (navigator.clipboard?.writeText) {
207 await navigator.clipboard.writeText(markdownContent);
208 } else {
209 // Fallback: throw error if clipboard API isn't available
210 throw new Error("Clipboard API not supported in this browser");
211 }
212
213 changeButtonIcon("fa fa-check", 1000);
214 showToast("Page content copied to clipboard!");
215 } catch (error) {
216 console.error("Error copying markdown:", error);
217 changeButtonIcon("fa fa-exclamation-triangle", 2000);
218 showToast("Failed to copy markdown. Please try again.", false);
219 } finally {
220 isLoading = false;
221 }
222 };
223
224 copyButton.addEventListener("click", fetchAndCopyMarkdown);
225};
226
227// Initialize functionality when DOM is loaded
228document.addEventListener("DOMContentLoaded", () => {
229 copyMarkdown();
230});
231
232// Collapsible sidebar navigation for entire sections
233// Note: Initial collapsed state is applied in index.hbs to prevent flicker
234function initCollapsibleSidebar() {
235 var sidebar = document.getElementById("sidebar");
236 if (!sidebar) return;
237
238 var chapterList = sidebar.querySelector("ol.chapter");
239 if (!chapterList) return;
240
241 var partTitles = Array.from(chapterList.querySelectorAll("li.part-title"));
242
243 partTitles.forEach(function (partTitle) {
244 // Get all sibling elements that belong to this section
245 var sectionItems = getSectionItems(partTitle);
246
247 if (sectionItems.length > 0) {
248 setupCollapsibleSection(partTitle, sectionItems);
249 }
250 });
251}
252
253// Saves the list of collapsed section names to sessionStorage
254// This gets reset when the tab is closed and opened again
255function saveCollapsedSections() {
256 var collapsedSections = [];
257 var partTitles = document.querySelectorAll(
258 "#sidebar li.part-title.collapsible",
259 );
260
261 partTitles.forEach(function (partTitle) {
262 if (!partTitle.classList.contains("expanded")) {
263 collapsedSections.push(partTitle._sectionName);
264 }
265 });
266
267 try {
268 sessionStorage.setItem(
269 "sidebar-collapsed-sections",
270 JSON.stringify(collapsedSections),
271 );
272 } catch (e) {
273 // sessionStorage might not be available
274 }
275}
276
277function getSectionItems(partTitle) {
278 var items = [];
279 var sibling = partTitle.nextElementSibling;
280
281 while (sibling) {
282 // Stop when we hit another part-title
283 if (sibling.classList.contains("part-title")) {
284 break;
285 }
286 items.push(sibling);
287 sibling = sibling.nextElementSibling;
288 }
289
290 return items;
291}
292
293function setupCollapsibleSection(partTitle, sectionItems) {
294 partTitle.classList.add("collapsible");
295 partTitle.setAttribute("role", "button");
296 partTitle.setAttribute("tabindex", "0");
297 partTitle._sectionItems = sectionItems;
298
299 var isCurrentlyCollapsed = partTitle._isCollapsed;
300 if (isCurrentlyCollapsed) {
301 partTitle.setAttribute("aria-expanded", "false");
302 } else {
303 partTitle.classList.add("expanded");
304 partTitle.setAttribute("aria-expanded", "true");
305 }
306
307 partTitle.addEventListener("click", function (e) {
308 e.preventDefault();
309 toggleSection(partTitle);
310 });
311
312 // a11y: Add keyboard support (Enter and Space)
313 partTitle.addEventListener("keydown", function (e) {
314 if (e.key === "Enter" || e.key === " ") {
315 e.preventDefault();
316 toggleSection(partTitle);
317 }
318 });
319}
320
321function toggleSection(partTitle) {
322 var isExpanded = partTitle.classList.contains("expanded");
323 var sectionItems = partTitle._sectionItems;
324 var spacerAfter = partTitle._spacerAfter;
325
326 if (isExpanded) {
327 partTitle.classList.remove("expanded");
328 partTitle.setAttribute("aria-expanded", "false");
329 sectionItems.forEach(function (item) {
330 item.classList.add("section-hidden");
331 });
332 if (spacerAfter) {
333 spacerAfter.classList.add("section-hidden");
334 }
335 } else {
336 partTitle.classList.add("expanded");
337 partTitle.setAttribute("aria-expanded", "true");
338 sectionItems.forEach(function (item) {
339 item.classList.remove("section-hidden");
340 });
341 if (spacerAfter) {
342 spacerAfter.classList.remove("section-hidden");
343 }
344 }
345
346 saveCollapsedSections();
347}
348
349document.addEventListener("DOMContentLoaded", function () {
350 initCollapsibleSidebar();
351});