mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 10:00:17 +05:30
feat: add custom print preview dialog with configurable options
This commit is contained in:
@@ -331,6 +331,81 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Print Preview Dialog -->
|
||||
<div class="export-dialog hidden" id="print-preview-overlay">
|
||||
<div class="export-dialog-content print-preview-dialog">
|
||||
<div class="export-dialog-header">
|
||||
<h3>Print Preview</h3>
|
||||
<button id="print-preview-close" title="Close" style="background:none;border:none;color:white;font-size:24px;cursor:pointer;">×</button>
|
||||
</div>
|
||||
<div class="print-preview-body">
|
||||
<div class="print-preview-sidebar">
|
||||
<div class="print-option-group">
|
||||
<label>Paper Size</label>
|
||||
<select id="print-paper-size">
|
||||
<option value="A3">A3</option>
|
||||
<option value="A4" selected>A4</option>
|
||||
<option value="A5">A5</option>
|
||||
<option value="Letter">Letter</option>
|
||||
<option value="Legal">Legal</option>
|
||||
<option value="Tabloid">Tabloid</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="print-option-group">
|
||||
<label>Orientation</label>
|
||||
<select id="print-orientation">
|
||||
<option value="portrait" selected>Portrait</option>
|
||||
<option value="landscape">Landscape</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="print-option-group">
|
||||
<label>Margins</label>
|
||||
<select id="print-margins">
|
||||
<option value="default" selected>Default</option>
|
||||
<option value="narrow">Narrow</option>
|
||||
<option value="wide">Wide</option>
|
||||
<option value="none">None</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="print-option-group">
|
||||
<label>Scale</label>
|
||||
<div class="scale-control">
|
||||
<input type="range" id="print-scale" min="50" max="200" value="100">
|
||||
<span id="print-scale-value">100%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="print-option-group">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="print-headers" checked>
|
||||
Headers & Footers
|
||||
</label>
|
||||
</div>
|
||||
<div class="print-option-group">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="print-background" checked>
|
||||
Background Colors
|
||||
</label>
|
||||
</div>
|
||||
<div class="print-option-group">
|
||||
<label>Pages</label>
|
||||
<select id="print-pages">
|
||||
<option value="all" selected>All</option>
|
||||
<option value="custom">Custom Range</option>
|
||||
</select>
|
||||
<input type="text" id="print-page-range" placeholder="e.g., 1-3, 5" class="hidden print-range-input">
|
||||
</div>
|
||||
<div class="print-actions">
|
||||
<button class="btn-primary" id="print-execute">Print</button>
|
||||
<button class="btn-secondary" id="print-cancel">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="print-preview-content">
|
||||
<iframe id="print-preview-frame" style="width:100%;height:100%;border:none;background:white;"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Command Palette -->
|
||||
<div class="command-palette-overlay hidden" id="command-palette-overlay">
|
||||
<div class="command-palette">
|
||||
|
||||
+26
@@ -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);
|
||||
|
||||
@@ -29,6 +29,7 @@ const ALLOWED_SEND_CHANNELS = [
|
||||
|
||||
// Print
|
||||
'do-print',
|
||||
'do-print-with-options',
|
||||
|
||||
// Export
|
||||
'export-with-options',
|
||||
|
||||
@@ -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 = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
margin: 20px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
@page { size: ${width} ${height}; }
|
||||
pre { background: #f5f5f5; padding: 12px; border-radius: 6px; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
||||
pre code { background: none; padding: 0; }
|
||||
table { border-collapse: collapse; width: 100%; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px; }
|
||||
blockquote { border-left: 4px solid #ddd; margin-left: 0; padding-left: 16px; color: #666; }
|
||||
img { max-width: 100%; }
|
||||
h1, h2, h3 { margin-top: 1.5em; }
|
||||
</style>
|
||||
</head>
|
||||
<body>${htmlContent || ''}</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
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 };
|
||||
+15
-24
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user