From 446b6cd9e77bb61ddfb6f140792df419613dcb2b Mon Sep 17 00:00:00 2001 From: isUnknown Date: Mon, 8 Dec 2025 16:41:13 +0100 Subject: [PATCH] feat: add page template hover label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add visual feedback for hovered page templates: - Display "@page {templateName}" label on page edge hover - Label positioned at top-left of page with 30% opacity - Orange background matching page highlight color - Automatically removed when hovering elements or clicking Ensures consistent UX between page and element hover states. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- public/assets/css/pagedjs-interface.css | 14 ++++++ src/App.vue | 61 +++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/public/assets/css/pagedjs-interface.css b/public/assets/css/pagedjs-interface.css index 6a530f3..c03eeb7 100644 --- a/public/assets/css/pagedjs-interface.css +++ b/public/assets/css/pagedjs-interface.css @@ -178,6 +178,20 @@ font-family: sans-serif; } +.page-hover-label { + position: absolute; + background: #ff8a50; + 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 d6db086..5d129f6 100644 --- a/src/App.vue +++ b/src/App.vue @@ -75,7 +75,13 @@ const getSelectorFromElement = (element) => { const tagName = element.tagName.toLowerCase(); // Filter out state classes (element-hovered, element-selected, page-hovered, page-selected) const classes = Array.from(element.classList).filter( - (cls) => !['element-hovered', 'element-selected', 'page-hovered', 'page-selected'].includes(cls) + (cls) => + ![ + 'element-hovered', + 'element-selected', + 'page-hovered', + 'page-selected', + ].includes(cls) ); if (classes.length > 0) { return `${tagName}.${classes[0]}`; @@ -98,7 +104,7 @@ const createElementLabel = (element) => { const label = doc.createElement('div'); label.className = 'element-hover-label'; label.textContent = getSelectorFromElement(element); - label.style.top = `${rect.top + scrollTop - 30}px`; + label.style.top = `${rect.top + scrollTop - 32}px`; label.style.left = `${rect.left + scrollLeft}px`; doc.body.appendChild(label); @@ -113,6 +119,38 @@ const removeElementLabel = (doc) => { } }; +// Create and position page label on hover +const createPageLabel = (page) => { + const doc = page.ownerDocument; + const existingLabel = doc.querySelector('.page-hover-label'); + if (existingLabel) { + existingLabel.remove(); + } + + const rect = page.getBoundingClientRect(); + const scrollTop = doc.documentElement.scrollTop || doc.body.scrollTop; + const scrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft; + + const templateName = page.getAttribute('data-page-type') || 'default'; + + const label = doc.createElement('div'); + label.className = 'page-hover-label'; + label.textContent = `@page ${templateName}`; + label.style.top = `${rect.top + scrollTop - 32}px`; + label.style.left = `${rect.left + scrollLeft}px`; + + doc.body.appendChild(label); + return label; +}; + +// Remove page label +const removePageLabel = (doc) => { + const label = doc.querySelector('.page-hover-label'); + if (label) { + label.remove(); + } +}; + // Handle mouse movement in iframe const handleIframeMouseMove = (event) => { const pages = event.target.ownerDocument.querySelectorAll('.pagedjs_page'); @@ -133,9 +171,13 @@ const handleIframeMouseMove = (event) => { hoveredPage.value.classList.remove('page-hovered'); } + // Remove previous page label + removePageLabel(event.target.ownerDocument); + // Add highlight to new page (only if not already selected) if (foundPage && !selectedPages.value.includes(foundPage)) { foundPage.classList.add('page-hovered'); + createPageLabel(foundPage); } hoveredPage.value = foundPage; @@ -155,8 +197,9 @@ const handleIframeMouseMove = (event) => { hoveredElement.value.classList.remove('element-hovered'); } - // Remove previous label + // Remove previous labels removeElementLabel(doc); + removePageLabel(doc); // Add highlight to new element (only if not already selected) if (contentElement && contentElement !== selectedElement.value) { @@ -175,7 +218,7 @@ const handleIframeMouseMove = (event) => { hoveredElement.value.classList.remove('element-hovered'); hoveredElement.value = null; } - // Remove label when hovering page edge + // Remove element label when hovering page edge removeElementLabel(event.target.ownerDocument); } }; @@ -276,6 +319,10 @@ const handleIframeClick = (event) => { }); selectedPages.value = sameTemplatePages; + // Remove labels when opening popup + removePageLabel(doc); + removeElementLabel(doc); + pagePopup.value.open(hoveredPage.value, event, sameTemplatePages.length); elementPopup.value.close(); return; @@ -323,12 +370,16 @@ const handleIframeClick = (event) => { hoveredElement.value = null; } + // Get document and remove labels when opening popup + const doc = event.target.ownerDocument; + removeElementLabel(doc); + removePageLabel(doc); + // Select the new element selectedElement.value = contentElement; contentElement.classList.add('element-selected'); // Get count of similar elements - const doc = event.target.ownerDocument; const count = getSimilarElementsCount(contentElement, doc); elementPopup.value.handleIframeClick(event, contentElement, count);