@@ -87,6 +87,11 @@
--download-btn-border-hover: hsla(220, 60%, 50%, 0.2);
--download-btn-shadow: hsla(220, 40%, 60%, 0.1);
+ --toast-bg: hsla(220, 93%, 98%);
+ --toast-border: hsla(220, 93%, 42%, 0.3);
+ --toast-border-success: hsla(120, 73%, 42%, 0.3);
+ --toast-border-error: hsla(0, 90%, 50%, 0.3);
+
--footer-btn-bg: hsl(220, 60%, 98%, 0.4);
--footer-btn-bg-hover: hsl(220, 60%, 93%, 0.5);
--footer-btn-border: hsla(220, 60%, 40%, 0.15);
@@ -166,6 +171,11 @@
--download-btn-border-hover: hsla(220, 90%, 80%, 0.4);
--download-btn-shadow: hsla(220, 50%, 60%, 0.15);
+ --toast-bg: hsla(220, 20%, 98%, 0.05);
+ --toast-border: hsla(220, 93%, 70%, 0.2);
+ --toast-border-success: hsla(120, 90%, 60%, 0.3);
+ --toast-border-error: hsla(0, 90%, 80%, 0.3);
+
--footer-btn-bg: hsl(220, 90%, 95%, 0.01);
--footer-btn-bg-hover: hsl(220, 90%, 50%, 0.05);
--footer-btn-border: hsla(220, 90%, 90%, 0.05);
@@ -131,7 +131,7 @@
<i class="fa fa-bars"></i>
</label>
- <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
+ <button id="theme-toggle" class="icon-button" type="button" title="Change Theme" aria-label="Change Theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
@@ -139,8 +139,12 @@
<li role="none"><button role="menuitem" class="theme" id="dark">Dark</button></li>
</ul>
+ <button id="copy-markdown-toggle" class="icon-button" type="button" title="Copy Page as Markdown" aria-label="Copy page as markdown">
+ <i class="fa fa-copy"></i>
+ </button>
+
{{#if search_enabled}}
- <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
+ <button id="search-toggle" class="icon-button" type="button" title="Search (s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
{{/if}}
@@ -6,3 +6,40 @@ kbd.keybinding {
display: inline-block;
margin: 0 2px;
}
+
+#copy-markdown-toggle i {
+ font-weight: 500 !important;
+ -webkit-text-stroke: 0.5px currentColor;
+}
+
+.copy-toast {
+ position: fixed;
+ top: 72px;
+ right: 16px;
+ padding: 12px 16px;
+ border-radius: 4px;
+ font-size: 14px;
+ font-weight: 500;
+ color: var(--fg);
+ background: var(--toast-bg);
+ border: 1px solid var(--toast-border);
+ z-index: 1000;
+ opacity: 0;
+ transform: translateY(-10px);
+ transition: all 0.1s ease-in-out;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
+ max-width: 280px;
+}
+
+.copy-toast.success {
+ border-color: var(--toast-border-success);
+}
+
+.copy-toast.error {
+ border-color: var(--toast-border-error);
+}
+
+.copy-toast.show {
+ opacity: 1;
+ transform: translateY(0);
+}
@@ -110,3 +110,122 @@ function darkModeToggle() {
}
});
}
+
+const copyMarkdown = () => {
+ const copyButton = document.getElementById("copy-markdown-toggle");
+ if (!copyButton) return;
+
+ // Store the original icon class, loading state, and timeout reference
+ const originalIconClass = "fa fa-copy";
+ let isLoading = false;
+ let iconTimeoutId = null;
+
+ const getCurrentPagePath = () => {
+ const pathname = window.location.pathname;
+
+ // Handle root docs path
+ if (pathname === "/docs/" || pathname === "/docs") {
+ return "getting-started.md";
+ }
+
+ // Remove /docs/ prefix and .html suffix, then add .md
+ const cleanPath = pathname
+ .replace(/^\/docs\//, "")
+ .replace(/\.html$/, "")
+ .replace(/\/$/, "");
+
+ return cleanPath ? cleanPath + ".md" : "getting-started.md";
+ };
+
+ const showToast = (message, isSuccess = true) => {
+ // Remove existing toast if any
+ const existingToast = document.getElementById("copy-toast");
+ existingToast?.remove();
+
+ const toast = document.createElement("div");
+ toast.id = "copy-toast";
+ toast.className = `copy-toast ${isSuccess ? "success" : "error"}`;
+ toast.textContent = message;
+
+ document.body.appendChild(toast);
+
+ // Show toast with animation
+ setTimeout(() => {
+ toast.classList.add("show");
+ }, 10);
+
+ // Hide and remove toast after 2 seconds
+ setTimeout(() => {
+ toast.classList.remove("show");
+ setTimeout(() => {
+ toast.parentNode?.removeChild(toast);
+ }, 300);
+ }, 2000);
+ };
+
+ const changeButtonIcon = (iconClass, duration = 1000) => {
+ const icon = copyButton.querySelector("i");
+ if (!icon) return;
+
+ // Clear any existing timeout
+ if (iconTimeoutId) {
+ clearTimeout(iconTimeoutId);
+ iconTimeoutId = null;
+ }
+
+ icon.className = iconClass;
+
+ if (duration > 0) {
+ iconTimeoutId = setTimeout(() => {
+ icon.className = originalIconClass;
+ iconTimeoutId = null;
+ }, duration);
+ }
+ };
+
+ const fetchAndCopyMarkdown = async () => {
+ // Prevent multiple simultaneous requests
+ if (isLoading) return;
+
+ try {
+ isLoading = true;
+ changeButtonIcon("fa fa-spinner fa-spin", 0); // Don't auto-restore spinner
+
+ const pagePath = getCurrentPagePath();
+ const rawUrl = `https://raw.githubusercontent.com/zed-industries/zed/main/docs/src/${pagePath}`;
+
+ const response = await fetch(rawUrl);
+ if (!response.ok) {
+ throw new Error(
+ `Failed to fetch markdown: ${response.status} ${response.statusText}`,
+ );
+ }
+
+ const markdownContent = await response.text();
+
+ // Copy to clipboard using modern API
+ if (navigator.clipboard?.writeText) {
+ await navigator.clipboard.writeText(markdownContent);
+ } else {
+ // Fallback: throw error if clipboard API isn't available
+ throw new Error("Clipboard API not supported in this browser");
+ }
+
+ changeButtonIcon("fa fa-check", 1000);
+ showToast("Page content copied to clipboard!");
+ } catch (error) {
+ console.error("Error copying markdown:", error);
+ changeButtonIcon("fa fa-exclamation-triangle", 2000);
+ showToast("Failed to copy markdown. Please try again.", false);
+ } finally {
+ isLoading = false;
+ }
+ };
+
+ copyButton.addEventListener("click", fetchAndCopyMarkdown);
+};
+
+// Initialize functionality when DOM is loaded
+document.addEventListener("DOMContentLoaded", () => {
+ copyMarkdown();
+});