From ee849dab8efb5ed65764b496e8d57529ed74a19e Mon Sep 17 00:00:00 2001 From: isUnknown Date: Mon, 8 Dec 2025 12:27:15 +0100 Subject: [PATCH 01/10] fix: only show popup when clicking inside page template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filter clicks to only trigger ElementPopup for elements inside .pagedjs_page, preventing popups from appearing when clicking on wrapper elements like .pagedjs_pages. Also adds lock/unlock SVG icons. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- public/assets/svg/arrow-left-double-line.svg | 1 + public/assets/svg/lock-line.svg | 1 + public/assets/svg/lock-unlock-line.svg | 1 + src/App.vue | 5 +++-- 4 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 public/assets/svg/arrow-left-double-line.svg create mode 100644 public/assets/svg/lock-line.svg create mode 100644 public/assets/svg/lock-unlock-line.svg diff --git a/public/assets/svg/arrow-left-double-line.svg b/public/assets/svg/arrow-left-double-line.svg new file mode 100644 index 0000000..9091346 --- /dev/null +++ b/public/assets/svg/arrow-left-double-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/svg/lock-line.svg b/public/assets/svg/lock-line.svg new file mode 100644 index 0000000..c8f7d93 --- /dev/null +++ b/public/assets/svg/lock-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/svg/lock-unlock-line.svg b/public/assets/svg/lock-unlock-line.svg new file mode 100644 index 0000000..205f472 --- /dev/null +++ b/public/assets/svg/lock-unlock-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 59bee17..81b2d0f 100644 --- a/src/App.vue +++ b/src/App.vue @@ -91,8 +91,9 @@ const handleIframeClick = (event) => { return; } - // Otherwise handle as element click - if (element.tagName === 'BODY' || element.tagName === 'HTML') { + // Only show popup for elements inside the page template + const isInsidePage = element.closest('.pagedjs_page'); + if (!isInsidePage) { elementPopup.value.close(); pagePopup.value.close(); return; From 7647aadb63eaeeeeb77fcde9f80f0eea94ed8ebf Mon Sep 17 00:00:00 2001 From: isUnknown Date: Mon, 8 Dec 2025 12:29:55 +0100 Subject: [PATCH 02/10] feat: improve page highlight with orange color and template grouping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add --color-page-highlight CSS variable (#ff8a50) - Change page edge highlight from blue to orange - Keep border visible while PagePopup is open - Highlight all pages using the same template (data-page-type) - Display dynamic page count in PagePopup header - Emit close event from PagePopup for proper cleanup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- public/assets/css/src/_variables.scss | 2 ++ src/App.vue | 52 +++++++++++++++++++++++---- src/components/PagePopup.vue | 22 ++++++------ 3 files changed, 57 insertions(+), 19 deletions(-) diff --git a/public/assets/css/src/_variables.scss b/public/assets/css/src/_variables.scss index 43bb561..c632964 100644 --- a/public/assets/css/src/_variables.scss +++ b/public/assets/css/src/_variables.scss @@ -4,6 +4,8 @@ --color-browngray-200: #d0c4ba; --color-browngray-300: #b5a9a1; + --color-page-highlight: #ff8a50; + --border-radius: 0.2rem; --space-xs: 0.5rem; diff --git a/src/App.vue b/src/App.vue index 81b2d0f..ba2b2f1 100644 --- a/src/App.vue +++ b/src/App.vue @@ -19,7 +19,9 @@ provide('activeTab', activeTab); // Page interaction state const hoveredPage = ref(null); +const selectedPages = ref([]); // Pages with active border (when popup is open) const EDGE_THRESHOLD = 30; // px from edge to trigger hover +const PAGE_HIGHLIGHT_COLOR = '#ff8a50'; let savedScrollPercentage = 0; const currentFrameIndex = ref(1); // 1 or 2, which iframe is currently visible @@ -51,6 +53,15 @@ const isNearPageEdge = (pageElement, mouseX, mouseY) => { ); }; +// Get all pages using the same template as the given page +const getPagesWithSameTemplate = (page, doc) => { + const pageType = page.getAttribute('data-page-type') || 'default'; + const allPages = doc.querySelectorAll('.pagedjs_page'); + return Array.from(allPages).filter( + (p) => (p.getAttribute('data-page-type') || 'default') === pageType + ); +}; + // Handle mouse movement in iframe const handleIframeMouseMove = (event) => { const pages = event.target.ownerDocument.querySelectorAll('.pagedjs_page'); @@ -65,20 +76,28 @@ const handleIframeMouseMove = (event) => { // Update hover state if (foundPage !== hoveredPage.value) { - // Remove highlight from previous page - if (hoveredPage.value) { + // Remove highlight from previous page (only if not in selectedPages) + if (hoveredPage.value && !selectedPages.value.includes(hoveredPage.value)) { hoveredPage.value.style.outline = ''; } - // Add highlight to new page - if (foundPage) { - foundPage.style.outline = '2px solid rgba(97, 175, 239, 0.3)'; + // 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`; } hoveredPage.value = foundPage; } }; +// Clear selection highlight from all selected pages +const clearSelectedPages = () => { + selectedPages.value.forEach((page) => { + page.style.outline = ''; + }); + selectedPages.value = []; +}; + // Handle click in iframe const handleIframeClick = (event) => { const element = event.target; @@ -86,7 +105,19 @@ const handleIframeClick = (event) => { // Check if clicking near a page edge if (hoveredPage.value) { event.stopPropagation(); - pagePopup.value.open(hoveredPage.value, event); + + // Clear previous selection + clearSelectedPages(); + + // Get all pages with same template and highlight them + const doc = event.target.ownerDocument; + const sameTemplatePages = getPagesWithSameTemplate(hoveredPage.value, doc); + sameTemplatePages.forEach((page) => { + page.style.outline = `2px solid ${PAGE_HIGHLIGHT_COLOR}`; + }); + selectedPages.value = sameTemplatePages; + + pagePopup.value.open(hoveredPage.value, event, sameTemplatePages.length); elementPopup.value.close(); return; } @@ -94,15 +125,22 @@ const handleIframeClick = (event) => { // Only show popup for elements inside the page template const isInsidePage = element.closest('.pagedjs_page'); if (!isInsidePage) { + clearSelectedPages(); elementPopup.value.close(); pagePopup.value.close(); return; } + clearSelectedPages(); elementPopup.value.handleIframeClick(event); pagePopup.value.close(); }; +// Expose clearSelectedPages for PagePopup to call when closing +const handlePagePopupClose = () => { + clearSelectedPages(); +}; + const renderPreview = async (shouldReloadFromFile = false) => { if (isTransitioning.value) return; isTransitioning.value = true; @@ -228,7 +266,7 @@ onMounted(() => renderPreview(true)); - + From ded974448574b56ae94b520259666f2f14d172cf Mon Sep 17 00:00:00 2001 From: isUnknown Date: Mon, 8 Dec 2025 14:01:27 +0100 Subject: [PATCH 08/10] fix: print PagedJS content via new window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Open iframe content in a new window for printing to avoid blank page issues with srcdoc iframes. The window opens, prints, then closes automatically. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/App.vue | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/App.vue b/src/App.vue index 05c4189..38a69d1 100644 --- a/src/App.vue +++ b/src/App.vue @@ -279,8 +279,28 @@ const handlePrint = (event) => { if ((event.metaKey || event.ctrlKey) && event.key === 'p') { event.preventDefault(); const frame = activeFrame.value; - if (frame && frame.contentWindow) { - frame.contentWindow.print(); + if (frame && frame.contentDocument) { + // Get the full HTML content of the iframe + const content = frame.contentDocument.documentElement.outerHTML; + + // Open a new window with the content + const printWindow = window.open('', '_blank'); + printWindow.document.write(content); + printWindow.document.close(); + + // Wait for content to load then print + printWindow.onload = () => { + printWindow.print(); + printWindow.close(); + }; + + // Fallback if onload doesn't fire + setTimeout(() => { + if (!printWindow.closed) { + printWindow.print(); + printWindow.close(); + } + }, 500); } } }; From 36d34201253a5be1d28157b924adf81a34b67c2d Mon Sep 17 00:00:00 2001 From: isUnknown Date: Mon, 8 Dec 2025 14:02:41 +0100 Subject: [PATCH 09/10] fix: inline all styles when printing PagedJS content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Collect all CSS (inline styles and stylesheet rules) and embed them directly in the print document. This ensures styles are available even when printed from a new window. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/App.vue | 66 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/src/App.vue b/src/App.vue index 38a69d1..f51be59 100644 --- a/src/App.vue +++ b/src/App.vue @@ -275,32 +275,66 @@ watch( ); // Handle Cmd+P / Ctrl+P to print the iframe content -const handlePrint = (event) => { +const handlePrint = async (event) => { if ((event.metaKey || event.ctrlKey) && event.key === 'p') { event.preventDefault(); const frame = activeFrame.value; if (frame && frame.contentDocument) { - // Get the full HTML content of the iframe - const content = frame.contentDocument.documentElement.outerHTML; + const doc = frame.contentDocument; - // Open a new window with the content + // Collect all styles (inline and from stylesheets) + let allStyles = ''; + + // Get inline + + ${bodyContent} + + `; + + // Open print window const printWindow = window.open('', '_blank'); - printWindow.document.write(content); + printWindow.document.write(printContent); printWindow.document.close(); - // Wait for content to load then print - printWindow.onload = () => { + // Wait for rendering then print + setTimeout(() => { printWindow.print(); printWindow.close(); - }; - - // Fallback if onload doesn't fire - setTimeout(() => { - if (!printWindow.closed) { - printWindow.print(); - printWindow.close(); - } - }, 500); + }, 100); } } }; From ea74d1891c3c57765d3bde94c226a2de5fbfcdfa Mon Sep 17 00:00:00 2001 From: isUnknown Date: Mon, 8 Dec 2025 14:05:16 +0100 Subject: [PATCH 10/10] feat: add print button that replaces page content for printing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of trying to print iframe directly, the print button: 1. Collects all styles from the iframe 2. Replaces the main page content with iframe content 3. Triggers window.print() 4. Reloads the page to restore the app This ensures the PagedJS rendered content prints correctly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/App.vue | 153 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 88 insertions(+), 65 deletions(-) diff --git a/src/App.vue b/src/App.vue index f51be59..cee7e1b 100644 --- a/src/App.vue +++ b/src/App.vue @@ -4,7 +4,7 @@ import EditorPanel from './components/editor/EditorPanel.vue'; import ElementPopup from './components/ElementPopup.vue'; import PagePopup from './components/PagePopup.vue'; import PreviewLoader from './components/PreviewLoader.vue'; -import { onMounted, onUnmounted, ref, watch, computed, provide } from 'vue'; +import { onMounted, ref, watch, computed, provide } from 'vue'; import { useStylesheetStore } from './stores/stylesheet'; import Coloris from '@melloware/coloris'; @@ -274,79 +274,66 @@ watch( } ); -// Handle Cmd+P / Ctrl+P to print the iframe content -const handlePrint = async (event) => { - if ((event.metaKey || event.ctrlKey) && event.key === 'p') { - event.preventDefault(); - const frame = activeFrame.value; - if (frame && frame.contentDocument) { - const doc = frame.contentDocument; +// Print the PagedJS content +const printPreview = async () => { + const frame = activeFrame.value; + if (!frame || !frame.contentDocument) return; - // Collect all styles (inline and from stylesheets) - let allStyles = ''; + const doc = frame.contentDocument; - // Get inline - - ${bodyContent} - - `; - - // Open print window - const printWindow = window.open('', '_blank'); - printWindow.document.write(printContent); - printWindow.document.close(); - - // Wait for rendering then print - setTimeout(() => { - printWindow.print(); - printWindow.close(); - }, 100); } } + + // Save current page content + const originalContent = document.body.innerHTML; + const originalStyles = document.head.innerHTML; + + // Replace page content with iframe content + document.head.innerHTML = ` + + Impression + + `; + document.body.innerHTML = doc.body.innerHTML; + + // Print + window.print(); + + // Restore original content after print dialog closes + setTimeout(() => { + document.head.innerHTML = originalStyles; + document.body.innerHTML = originalContent; + // Re-mount Vue app would be needed, so we reload instead + window.location.reload(); + }, 100); }; -onMounted(() => { - renderPreview(true); - window.addEventListener('keydown', handlePrint); -}); - -onUnmounted(() => { - window.removeEventListener('keydown', handlePrint); -}); +onMounted(() => renderPreview(true));