diff --git a/CHANGELOG.md b/CHANGELOG.md index fe38a4d..0897270 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Blue highlight indicator on selected element while feedback panel is open, so users can see which element they picked + +### Fixed + +- Shift+C keyboard shortcut no longer fires while typing in the feedback textarea (Shadow DOM focus detection) +- html2canvas loading in browser extension context - uses fetch instead of script injection to avoid CSP restrictions + - CSS isolation wrapper (`.cf-root`) inside Shadow DOM - uses `all: initial` to fully break CSS inheritance from host page, preventing dark-themed sites from affecting widget appearance - Shadow DOM isolation for widget - host page CSS no longer leaks into the widget UI - Tooltip selector truncation - long CSS selectors in hover tooltip are now truncated to 2 levels with `... >` prefix diff --git a/src/widget.js b/src/widget.js index 5541ddb..0030793 100644 --- a/src/widget.js +++ b/src/widget.js @@ -405,6 +405,11 @@ display: none; } + #${WIDGET_ID}-highlight.selected { + border-color: #3b82f6; + background: rgba(59, 130, 246, 0.1); + } + #${WIDGET_ID}-tooltip { position: fixed; background: #1f2937; @@ -939,25 +944,31 @@ if (typeof html2canvas !== 'undefined') return Promise.resolve(); if (html2canvasPromise) return html2canvasPromise; - html2canvasPromise = new Promise((resolve, reject) => { - // Derive HTTP URL from WS_URL (same host/port) - let baseUrl; - try { - const wsUrl = new URL(WS_URL); - baseUrl = `http://${wsUrl.host}`; - } catch { - baseUrl = `http://localhost:9877`; - } - - const script = document.createElement('script'); - script.src = `${baseUrl}/html2canvas.min.js`; - script.onload = resolve; - script.onerror = () => { + // Derive HTTP URL from WS_URL (same host/port) + let baseUrl; + try { + const wsUrl = new URL(WS_URL); + baseUrl = `http://${wsUrl.host}`; + } catch { + baseUrl = `http://localhost:9877`; + } + + const url = `${baseUrl}/html2canvas.min.js`; + + // Use fetch + new Function to avoid CSP script-src restrictions + // (e.g. when loaded via browser extension on pages with strict CSP) + html2canvasPromise = fetch(url) + .then(res => { + if (!res.ok) throw new Error(`HTTP ${res.status}`); + return res.text(); + }) + .then(scriptText => { + new Function(scriptText)(); + }) + .catch(err => { html2canvasPromise = null; - reject(new Error('Failed to load html2canvas')); - }; - document.head.appendChild(script); - }); + throw new Error('Failed to load html2canvas: ' + (err.message || err)); + }); return html2canvasPromise; } @@ -1431,9 +1442,15 @@ // Shift+C to start annotation mode if (e.key === 'C' && e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey) { - // Don't trigger when typing in input fields - const isInputFocused = ['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName) - || document.activeElement.isContentEditable; + // Don't trigger when the feedback panel is open + const panel = getEl(`${WIDGET_ID}-panel`); + if (panel && panel.classList.contains('active')) return; + + // Don't trigger when typing in input fields (including inside Shadow DOM) + const active = document.activeElement; + const deepActive = active?.shadowRoot?.activeElement || active; + const isInputFocused = ['INPUT', 'TEXTAREA'].includes(deepActive.tagName) + || deepActive.isContentEditable; if (!isInputFocused && !isAnnotationMode) { e.preventDefault(); @@ -1522,6 +1539,18 @@ screenshotEl.style.display = 'none'; } + // Show confirmed-selection highlight on the selected element + if (selectedElement) { + const highlight = getEl(`${WIDGET_ID}-highlight`); + const rect = selectedElement.getBoundingClientRect(); + highlight.style.top = `${rect.top}px`; + highlight.style.left = `${rect.left}px`; + highlight.style.width = `${rect.width}px`; + highlight.style.height = `${rect.height}px`; + highlight.classList.add('selected'); + highlight.style.display = 'block'; + } + panel.classList.add('active'); getEl(`${WIDGET_ID}-description`).focus(); } @@ -1530,6 +1559,9 @@ getEl(`${WIDGET_ID}-panel`).classList.remove('active'); getEl(`${WIDGET_ID}-description`).value = ''; selectedElement = null; + const highlight = getEl(`${WIDGET_ID}-highlight`); + highlight.style.display = 'none'; + highlight.classList.remove('selected'); } async function addItem() {