feat: add print button that replaces page content for printing
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 <noreply@anthropic.com>
This commit is contained in:
parent
36d3420125
commit
ea74d1891c
1 changed files with 88 additions and 65 deletions
153
src/App.vue
153
src/App.vue
|
|
@ -4,7 +4,7 @@ import EditorPanel from './components/editor/EditorPanel.vue';
|
||||||
import ElementPopup from './components/ElementPopup.vue';
|
import ElementPopup from './components/ElementPopup.vue';
|
||||||
import PagePopup from './components/PagePopup.vue';
|
import PagePopup from './components/PagePopup.vue';
|
||||||
import PreviewLoader from './components/PreviewLoader.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 { useStylesheetStore } from './stores/stylesheet';
|
||||||
import Coloris from '@melloware/coloris';
|
import Coloris from '@melloware/coloris';
|
||||||
|
|
||||||
|
|
@ -274,79 +274,66 @@ watch(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle Cmd+P / Ctrl+P to print the iframe content
|
// Print the PagedJS content
|
||||||
const handlePrint = async (event) => {
|
const printPreview = async () => {
|
||||||
if ((event.metaKey || event.ctrlKey) && event.key === 'p') {
|
const frame = activeFrame.value;
|
||||||
event.preventDefault();
|
if (!frame || !frame.contentDocument) return;
|
||||||
const frame = activeFrame.value;
|
|
||||||
if (frame && frame.contentDocument) {
|
|
||||||
const doc = frame.contentDocument;
|
|
||||||
|
|
||||||
// Collect all styles (inline and from stylesheets)
|
const doc = frame.contentDocument;
|
||||||
let allStyles = '';
|
|
||||||
|
|
||||||
// Get inline <style> tags
|
// Collect all styles
|
||||||
doc.querySelectorAll('style').forEach((style) => {
|
let allStyles = '';
|
||||||
allStyles += style.outerHTML;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get computed styles from linked stylesheets
|
// Get inline <style> tags content
|
||||||
for (const sheet of doc.styleSheets) {
|
doc.querySelectorAll('style').forEach((style) => {
|
||||||
|
allStyles += style.innerHTML + '\n';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get rules from stylesheets
|
||||||
|
for (const sheet of doc.styleSheets) {
|
||||||
|
try {
|
||||||
|
for (const rule of sheet.cssRules) {
|
||||||
|
allStyles += rule.cssText + '\n';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Cross-origin stylesheet, try to fetch it
|
||||||
|
if (sheet.href) {
|
||||||
try {
|
try {
|
||||||
for (const rule of sheet.cssRules) {
|
const response = await fetch(sheet.href);
|
||||||
allStyles += rule.cssText + '\n';
|
const css = await response.text();
|
||||||
}
|
allStyles += css;
|
||||||
} catch (e) {
|
} catch (fetchError) {
|
||||||
// Cross-origin stylesheet, try to fetch it
|
console.warn('Could not fetch stylesheet:', sheet.href);
|
||||||
if (sheet.href) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(sheet.href);
|
|
||||||
const css = await response.text();
|
|
||||||
allStyles += css;
|
|
||||||
} catch (fetchError) {
|
|
||||||
console.warn('Could not fetch stylesheet:', sheet.href);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the body content (already rendered by PagedJS)
|
|
||||||
const bodyContent = doc.body.innerHTML;
|
|
||||||
|
|
||||||
// Build the print document
|
|
||||||
const printContent = `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<style>${allStyles}</style>
|
|
||||||
</head>
|
|
||||||
<body>${bodyContent}</body>
|
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 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 = `
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Impression</title>
|
||||||
|
<style>${allStyles}</style>
|
||||||
|
`;
|
||||||
|
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(() => {
|
onMounted(() => renderPreview(true));
|
||||||
renderPreview(true);
|
|
||||||
window.addEventListener('keydown', handlePrint);
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
window.removeEventListener('keydown', handlePrint);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -371,6 +358,12 @@ onUnmounted(() => {
|
||||||
|
|
||||||
<ElementPopup ref="elementPopup" :iframeRef="activeFrame" />
|
<ElementPopup ref="elementPopup" :iframeRef="activeFrame" />
|
||||||
<PagePopup ref="pagePopup" :iframeRef="activeFrame" @close="handlePagePopupClose" />
|
<PagePopup ref="pagePopup" :iframeRef="activeFrame" @close="handlePagePopupClose" />
|
||||||
|
|
||||||
|
<button class="print-btn" @click="printPreview" title="Imprimer">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M17 2H7V6H17V2ZM19 8H5C3.34 8 2 9.34 2 11V17H6V21H18V17H22V11C22 9.34 20.66 8 19 8ZM16 19H8V14H16V19ZM19 12C18.45 12 18 11.55 18 11C18 10.45 18.45 10 19 10C19.55 10 20 10.45 20 11C20 11.55 19.55 12 19 12Z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
@ -403,12 +396,42 @@ onUnmounted(() => {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.print-btn {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 2rem;
|
||||||
|
right: 2rem;
|
||||||
|
width: 3.5rem;
|
||||||
|
height: 3.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: none;
|
||||||
|
background: var(--color-page-highlight);
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-btn:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-btn svg {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Hide UI elements when printing */
|
/* Hide UI elements when printing */
|
||||||
@media print {
|
@media print {
|
||||||
#editor-panel,
|
#editor-panel,
|
||||||
#element-popup,
|
#element-popup,
|
||||||
#page-popup,
|
#page-popup,
|
||||||
.preview-loader {
|
.preview-loader,
|
||||||
|
.print-btn {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue