From f9e9e657127fcaa9dd18a0f90ba61682d8714fff Mon Sep 17 00:00:00 2001 From: isUnknown Date: Mon, 8 Dec 2025 16:26:39 +0100 Subject: [PATCH] feat: add element hover label and refactor to use CSS classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add visual feedback for hovered elements: - Display element selector label (e.g., "p", "h1.title") on hover - Label positioned at top-left of element with 30% opacity Refactor all hover/selection styles to use CSS classes instead of inline styles: - .page-hovered, .page-selected for page states - .element-hovered, .element-selected for element states - .element-hover-label for the floating label This improves maintainability and separation of concerns by moving styling logic to CSS files instead of JavaScript. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- public/assets/css/pagedjs-interface.css | 32 ++++++++++++ src/App.vue | 68 +++++++++++++++++++++---- 2 files changed, 89 insertions(+), 11 deletions(-) diff --git a/public/assets/css/pagedjs-interface.css b/public/assets/css/pagedjs-interface.css index bcef24a..b4ad502 100644 --- a/public/assets/css/pagedjs-interface.css +++ b/public/assets/css/pagedjs-interface.css @@ -144,6 +144,38 @@ /*--------------------------------------------------------------------------------------*/ } +/* Hover and selection states for pages and elements */ +.page-hovered { + outline: 2px solid #ff8a5050 !important; +} + +.page-selected { + outline: 2px solid #ff8a50 !important; +} + +.element-hovered { + outline: 2px solid #7136ff50 !important; +} + +.element-selected { + outline: 2px dashed #7136ff !important; + background-color: #7136ff1a !important; +} + +.element-hover-label { + position: absolute; + background: #7136ff; + color: white; + padding: 0.25rem 0.5rem; + border-radius: 4px; + font-size: 0.875rem; + font-weight: 600; + opacity: 0.3; + pointer-events: none; + z-index: 9999; + font-family: sans-serif; +} + /* Marks (to delete when merge in paged.js) */ .pagedjs_marks-crop { diff --git a/src/App.vue b/src/App.vue index 238a104..2826c7f 100644 --- a/src/App.vue +++ b/src/App.vue @@ -65,6 +65,45 @@ const getPagesWithSameTemplate = (page, doc) => { ); }; +// Get selector for element (same logic as ElementPopup) +const getSelectorFromElement = (element) => { + if (element.id) { + return `#${element.id}`; + } + const tagName = element.tagName.toLowerCase(); + const classes = Array.from(element.classList); + if (classes.length > 0) { + return `${tagName}.${classes[0]}`; + } + return tagName; +}; + +// Create and position element label on hover +const createElementLabel = (element) => { + const doc = element.ownerDocument; + const existingLabel = doc.querySelector('.element-hover-label'); + if (existingLabel) { + existingLabel.remove(); + } + + const label = doc.createElement('div'); + label.className = 'element-hover-label'; + label.textContent = getSelectorFromElement(element); + label.style.top = `${element.offsetTop}px`; + label.style.left = `${element.offsetLeft}px`; + + doc.body.appendChild(label); + return label; +}; + +// Remove element label +const removeElementLabel = (doc) => { + const label = doc.querySelector('.element-hover-label'); + if (label) { + label.remove(); + } +}; + // Handle mouse movement in iframe const handleIframeMouseMove = (event) => { const pages = event.target.ownerDocument.querySelectorAll('.pagedjs_page'); @@ -82,12 +121,12 @@ const handleIframeMouseMove = (event) => { if (foundPage !== hoveredPage.value) { // Remove highlight from previous page (only if not in selectedPages) if (hoveredPage.value && !selectedPages.value.includes(hoveredPage.value)) { - hoveredPage.value.style.outline = ''; + hoveredPage.value.classList.remove('page-hovered'); } // Add highlight to new page (only if not already selected) if (foundPage && !selectedPages.value.includes(foundPage)) { - foundPage.style.outline = `2px solid ${PAGE_HIGHLIGHT_COLOR}50`; + foundPage.classList.add('page-hovered'); } hoveredPage.value = foundPage; @@ -96,16 +135,21 @@ const handleIframeMouseMove = (event) => { // If not near page edge, check for content element hover if (!foundPage) { const contentElement = getContentElement(event.target); + const doc = event.target.ownerDocument; if (contentElement !== hoveredElement.value) { // Remove highlight from previous element (only if not selected) if (hoveredElement.value && hoveredElement.value !== selectedElement.value) { - hoveredElement.value.style.outline = ''; + hoveredElement.value.classList.remove('element-hovered'); } + // Remove previous label + removeElementLabel(doc); + // Add highlight to new element (only if not already selected) if (contentElement && contentElement !== selectedElement.value) { - contentElement.style.outline = `2px solid ${ELEMENT_HIGHLIGHT_COLOR}50`; + contentElement.classList.add('element-hovered'); + createElementLabel(contentElement); } hoveredElement.value = contentElement; @@ -113,16 +157,18 @@ const handleIframeMouseMove = (event) => { } else { // Clear element hover when hovering page edge if (hoveredElement.value && hoveredElement.value !== selectedElement.value) { - hoveredElement.value.style.outline = ''; + hoveredElement.value.classList.remove('element-hovered'); hoveredElement.value = null; } + // Remove label when hovering page edge + removeElementLabel(event.target.ownerDocument); } }; // Clear selection highlight from all selected pages const clearSelectedPages = () => { selectedPages.value.forEach((page) => { - page.style.outline = ''; + page.classList.remove('page-selected'); }); selectedPages.value = []; }; @@ -153,8 +199,9 @@ const getContentElement = (element) => { // Clear selected element highlight const clearSelectedElement = () => { if (selectedElement.value) { - selectedElement.value.style.outline = ''; - selectedElement.value.style.backgroundColor = ''; + selectedElement.value.classList.remove('element-selected'); + const doc = selectedElement.value.ownerDocument; + removeElementLabel(doc); selectedElement.value = null; } }; @@ -182,7 +229,7 @@ const handleIframeClick = (event) => { const doc = event.target.ownerDocument; const sameTemplatePages = getPagesWithSameTemplate(hoveredPage.value, doc); sameTemplatePages.forEach((page) => { - page.style.outline = `2px solid ${PAGE_HIGHLIGHT_COLOR}`; + page.classList.add('page-selected'); }); selectedPages.value = sameTemplatePages; @@ -219,8 +266,7 @@ const handleIframeClick = (event) => { // Select the new element selectedElement.value = contentElement; - contentElement.style.outline = `2px dashed ${ELEMENT_HIGHLIGHT_COLOR}`; - contentElement.style.backgroundColor = `${ELEMENT_HIGHLIGHT_COLOR}1A`; // 10% opacity + contentElement.classList.add('element-selected'); // Get count of similar elements const doc = event.target.ownerDocument;