diff --git a/docs/theme/css/variables.css b/docs/theme/css/variables.css index 1fe0e7dc8514d46acf202c6c995a7f50f5acab2b..dceba25af87e62ee64459984950d3e6921421d39 100644 --- a/docs/theme/css/variables.css +++ b/docs/theme/css/variables.css @@ -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); diff --git a/docs/theme/index.hbs b/docs/theme/index.hbs index 4339a02d1722d0d64e67b35de66889d9a849e9a4..86008ed690a9644b32227f68fbec148b94907640 100644 --- a/docs/theme/index.hbs +++ b/docs/theme/index.hbs @@ -131,7 +131,7 @@ - + + {{#if search_enabled}} - {{/if}} diff --git a/docs/theme/plugins.css b/docs/theme/plugins.css index 9d5d09fe736a96eeb0f26f6bc3c7a20a55664f31..8c9f0c438e8e1ecd43cd770183d0a6a3bbfe0a4f 100644 --- a/docs/theme/plugins.css +++ b/docs/theme/plugins.css @@ -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); +} diff --git a/docs/theme/plugins.js b/docs/theme/plugins.js index 76a295353f7abc391ba4d84998a636a8ed6dab36..44c4c59978d31bd24ed0f0a28266868bb9951e51 100644 --- a/docs/theme/plugins.js +++ b/docs/theme/plugins.js @@ -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(); +});