diff --git a/public/app.js b/public/app.js index 6ae7f41c0..7ab784756 100644 --- a/public/app.js +++ b/public/app.js @@ -1,6 +1,6 @@ import { - initGlassTerminal, - renderTerminalLayout, + initGlassTerminal, + renderTerminalLayout, } from "./js/components/glass-terminal.js"; import { initLensEffect } from "./js/components/lens.js"; import { initFrameworkViz } from "./js/components/framework-viz.js"; @@ -18,66 +18,49 @@ let allCommands = []; // ============================================ function escapeHtml(value) { - if (typeof value !== "string") return ""; - return value - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """) - .replaceAll("'", "'"); -} - -function copyTextFallback(text) { - const textArea = document.createElement("textarea"); - textArea.value = text; - textArea.setAttribute("readonly", ""); - textArea.style.position = "absolute"; - textArea.style.left = "-9999px"; - document.body.appendChild(textArea); - textArea.select(); - try { - return document.execCommand("copy"); - } catch { - return false; - } finally { - document.body.removeChild(textArea); - } + if (typeof value !== "string") return ""; + return value + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); } async function loadContent() { - try { - const [commandsRes, patternsRes] = await Promise.all([ - fetch("/api/commands"), - fetch("/api/patterns"), - ]); + try { + const [commandsRes, patternsRes] = await Promise.all([ + fetch("/api/commands"), + fetch("/api/patterns"), + ]); - // Check for HTTP errors - if (!commandsRes.ok) { - throw new Error(`Commands API failed: ${commandsRes.status}`); - } - if (!patternsRes.ok) { - throw new Error(`Patterns API failed: ${patternsRes.status}`); - } + // Check for HTTP errors + if (!commandsRes.ok) { + throw new Error(`Commands API failed: ${commandsRes.status}`); + } + if (!patternsRes.ok) { + throw new Error(`Patterns API failed: ${patternsRes.status}`); + } - allCommands = await commandsRes.json(); - const patternsData = await patternsRes.json(); + allCommands = await commandsRes.json(); + const patternsData = await patternsRes.json(); - // Render commands (Glass Terminal) - renderTerminalLayout(allCommands); + // Render commands (Glass Terminal) + renderTerminalLayout(allCommands); - // Render patterns with tabbed navigation - renderPatternsWithTabs(patternsData.patterns, patternsData.antipatterns); - } catch (error) { - console.error("Failed to load content:", error); - showLoadError(error); - } + // Render patterns with tabbed navigation + renderPatternsWithTabs(patternsData.patterns, patternsData.antipatterns); + } catch (error) { + console.error("Failed to load content:", error); + showLoadError(error); + } } function showLoadError(error) { - // Show error in commands section - const commandsGallery = document.querySelector(".commands-gallery"); - if (commandsGallery) { - commandsGallery.innerHTML = ` + // Show error in commands section + const commandsGallery = document.querySelector('.commands-gallery'); + if (commandsGallery) { + commandsGallery.innerHTML = ` `; - } + } - // Show error in patterns section - const patternsContainer = document.getElementById("patterns-categories"); - if (patternsContainer) { - patternsContainer.innerHTML = ` + // Show error in patterns section + const patternsContainer = document.getElementById("patterns-categories"); + if (patternsContainer) { + patternsContainer.innerHTML = ` `; - } + } } function renderPatternsWithTabs(patterns, antipatterns) { - const container = document.getElementById("patterns-categories"); - if (!container || !patterns || !antipatterns) return; + const container = document.getElementById("patterns-categories"); + if (!container || !patterns || !antipatterns) return; - // Create a map of antipatterns by category name - const antipatternMap = {}; - antipatterns.forEach((cat) => { - antipatternMap[cat.name] = cat.items; - }); + // Create a map of antipatterns by category name + const antipatternMap = {}; + antipatterns.forEach(cat => { + antipatternMap[cat.name] = cat.items; + }); - // Generate unique IDs for tabs - const tabId = (name) => - `pattern-tab-${name.toLowerCase().replace(/\s+/g, "-")}`; - const panelId = (name) => - `pattern-panel-${name.toLowerCase().replace(/\s+/g, "-")}`; + // Generate unique IDs for tabs + const tabId = (name) => `pattern-tab-${name.toLowerCase().replace(/\s+/g, '-')}`; + const panelId = (name) => `pattern-panel-${name.toLowerCase().replace(/\s+/g, '-')}`; - // Build tabs with WAI-ARIA attributes - const tabsHTML = patterns - .map((category, i) => { - const categoryName = String(category.name ?? ""); - return ``; - }) - .join(""); + id="${tabId(category.name)}" + aria-selected="${i === 0 ? 'true' : 'false'}" + aria-controls="${panelId(category.name)}" + tabindex="${i === 0 ? '0' : '-1'}" + >${escapeHtml(category.name)}`) + .join(""); - // Build panels with WAI-ARIA attributes - const panelsHTML = patterns - .map((category, i) => { - const categoryName = String(category.name ?? ""); - const antiItems = antipatternMap[category.name] || []; - return ` + // Build panels with WAI-ARIA attributes + const panelsHTML = patterns + .map((category, i) => { + const antiItems = antipatternMap[category.name] || []; + return `
@@ -167,87 +144,82 @@ function renderPatternsWithTabs(patterns, antipatterns) {
`; - }) - .join(""); + }) + .join(""); - container.innerHTML = ` + container.innerHTML = `
${tabsHTML}
${panelsHTML}
`; - const tabs = container.querySelectorAll(".pattern-tab"); - const panels = container.querySelectorAll(".pattern-panel"); + const tabs = container.querySelectorAll('.pattern-tab'); + const panels = container.querySelectorAll('.pattern-panel'); - // Function to switch tabs - const switchTab = (newTab) => { - const tabName = newTab.dataset.tab; + // Function to switch tabs + const switchTab = (newTab) => { + const tabName = newTab.dataset.tab; - // Update ARIA attributes on all tabs - tabs.forEach((t) => { - t.classList.remove("active"); - t.setAttribute("aria-selected", "false"); - t.setAttribute("tabindex", "-1"); - }); + // Update ARIA attributes on all tabs + tabs.forEach(t => { + t.classList.remove('active'); + t.setAttribute('aria-selected', 'false'); + t.setAttribute('tabindex', '-1'); + }); - // Activate the new tab - newTab.classList.add("active"); - newTab.setAttribute("aria-selected", "true"); - newTab.setAttribute("tabindex", "0"); - newTab.focus(); + // Activate the new tab + newTab.classList.add('active'); + newTab.setAttribute('aria-selected', 'true'); + newTab.setAttribute('tabindex', '0'); + newTab.focus(); - // Update panels - panels.forEach((p) => { - p.classList.remove("active"); - p.setAttribute("hidden", ""); - }); - const escapedTabName = - typeof CSS !== "undefined" && CSS.escape - ? CSS.escape(tabName) - : tabName.replaceAll('"', '\\"'); - const activePanel = container.querySelector( - `[data-panel="${escapedTabName}"]`, - ); - if (!activePanel) return; - activePanel.classList.add("active"); - activePanel.removeAttribute("hidden"); - }; + // Update panels + panels.forEach(p => { + p.classList.remove('active'); + p.setAttribute('hidden', ''); + }); + const escapedName = CSS.escape(tabName); + const activePanel = container.querySelector(`[data-panel="${escapedName}"]`); + if (!activePanel) return; + activePanel.classList.add('active'); + activePanel.removeAttribute('hidden'); + }; - // Tab click handling - tabs.forEach((tab) => { - tab.addEventListener("click", () => switchTab(tab)); - }); + // Tab click handling + tabs.forEach(tab => { + tab.addEventListener('click', () => switchTab(tab)); + }); - // Keyboard navigation (Arrow keys, Home, End) - tabs.forEach((tab, index) => { - tab.addEventListener("keydown", (e) => { - let targetIndex = index; + // Keyboard navigation (Arrow keys, Home, End) + tabs.forEach((tab, index) => { + tab.addEventListener('keydown', (e) => { + let targetIndex = index; - switch (e.key) { - case "ArrowLeft": - case "ArrowUp": - e.preventDefault(); - targetIndex = index === 0 ? tabs.length - 1 : index - 1; - break; - case "ArrowRight": - case "ArrowDown": - e.preventDefault(); - targetIndex = index === tabs.length - 1 ? 0 : index + 1; - break; - case "Home": - e.preventDefault(); - targetIndex = 0; - break; - case "End": - e.preventDefault(); - targetIndex = tabs.length - 1; - break; - default: - return; - } + switch (e.key) { + case 'ArrowLeft': + case 'ArrowUp': + e.preventDefault(); + targetIndex = index === 0 ? tabs.length - 1 : index - 1; + break; + case 'ArrowRight': + case 'ArrowDown': + e.preventDefault(); + targetIndex = index === tabs.length - 1 ? 0 : index + 1; + break; + case 'Home': + e.preventDefault(); + targetIndex = 0; + break; + case 'End': + e.preventDefault(); + targetIndex = tabs.length - 1; + break; + default: + return; + } - switchTab(tabs[targetIndex]); - }); - }); + switchTab(tabs[targetIndex]); + }); + }); } // ============================================ @@ -256,57 +228,55 @@ function renderPatternsWithTabs(patterns, antipatterns) { // Handle bundle download clicks via event delegation document.addEventListener("click", (e) => { - const bundleBtn = e.target.closest("[data-bundle]"); - if (bundleBtn) { - const provider = bundleBtn.dataset.bundle; - const prefixToggle = document.getElementById("prefix-toggle"); - const usePrefixed = prefixToggle && prefixToggle.checked; - const bundleName = usePrefixed ? `${provider}-prefixed` : provider; - window.location.href = `/api/download/bundle/${bundleName}`; - } + const bundleBtn = e.target.closest("[data-bundle]"); + if (bundleBtn) { + const provider = bundleBtn.dataset.bundle; + const prefixToggle = document.getElementById('prefix-toggle'); + const usePrefixed = prefixToggle && prefixToggle.checked; + const bundleName = usePrefixed ? `${provider}-prefixed` : provider; + window.location.href = `/api/download/bundle/${bundleName}`; + } - // Handle copy button clicks - const copyBtn = e.target.closest("[data-copy]"); - if (copyBtn) { - const textToCopy = copyBtn.dataset.copy; - const onCopySuccess = () => { - copyBtn.classList.add("copied"); - setTimeout(() => copyBtn.classList.remove("copied"), 1500); - }; - - if (navigator.clipboard?.writeText) { - navigator.clipboard - .writeText(textToCopy) - .then(onCopySuccess) - .catch(() => { - if (copyTextFallback(textToCopy)) { - onCopySuccess(); - } - }); - } else if (copyTextFallback(textToCopy)) { - onCopySuccess(); - } - } + // Handle copy button clicks + const copyBtn = e.target.closest("[data-copy]"); + if (copyBtn) { + const textToCopy = copyBtn.dataset.copy; + const onCopied = () => { + copyBtn.classList.add('copied'); + setTimeout(() => copyBtn.classList.remove('copied'), 1500); + }; + if (navigator.clipboard?.writeText) { + navigator.clipboard.writeText(textToCopy).then(onCopied).catch(() => {}); + } else { + // Fallback for non-HTTPS or older browsers + const ta = Object.assign(document.createElement('textarea'), { value: textToCopy, style: 'position:fixed;left:-9999px' }); + document.body.appendChild(ta); + ta.select(); + try { document.execCommand('copy'); onCopied(); } catch {} + ta.remove(); + } + } }); + // ============================================ // STARTUP // ============================================ function init() { - initAnchorScroll(); - initHashTracking(); - initLensEffect(); - initScrollReveal(); - initGlassTerminal(); - initFrameworkViz(); - loadContent(); + initAnchorScroll(); + initHashTracking(); + initLensEffect(); + initScrollReveal(); + initGlassTerminal(); + initFrameworkViz(); + loadContent(); - document.body.classList.add("loaded"); + document.body.classList.add("loaded"); } if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", init); + document.addEventListener("DOMContentLoaded", init); } else { - init(); + init(); }