diff --git a/src/index.html b/src/index.html
index 98fa29d..7912757 100644
--- a/src/index.html
+++ b/src/index.html
@@ -331,6 +331,81 @@
+
+
diff --git a/src/main.js b/src/main.js
index bfc32b0..4a7757a 100644
--- a/src/main.js
+++ b/src/main.js
@@ -2472,6 +2472,32 @@ ipcMain.on('do-print', (event, { withStyles }) => {
}
});
+// Handle printing with custom options from print preview dialog
+ipcMain.on('do-print-with-options', (event, options) => {
+ if (!mainWindow) return;
+
+ const marginsMap = {
+ 'default': { marginType: 'default' },
+ 'narrow': { top: 0.4, bottom: 0.4, left: 0.4, right: 0.4 },
+ 'wide': { top: 1.0, bottom: 1.0, left: 1.0, right: 1.0 },
+ 'none': { top: 0, bottom: 0, left: 0, right: 0 },
+ };
+
+ const printOptions = {
+ silent: false,
+ printBackground: options.background,
+ landscape: options.orientation === 'landscape',
+ scaleFactor: options.scale / 100,
+ pageSize: options.paperSize,
+ };
+
+ if (options.margins && options.margins !== 'default') {
+ printOptions.margins = marginsMap[options.margins];
+ }
+
+ mainWindow.webContents.print(printOptions);
+});
+
// Handle renderer ready for file association
ipcMain.on('renderer-ready', (event) => {
console.log('[MAIN] renderer-ready received, rendererReady was:', rendererReady);
diff --git a/src/preload.js b/src/preload.js
index bd04aa4..1813ada 100644
--- a/src/preload.js
+++ b/src/preload.js
@@ -29,6 +29,7 @@ const ALLOWED_SEND_CHANNELS = [
// Print
'do-print',
+ 'do-print-with-options',
// Export
'export-with-options',
diff --git a/src/print-preview.js b/src/print-preview.js
new file mode 100644
index 0000000..8f7b89e
--- /dev/null
+++ b/src/print-preview.js
@@ -0,0 +1,139 @@
+class PrintPreview {
+ constructor() {
+ this.overlay = document.getElementById('print-preview-overlay');
+ this._lastContent = '';
+ this.setupEventListeners();
+ }
+
+ open(htmlContent) {
+ this._lastContent = htmlContent;
+ this.overlay.classList.remove('hidden');
+ this.updatePreview(htmlContent);
+ this.updateScaleLabel();
+ }
+
+ close() {
+ this.overlay.classList.add('hidden');
+ }
+
+ setupEventListeners() {
+ document.getElementById('print-preview-close')?.addEventListener('click', () => this.close());
+ document.getElementById('print-cancel')?.addEventListener('click', () => this.close());
+ document.getElementById('print-execute')?.addEventListener('click', () => this.executePrint());
+
+ // Update preview on option changes
+ ['print-paper-size', 'print-orientation', 'print-margins'].forEach(id => {
+ document.getElementById(id)?.addEventListener('change', () => this.refreshPreview());
+ });
+
+ // Scale slider
+ const scaleSlider = document.getElementById('print-scale');
+ scaleSlider?.addEventListener('input', () => this.updateScaleLabel());
+
+ // Page range toggle
+ document.getElementById('print-pages')?.addEventListener('change', (e) => {
+ const rangeInput = document.getElementById('print-page-range');
+ if (rangeInput) {
+ rangeInput.classList.toggle('hidden', e.target.value !== 'custom');
+ }
+ });
+
+ // Close on overlay click
+ this.overlay?.addEventListener('click', (e) => {
+ if (e.target === this.overlay) this.close();
+ });
+
+ // Close on Escape
+ document.addEventListener('keydown', (e) => {
+ if (e.key === 'Escape' && !this.overlay.classList.contains('hidden')) {
+ this.close();
+ }
+ });
+ }
+
+ updateScaleLabel() {
+ const scale = document.getElementById('print-scale')?.value || 100;
+ const label = document.getElementById('print-scale-value');
+ if (label) label.textContent = `${scale}%`;
+ }
+
+ updatePreview(htmlContent) {
+ const frame = document.getElementById('print-preview-frame');
+ if (!frame) return;
+
+ this._lastContent = htmlContent;
+
+ const orientation = document.getElementById('print-orientation')?.value || 'portrait';
+ const paperSize = document.getElementById('print-paper-size')?.value || 'A4';
+
+ // Get dimensions for paper size
+ const sizes = {
+ 'A3': { width: '297mm', height: '420mm' },
+ 'A4': { width: '210mm', height: '297mm' },
+ 'A5': { width: '148mm', height: '210mm' },
+ 'Letter': { width: '8.5in', height: '11in' },
+ 'Legal': { width: '8.5in', height: '14in' },
+ 'Tabloid': { width: '11in', height: '17in' },
+ };
+
+ const size = sizes[paperSize] || sizes['A4'];
+ const width = orientation === 'landscape' ? size.height : size.width;
+ const height = orientation === 'landscape' ? size.width : size.height;
+
+ const previewHtml = `
+
+
+
+
+
+ ${htmlContent || ''}
+
+ `;
+
+ frame.srcdoc = previewHtml;
+ }
+
+ refreshPreview() {
+ if (this._lastContent) {
+ this.updatePreview(this._lastContent);
+ }
+ }
+
+ getOptions() {
+ return {
+ paperSize: document.getElementById('print-paper-size')?.value || 'A4',
+ orientation: document.getElementById('print-orientation')?.value || 'portrait',
+ margins: document.getElementById('print-margins')?.value || 'default',
+ scale: parseInt(document.getElementById('print-scale')?.value || '100'),
+ headers: document.getElementById('print-headers')?.checked ?? true,
+ background: document.getElementById('print-background')?.checked ?? true,
+ pages: document.getElementById('print-pages')?.value || 'all',
+ pageRange: document.getElementById('print-page-range')?.value || '',
+ };
+ }
+
+ executePrint() {
+ const options = this.getOptions();
+ const { ipcRenderer } = require('electron');
+ ipcRenderer.send('do-print-with-options', options);
+ this.close();
+ }
+}
+
+module.exports = { PrintPreview };
diff --git a/src/renderer.js b/src/renderer.js
index d8aabee..7dd1caf 100644
--- a/src/renderer.js
+++ b/src/renderer.js
@@ -12,6 +12,7 @@ const { createEditor } = require('./editor/codemirror-setup');
const { undo, redo } = require('@codemirror/commands');
const { SidebarManager } = require('./sidebar/sidebar-manager');
const { CommandPalette } = require('./command-palette');
+const { PrintPreview } = require('./print-preview');
// Configure marked with highlight extension
marked.use(markedHighlight({
@@ -1051,6 +1052,9 @@ document.addEventListener('DOMContentLoaded', () => {
// Initialize command palette
const commandPalette = new CommandPalette();
+ // Initialize print preview
+ const printPreview = new PrintPreview();
+
// Register commands
commandPalette.register('New File', 'Ctrl+N', () => tabManager.createNewTab());
commandPalette.register('Open File', 'Ctrl+O', () => ipcRenderer.send('menu-open'));
@@ -1087,6 +1091,11 @@ document.addEventListener('DOMContentLoaded', () => {
commandPalette.register('Toggle Sidebar: Git', 'Ctrl+Shift+G', () => sidebarManager.togglePanel('git'));
commandPalette.register('Toggle Sidebar: Snippets', '', () => sidebarManager.togglePanel('snippets'));
commandPalette.register('Toggle Sidebar: Templates', '', () => sidebarManager.togglePanel('templates'));
+ commandPalette.register('Print Preview', 'Ctrl+P', () => {
+ const tab = tabManager.tabs.get(tabManager.activeTabId);
+ const preview = document.getElementById(`preview-${tab.id}`);
+ printPreview.open(preview?.innerHTML || '');
+ });
commandPalette.register('Export to PDF', '', () => ipcRenderer.send('export', 'pdf'));
commandPalette.register('Export to DOCX', '', () => ipcRenderer.send('export', 'docx'));
commandPalette.register('Export to HTML', '', () => ipcRenderer.send('export', 'html'));
@@ -1261,18 +1270,18 @@ ipcRenderer.on('adjust-font-size', (event, action) => {
updateFontSizes(currentFontSize);
});
-// Print preview request handlers - handle printing directly
+// Print preview request handlers - open print preview dialog
ipcRenderer.on('print-preview', () => {
console.log('[RENDERER] print-preview received');
- handlePrintPreview(false);
+ openPrintPreviewDialog();
});
ipcRenderer.on('print-preview-styled', () => {
console.log('[RENDERER] print-preview-styled received');
- handlePrintPreview(true);
+ openPrintPreviewDialog();
});
-function handlePrintPreview(withStyles) {
+function openPrintPreviewDialog() {
const activeTabId = tabManager ? tabManager.activeTabId : 1;
const previewContent = document.getElementById(`preview-${activeTabId}`);
@@ -1281,26 +1290,8 @@ function handlePrintPreview(withStyles) {
return;
}
- console.log('[RENDERER] Starting print with withStyles:', withStyles);
- console.log('[RENDERER] Preview content length:', previewContent.innerHTML.length);
-
- // Add body classes for print mode - let CSS handle everything
- document.body.classList.add('printing');
- if (!withStyles) {
- document.body.classList.add('printing-no-styles');
- }
-
- // Wait for CSS to apply then print
- setTimeout(() => {
- console.log('[RENDERER] Sending do-print to main process');
- ipcRenderer.send('do-print', { withStyles });
-
- // Restore classes after print dialog opens
- setTimeout(() => {
- document.body.classList.remove('printing', 'printing-no-styles');
- console.log('[RENDERER] Print classes removed');
- }, 1000);
- }, 100);
+ const printPreviewInstance = new PrintPreview();
+ printPreviewInstance.open(previewContent.innerHTML);
}
// Export Dialog functionality
diff --git a/src/styles-modern.css b/src/styles-modern.css
index 75e02ae..f38a7ef 100644
--- a/src/styles-modern.css
+++ b/src/styles-modern.css
@@ -2232,3 +2232,104 @@ body[class*="dark"] .breadcrumb-bar {
color: #888;
border-color: #333;
}
+
+/* Print Preview Dialog */
+.print-preview-dialog {
+ width: 90vw;
+ max-width: 1200px;
+ height: 80vh;
+ display: flex;
+ flex-direction: column;
+}
+.print-preview-body {
+ display: flex;
+ flex: 1;
+ overflow: hidden;
+}
+.print-preview-sidebar {
+ width: 260px;
+ padding: 16px;
+ border-right: 1px solid var(--gray-200, #e5e7eb);
+ overflow-y: auto;
+ flex-shrink: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+.print-preview-content {
+ flex: 1;
+ padding: 16px;
+ background: #e5e7eb;
+ display: flex;
+ justify-content: center;
+ overflow: auto;
+}
+.print-preview-content iframe {
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15);
+ border-radius: 4px;
+}
+.print-option-group {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+.print-option-group label {
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--gray-600, #4b5563);
+}
+.print-option-group select,
+.print-option-group input[type="text"] {
+ padding: 8px 10px;
+ border: 1px solid var(--gray-300, #d1d5db);
+ border-radius: 8px;
+ font-size: 13px;
+ background: white;
+}
+.scale-control {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+.scale-control input[type="range"] {
+ flex: 1;
+}
+.checkbox-label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ cursor: pointer;
+}
+.print-range-input {
+ margin-top: 6px;
+}
+.print-actions {
+ display: flex;
+ gap: 8px;
+ margin-top: auto;
+ padding-top: 16px;
+}
+.btn-primary {
+ flex: 1;
+ padding: 10px;
+ background: var(--primary-dark, #5661b3);
+ color: white;
+ border: none;
+ border-radius: 8px;
+ font-size: 14px;
+ font-weight: 500;
+ cursor: pointer;
+}
+.btn-primary:hover {
+ opacity: 0.9;
+}
+.btn-secondary {
+ flex: 1;
+ padding: 10px;
+ background: var(--gray-100, #f3f4f6);
+ color: var(--gray-700, #374151);
+ border: 1px solid var(--gray-300, #d1d5db);
+ border-radius: 8px;
+ font-size: 14px;
+ cursor: pointer;
+}