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));
-
+