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:
isUnknown 2025-12-08 14:05:16 +01:00
parent 36d3420125
commit ea74d1891c

View file

@ -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 <style> tags
doc.querySelectorAll('style').forEach((style) => {
allStyles += style.outerHTML;
});
// Collect all styles
let allStyles = '';
// Get computed styles from linked stylesheets
for (const sheet of doc.styleSheets) {
// Get inline <style> tags content
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 {
for (const rule of sheet.cssRules) {
allStyles += rule.cssText + '\n';
}
} catch (e) {
// Cross-origin stylesheet, try to fetch it
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);
}
}
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(() => {
renderPreview(true);
window.addEventListener('keydown', handlePrint);
});
onUnmounted(() => {
window.removeEventListener('keydown', handlePrint);
});
onMounted(() => renderPreview(true));
</script>
<template>
@ -371,6 +358,12 @@ onUnmounted(() => {
<ElementPopup ref="elementPopup" :iframeRef="activeFrame" />
<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>
<style>
@ -403,12 +396,42 @@ onUnmounted(() => {
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 */
@media print {
#editor-panel,
#element-popup,
#page-popup,
.preview-loader {
.preview-loader,
.print-btn {
display: none !important;
}