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
 24// Usage
 25var os = detectOS();
 26console.log("Operating System:", os);
 27
 28(function 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  function walkDOM(node) {
 39    if (node.nodeType === Node.ELEMENT_NODE) {
 40      if (node.tagName.toLowerCase() === "kbd") {
 41        processKeybinding(node);
 42      } else {
 43        Array.from(node.children).forEach(walkDOM);
 44      }
 45    }
 46  }
 47
 48  // Start the process from the body
 49  walkDOM(document.body);
 50})();
 51
 52function darkModeToggle() {
 53  var html = document.documentElement;
 54  var themeToggleButton = document.getElementById("theme-toggle");
 55  var themePopup = document.getElementById("theme-list");
 56  var themePopupButtons = themePopup.querySelectorAll("button");
 57
 58  function setTheme(theme) {
 59    html.setAttribute("data-theme", theme);
 60    html.setAttribute("data-color-scheme", theme);
 61    html.className = theme;
 62    localStorage.setItem("mdbook-theme", theme);
 63
 64    // Force a repaint to ensure the changes take effect in the client immediately
 65    document.body.style.display = "none";
 66    document.body.offsetHeight;
 67    document.body.style.display = "";
 68  }
 69
 70  themeToggleButton.addEventListener("click", function (event) {
 71    event.preventDefault();
 72    themePopup.style.display =
 73      themePopup.style.display === "block" ? "none" : "block";
 74  });
 75
 76  themePopupButtons.forEach(function (button) {
 77    button.addEventListener("click", function () {
 78      setTheme(this.id);
 79      themePopup.style.display = "none";
 80    });
 81  });
 82
 83  document.addEventListener("click", function (event) {
 84    if (
 85      !themePopup.contains(event.target) &&
 86      !themeToggleButton.contains(event.target)
 87    ) {
 88      themePopup.style.display = "none";
 89    }
 90  });
 91
 92  // Set initial theme
 93  var currentTheme = localStorage.getItem("mdbook-theme");
 94  if (currentTheme) {
 95    setTheme(currentTheme);
 96  } else {
 97    // If no theme is set, use the system's preference
 98    var systemPreference = window.matchMedia("(prefers-color-scheme: dark)")
 99      .matches
100      ? "dark"
101      : "light";
102    setTheme(systemPreference);
103  }
104
105  // Listen for system's preference changes
106  const darkModeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
107  darkModeMediaQuery.addEventListener("change", function (e) {
108    if (!localStorage.getItem("mdbook-theme")) {
109      setTheme(e.matches ? "dark" : "light");
110    }
111  });
112}
113
114const copyMarkdown = () => {
115  const copyButton = document.getElementById("copy-markdown-toggle");
116  if (!copyButton) return;
117
118  // Store the original icon class, loading state, and timeout reference
119  const originalIconClass = "fa fa-copy";
120  let isLoading = false;
121  let iconTimeoutId = null;
122
123  const getCurrentPagePath = () => {
124    const pathname = window.location.pathname;
125
126    // Handle root docs path
127    if (pathname === "/docs/" || pathname === "/docs") {
128      return "getting-started.md";
129    }
130
131    // Remove /docs/ prefix and .html suffix, then add .md
132    const cleanPath = pathname
133      .replace(/^\/docs\//, "")
134      .replace(/\.html$/, "")
135      .replace(/\/$/, "");
136
137    return cleanPath ? cleanPath + ".md" : "getting-started.md";
138  };
139
140  const showToast = (message, isSuccess = true) => {
141    // Remove existing toast if any
142    const existingToast = document.getElementById("copy-toast");
143    existingToast?.remove();
144
145    const toast = document.createElement("div");
146    toast.id = "copy-toast";
147    toast.className = `copy-toast ${isSuccess ? "success" : "error"}`;
148    toast.textContent = message;
149
150    document.body.appendChild(toast);
151
152    // Show toast with animation
153    setTimeout(() => {
154      toast.classList.add("show");
155    }, 10);
156
157    // Hide and remove toast after 2 seconds
158    setTimeout(() => {
159      toast.classList.remove("show");
160      setTimeout(() => {
161        toast.parentNode?.removeChild(toast);
162      }, 300);
163    }, 2000);
164  };
165
166  const changeButtonIcon = (iconClass, duration = 1000) => {
167    const icon = copyButton.querySelector("i");
168    if (!icon) return;
169
170    // Clear any existing timeout
171    if (iconTimeoutId) {
172      clearTimeout(iconTimeoutId);
173      iconTimeoutId = null;
174    }
175
176    icon.className = iconClass;
177
178    if (duration > 0) {
179      iconTimeoutId = setTimeout(() => {
180        icon.className = originalIconClass;
181        iconTimeoutId = null;
182      }, duration);
183    }
184  };
185
186  const fetchAndCopyMarkdown = async () => {
187    // Prevent multiple simultaneous requests
188    if (isLoading) return;
189
190    try {
191      isLoading = true;
192      changeButtonIcon("fa fa-spinner fa-spin", 0); // Don't auto-restore spinner
193
194      const pagePath = getCurrentPagePath();
195      const rawUrl = `https://raw.githubusercontent.com/zed-industries/zed/main/docs/src/${pagePath}`;
196
197      const response = await fetch(rawUrl);
198      if (!response.ok) {
199        throw new Error(
200          `Failed to fetch markdown: ${response.status} ${response.statusText}`,
201        );
202      }
203
204      const markdownContent = await response.text();
205
206      // Copy to clipboard using modern API
207      if (navigator.clipboard?.writeText) {
208        await navigator.clipboard.writeText(markdownContent);
209      } else {
210        // Fallback: throw error if clipboard API isn't available
211        throw new Error("Clipboard API not supported in this browser");
212      }
213
214      changeButtonIcon("fa fa-check", 1000);
215      showToast("Page content copied to clipboard!");
216    } catch (error) {
217      console.error("Error copying markdown:", error);
218      changeButtonIcon("fa fa-exclamation-triangle", 2000);
219      showToast("Failed to copy markdown. Please try again.", false);
220    } finally {
221      isLoading = false;
222    }
223  };
224
225  copyButton.addEventListener("click", fetchAndCopyMarkdown);
226};
227
228// Initialize functionality when DOM is loaded
229document.addEventListener("DOMContentLoaded", () => {
230  copyMarkdown();
231});