From e63a0ec1bfb8dc5e1d585a5ebdf0aab111536242 Mon Sep 17 00:00:00 2001 From: Amit Haridas Date: Sat, 13 Dec 2025 19:40:30 +0530 Subject: [PATCH] =?UTF-8?q?Release=20v2.0.0:=20Major=20Feature=20Update=20?= =?UTF-8?q?New=20Features:=20=E2=80=A2=20Export=20Profiles=20-=20Save=20an?= =?UTF-8?q?d=20load=20export=20configurations=20=E2=80=A2=20Mermaid.js=20d?= =?UTF-8?q?iagram=20support=20-=20Render=20flowcharts,=20sequence=20diagra?= =?UTF-8?q?ms,=20and=20more=20=E2=80=A2=20Command=20Palette=20(Ctrl+Shift+?= =?UTF-8?q?P)=20-=20Quick=20access=20to=20all=20commands=20=E2=80=A2=20Git?= =?UTF-8?q?Hub=20Light/Dark=20preview=20themes=20-=20Beautiful=20code=20pr?= =?UTF-8?q?eview=20styling=20=E2=80=A2=20Table=20Generator=20-=20Interacti?= =?UTF-8?q?ve=20table=20creation=20tool=20=E2=80=A2=20ASCII=20Art=20Genera?= =?UTF-8?q?tor=20-=20Create=20text=20banners,=20boxes,=20and=20templates?= =?UTF-8?q?=20=E2=80=A2=20Resizable=20Preview=20Pane=20-=20Drag=20divider?= =?UTF-8?q?=20to=20adjust=20editor/preview=20sizes=20=E2=80=A2=20Pop-out?= =?UTF-8?q?=20Preview=20Window=20-=20Open=20preview=20in=20separate=20wind?= =?UTF-8?q?ow=20with=20live=20sync=20UI/UX=20Improvements:=20=E2=80=A2=20F?= =?UTF-8?q?ixed=20advanced=20export=20dialog=20visibility=20(added=20.hidd?= =?UTF-8?q?en=20CSS=20class)=20=E2=80=A2=20GitHub-style=20table=20headers?= =?UTF-8?q?=20with=20light=20grey=20background=20=E2=80=A2=20Removed=20gra?= =?UTF-8?q?dients=20from=20table=20headers=20=E2=80=A2=20Clean,=20professi?= =?UTF-8?q?onal=20preview=20rendering=20Technical=20Changes:=20=E2=80=A2?= =?UTF-8?q?=20Updated=20all=20UI=20components=20for=20new=20features=20?= =?UTF-8?q?=E2=80=A2=20Enhanced=20TabManager=20to=20support=20resizable=20?= =?UTF-8?q?panes=20=E2=80=A2=20Added=20MutationObserver=20for=20live=20pre?= =?UTF-8?q?view=20sync=20in=20pop-out=20window=20=E2=80=A2=20Improved=20CS?= =?UTF-8?q?S=20styling=20consistency=20=F0=9F=A4=96=20Generated=20with=20C?= =?UTF-8?q?laude=20Code=20(https://claude.com/claude-code)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/index.html | 198 ++++++ src/main.js | 204 ++++++- src/renderer.js | 1140 ++++++++++++++++++++++++++++++++++- src/styles.css | 375 +++++++++++- src/wordTemplateExporter.js | 100 ++- 6 files changed, 1994 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 8b02dec..c7b51b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pan-converter", - "version": "1.9.2", + "version": "2.0.0", "description": "Cross-platform Markdown editor and converter using Pandoc", "main": "src/main.js", "scripts": { diff --git a/src/index.html b/src/index.html index bfc2744..858b961 100644 --- a/src/index.html +++ b/src/index.html @@ -11,6 +11,8 @@ + +
@@ -148,6 +150,19 @@
+ +
+ +
+ + + +
+ Save and reuse your favorite export configurations +
+
+
+ +
+ + +
+
+ + +
+ +
+
@@ -258,6 +304,154 @@
+ + + + + + + + + +
+
+ +
diff --git a/src/main.js b/src/main.js index 6683660..317cb09 100644 --- a/src/main.js +++ b/src/main.js @@ -78,6 +78,66 @@ let headerFooterSettings = { } }; +// Page Size Definitions (in twentieths of a point for Word, mm/inches for Pandoc) +const PAGE_SIZES = { + a4: { + name: 'A4', + pandoc: 'a4', + word: { width: 11906, height: 16838 }, // 210Ɨ297mm + dimensions: '210Ɨ297mm' + }, + a3: { + name: 'A3', + pandoc: 'a3', + word: { width: 16838, height: 23811 }, // 297Ɨ420mm + dimensions: '297Ɨ420mm' + }, + a5: { + name: 'A5', + pandoc: 'a5', + word: { width: 8391, height: 11906 }, // 148Ɨ210mm + dimensions: '148Ɨ210mm' + }, + b4: { + name: 'B4', + pandoc: 'b4', + word: { width: 14170, height: 20015 }, // 250Ɨ353mm + dimensions: '250Ɨ353mm' + }, + b5: { + name: 'B5', + pandoc: 'b5', + word: { width: 9979, height: 14170 }, // 176Ɨ250mm + dimensions: '176Ɨ250mm' + }, + letter: { + name: 'Letter', + pandoc: 'letter', + word: { width: 12240, height: 15840 }, // 8.5Ɨ11in + dimensions: '8.5Ɨ11in' + }, + legal: { + name: 'Legal', + pandoc: 'legal', + word: { width: 12240, height: 20160 }, // 8.5Ɨ14in + dimensions: '8.5Ɨ14in' + }, + tabloid: { + name: 'Tabloid', + pandoc: 'tabloid', + word: { width: 15840, height: 24480 }, // 11Ɨ17in + dimensions: '11Ɨ17in' + } +}; + +// Default page settings +let pageSettings = { + size: 'a4', + orientation: 'portrait', + customWidth: null, + customHeight: null +}; + // Handle single instance lock for Windows file association // When a file is double-clicked and the app is already running, // Windows tries to start a second instance. We prevent this and @@ -505,6 +565,23 @@ function createMenu() { } ] }, + { + label: 'Tools', + submenu: [ + { + label: 'Table Generator', + click: () => mainWindow.webContents.send('show-table-generator') + }, + { + label: 'ASCII Art Generator', + click: () => mainWindow.webContents.send('show-ascii-generator') + }, + { + label: 'Document Compare', + click: () => mainWindow.webContents.send('show-document-compare') + } + ] + }, { label: 'Help', submenu: [ @@ -515,7 +592,7 @@ function createMenu() { type: 'info', title: 'About PanConverter', message: 'PanConverter', - detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 1.9.2\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\nFeatures:\n• Preserved ASCII art, charts, tables, flowcharts in exports with full-width background\n• Custom Headers & Footers for PDF, DOCX, ODT, and PowerPoint exports\n• Dynamic field support: $PAGE$, $TOTAL$, $DATE$, $TIME$, $TITLE$, $AUTHOR$, $FILENAME$\n• Logo/image embedding in headers and footers\n• Modern glassmorphism UI with gradient backgrounds\n• Enhanced PDF export via Word template with configurable start page\n• Configurable template settings (start page selection)\n• Streamlined PDF Editor UI (merge, split, compress, rotate, watermark, encrypt)\n• Universal File Converter (LibreOffice, ImageMagick, FFmpeg, Pandoc)\n• Windows Explorer context menu integration\n• Tabbed interface for multiple files\n• Advanced markdown editing with live preview\n• Real-time preview updates while typing\n• Full toolbar markdown editing functions\n• Enhanced PDF export with built-in Electron fallback\n• Enhanced Word export with template support (single file & batch)\n• File association support for .md files\n• Command-line interface for batch conversion\n• Advanced export options with templates and metadata\n• Batch file conversion with progress tracking\n• Improved preview typography and spacing\n• Adjustable font sizes via menu (Ctrl+Shift+Plus/Minus)\n• Complete theme support including Monokai fixes\n• Find & replace with match highlighting\n• Line numbers and auto-indentation\n• Export to multiple formats via Pandoc\n• PowerPoint & presentation export\n• Export tables to Excel/ODS spreadsheets\n• Document import & conversion\n• Table creation helper\n• 22 beautiful themes (including Dracula, Nord, Tokyo Night, Gruvbox, Ayu, Concrete, and more)\n• Undo/redo functionality\n• Live word count and statistics', + detail: 'A cross-platform Markdown editor and converter using Pandoc.\n\nVersion: 2.0.0\nAuthor: Amit Haridas\nEmail: amit.wh@gmail.com\nLicense: MIT\n\n✨ New in v2.0.0:\n• Export Profiles - Save and load export configurations\n• Mermaid.js diagram support - Render flowcharts, sequence diagrams, and more\n• Command Palette (Ctrl+Shift+P) - Quick access to all commands\n• GitHub Light/Dark preview themes - Beautiful code preview styling\n• Table Generator - Interactive table creation tool\n• ASCII Art Generator - Create text banners, boxes, and templates\n• Resizable Preview Pane - Drag divider to adjust editor/preview sizes\n• Pop-out Preview Window - Open preview in separate window with live sync\n\nCore Features:\n• Configurable page sizes (A3, A4, A5, B4, B5, Letter, Legal, Tabloid, Custom) with orientation support\n• Page size support for all export formats (PDF, DOCX, ODT, PowerPoint) and batch conversion\n• Preserved ASCII art, charts, tables, flowcharts in exports with full-width background\n• Custom Headers & Footers for PDF, DOCX, ODT, and PowerPoint exports\n• Dynamic field support: $PAGE$, $TOTAL$, $DATE$, $TIME$, $TITLE$, $AUTHOR$, $FILENAME$\n• Logo/image embedding in headers and footers\n• Modern glassmorphism UI with gradient backgrounds\n• Enhanced PDF export via Word template with configurable start page\n• Streamlined PDF Editor UI (merge, split, compress, rotate, watermark, encrypt)\n• Universal File Converter (LibreOffice, ImageMagick, FFmpeg, Pandoc)\n• Windows Explorer context menu integration\n• Tabbed interface for multiple files\n• Advanced markdown editing with live preview\n• Real-time preview updates while typing\n• Full toolbar markdown editing functions\n• Enhanced PDF export with built-in Electron fallback\n• Enhanced Word export with template support (single file & batch)\n• File association support for .md files\n• Command-line interface for batch conversion\n• Advanced export options with templates and metadata\n• Batch file conversion with progress tracking\n• Adjustable font sizes via menu (Ctrl+Shift+Plus/Minus)\n• 22 beautiful themes (including Dracula, Nord, Tokyo Night, Gruvbox, Ayu, Concrete, and more)\n• Find & replace with match highlighting\n• Line numbers and auto-indentation\n• Undo/redo functionality\n• Live word count and statistics', buttons: ['OK'] }); } @@ -672,6 +749,18 @@ ipcMain.on('save-header-footer-settings', (event, settings) => { }); }); +// Get current page settings +ipcMain.on('get-page-settings', (event) => { + event.reply('page-settings-data', pageSettings); +}); + +// Update page settings from export dialog +ipcMain.on('update-page-settings', (event, settings) => { + pageSettings = settings; + store.set('pageSettings', pageSettings); + console.log('Page settings updated:', pageSettings); +}); + // Save header/footer logo image // Browse for header/footer logo ipcMain.on('browse-header-footer-logo', async (event, position) => { @@ -794,6 +883,67 @@ function processDynamicFields(text, metadata = {}) { } // Add headers/footers to DOCX file using PizZip and docx4js +// Function to set page size in DOCX files +async function setDocxPageSize(docxPath) { + try { + const PizZip = require('pizzip'); + + // Read the DOCX file + const docxBuffer = fs.readFileSync(docxPath); + const zip = new PizZip(docxBuffer); + + // Get document.xml + let documentXml = zip.file('word/document.xml').asText(); + + // Get page dimensions + let width, height; + const pageSize = PAGE_SIZES[pageSettings.size]; + + if (pageSize) { + width = pageSize.word.width; + height = pageSize.word.height; + } else if (pageSettings.customWidth && pageSettings.customHeight) { + // Parse custom dimensions (convert to twentieths of a point) + // Note: This is simplified - production code should handle various units + width = parseInt(pageSettings.customWidth) || 11906; + height = parseInt(pageSettings.customHeight) || 16838; + } else { + // Default to A4 + width = 11906; + height = 16838; + } + + // Swap dimensions for landscape + if (pageSettings.orientation === 'landscape') { + [width, height] = [height, width]; + } + + // Update all elements in section properties + const pgSzRegex = /]*\/>/g; + documentXml = documentXml.replace(pgSzRegex, () => { + return ``; + }); + + // If no pgSz found, add it to all sectPr elements + if (!pgSzRegex.test(documentXml)) { + const sectPrRegex = /]*>/g; + documentXml = documentXml.replace(sectPrRegex, (match) => { + return `${match}`; + }); + } + + // Save updated document.xml + zip.file('word/document.xml', documentXml); + + // Write modified DOCX + const newDocxBuffer = zip.generate({ type: 'nodebuffer' }); + fs.writeFileSync(docxPath, newDocxBuffer); + + } catch (error) { + console.error('Failed to set DOCX page size:', error); + } +} + async function addHeaderFooterToDocx(docxPath, metadata = {}) { if (!headerFooterSettings.enabled) return; @@ -939,8 +1089,8 @@ async function exportWordWithTemplate() { if (result.canceled) return; - // Create exporter instance with selected template and start page - const exporter = new WordTemplateExporter(wordTemplatePath, templateStartPage); + // Create exporter instance with selected template, start page, and page settings + const exporter = new WordTemplateExporter(wordTemplatePath, templateStartPage, pageSettings); // Convert markdown to DOCX await exporter.convert(content, result.filePath); @@ -1444,6 +1594,16 @@ function exportWithPandoc(pandocCmd, outputFile, format) { console.warn(`Pandoc stderr (non-fatal):`, stderr); } + // Set page size for DOCX + if (format === 'docx') { + try { + await setDocxPageSize(outputFile); + console.log('Page size set for DOCX'); + } catch (pageSizeError) { + console.error('Error setting page size for DOCX:', pageSizeError); + } + } + // Add headers/footers to DOCX if enabled if (format === 'docx' && headerFooterSettings.enabled) { try { @@ -1461,6 +1621,16 @@ function exportWithPandoc(pandocCmd, outputFile, format) { } } + // Set page size for ODT + if (format === 'odt') { + try { + await setDocxPageSize(outputFile); // ODT has similar structure + console.log('Page size set for ODT'); + } catch (pageSizeError) { + console.error('Error setting page size for ODT:', pageSizeError); + } + } + // Add headers/footers to ODT if enabled if (format === 'odt' && headerFooterSettings.enabled) { // ODT format is similar to DOCX in structure, we could implement this @@ -2198,9 +2368,13 @@ function performBatchConversion(inputFolder, outputFolder, format, options) { // Add PDF-specific options with header/footer support if (format === 'pdf') { const pdfEngine = options.pdfEngine || 'xelatex'; - pandocCmd += ` --pdf-engine="${pdfEngine}"`; + pandocCmd += ` --pdf-engine=${pdfEngine}`; if (options.geometry) pandocCmd += ` -V geometry:"${options.geometry}"`; + // Add monospace font settings for code blocks (ASCII art preservation) + pandocCmd += ' -V monofont="Consolas"'; + pandocCmd += ' --highlight-style=tango'; + // Add header/footer if enabled if (headerFooterSettings.enabled) { const filename = path.basename(inputFile, path.extname(inputFile)); @@ -2407,6 +2581,22 @@ function buildPandocCommand(content, format, outputPath) { switch (format) { case 'pdf': command += ' --pdf-engine=xelatex --variable geometry:margin=1in'; + + // Add page size and orientation + const pageSize = PAGE_SIZES[pageSettings.size]; + if (pageSize) { + command += ` -V geometry:papersize=${pageSize.pandoc}`; + } else if (pageSettings.customWidth && pageSettings.customHeight) { + // Custom page size + command += ` -V geometry:paperwidth=${pageSettings.customWidth}`; + command += ` -V geometry:paperheight=${pageSettings.customHeight}`; + } + + // Add orientation + if (pageSettings.orientation === 'landscape') { + command += ' -V geometry:landscape'; + } + // Add monospace font settings for code blocks (ASCII art preservation) command += ' -V monofont="Consolas"'; command += ' --highlight-style=tango'; @@ -2495,6 +2685,12 @@ app.whenReady().then(() => { headerFooterSettings = savedHFSettings; } + // Load page size settings + const savedPageSettings = store.get('pageSettings', null); + if (savedPageSettings) { + pageSettings = savedPageSettings; + } + // Check for command line conversion requests const args = process.argv.slice(2); if (args.length >= 2 && (args[0] === '--convert' || args[0] === '--convert-to')) { diff --git a/src/renderer.js b/src/renderer.js index f096dce..42c31be 100644 --- a/src/renderer.js +++ b/src/renderer.js @@ -133,7 +133,11 @@ class TabManager { +
+
+ +
`; @@ -323,6 +327,47 @@ class TabManager { console.warn('Math rendering error:', mathError); } } + + // Render Mermaid diagrams if Mermaid is available + if (window.mermaid) { + try { + // Find all code blocks with language-mermaid class + const mermaidBlocks = preview.querySelectorAll('pre code.language-mermaid'); + mermaidBlocks.forEach((block, index) => { + const code = block.textContent; + const pre = block.parentElement; + + // Create a div for mermaid rendering + const mermaidDiv = document.createElement('div'); + mermaidDiv.className = 'mermaid'; + mermaidDiv.setAttribute('data-processed', 'true'); + mermaidDiv.textContent = code; + + // Replace the pre element with the mermaid div + pre.parentElement.replaceChild(mermaidDiv, pre); + }); + + // Initialize Mermaid with dark theme support + const theme = document.body.className.includes('theme-dark') ? 'dark' : 'default'; + mermaid.initialize({ + startOnLoad: false, + theme: theme, + securityLevel: 'loose' + }); + + // Render all mermaid diagrams + mermaid.run({ + querySelector: '.mermaid:not([data-rendered])' + }).then(() => { + // Mark as rendered + preview.querySelectorAll('.mermaid').forEach(el => { + el.setAttribute('data-rendered', 'true'); + }); + }); + } catch (mermaidError) { + console.warn('Mermaid rendering error:', mermaidError); + } + } } catch (error) { console.error('Error rendering preview:', error); preview.innerHTML = '

Error rendering preview. Please check your markdown syntax.

'; @@ -1215,15 +1260,23 @@ ipcRenderer.on('show-export-dialog', (event, format) => { }); function showExportDialog(format) { + console.log('showExportDialog called with format:', format); const dialog = document.getElementById('export-dialog'); const title = document.getElementById('export-dialog-title'); + if (!dialog) { + console.error('Export dialog element not found!'); + return; + } + + console.log('Dialog found, showing export options for:', format); title.textContent = `Export as ${format.toUpperCase()}`; dialog.setAttribute('data-format', format); dialog.classList.remove('hidden'); // Initialize form values initializeExportForm(format); + console.log('Export dialog should now be visible'); } function hideExportDialog() { @@ -1271,6 +1324,9 @@ function initializeExportForm(format) { // Clear bibliography fields document.getElementById('bibliography-file').value = ''; document.getElementById('csl-file').value = ''; + + // Request current page settings from main process and apply them + ipcRenderer.send('get-page-settings'); } function collectExportOptions() { @@ -1337,11 +1393,155 @@ function collectExportOptions() { } } + // Collect page size and orientation (always collected, from basic options) + const pageSize = document.getElementById('page-size').value; + const pageOrientation = document.getElementById('page-orientation').value; + const customWidth = document.getElementById('custom-width').value.trim(); + const customHeight = document.getElementById('custom-height').value.trim(); + + // Send page settings to main process + ipcRenderer.send('update-page-settings', { + size: pageSize, + orientation: pageOrientation, + customWidth: customWidth || null, + customHeight: customHeight || null + }); + return options; } +// Export Profiles Management +let exportProfiles = {}; + +function loadExportProfiles() { + const saved = localStorage.getItem('exportProfiles'); + if (saved) { + try { + exportProfiles = JSON.parse(saved); + populateProfileDropdown(); + } catch (e) { + console.error('Failed to load export profiles:', e); + exportProfiles = {}; + } + } +} + +function saveExportProfiles() { + localStorage.setItem('exportProfiles', JSON.stringify(exportProfiles)); +} + +function populateProfileDropdown() { + const select = document.getElementById('export-profile-select'); + if (!select) return; + + // Clear existing options except the first one + while (select.options.length > 1) { + select.remove(1); + } + + // Add saved profiles + Object.keys(exportProfiles).forEach(name => { + const option = document.createElement('option'); + option.value = name; + option.textContent = name; + select.appendChild(option); + }); +} + +function saveCurrentProfile() { + const name = prompt('Enter a name for this export profile:', 'My Profile'); + if (!name || name.trim() === '') return; + + const profileName = name.trim(); + + // Collect current settings + const profile = { + format: currentExportFormat, + advancedMode: document.getElementById('advanced-export-toggle').checked, + pageSize: document.getElementById('page-size').value, + pageOrientation: document.getElementById('page-orientation').value, + basicToc: document.getElementById('basic-toc').checked, + basicNumberSections: document.getElementById('basic-number-sections').checked + }; + + // Add advanced options if enabled + if (profile.advancedMode) { + profile.template = document.getElementById('export-template').value; + profile.toc = document.getElementById('export-toc').checked; + profile.tocDepth = document.getElementById('export-toc-depth').value; + profile.numberSections = document.getElementById('export-number-sections').checked; + profile.citeproc = document.getElementById('export-citeproc').checked; + + if (currentExportFormat === 'pdf') { + profile.pdfEngine = document.getElementById('pdf-engine').value; + profile.pdfGeometry = document.getElementById('pdf-geometry').value; + } + } + + exportProfiles[profileName] = profile; + saveExportProfiles(); + populateProfileDropdown(); + + // Select the newly created profile + document.getElementById('export-profile-select').value = profileName; + + alert(`Profile "${profileName}" saved successfully!`); +} + +function loadProfile(profileName) { + if (!profileName || !exportProfiles[profileName]) return; + + const profile = exportProfiles[profileName]; + + // Apply settings + if (profile.advancedMode !== undefined) { + document.getElementById('advanced-export-toggle').checked = profile.advancedMode; + const advancedOptions = document.getElementById('advanced-export-options'); + if (profile.advancedMode) { + advancedOptions.classList.remove('hidden'); + } else { + advancedOptions.classList.add('hidden'); + } + } + + if (profile.pageSize) document.getElementById('page-size').value = profile.pageSize; + if (profile.pageOrientation) document.getElementById('page-orientation').value = profile.pageOrientation; + if (profile.basicToc !== undefined) document.getElementById('basic-toc').checked = profile.basicToc; + if (profile.basicNumberSections !== undefined) document.getElementById('basic-number-sections').checked = profile.basicNumberSections; + + // Advanced options + if (profile.advancedMode && profile.template) document.getElementById('export-template').value = profile.template; + if (profile.toc !== undefined) document.getElementById('export-toc').checked = profile.toc; + if (profile.tocDepth) document.getElementById('export-toc-depth').value = profile.tocDepth; + if (profile.numberSections !== undefined) document.getElementById('export-number-sections').checked = profile.numberSections; + if (profile.citeproc !== undefined) document.getElementById('export-citeproc').checked = profile.citeproc; + + if (profile.pdfEngine) document.getElementById('pdf-engine').value = profile.pdfEngine; + if (profile.pdfGeometry) document.getElementById('pdf-geometry').value = profile.pdfGeometry; +} + +function deleteSelectedProfile() { + const select = document.getElementById('export-profile-select'); + const profileName = select.value; + + if (!profileName) { + alert('Please select a profile to delete.'); + return; + } + + if (confirm(`Are you sure you want to delete the profile "${profileName}"?`)) { + delete exportProfiles[profileName]; + saveExportProfiles(); + populateProfileDropdown(); + select.value = ''; + alert(`Profile "${profileName}" deleted successfully!`); + } +} + // Event listeners for export dialog document.addEventListener('DOMContentLoaded', () => { + // Load export profiles on startup + loadExportProfiles(); // Template selection document.getElementById('export-template').addEventListener('change', (e) => { const customPath = document.getElementById('custom-template-path'); @@ -1389,6 +1589,57 @@ document.addEventListener('DOMContentLoaded', () => { } }); + // Page size selection - show/hide custom size inputs + document.getElementById('page-size').addEventListener('change', (e) => { + const customPageSize = document.getElementById('custom-page-size'); + if (e.target.value === 'custom') { + customPageSize.style.display = 'block'; + } else { + customPageSize.style.display = 'none'; + } + }); + + // Load saved page settings on startup + ipcRenderer.send('get-page-settings'); + + // Handle page settings data (can be called multiple times) + ipcRenderer.on('page-settings-data', (event, settings) => { + console.log('Received page settings:', settings); + if (settings) { + const pageSizeEl = document.getElementById('page-size'); + const pageOrientationEl = document.getElementById('page-orientation'); + const customPageSizeEl = document.getElementById('custom-page-size'); + const customWidthEl = document.getElementById('custom-width'); + const customHeightEl = document.getElementById('custom-height'); + + if (pageSizeEl) pageSizeEl.value = settings.size || 'a4'; + if (pageOrientationEl) pageOrientationEl.value = settings.orientation || 'portrait'; + + if (customWidthEl && settings.customWidth) { + customWidthEl.value = settings.customWidth; + } + if (customHeightEl && settings.customHeight) { + customHeightEl.value = settings.customHeight; + } + + // Show custom inputs if size is custom + if (customPageSizeEl) { + if (settings.size === 'custom') { + customPageSizeEl.style.display = 'block'; + } else { + customPageSizeEl.style.display = 'none'; + } + } + } + }); + + // Export Profile buttons + document.getElementById('save-profile-btn').addEventListener('click', saveCurrentProfile); + document.getElementById('delete-profile-btn').addEventListener('click', deleteSelectedProfile); + document.getElementById('export-profile-select').addEventListener('change', (e) => { + loadProfile(e.target.value); + }); + // Add metadata field document.getElementById('add-metadata-field').addEventListener('click', () => { const container = document.querySelector('.metadata-container'); @@ -2051,6 +2302,11 @@ ipcRenderer.on('recent-files-cleared', () => { let currentPDFOperation = null; let mergeFilePaths = []; +// Show Table Generator Dialog +ipcRenderer.on('show-table-generator', () => { + showTableGenerator(); +}); + // Show PDF Editor Dialog ipcRenderer.on('show-pdf-editor-dialog', (event, operation) => { currentPDFOperation = operation; @@ -2736,4 +2992,886 @@ window.openHeaderFooterDialog = openHeaderFooterDialog; // Listen for menu command to open dialog ipcRenderer.on('open-header-footer-dialog', () => { openHeaderFooterDialog(); -}); \ No newline at end of file +}); +// Command Palette Implementation +const commands = [ + { name: 'New File', action: () => tabManager.createTab(), shortcut: 'Ctrl+N' }, + { name: 'Open File', action: () => ipcRenderer.send('open-file'), shortcut: 'Ctrl+O' }, + { name: 'Save File', action: () => ipcRenderer.send('save-file'), shortcut: 'Ctrl+S' }, + { name: 'Save As', action: () => ipcRenderer.send('save-file-as'), shortcut: 'Ctrl+Shift+S' }, + { name: 'Export to PDF', action: () => ipcRenderer.send('export', 'pdf'), shortcut: '' }, + { name: 'Export to DOCX', action: () => ipcRenderer.send('export', 'docx'), shortcut: '' }, + { name: 'Export to HTML', action: () => ipcRenderer.send('export', 'html'), shortcut: '' }, + { name: 'Toggle Preview', action: () => tabManager.togglePreview(), shortcut: 'Ctrl+Shift+P' }, + { name: 'Toggle Line Numbers', action: () => tabManager.toggleLineNumbers(), shortcut: '' }, + { name: 'Find & Replace', action: () => showFindDialog(), shortcut: 'Ctrl+F' }, + { name: 'New Tab', action: () => tabManager.createTab(), shortcut: 'Ctrl+T' }, + { name: 'Close Tab', action: () => tabManager.closeTab(tabManager.activeTabId), shortcut: 'Ctrl+W' }, + { name: 'Undo', action: () => tabManager.undo(), shortcut: 'Ctrl+Z' }, + { name: 'Redo', action: () => tabManager.redo(), shortcut: 'Ctrl+Shift+Z' }, + { name: 'Bold', action: () => insertMarkdown('**', '**'), shortcut: 'Ctrl+B' }, + { name: 'Italic', action: () => insertMarkdown('*', '*'), shortcut: 'Ctrl+I' }, + { name: 'Heading 1', action: () => insertMarkdown('# ', ''), shortcut: '' }, + { name: 'Heading 2', action: () => insertMarkdown('## ', ''), shortcut: '' }, + { name: 'Heading 3', action: () => insertMarkdown('### ', ''), shortcut: '' }, + { name: 'Insert Link', action: () => insertMarkdown('[', '](url)'), shortcut: '' }, + { name: 'Insert Image', action: () => insertMarkdown('![', '](image.jpg)'), shortcut: '' }, + { name: 'Insert Code Block', action: () => insertMarkdown('```\n', '\n```'), shortcut: '' }, + { name: 'Insert Table', action: () => insertMarkdown('\n| Header 1 | Header 2 |\n|----------|----------|\n| Cell 1 | Cell 2 |\n', ''), shortcut: '' }, + { name: 'Table Generator', action: () => showTableGenerator(), shortcut: '' }, + { name: 'ASCII Art Generator', action: () => showASCIIGenerator(), shortcut: '' }, + { name: 'Increase Font Size', action: () => ipcRenderer.send('adjust-font-size', 'increase'), shortcut: 'Ctrl+Shift++' }, + { name: 'Decrease Font Size', action: () => ipcRenderer.send('adjust-font-size', 'decrease'), shortcut: 'Ctrl+Shift+-' } +]; + +let commandPaletteSelectedIndex = 0; +let filteredCommands = [...commands]; + +function showCommandPalette() { + const palette = document.getElementById('command-palette'); + const searchInput = document.getElementById('command-search'); + + palette.classList.remove('hidden'); + searchInput.value = ''; + searchInput.focus(); + + filteredCommands = [...commands]; + commandPaletteSelectedIndex = 0; + renderCommandList(); +} + +function hideCommandPalette() { + const palette = document.getElementById('command-palette'); + palette.classList.add('hidden'); +} + +function renderCommandList() { + const list = document.getElementById('command-list'); + list.innerHTML = ''; + + filteredCommands.forEach((cmd, index) => { + const item = document.createElement('div'); + item.className = 'command-item'; + if (index === commandPaletteSelectedIndex) { + item.classList.add('selected'); + } + + const name = document.createElement('span'); + name.className = 'command-name'; + name.textContent = cmd.name; + + const shortcut = document.createElement('span'); + shortcut.className = 'command-shortcut'; + shortcut.textContent = cmd.shortcut || ''; + + item.appendChild(name); + if (cmd.shortcut) { + item.appendChild(shortcut); + } + + item.addEventListener('click', () => { + executeCommand(cmd); + }); + + list.appendChild(item); + }); +} + +function filterCommands(query) { + const lowerQuery = query.toLowerCase(); + filteredCommands = commands.filter(cmd => + cmd.name.toLowerCase().includes(lowerQuery) + ); + commandPaletteSelectedIndex = 0; + renderCommandList(); +} + +function executeCommand(cmd) { + hideCommandPalette(); + if (cmd && cmd.action) { + cmd.action(); + } +} + +function navigateCommandPalette(direction) { + commandPaletteSelectedIndex += direction; + if (commandPaletteSelectedIndex < 0) { + commandPaletteSelectedIndex = filteredCommands.length - 1; + } + if (commandPaletteSelectedIndex >= filteredCommands.length) { + commandPaletteSelectedIndex = 0; + } + renderCommandList(); +} + +// Command Palette Event Listeners +document.addEventListener('keydown', (e) => { + // Ctrl+Shift+P to open command palette + if (e.ctrlKey && e.shiftKey && e.key === 'P') { + e.preventDefault(); + showCommandPalette(); + return; + } + + // Handle command palette navigation when open + const palette = document.getElementById('command-palette'); + if (!palette.classList.contains('hidden')) { + if (e.key === 'Escape') { + e.preventDefault(); + hideCommandPalette(); + } else if (e.key === 'ArrowDown') { + e.preventDefault(); + navigateCommandPalette(1); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + navigateCommandPalette(-1); + } else if (e.key === 'Enter') { + e.preventDefault(); + if (filteredCommands[commandPaletteSelectedIndex]) { + executeCommand(filteredCommands[commandPaletteSelectedIndex]); + } + } + } +}); + +document.getElementById('command-search').addEventListener('input', (e) => { + filterCommands(e.target.value); +}); + +// Close on backdrop click +document.getElementById('command-palette').addEventListener('click', (e) => { + if (e.target === document.getElementById('command-palette')) { + hideCommandPalette(); + } +}); + +// ============================================================================ +// TABLE GENERATOR +// ============================================================================ + +function showTableGenerator() { + const dialog = document.getElementById('table-generator-dialog'); + dialog.classList.remove('hidden'); + + // Generate initial preview + generateTablePreview(); + + // Focus on rows input + setTimeout(() => { + document.getElementById('table-rows').focus(); + }, 100); +} + +function hideTableGenerator() { + const dialog = document.getElementById('table-generator-dialog'); + dialog.classList.add('hidden'); +} + +function generateTablePreview() { + const rows = parseInt(document.getElementById('table-rows').value) || 3; + const cols = parseInt(document.getElementById('table-cols').value) || 3; + const hasHeader = document.getElementById('table-has-header').checked; + const alignment = document.getElementById('table-alignment').value; + + const table = generateMarkdownTable(rows, cols, hasHeader, alignment); + document.getElementById('table-preview').textContent = table; +} + +function generateMarkdownTable(rows, cols, hasHeader, alignment) { + let table = ''; + + // Generate alignment string + let alignChar = '-'; + if (alignment === 'center') { + alignChar = ':' + '-'.repeat(Math.max(8, 10)) + ':'; + } else if (alignment === 'right') { + alignChar = '-'.repeat(Math.max(8, 10)) + ':'; + } else { + alignChar = '-'.repeat(Math.max(8, 10)); + } + + // Generate header row + if (hasHeader) { + table += '| '; + for (let c = 1; c <= cols; c++) { + table += `Header ${c}`; + if (c < cols) table += ' | '; + } + table += ' |\n'; + + // Separator row + table += '| '; + for (let c = 1; c <= cols; c++) { + table += alignChar; + if (c < cols) table += ' | '; + } + table += ' |\n'; + + // Data rows (excluding header) + for (let r = 1; r < rows; r++) { + table += '| '; + for (let c = 1; c <= cols; c++) { + table += `Cell ${r},${c}`; + if (c < cols) table += ' | '; + } + table += ' |\n'; + } + } else { + // No header - all rows are data rows + // First row (acts as separator position) + table += '| '; + for (let c = 1; c <= cols; c++) { + table += `Cell 1,${c}`; + if (c < cols) table += ' | '; + } + table += ' |\n'; + + // Separator row + table += '| '; + for (let c = 1; c <= cols; c++) { + table += alignChar; + if (c < cols) table += ' | '; + } + table += ' |\n'; + + // Remaining data rows + for (let r = 2; r <= rows; r++) { + table += '| '; + for (let c = 1; c <= cols; c++) { + table += `Cell ${r},${c}`; + if (c < cols) table += ' | '; + } + table += ' |\n'; + } + } + + return table; +} + +function insertGeneratedTable() { + const table = document.getElementById('table-preview').textContent; + + if (!table) { + alert('Please generate a table preview first'); + return; + } + + // Get active editor + const activeTabId = tabManager ? tabManager.activeTabId : 1; + const editor = document.getElementById(`editor-${activeTabId}`); + + if (!editor) { + alert('No active editor found'); + return; + } + + // Insert table at cursor position + const start = editor.selectionStart; + const before = editor.value.substring(0, start); + const after = editor.value.substring(editor.selectionEnd); + + // Add newlines for spacing + const tableWithSpacing = '\n' + table + '\n'; + editor.value = before + tableWithSpacing + after; + + // Update cursor position + editor.selectionStart = editor.selectionEnd = start + tableWithSpacing.length; + editor.focus(); + + // Trigger update + if (tabManager) { + tabManager.handleEditorInput(activeTabId); + } + + // Close dialog + hideTableGenerator(); +} + +// Table Generator Event Listeners +document.getElementById('table-dialog-close').addEventListener('click', hideTableGenerator); +document.getElementById('table-cancel').addEventListener('click', hideTableGenerator); +document.getElementById('table-generate-preview').addEventListener('click', generateTablePreview); +document.getElementById('table-insert').addEventListener('click', insertGeneratedTable); + +// Auto-update preview when inputs change +document.getElementById('table-rows').addEventListener('input', generateTablePreview); +document.getElementById('table-cols').addEventListener('input', generateTablePreview); +document.getElementById('table-has-header').addEventListener('change', generateTablePreview); +document.getElementById('table-alignment').addEventListener('change', generateTablePreview); + +// Close dialog on backdrop click +document.getElementById('table-generator-dialog').addEventListener('click', (e) => { + if (e.target === document.getElementById('table-generator-dialog')) { + hideTableGenerator(); + } +}); + +// Handle Enter key in inputs +document.getElementById('table-rows').addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + insertGeneratedTable(); + } +}); + +document.getElementById('table-cols').addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + insertGeneratedTable(); + } +}); + +// ============================================================================ +// ASCII ART GENERATOR +// ============================================================================ + +let currentASCIIMode = 'text'; + +function showASCIIGenerator() { + const dialog = document.getElementById('ascii-art-dialog'); + dialog.classList.remove('hidden'); + + // Initialize with text mode + switchASCIIMode('text'); + + // Generate initial preview + setTimeout(() => { + generateASCIIPreview(); + }, 100); +} + +function hideASCIIGenerator() { + const dialog = document.getElementById('ascii-art-dialog'); + dialog.classList.add('hidden'); +} + +function switchASCIIMode(mode) { + currentASCIIMode = mode; + + // Update button states + document.querySelectorAll('.ascii-mode-btn').forEach(btn => { + btn.classList.remove('active'); + }); + + // Hide all mode sections + document.querySelectorAll('.ascii-mode-section').forEach(section => { + section.classList.add('hidden'); + }); + + // Show selected mode + if (mode === 'text') { + document.getElementById('ascii-mode-text').classList.add('active'); + document.getElementById('ascii-text-mode').classList.remove('hidden'); + } else if (mode === 'box') { + document.getElementById('ascii-mode-box').classList.add('active'); + document.getElementById('ascii-box-mode').classList.remove('hidden'); + } else if (mode === 'templates') { + document.getElementById('ascii-mode-templates').classList.add('active'); + document.getElementById('ascii-templates-mode').classList.remove('hidden'); + } + + // Generate preview + generateASCIIPreview(); +} + +function generateASCIIPreview() { + let result = ''; + + if (currentASCIIMode === 'text') { + const text = document.getElementById('ascii-text-input').value || 'Sample'; + const style = document.getElementById('ascii-font-style').value; + result = textToASCII(text, style); + } else if (currentASCIIMode === 'box') { + const text = document.getElementById('ascii-box-text').value || 'Sample Text'; + const style = document.getElementById('ascii-box-style').value; + const padding = parseInt(document.getElementById('ascii-box-padding').value) || 2; + result = createASCIIBox(text, style, padding); + } else if (currentASCIIMode === 'templates') { + result = 'Select a template from the buttons above'; + } + + document.getElementById('ascii-preview').textContent = result; +} + +// Simple ASCII art text generator +function textToASCII(text, style) { + const fonts = { + standard: { + height: 5, + chars: { + 'A': [' _ ', ' / \\ ', '/___\\', '| |', '| |'], + 'B': ['____ ', '| \\', '|___/', '| \\', '|___/'], + 'C': [' ___ ', '/ _/', '| \\', '| ', '\\___/'], + 'D': ['____ ', '| \\', '| |', '| |', '|___/'], + 'E': ['_____', '| ', '|___ ', '| ', '|____'], + 'F': ['_____', '| ', '|___ ', '| ', '| '], + 'G': [' ___ ', '/ _', '| ||', '| |', '\\___/'], + 'H': ['| |', '| |', '|___|', '| |', '| |'], + 'I': ['_____', ' | ', ' | ', ' | ', '_____'], + 'J': ['_____', ' | ', ' | ', '| | ', '\\__/ '], + 'K': ['| |', '| / ', '|_/ ', '| \\ ', '| \\ '], + 'L': ['| ', '| ', '| ', '| ', '|____'], + 'M': ['| |', '|\\ /|', '| V |', '| |', '| |'], + 'N': ['| |', '|\\ |', '| \\ |', '| \\|', '| |'], + 'O': [' ___ ', '/ \\', '| |', '| |', '\\___/'], + 'P': ['____ ', '| \\', '|___/', '| ', '| '], + 'Q': [' ___ ', '/ \\', '| |', '| |\\ ', '\\__\\|'], + 'R': ['____ ', '| \\', '|___/', '| \\ ', '| \\ '], + 'S': [' ___ ', '/ _/', '\\_ \\', ' \\ ', '\\___/'], + 'T': ['_____', ' | ', ' | ', ' | ', ' | '], + 'U': ['| |', '| |', '| |', '| |', '\\___/'], + 'V': ['| |', '| |', '| |', ' \\ / ', ' V '], + 'W': ['| |', '| |', '| W |', '|/ \\|', '| |'], + 'X': ['| |', ' \\ / ', ' X ', ' / \\ ', '| |'], + 'Y': ['| |', ' \\ / ', ' Y ', ' | ', ' | '], + 'Z': ['_____', ' / ', ' / ', ' / ', '/___ '], + ' ': [' ', ' ', ' ', ' ', ' '], + '0': [' ___ ', '/ \\', '| | |', '| |', '\\___/'], + '1': [' | ', ' || ', ' | ', ' | ', ' _|_ '], + '2': [' ___ ', '\\ /', ' __/ ', '/ ', '\\____'], + '3': [' ___ ', '\\ /', ' __/ ', ' / ', '\\___/'], + '4': ['| |', '| |', '|___|', ' |', ' |'], + '5': [' ___ ', '| ', '|___ ', ' / ', '\\___/'], + '6': [' ___ ', '/ ', '|___ ', '| |', '\\___/'], + '7': ['_____', ' / ', ' / ', ' / ', '/ '], + '8': [' ___ ', '| |', ' ___ ', '| |', '\\___/'], + '9': [' ___ ', '| |', '\\___|', ' |', '\\___/'] + } + }, + banner: { + height: 7, + chars: { + 'A': [' ### ', ' ## ## ', ' ## ## ', ' ## ## ', ' ####### ', ' ## ## ', ' ## ## '], + 'B': [' ###### ', ' ## ## ', ' ## ## ', ' ###### ', ' ## ## ', ' ## ## ', ' ###### '], + 'C': [' ##### ', ' ## ## ', ' ## ', ' ## ', ' ## ', ' ## ## ', ' ##### '], + ' ': [' ', ' ', ' ', ' ', ' ', ' ', ' '] + } + } + }; + + // Use standard font or fallback to simple text + const font = fonts[style] || fonts.standard; + const lines = []; + const upperText = text.toUpperCase(); + + for (let i = 0; i < font.height; i++) { + lines[i] = ''; + } + + for (let char of upperText) { + const charLines = font.chars[char] || font.chars[' ']; + for (let i = 0; i < font.height; i++) { + lines[i] += charLines[i]; + } + } + + return lines.join('\n'); +} + +// Create ASCII box/frame +function createASCIIBox(text, style, padding) { + const styles = { + single: { tl: 'ā”Œ', tr: '┐', bl: 'ā””', br: 'ā”˜', h: '─', v: '│' }, + double: { tl: 'ā•”', tr: 'ā•—', bl: 'ā•š', br: 'ā•', h: '═', v: 'ā•‘' }, + rounded: { tl: 'ā•­', tr: 'ā•®', bl: 'ā•°', br: '╯', h: '─', v: '│' }, + bold: { tl: 'ā”', tr: '┓', bl: 'ā”—', br: 'ā”›', h: '━', v: 'ā”ƒ' }, + ascii: { tl: '+', tr: '+', bl: '+', br: '+', h: '-', v: '|' } + }; + + const chars = styles[style] || styles.single; + const lines = text.split('\n'); + const maxLength = Math.max(...lines.map(l => l.length)); + const width = maxLength + (padding * 2); + + let result = ''; + + // Top border + result += chars.tl + chars.h.repeat(width) + chars.tr + '\n'; + + // Padding rows above + for (let i = 0; i < padding - 1; i++) { + result += chars.v + ' '.repeat(width) + chars.v + '\n'; + } + + // Content lines + for (let line of lines) { + const paddedLine = line.padEnd(maxLength, ' '); + result += chars.v + ' '.repeat(padding) + paddedLine + ' '.repeat(padding) + chars.v + '\n'; + } + + // Padding rows below + for (let i = 0; i < padding - 1; i++) { + result += chars.v + ' '.repeat(width) + chars.v + '\n'; + } + + // Bottom border + result += chars.bl + chars.h.repeat(width) + chars.br; + + return result; +} + +// ASCII Art Templates +function getASCIITemplate(templateName) { + const templates = { + 'arrow-right': ` + ā”Œā”€ā”€ā”€ā”€ā” + │ │ +────► │ + │ │ + ā””ā”€ā”€ā”€ā”€ā”˜`, + 'arrow-down': ` + │ + │ + ā–¼ + ā”Œā”€ā”€ā”€ā” + │ │ + ā””ā”€ā”€ā”€ā”˜`, + 'check': ` + āœ“ Task completed + āœ“ All tests passed + āœ“ Ready to deploy`, + 'divider': ` +═══════════════════════════════════════════════════════════ +`, + 'header': ` +╔══════════════════════════════════════╗ +ā•‘ SECTION HEADER ā•‘ +ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•`, + 'flowchart': ` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ START │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ PROCESS │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ END │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜` + }; + + return templates[templateName] || ''; +} + +function loadASCIITemplate(templateName) { + const template = getASCIITemplate(templateName); + document.getElementById('ascii-preview').textContent = template; +} + +function insertASCIIArt() { + const asciiArt = document.getElementById('ascii-preview').textContent; + + if (!asciiArt || asciiArt === 'Select a template from the buttons above') { + alert('Please generate ASCII art first'); + return; + } + + // Get active editor + const activeTabId = tabManager ? tabManager.activeTabId : 1; + const editor = document.getElementById(`editor-${activeTabId}`); + + if (!editor) { + alert('No active editor found'); + return; + } + + // Insert in code block for proper rendering + const start = editor.selectionStart; + const before = editor.value.substring(0, start); + const after = editor.value.substring(editor.selectionEnd); + + const codeBlock = '\n```\n' + asciiArt + '\n```\n'; + editor.value = before + codeBlock + after; + + // Update cursor position + editor.selectionStart = editor.selectionEnd = start + codeBlock.length; + editor.focus(); + + // Trigger update + if (tabManager) { + tabManager.handleEditorInput(activeTabId); + } + + // Close dialog + hideASCIIGenerator(); +} + +// ASCII Art Event Listeners +document.getElementById('ascii-dialog-close').addEventListener('click', hideASCIIGenerator); +document.getElementById('ascii-cancel').addEventListener('click', hideASCIIGenerator); +document.getElementById('ascii-generate').addEventListener('click', generateASCIIPreview); +document.getElementById('ascii-insert').addEventListener('click', insertASCIIArt); + +// Mode switching +document.getElementById('ascii-mode-text').addEventListener('click', () => switchASCIIMode('text')); +document.getElementById('ascii-mode-box').addEventListener('click', () => switchASCIIMode('box')); +document.getElementById('ascii-mode-templates').addEventListener('click', () => switchASCIIMode('templates')); + +// Auto-update preview for text mode +document.getElementById('ascii-text-input').addEventListener('input', generateASCIIPreview); +document.getElementById('ascii-font-style').addEventListener('change', generateASCIIPreview); + +// Auto-update preview for box mode +document.getElementById('ascii-box-text').addEventListener('input', generateASCIIPreview); +document.getElementById('ascii-box-style').addEventListener('change', generateASCIIPreview); +document.getElementById('ascii-box-padding').addEventListener('input', generateASCIIPreview); + +// Template buttons +document.querySelectorAll('.ascii-template-btn').forEach(btn => { + btn.addEventListener('click', function() { + const template = this.getAttribute('data-template'); + loadASCIITemplate(template); + }); +}); + +// Close dialog on backdrop click +document.getElementById('ascii-art-dialog').addEventListener('click', (e) => { + if (e.target === document.getElementById('ascii-art-dialog')) { + hideASCIIGenerator(); + } +}); + +// IPC listener for menu +ipcRenderer.on('show-ascii-generator', () => { + showASCIIGenerator(); +}); + +// ============================================================================ +// PANE RESIZING +// ============================================================================ + +let isResizing = false; +let currentResizer = null; + +document.addEventListener('mousedown', (e) => { + if (e.target.classList.contains('pane-resizer')) { + isResizing = true; + currentResizer = e.target; + document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + } +}); + +document.addEventListener('mousemove', (e) => { + if (!isResizing) return; + + const container = currentResizer.parentElement; + const editorPane = currentResizer.previousElementSibling; + const previewPane = currentResizer.nextElementSibling; + + const containerRect = container.getBoundingClientRect(); + const containerWidth = containerRect.width; + const mouseX = e.clientX - containerRect.left; + + // Calculate percentages (minimum 20%, maximum 80% for each pane) + let editorPercentage = (mouseX / containerWidth) * 100; + editorPercentage = Math.max(20, Math.min(80, editorPercentage)); + + const previewPercentage = 100 - editorPercentage; + + editorPane.style.flex = `0 0 ${editorPercentage}%`; + previewPane.style.flex = `0 0 ${previewPercentage}%`; +}); + +document.addEventListener('mouseup', () => { + if (isResizing) { + isResizing = false; + currentResizer = null; + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + } +}); + +// ============================================================================ +// PREVIEW POP-OUT +// ============================================================================ + +let popoutWindow = null; + +function popoutPreview(tabId) { + const previewContent = document.getElementById(`preview-${tabId}`); + + if (!previewContent) { + alert('No preview content to pop out'); + return; + } + + // Close existing popout if open + if (popoutWindow && !popoutWindow.closed) { + popoutWindow.close(); + } + + // Create new window + popoutWindow = window.open('', 'Preview Window', 'width=800,height=600,menubar=no,toolbar=no,location=no,status=no'); + + if (!popoutWindow) { + alert('Pop-up blocked! Please allow pop-ups for this application.'); + return; + } + + // Write content to popout window + const htmlContent = ` + + + + Preview - PanConverter + + + +
+ ${previewContent.innerHTML} +
+ + + + `; + + popoutWindow.document.write(htmlContent); + popoutWindow.document.close(); + + // Setup auto-update + setupPopoutAutoUpdate(tabId); +} + +function getPreviewStyles() { + // Extract relevant preview styles + return ` + h1, h2, h3, h4, h5, h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: 600; + line-height: 1.25; + } + h1 { + font-size: 2em; + border-bottom: 1px solid #eaecef; + padding-bottom: 0.3em; + } + h2 { + font-size: 1.5em; + border-bottom: 1px solid #eaecef; + padding-bottom: 0.3em; + } + p { + margin-bottom: 16px; + } + code { + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + background-color: #f6f8fa; + border: 1px solid #d0d7de; + border-radius: 6px; + font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace; + } + pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + background-color: #f6f8fa; + border: 1px solid #d0d7de; + border-radius: 6px; + margin-bottom: 16px; + } + pre code { + background: transparent; + border: none; + padding: 0; + } + table { + border-collapse: collapse; + margin-bottom: 16px; + width: 100%; + } + table th, table td { + padding: 6px 13px; + border: 1px solid #dfe2e5; + } + table th { + font-weight: 600; + background-color: #f6f8fa; + } + a { + color: #0366d6; + text-decoration: none; + } + a:hover { + text-decoration: underline; + } + blockquote { + padding: 0 1em; + color: #6a737d; + border-left: 0.25em solid #dfe2e5; + margin-bottom: 16px; + } + img { + max-width: 100%; + height: auto; + } + ul, ol { + padding-left: 2em; + margin-bottom: 16px; + } + li { + margin-bottom: 4px; + } + `; +} + +function setupPopoutAutoUpdate(tabId) { + // Monitor for preview changes and update popout + const observer = new MutationObserver(() => { + if (popoutWindow && !popoutWindow.closed) { + const previewContent = document.getElementById(`preview-${tabId}`); + popoutWindow.postMessage({ + type: 'preview-update', + content: previewContent.innerHTML + }, '*'); + } else { + observer.disconnect(); + } + }); + + const previewElement = document.getElementById(`preview-${tabId}`); + if (previewElement) { + observer.observe(previewElement, { + childList: true, + subtree: true, + characterData: true + }); + } +} + +// Pop-out button event listener +document.addEventListener('click', (e) => { + if (e.target.classList.contains('preview-popout-btn')) { + const tabId = e.target.id.replace('preview-popout-', ''); + popoutPreview(tabId); + } +}); + diff --git a/src/styles.css b/src/styles.css index a6b7945..4307047 100644 --- a/src/styles.css +++ b/src/styles.css @@ -10,6 +10,10 @@ body { height: 100vh; } +.hidden { + display: none !important; +} + .container { display: flex; flex-direction: column; @@ -156,7 +160,53 @@ body { } .pane:first-child { - border-right: 1px solid #ddd; + border-right: none; +} + +/* Pane Resizer */ +.pane-resizer { + width: 6px; + background: #e0e0e0; + cursor: col-resize; + position: relative; + flex-shrink: 0; + transition: background 0.2s; +} + +.pane-resizer:hover { + background: #999; +} + +.pane-resizer:active { + background: #666; +} + +/* Preview Header */ +.preview-header { + display: flex; + justify-content: flex-end; + align-items: center; + padding: 4px 8px; + background: #f6f8fa; + border-bottom: 1px solid #dfe2e5; + min-height: 32px; +} + +.preview-popout-btn { + background: transparent; + border: 1px solid #d0d7de; + border-radius: 4px; + padding: 4px 8px; + cursor: pointer; + font-size: 14px; + color: #57606a; + transition: all 0.2s; +} + +.preview-popout-btn:hover { + background: #f3f4f6; + border-color: #8c959f; + color: #24292f; } .editor-textarea { @@ -207,10 +257,15 @@ body { background: #fff; } -#preview { +/* GitHub Light Theme for Preview (Default) */ +#preview, [id^="preview-"], .preview-content { max-width: 800px; margin: 0 auto; font-size: 15px; + padding: 20px; + background: #ffffff; + color: #24292f; + line-height: 1.6; } /* Status Bar */ @@ -251,22 +306,25 @@ body { line-height: 1.6; } -#preview code, .preview-content code { +#preview code, .preview-content code, [id^="preview-"] code { padding: 0.2em 0.4em; margin: 0; font-size: 85%; - background-color: rgba(27,31,35,0.05); - border-radius: 3px; - font-family: 'SF Mono', Monaco, 'Courier New', monospace; + background-color: #f6f8fa; + border: 1px solid #d0d7de; + border-radius: 6px; + font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace; + color: #24292f; } -#preview pre, .preview-content pre { +#preview pre, .preview-content pre, [id^="preview-"] pre { padding: 16px; overflow: auto; font-size: 85%; line-height: 1.45; background-color: #f6f8fa; - border-radius: 3px; + border: 1px solid #d0d7de; + border-radius: 6px; margin-bottom: 16px; } @@ -289,6 +347,126 @@ body { margin-bottom: 16px; } +/* GitHub Dark Theme for Preview */ +body.theme-dark #preview, +body.theme-dark [id^="preview-"], +body.theme-dark .preview-content { + background: #0d1117; + color: #c9d1d9; +} + +body.theme-dark #preview h1, +body.theme-dark [id^="preview-"] h1, +body.theme-dark .preview-content h1 { + color: #c9d1d9; + border-bottom-color: #21262d; +} + +body.theme-dark #preview h2, +body.theme-dark [id^="preview-"] h2, +body.theme-dark .preview-content h2 { + color: #c9d1d9; + border-bottom-color: #21262d; +} + +body.theme-dark #preview h3, +body.theme-dark #preview h4, +body.theme-dark #preview h5, +body.theme-dark #preview h6, +body.theme-dark [id^="preview-"] h3, +body.theme-dark [id^="preview-"] h4, +body.theme-dark [id^="preview-"] h5, +body.theme-dark [id^="preview-"] h6, +body.theme-dark .preview-content h3, +body.theme-dark .preview-content h4, +body.theme-dark .preview-content h5, +body.theme-dark .preview-content h6 { + color: #c9d1d9; +} + +body.theme-dark #preview p, +body.theme-dark [id^="preview-"] p, +body.theme-dark .preview-content p { + color: #c9d1d9; +} + +body.theme-dark #preview code, +body.theme-dark [id^="preview-"] code, +body.theme-dark .preview-content code { + background-color: #161b22; + border-color: #30363d; + color: #c9d1d9; +} + +body.theme-dark #preview pre, +body.theme-dark [id^="preview-"] pre, +body.theme-dark .preview-content pre { + background-color: #161b22; + border-color: #30363d; +} + +body.theme-dark #preview blockquote, +body.theme-dark [id^="preview-"] blockquote, +body.theme-dark .preview-content blockquote { + color: #8b949e; + border-left-color: #3b434b; +} + +body.theme-dark #preview a, +body.theme-dark [id^="preview-"] a, +body.theme-dark .preview-content a { + color: #58a6ff; +} + +body.theme-dark #preview a:hover, +body.theme-dark [id^="preview-"] a:hover, +body.theme-dark .preview-content a:hover { + color: #79c0ff; +} + +body.theme-dark #preview table, +body.theme-dark [id^="preview-"] table, +body.theme-dark .preview-content table { + border-color: #30363d; +} + +body.theme-dark #preview table th, +body.theme-dark [id^="preview-"] table th, +body.theme-dark .preview-content table th { + background-color: #161b22; + color: #c9d1d9; + border-color: #30363d; +} + +body.theme-dark #preview table td, +body.theme-dark [id^="preview-"] table td, +body.theme-dark .preview-content table td { + border-color: #30363d; + color: #c9d1d9; +} + +body.theme-dark #preview hr, +body.theme-dark [id^="preview-"] hr, +body.theme-dark .preview-content hr { + background-color: #21262d; + border-color: #21262d; +} + +body.theme-dark #preview ul, +body.theme-dark #preview ol, +body.theme-dark [id^="preview-"] ul, +body.theme-dark [id^="preview-"] ol, +body.theme-dark .preview-content ul, +body.theme-dark .preview-content ol { + color: #c9d1d9; +} + +body.theme-dark #preview li, +body.theme-dark [id^="preview-"] li, +body.theme-dark .preview-content li { + color: #c9d1d9; +} + #preview ul, #preview ol, .preview-content ul, .preview-content ol { padding-left: 2em; margin-bottom: 16px; @@ -326,10 +504,11 @@ body { #preview table th, .preview-content table th { font-weight: 600; + background-color: #f6f8fa; } #preview table tr:nth-child(2n), .preview-content table tr:nth-child(2n) { - background-color: #f6f8fa; + background-color: #ffffff; } /* Theme: Dark */ @@ -1516,6 +1695,42 @@ body.theme-github .line-numbers { color: #586069; } +/* Export Profiles Styles */ +.export-profiles { + border-bottom: 1px solid #ddd; + padding-bottom: 15px; + margin-bottom: 15px; +} + +.profile-controls { + display: flex; + gap: 8px; + align-items: center; + margin-top: 8px; +} + +.profile-controls select { + flex: 1; + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; +} + +.profile-controls button { + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: 4px; + background: #f5f5f5; + cursor: pointer; + font-size: 13px; + transition: all 0.2s; +} + +.profile-controls button:hover { + background: #e8e8e8; +} + /* Advanced Export Toggle Styles */ .export-mode-toggle { border-bottom: 1px solid #ddd; @@ -3445,4 +3660,144 @@ body[data-theme="dark"] .field-option { body[data-theme="dark"] .field-option:hover { background: #0d6efd; color: white; -} \ No newline at end of file +} +/* Mermaid Diagram Styles */ +.mermaid { + background: #f9f9f9; + padding: 20px; + border-radius: 8px; + margin: 20px 0; + text-align: center; + border: 1px solid #e0e0e0; +} + +.mermaid svg { + max-width: 100%; + height: auto; +} + +body.theme-dark .mermaid { + background: #2d2d2d; + border-color: #444; +} + +body.theme-solarized .mermaid { + background: #fdf6e3; + border-color: #eee8d5; +} + +body.theme-monokai .mermaid { + background: #2f2f2f; + border-color: #49483e; +} + +body.theme-github .mermaid { + background: #f6f8fa; + border-color: #e1e4e8; +} + +/* Command Palette Styles */ +.command-palette { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + display: flex; + align-items: flex-start; + justify-content: center; + padding-top: 100px; + z-index: 10000; +} + +.command-palette.hidden { + display: none; +} + +.command-palette-content { + background: #fff; + border-radius: 12px; + width: 600px; + max-width: 90%; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + overflow: hidden; +} + +#command-search { + width: 100%; + padding: 20px; + font-size: 18px; + border: none; + border-bottom: 1px solid #e0e0e0; + outline: none; +} + +.command-list { + max-height: 400px; + overflow-y: auto; +} + +.command-item { + padding: 12px 20px; + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; + transition: background 0.15s; +} + +.command-item:hover, +.command-item.selected { + background: #f0f0f0; +} + +.command-name { + font-weight: 500; +} + +.command-shortcut { + font-size: 12px; + color: #666; + background: #f5f5f5; + padding: 3px 8px; + border-radius: 3px; +} + +.command-palette-footer { + padding: 12px 20px; + background: #f9f9f9; + border-top: 1px solid #e0e0e0; + display: flex; + gap: 20px; + font-size: 12px; + color: #666; +} + +/* Dark theme support for command palette */ +body.theme-dark .command-palette-content { + background: #2d2d2d; + color: #f0f0f0; +} + +body.theme-dark #command-search { + background: #2d2d2d; + color: #f0f0f0; + border-color: #444; +} + +body.theme-dark .command-item:hover, +body.theme-dark .command-item.selected { + background: #3d3d3d; +} + +body.theme-dark .command-shortcut { + background: #3d3d3d; + color: #ccc; +} + +body.theme-dark .command-palette-footer { + background: #252525; + border-color: #444; + color: #999; +} diff --git a/src/wordTemplateExporter.js b/src/wordTemplateExporter.js index 73060fd..534d6e2 100644 --- a/src/wordTemplateExporter.js +++ b/src/wordTemplateExporter.js @@ -10,9 +10,10 @@ const PizZip = require('pizzip'); const Docx = require('docx4js').default; class WordTemplateExporter { - constructor(templatePath, startPage = 3) { + constructor(templatePath, startPage = 3, pageSettings = null) { this.templatePath = templatePath || path.join(__dirname, '../word_template.docx'); this.startPage = startPage; // Which page to start inserting content + this.pageSettings = pageSettings; // Page size and orientation settings } /** @@ -25,7 +26,12 @@ class WordTemplateExporter { const zip = new PizZip(templateBuffer); // Extract document.xml - const documentXml = zip.file('word/document.xml').asText(); + let documentXml = zip.file('word/document.xml').asText(); + + // Set page size if settings provided + if (this.pageSettings) { + documentXml = this.setPageSize(documentXml); + } // Parse markdown and generate Word XML const newContentXml = this.markdownToWordXml(markdownContent); @@ -47,6 +53,56 @@ class WordTemplateExporter { } } + /** + * Set page size in document XML + */ + setPageSize(documentXml) { + // Import PAGE_SIZES from main process (need to pass as parameter) + const PAGE_SIZES = { + a4: { width: 11906, height: 16838 }, + a3: { width: 16838, height: 23811 }, + a5: { width: 8391, height: 11906 }, + b4: { width: 14170, height: 20015 }, + b5: { width: 9979, height: 14170 }, + letter: { width: 12240, height: 15840 }, + legal: { width: 12240, height: 20160 }, + tabloid: { width: 15840, height: 24480 } + }; + + let width, height; + const pageSize = PAGE_SIZES[this.pageSettings.size]; + + if (pageSize) { + width = pageSize.width; + height = pageSize.height; + } else { + // Default to A4 + width = 11906; + height = 16838; + } + + // Swap dimensions for landscape + if (this.pageSettings.orientation === 'landscape') { + [width, height] = [height, width]; + } + + // Update all elements in section properties + const pgSzRegex = /]*\/>/g; + let modifiedXml = documentXml.replace(pgSzRegex, () => { + return ``; + }); + + // If no pgSz found, add it to all sectPr elements + if (!pgSzRegex.test(documentXml)) { + const sectPrRegex = /]*>/g; + modifiedXml = modifiedXml.replace(sectPrRegex, (match) => { + return `${match}`; + }); + } + + return modifiedXml; + } + /** * Insert markdown content after the specified page in the document * @param {string} documentXml - The document XML @@ -306,6 +362,7 @@ class WordTemplateExporter { /** * Create table XML from markdown table lines with template styling + * Uses full-width tables with equal column distribution matching template style */ createTableXml(tableLines) { // Parse table @@ -327,13 +384,19 @@ class WordTemplateExporter { // Calculate number of columns const numCols = Math.max(...rows.map(row => row.length)); + // Calculate column width in twips (1440 twips = 1 inch) + // Assume standard page width of 9360 twips (6.5 inches usable) + const totalTableWidth = 9360; + const colWidth = Math.floor(totalTableWidth / numCols); + // Build table XML let tableXml = ''; - // Table properties - use template table style + // Table properties - use template table style with full width tableXml += ` - + + @@ -345,20 +408,22 @@ class WordTemplateExporter { `; - // Table grid + // Table grid with explicit column widths tableXml += ''; for (let i = 0; i < numCols; i++) { - tableXml += ''; + tableXml += ``; } tableXml += ''; // Table rows rows.forEach((rowCells, rowIndex) => { const isHeader = rowIndex === 0; - const isEvenRow = rowIndex % 2 === 0; tableXml += ''; + // Row properties for consistent height + tableXml += ''; + // Pad row to have same number of columns while (rowCells.length < numCols) { rowCells.push(''); @@ -368,9 +433,14 @@ class WordTemplateExporter { tableXml += ''; tableXml += ''; + // Cell width + tableXml += ``; + // Cell shading - orange for header, white for data rows if (isHeader) { - tableXml += ''; + tableXml += ''; + } else { + tableXml += ''; } // Cell borders @@ -380,12 +450,21 @@ class WordTemplateExporter { '' + '' + ''; + + // Cell margins for proper padding + tableXml += '' + + '' + + '' + + '' + + '' + + ''; + tableXml += ''; // Cell content tableXml += ''; tableXml += ''; - tableXml += ''; + tableXml += ''; tableXml += ''; const runs = this.parseInlineFormatting(cellText); @@ -407,6 +486,9 @@ class WordTemplateExporter { tableXml += ''; + // Add spacing after table + tableXml += ''; + return tableXml; }