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>
|
||||||
</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 -->
|
<!-- Command Palette -->
|
||||||
<div class="command-palette-overlay hidden" id="command-palette-overlay">
|
<div class="command-palette-overlay hidden" id="command-palette-overlay">
|
||||||
<div class="command-palette">
|
<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
|
// Handle renderer ready for file association
|
||||||
ipcMain.on('renderer-ready', (event) => {
|
ipcMain.on('renderer-ready', (event) => {
|
||||||
console.log('[MAIN] renderer-ready received, rendererReady was:', rendererReady);
|
console.log('[MAIN] renderer-ready received, rendererReady was:', rendererReady);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const ALLOWED_SEND_CHANNELS = [
|
|||||||
|
|
||||||
// Print
|
// Print
|
||||||
'do-print',
|
'do-print',
|
||||||
|
'do-print-with-options',
|
||||||
|
|
||||||
// Export
|
// Export
|
||||||
'export-with-options',
|
'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 { undo, redo } = require('@codemirror/commands');
|
||||||
const { SidebarManager } = require('./sidebar/sidebar-manager');
|
const { SidebarManager } = require('./sidebar/sidebar-manager');
|
||||||
const { CommandPalette } = require('./command-palette');
|
const { CommandPalette } = require('./command-palette');
|
||||||
|
const { PrintPreview } = require('./print-preview');
|
||||||
|
|
||||||
// Configure marked with highlight extension
|
// Configure marked with highlight extension
|
||||||
marked.use(markedHighlight({
|
marked.use(markedHighlight({
|
||||||
@@ -1051,6 +1052,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// Initialize command palette
|
// Initialize command palette
|
||||||
const commandPalette = new CommandPalette();
|
const commandPalette = new CommandPalette();
|
||||||
|
|
||||||
|
// Initialize print preview
|
||||||
|
const printPreview = new PrintPreview();
|
||||||
|
|
||||||
// Register commands
|
// Register commands
|
||||||
commandPalette.register('New File', 'Ctrl+N', () => tabManager.createNewTab());
|
commandPalette.register('New File', 'Ctrl+N', () => tabManager.createNewTab());
|
||||||
commandPalette.register('Open File', 'Ctrl+O', () => ipcRenderer.send('menu-open'));
|
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: Git', 'Ctrl+Shift+G', () => sidebarManager.togglePanel('git'));
|
||||||
commandPalette.register('Toggle Sidebar: Snippets', '', () => sidebarManager.togglePanel('snippets'));
|
commandPalette.register('Toggle Sidebar: Snippets', '', () => sidebarManager.togglePanel('snippets'));
|
||||||
commandPalette.register('Toggle Sidebar: Templates', '', () => sidebarManager.togglePanel('templates'));
|
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 PDF', '', () => ipcRenderer.send('export', 'pdf'));
|
||||||
commandPalette.register('Export to DOCX', '', () => ipcRenderer.send('export', 'docx'));
|
commandPalette.register('Export to DOCX', '', () => ipcRenderer.send('export', 'docx'));
|
||||||
commandPalette.register('Export to HTML', '', () => ipcRenderer.send('export', 'html'));
|
commandPalette.register('Export to HTML', '', () => ipcRenderer.send('export', 'html'));
|
||||||
@@ -1261,18 +1270,18 @@ ipcRenderer.on('adjust-font-size', (event, action) => {
|
|||||||
updateFontSizes(currentFontSize);
|
updateFontSizes(currentFontSize);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Print preview request handlers - handle printing directly
|
// Print preview request handlers - open print preview dialog
|
||||||
ipcRenderer.on('print-preview', () => {
|
ipcRenderer.on('print-preview', () => {
|
||||||
console.log('[RENDERER] print-preview received');
|
console.log('[RENDERER] print-preview received');
|
||||||
handlePrintPreview(false);
|
openPrintPreviewDialog();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on('print-preview-styled', () => {
|
ipcRenderer.on('print-preview-styled', () => {
|
||||||
console.log('[RENDERER] print-preview-styled received');
|
console.log('[RENDERER] print-preview-styled received');
|
||||||
handlePrintPreview(true);
|
openPrintPreviewDialog();
|
||||||
});
|
});
|
||||||
|
|
||||||
function handlePrintPreview(withStyles) {
|
function openPrintPreviewDialog() {
|
||||||
const activeTabId = tabManager ? tabManager.activeTabId : 1;
|
const activeTabId = tabManager ? tabManager.activeTabId : 1;
|
||||||
const previewContent = document.getElementById(`preview-${activeTabId}`);
|
const previewContent = document.getElementById(`preview-${activeTabId}`);
|
||||||
|
|
||||||
@@ -1281,26 +1290,8 @@ function handlePrintPreview(withStyles) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[RENDERER] Starting print with withStyles:', withStyles);
|
const printPreviewInstance = new PrintPreview();
|
||||||
console.log('[RENDERER] Preview content length:', previewContent.innerHTML.length);
|
printPreviewInstance.open(previewContent.innerHTML);
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export Dialog functionality
|
// Export Dialog functionality
|
||||||
|
|||||||
@@ -2232,3 +2232,104 @@ body[class*="dark"] .breadcrumb-bar {
|
|||||||
color: #888;
|
color: #888;
|
||||||
border-color: #333;
|
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