diff --git a/public/assets/css/src/_variables.scss b/public/assets/css/src/_variables.scss index c632964..43bb561 100644 --- a/public/assets/css/src/_variables.scss +++ b/public/assets/css/src/_variables.scss @@ -4,8 +4,6 @@ --color-browngray-200: #d0c4ba; --color-browngray-300: #b5a9a1; - --color-page-highlight: #ff8a50; - --border-radius: 0.2rem; --space-xs: 0.5rem; diff --git a/public/assets/css/style.css b/public/assets/css/style.css index ce20e3f..4be3450 100644 --- a/public/assets/css/style.css +++ b/public/assets/css/style.css @@ -30,7 +30,6 @@ button { --color-browngray-050: #f5f3f0; --color-browngray-200: #d0c4ba; --color-browngray-300: #b5a9a1; - --color-page-highlight: #ff8a50; --border-radius: 0.2rem; --space-xs: 0.5rem; --curve: cubic-bezier(0.86, 0, 0.07, 1); diff --git a/public/assets/css/style.css.map b/public/assets/css/style.css.map index 67cbf6d..6140ba4 100644 --- a/public/assets/css/style.css.map +++ b/public/assets/css/style.css.map @@ -1 +1 @@ -{"version":3,"sources":["src/_reset.scss","style.css","src/_variables.scss","src/_text.scss","src/_print-styles.scss","src/_forms.scss","src/_buttons.scss"],"names":[],"mappings":"AAAA;;EAEE,UAAA;EACA,SAAA;ACCF;;ADEA;;;;;;EAME,SAAA;ACCF;;ADEA;;EAEE,YAAA;EACA,aAAA;EAEA,mCAAA;ACAF;;ADGA;EACE,6BAAA;EACA,YAAA;ACAF;;ACzBA;EACE,yBAAA;EACA,8BAAA;EACA,8BAAA;EACA,8BAAA;EAEA,+BAAA;EAEA,uBAAA;EAEA,kBAAA;EAEA,uCAAA;ADwBF;;AEpCA;;;;;;;;;;;;;EAaE,uBAAA;AFuCF;;AGpDA,yBAAA;AACA;EACE,QAAA;EACA,2BAAA;AHuDF;AGrDA;EACE,8BAAA;OAAA,kBAAA;AHuDF;;AGpDA;EACE;IACE,sBAAA;EHuDF;AACF;AGrDA;EACE,+BAAA;AHuDF;;AItEA;;;EAGE,4CAAA;AJyEF;;AItEA;EACE,YAAA;AJyEF;;AItEA,2BAAA;AACA;EACE,yCAAA;UAAA,iCAAA;EACA,iDAAA;EACA,0BAAA;EACA,YAAA;EACA,kBAAA;AJyEF;AIvEE;EACE,uBAAA;EACA,kBAAA;EACA,YAAA;EACA,OAAA;EACA,kBAAA;EACA,uBAAA;EACA,sCAAA;EACA,iCAAA;EACA,8CAAA;EACA,kBAAA;EACA,kBAAA;EACA,mBAAA;EACA,UAAA;EACA,kBAAA;EACA,qDACE;EAEF,WAAA;AJuEJ;AIpEE;EACE,UAAA;EACA,mBAAA;AJsEJ;;AIjEE;EACE,6BAAA;EACA,8BAAA;AJoEJ;AIlEE;EACE,mDAAA;AJoEJ;AIjEE;EACE,0BAAA;AJmEJ;AIjEI;EACE,8BAAA;AJmEN;AIhEI;EACE,aAAA;AJkEN;AIhEM;;EAEE,UAAA;AJkER;AI/DM;EACE,oCAAA;AJiER;AI9DM;EACE,aAAA;EACA,WAAA;AJgER;AI9DQ;EACE,aAAA;EACA,WAAA;AJgEV;AI5DM;EACE,UAAA;AJ8DR;AI7DQ;EACE,aAAA;AJ+DV;AI9DU;EACE,kBAAA;EACA,eAAA;EACA,cAAA;EACA,SAAA;EACA,WAAA;EACA,eAAA;AJgEZ;AI7DU;EACE,oBAAA;EACA,WAAA;AJ+DZ;AIzDI;EACE,aAAA;EACA,eAAA;EACA,wBAAA;AJ2DN;AIzDM;EACE,WAAA;AJ2DR;AIzDM;EACE,UAAA;AJ2DR;AIzDQ;EACE,UAAA;AJ2DV;AIvDU;EACE,UAAA;AJyDZ;;AKlLA;EACE,eAAA;EAEA,4CAAA;EACA,iCAAA;EACA,uCAAA;EACA,mCAAA;EACA,sBAAA;ALoLF;AKlLE;EACE,sBAAA;EACA,WAAA;ALoLJ;AKhLI;EACE,sBAAA;EACA,WAAA;EACA,YAAA;ALkLN","file":"style.css"} \ No newline at end of file +{"version":3,"sources":["src/_reset.scss","style.css","src/_variables.scss","src/_text.scss","src/_print-styles.scss","src/_forms.scss","src/_buttons.scss"],"names":[],"mappings":"AAAA;;EAEE,UAAA;EACA,SAAA;ACCF;;ADEA;;;;;;EAME,SAAA;ACCF;;ADEA;;EAEE,YAAA;EACA,aAAA;EAEA,mCAAA;ACAF;;ADGA;EACE,6BAAA;EACA,YAAA;ACAF;;ACzBA;EACE,yBAAA;EACA,8BAAA;EACA,8BAAA;EACA,8BAAA;EAEA,uBAAA;EAEA,kBAAA;EAEA,uCAAA;ADyBF;;AEnCA;;;;;;;;;;;;;EAaE,uBAAA;AFsCF;;AGnDA,yBAAA;AACA;EACE,QAAA;EACA,2BAAA;AHsDF;AGpDA;EACE,8BAAA;OAAA,kBAAA;AHsDF;;AGnDA;EACE;IACE,sBAAA;EHsDF;AACF;AGpDA;EACE,+BAAA;AHsDF;;AIrEA;;;EAGE,4CAAA;AJwEF;;AIrEA;EACE,YAAA;AJwEF;;AIrEA,2BAAA;AACA;EACE,yCAAA;UAAA,iCAAA;EACA,iDAAA;EACA,0BAAA;EACA,YAAA;EACA,kBAAA;AJwEF;AItEE;EACE,uBAAA;EACA,kBAAA;EACA,YAAA;EACA,OAAA;EACA,kBAAA;EACA,uBAAA;EACA,sCAAA;EACA,iCAAA;EACA,8CAAA;EACA,kBAAA;EACA,kBAAA;EACA,mBAAA;EACA,UAAA;EACA,kBAAA;EACA,qDACE;EAEF,WAAA;AJsEJ;AInEE;EACE,UAAA;EACA,mBAAA;AJqEJ;;AIhEE;EACE,6BAAA;EACA,8BAAA;AJmEJ;AIjEE;EACE,mDAAA;AJmEJ;AIhEE;EACE,0BAAA;AJkEJ;AIhEI;EACE,8BAAA;AJkEN;AI/DI;EACE,aAAA;AJiEN;AI/DM;;EAEE,UAAA;AJiER;AI9DM;EACE,oCAAA;AJgER;AI7DM;EACE,aAAA;EACA,WAAA;AJ+DR;AI7DQ;EACE,aAAA;EACA,WAAA;AJ+DV;AI3DM;EACE,UAAA;AJ6DR;AI5DQ;EACE,aAAA;AJ8DV;AI7DU;EACE,kBAAA;EACA,eAAA;EACA,cAAA;EACA,SAAA;EACA,WAAA;EACA,eAAA;AJ+DZ;AI5DU;EACE,oBAAA;EACA,WAAA;AJ8DZ;AIxDI;EACE,aAAA;EACA,eAAA;EACA,wBAAA;AJ0DN;AIxDM;EACE,WAAA;AJ0DR;AIxDM;EACE,UAAA;AJ0DR;AIxDQ;EACE,UAAA;AJ0DV;AItDU;EACE,UAAA;AJwDZ;;AKjLA;EACE,eAAA;EAEA,4CAAA;EACA,iCAAA;EACA,uCAAA;EACA,mCAAA;EACA,sBAAA;ALmLF;AKjLE;EACE,sBAAA;EACA,WAAA;ALmLJ;AK/KI;EACE,sBAAA;EACA,WAAA;EACA,YAAA;ALiLN","file":"style.css"} \ No newline at end of file diff --git a/public/assets/svg/arrow-left-double-line.svg b/public/assets/svg/arrow-left-double-line.svg deleted file mode 100644 index 9091346..0000000 --- a/public/assets/svg/arrow-left-double-line.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/assets/svg/lock-line.svg b/public/assets/svg/lock-line.svg deleted file mode 100644 index c8f7d93..0000000 --- a/public/assets/svg/lock-line.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/assets/svg/lock-unlock-line.svg b/public/assets/svg/lock-unlock-line.svg deleted file mode 100644 index 205f472..0000000 --- a/public/assets/svg/lock-unlock-line.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index cee7e1b..59bee17 100644 --- a/src/App.vue +++ b/src/App.vue @@ -19,9 +19,7 @@ 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 @@ -53,15 +51,6 @@ 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'); @@ -76,51 +65,20 @@ const handleIframeMouseMove = (event) => { // Update hover state if (foundPage !== hoveredPage.value) { - // Remove highlight from previous page (only if not in selectedPages) - if (hoveredPage.value && !selectedPages.value.includes(hoveredPage.value)) { + // Remove highlight from previous page + if (hoveredPage.value) { hoveredPage.value.style.outline = ''; } - // 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`; + // Add highlight to new page + if (foundPage) { + foundPage.style.outline = '2px solid rgba(97, 175, 239, 0.3)'; } hoveredPage.value = foundPage; } }; -// Clear selection highlight from all selected pages -const clearSelectedPages = () => { - selectedPages.value.forEach((page) => { - page.style.outline = ''; - }); - selectedPages.value = []; -}; - -// Content elements that can trigger ElementPopup -const CONTENT_ELEMENTS = [ - 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', - 'IMG', 'FIGURE', 'FIGCAPTION', - 'UL', 'OL', 'LI', - 'BLOCKQUOTE', 'PRE', 'CODE', - 'TABLE', 'THEAD', 'TBODY', 'TR', 'TH', 'TD', - 'A', 'SPAN', 'STRONG', 'EM', 'B', 'I', 'U', - 'ARTICLE', 'SECTION', 'ASIDE', 'HEADER', 'FOOTER', 'NAV' -]; - -// Check if element is a content element (or find closest content parent) -const getContentElement = (element) => { - let current = element; - while (current && current.tagName !== 'BODY') { - if (CONTENT_ELEMENTS.includes(current.tagName)) { - return current; - } - current = current.parentElement; - } - return null; -}; - // Handle click in iframe const handleIframeClick = (event) => { const element = event.target; @@ -128,51 +86,22 @@ const handleIframeClick = (event) => { // Check if clicking near a page edge if (hoveredPage.value) { event.stopPropagation(); - - // 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); + pagePopup.value.open(hoveredPage.value, event); elementPopup.value.close(); return; } - // Only show popup for elements inside the page template - const isInsidePage = element.closest('.pagedjs_page'); - if (!isInsidePage) { - clearSelectedPages(); + // Otherwise handle as element click + if (element.tagName === 'BODY' || element.tagName === 'HTML') { elementPopup.value.close(); pagePopup.value.close(); return; } - // Only show ElementPopup for content elements, not divs - const contentElement = getContentElement(element); - if (!contentElement) { - clearSelectedPages(); - elementPopup.value.close(); - pagePopup.value.close(); - return; - } - - clearSelectedPages(); - elementPopup.value.handleIframeClick(event, contentElement); + 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; @@ -274,65 +203,6 @@ watch( } ); -// Print the PagedJS content -const printPreview = async () => { - const frame = activeFrame.value; - if (!frame || !frame.contentDocument) return; - - const doc = frame.contentDocument; - - // Collect all styles - let allStyles = ''; - - // Get inline - `; - 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)); @@ -357,13 +227,7 @@ onMounted(() => renderPreview(true)); - - - + diff --git a/src/components/ElementPopup.vue b/src/components/ElementPopup.vue index 9744fcf..b383d12 100644 --- a/src/components/ElementPopup.vue +++ b/src/components/ElementPopup.vue @@ -639,8 +639,8 @@ const close = () => { selectedElement.value = null; }; -const handleIframeClick = (event, targetElement = null) => { - const element = targetElement || event.target; +const handleIframeClick = (event) => { + const element = event.target; if (element.tagName === 'BODY' || element.tagName === 'HTML') { close(); diff --git a/src/components/PagePopup.vue b/src/components/PagePopup.vue index 8ec9449..804ab50 100644 --- a/src/components/PagePopup.vue +++ b/src/components/PagePopup.vue @@ -7,8 +7,8 @@ @@ -298,13 +298,9 @@ const props = defineProps({ iframeRef: Object, }); -const emit = defineEmits(['close']); - const visible = ref(false); const position = ref({ x: 0, y: 0 }); const selectedPageElement = ref(null); -const pageCount = ref(0); -const templateName = ref(''); const isEditable = ref(false); const inheritanceLocked = ref(true); const backgroundColorInput = ref(null); @@ -313,10 +309,10 @@ let isUpdatingFromStore = false; let updateTimer = null; const margins = ref({ - top: { value: 0, unit: 'mm' }, - bottom: { value: 0, unit: 'mm' }, - left: { value: 0, unit: 'mm' }, - right: { value: 0, unit: 'mm' }, + top: { value: 6, unit: 'mm' }, + bottom: { value: 7, unit: 'mm' }, + left: { value: 6, unit: 'mm' }, + right: { value: 7, unit: 'mm' }, }); const background = ref({ @@ -340,89 +336,27 @@ const POPUP_HEIGHT = 600; const { calculatePosition } = usePopupPosition(POPUP_WIDTH, POPUP_HEIGHT); -// Get the selector for the current template's @page rule -const getTemplateSelector = () => { - return templateName.value ? `@page ${templateName.value}` : '@page'; -}; - -// Get or create the template-specific @page block -const getOrCreateTemplateBlock = () => { - const selector = getTemplateSelector(); - let block = stylesheetStore.extractBlock(selector); - - if (!block && templateName.value) { - // Create new block with current values from @page - const baseBlock = stylesheetStore.extractBlock('@page'); - if (baseBlock) { - // Insert the new template block after @page - const marginValue = `${margins.value.top.value}${margins.value.top.unit} ${margins.value.right.value}${margins.value.right.unit} ${margins.value.bottom.value}${margins.value.bottom.unit} ${margins.value.left.value}${margins.value.left.unit}`; - const newBlock = `\n@page ${templateName.value} {\n margin: ${marginValue};${background.value.value ? `\n background: ${background.value.value};` : ''}\n}\n`; - - stylesheetStore.content = stylesheetStore.content.replace( - baseBlock, - baseBlock + newBlock - ); - block = stylesheetStore.extractBlock(selector); - } - } - - return block; -}; - -// Remove the template-specific @page block -const removeTemplateBlock = () => { - if (!templateName.value) return; - - const selector = `@page ${templateName.value}`; - const block = stylesheetStore.extractBlock(selector); - - if (block) { - // Remove the block and any surrounding whitespace - stylesheetStore.content = stylesheetStore.content.replace( - new RegExp(`\\n?${selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*\\{[^}]*\\}\\n?`), - '\n' - ); - } -}; - const updateMargins = () => { - // Only update if inheritance is unlocked - if (inheritanceLocked.value) return; - const marginValue = `${margins.value.top.value}${margins.value.top.unit} ${margins.value.right.value}${margins.value.right.unit} ${margins.value.bottom.value}${margins.value.bottom.unit} ${margins.value.left.value}${margins.value.left.unit}`; - const currentBlock = getOrCreateTemplateBlock(); + const currentBlock = stylesheetStore.extractBlock('@page'); if (!currentBlock) return; - const selector = getTemplateSelector(); + const updatedBlock = currentBlock.replace( + /(margin:\s*)[^;]+/, + `$1${marginValue}` + ); - if (currentBlock.includes('margin:')) { - const updatedBlock = currentBlock.replace( - /(margin:\s*)[^;]+/, - `$1${marginValue}` - ); - stylesheetStore.content = stylesheetStore.content.replace( - currentBlock, - updatedBlock - ); - } else { - const updatedBlock = currentBlock.replace( - /(\s*})$/, - ` margin: ${marginValue};\n$1` - ); - stylesheetStore.content = stylesheetStore.content.replace( - currentBlock, - updatedBlock - ); - } + stylesheetStore.content = stylesheetStore.content.replace( + currentBlock, + updatedBlock + ); }; const updateBackground = () => { - // Only update if inheritance is unlocked - if (inheritanceLocked.value) return; if (!background.value.value) return; - const currentBlock = getOrCreateTemplateBlock(); + const currentBlock = stylesheetStore.extractBlock('@page'); if (!currentBlock) return; if (currentBlock.includes('background:')) { @@ -487,37 +421,39 @@ const loadValuesFromStylesheet = () => { try { isUpdatingFromStore = true; - // Extract values from @page block (same logic as PageSettings) - const pageBlock = stylesheetStore.extractBlock('@page'); - if (!pageBlock) return; + // Extract margin from @page + const marginData = stylesheetStore.extractValue('@page', 'margin'); + if (marginData) { + // Parse margin shorthand (top right bottom left) + const marginStr = typeof marginData === 'string' ? marginData : marginData.value; + const marginValues = marginStr.split(' '); - // Parse margins with regex (top right bottom left) - const marginMatch = pageBlock.match( - /margin:\s*([0-9.]+)([a-z]+)\s+([0-9.]+)([a-z]+)\s+([0-9.]+)([a-z]+)\s+([0-9.]+)([a-z]+)/i - ); - if (marginMatch) { - margins.value.top = { - value: parseFloat(marginMatch[1]), - unit: marginMatch[2], - }; - margins.value.right = { - value: parseFloat(marginMatch[3]), - unit: marginMatch[4], - }; - margins.value.bottom = { - value: parseFloat(marginMatch[5]), - unit: marginMatch[6], - }; - margins.value.left = { - value: parseFloat(marginMatch[7]), - unit: marginMatch[8], - }; + if (marginValues.length === 4) { + // Parse each margin value + const parseMargin = (value) => { + const match = value.match(/^([\d.]+)(\w+)$/); + if (match) { + return { value: parseFloat(match[1]), unit: match[2] }; + } + return { value: 0, unit: 'mm' }; + }; + + const top = parseMargin(marginValues[0]); + const right = parseMargin(marginValues[1]); + const bottom = parseMargin(marginValues[2]); + const left = parseMargin(marginValues[3]); + + margins.value.top = top; + margins.value.right = right; + margins.value.bottom = bottom; + margins.value.left = left; + } } - // Extract background - const bgMatch = pageBlock.match(/background:\s*([^;]+)/); - if (bgMatch) { - background.value.value = bgMatch[1].trim(); + // Extract background from @page + const backgroundData = stylesheetStore.extractValue('@page', 'background'); + if (backgroundData) { + background.value.value = typeof backgroundData === 'string' ? backgroundData : backgroundData.value; } } catch (error) { console.error('Error loading values from stylesheet:', error); @@ -526,18 +462,14 @@ const loadValuesFromStylesheet = () => { } }; -const open = (pageElement, event, count = 1) => { +const open = (pageElement, event) => { selectedPageElement.value = pageElement; - pageCount.value = count; position.value = calculatePosition(event); - // Extract template name from data-page-type attribute - templateName.value = pageElement.getAttribute('data-page-type') || ''; + // Add border to the selected page + pageElement.style.outline = '2px solid #61afef'; - // Reset inheritance state when opening - inheritanceLocked.value = true; - - // Load values from stylesheet (@page block) + // Load values from stylesheet loadValuesFromStylesheet(); visible.value = true; @@ -575,30 +507,21 @@ const open = (pageElement, event, count = 1) => { }; const close = () => { - selectedPageElement.value = null; + // Remove border from the selected page + if (selectedPageElement.value) { + selectedPageElement.value.style.outline = ''; + selectedPageElement.value = null; + } visible.value = false; isEditable.value = false; - emit('close'); }; const toggleInheritance = () => { - const wasLocked = inheritanceLocked.value; inheritanceLocked.value = !inheritanceLocked.value; - - if (inheritanceLocked.value && !wasLocked) { - // Re-locking: remove the template-specific block - // Fields keep their values, but preview returns to @page defaults - removeTemplateBlock(); - } - // When unlocking: fields already have values, block will be created on first edit + // TODO: Implement CSS priority logic when unlocked }; const pageCss = computed(() => { - // Show template-specific block if unlocked and exists, otherwise show @page - if (!inheritanceLocked.value && templateName.value) { - const templateBlock = stylesheetStore.extractBlock(`@page ${templateName.value}`); - if (templateBlock) return templateBlock; - } return stylesheetStore.extractBlock('@page') || ''; }); @@ -679,7 +602,7 @@ defineExpose({ open, close, visible }); } .page-label { - background: var(--color-page-highlight); + background: #ff8a50; color: white; padding: 0.25rem 0.5rem; border-radius: 4px; @@ -693,7 +616,7 @@ defineExpose({ open, close, visible }); } .page-count { - color: var(--color-page-highlight); + color: #ff8a50; font-size: 0.875rem; }