From 12595c54545bd8328e2fe421660e14461b7a60d9 Mon Sep 17 00:00:00 2001 From: isUnknown Date: Mon, 8 Dec 2025 14:15:06 +0100 Subject: [PATCH] feat: add purple highlight for content elements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add --color-purple variable (#7136ff) - Hover: dashed purple outline on content elements - Selected: purple background with 30% opacity - ElementPopup: purple label and instance count - Track hovered and selected elements separately from pages - Clear element selection when popup closes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- public/assets/css/src/_variables.scss | 1 + src/App.vue | 75 +++++++++++++++++++++++++-- src/components/ElementPopup.vue | 19 ++++--- 3 files changed, 85 insertions(+), 10 deletions(-) diff --git a/public/assets/css/src/_variables.scss b/public/assets/css/src/_variables.scss index c632964..e8b86eb 100644 --- a/public/assets/css/src/_variables.scss +++ b/public/assets/css/src/_variables.scss @@ -5,6 +5,7 @@ --color-browngray-300: #b5a9a1; --color-page-highlight: #ff8a50; + --color-purple: #7136ff; --border-radius: 0.2rem; diff --git a/src/App.vue b/src/App.vue index cee7e1b..44ce2a6 100644 --- a/src/App.vue +++ b/src/App.vue @@ -20,8 +20,11 @@ provide('activeTab', activeTab); // Page interaction state const hoveredPage = ref(null); const selectedPages = ref([]); // Pages with active border (when popup is open) +const hoveredElement = ref(null); // Currently hovered content element +const selectedElement = ref(null); // Selected element (when popup is open) const EDGE_THRESHOLD = 30; // px from edge to trigger hover const PAGE_HIGHLIGHT_COLOR = '#ff8a50'; +const ELEMENT_HIGHLIGHT_COLOR = '#7136ff'; let savedScrollPercentage = 0; const currentFrameIndex = ref(1); // 1 or 2, which iframe is currently visible @@ -67,6 +70,7 @@ const handleIframeMouseMove = (event) => { const pages = event.target.ownerDocument.querySelectorAll('.pagedjs_page'); let foundPage = null; + // Check if hovering near page edge for (const page of pages) { if (isNearPageEdge(page, event.clientX, event.clientY)) { foundPage = page; @@ -74,7 +78,7 @@ const handleIframeMouseMove = (event) => { } } - // Update hover state + // Update page hover state if (foundPage !== hoveredPage.value) { // Remove highlight from previous page (only if not in selectedPages) if (hoveredPage.value && !selectedPages.value.includes(hoveredPage.value)) { @@ -88,6 +92,31 @@ const handleIframeMouseMove = (event) => { hoveredPage.value = foundPage; } + + // If not near page edge, check for content element hover + if (!foundPage) { + const contentElement = getContentElement(event.target); + + if (contentElement !== hoveredElement.value) { + // Remove highlight from previous element (only if not selected) + if (hoveredElement.value && hoveredElement.value !== selectedElement.value) { + hoveredElement.value.style.outline = ''; + } + + // Add highlight to new element (only if not already selected) + if (contentElement && contentElement !== selectedElement.value) { + contentElement.style.outline = `2px dashed ${ELEMENT_HIGHLIGHT_COLOR}`; + } + + hoveredElement.value = contentElement; + } + } else { + // Clear element hover when hovering page edge + if (hoveredElement.value && hoveredElement.value !== selectedElement.value) { + hoveredElement.value.style.outline = ''; + hoveredElement.value = null; + } + } }; // Clear selection highlight from all selected pages @@ -121,6 +150,22 @@ const getContentElement = (element) => { return null; }; +// Clear selected element highlight +const clearSelectedElement = () => { + if (selectedElement.value) { + selectedElement.value.style.outline = ''; + selectedElement.value.style.backgroundColor = ''; + selectedElement.value = null; + } +}; + +// Get count of similar elements (same tag) +const getSimilarElementsCount = (element, doc) => { + const tagName = element.tagName; + const allElements = doc.querySelectorAll(tagName); + return allElements.length; +}; + // Handle click in iframe const handleIframeClick = (event) => { const element = event.target; @@ -129,8 +174,9 @@ const handleIframeClick = (event) => { if (hoveredPage.value) { event.stopPropagation(); - // Clear previous selection + // Clear previous selections clearSelectedPages(); + clearSelectedElement(); // Get all pages with same template and highlight them const doc = event.target.ownerDocument; @@ -149,6 +195,7 @@ const handleIframeClick = (event) => { const isInsidePage = element.closest('.pagedjs_page'); if (!isInsidePage) { clearSelectedPages(); + clearSelectedElement(); elementPopup.value.close(); pagePopup.value.close(); return; @@ -158,13 +205,28 @@ const handleIframeClick = (event) => { const contentElement = getContentElement(element); if (!contentElement) { clearSelectedPages(); + clearSelectedElement(); elementPopup.value.close(); pagePopup.value.close(); return; } + // Clear page selections clearSelectedPages(); - elementPopup.value.handleIframeClick(event, contentElement); + + // Clear previous element selection + clearSelectedElement(); + + // Select the new element + selectedElement.value = contentElement; + contentElement.style.outline = ''; + contentElement.style.backgroundColor = `${ELEMENT_HIGHLIGHT_COLOR}4D`; // 30% opacity + + // Get count of similar elements + const doc = event.target.ownerDocument; + const count = getSimilarElementsCount(contentElement, doc); + + elementPopup.value.handleIframeClick(event, contentElement, count); pagePopup.value.close(); }; @@ -173,6 +235,11 @@ const handlePagePopupClose = () => { clearSelectedPages(); }; +// Handle ElementPopup close +const handleElementPopupClose = () => { + clearSelectedElement(); +}; + const renderPreview = async (shouldReloadFromFile = false) => { if (isTransitioning.value) return; isTransitioning.value = true; @@ -356,7 +423,7 @@ onMounted(() => renderPreview(true)); - +