diff --git a/public/app.js b/public/app.js index 62bcb5896..7ab784756 100644 --- a/public/app.js +++ b/public/app.js @@ -17,6 +17,16 @@ let allCommands = []; // CONTENT LOADING // ============================================ +function escapeHtml(value) { + if (typeof value !== "string") return ""; + return value + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); +} + async function loadContent() { try { const [commandsRes, patternsRes] = await Promise.all([ @@ -96,13 +106,13 @@ function renderPatternsWithTabs(patterns, antipatterns) { const tabsHTML = patterns .map((category, i) => ``) + >${escapeHtml(category.name)}`) .join(""); // Build panels with WAI-ARIA attributes @@ -112,7 +122,7 @@ function renderPatternsWithTabs(patterns, antipatterns) { return `
Don't
Do
@@ -167,7 +177,9 @@ function renderPatternsWithTabs(patterns, antipatterns) { p.classList.remove('active'); p.setAttribute('hidden', ''); }); - const activePanel = container.querySelector(`[data-panel="${tabName}"]`); + const escapedName = CSS.escape(tabName); + const activePanel = container.querySelector(`[data-panel="${escapedName}"]`); + if (!activePanel) return; activePanel.classList.add('active'); activePanel.removeAttribute('hidden'); }; @@ -229,10 +241,20 @@ document.addEventListener("click", (e) => { const copyBtn = e.target.closest("[data-copy]"); if (copyBtn) { const textToCopy = copyBtn.dataset.copy; - navigator.clipboard.writeText(textToCopy).then(() => { + 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(); + } } });