fix: resolve editor, batch conversion, and startup performance issues

- Remove popout preview button (HTML, JS, CSS)
- Fix Save/Save As flow for new untitled files
- Fix batch conversion menu items (wire show-batch-converter IPC)
- Add universal-convert-batch IPC handler for batch file conversion
- Lazy-load mermaid, pdfjs-dist, sidebar panels, command palette
- Switch highlight.js CSS from CDN to local
- Defer CodeMirror language extensions until first use
- Add show:false + ready-to-show for faster perceived startup
- Install mermaid as local dependency (remove CDN script tag)
This commit is contained in:
2026-03-04 17:44:04 +05:30
parent 10d2fc8b8f
commit 01d833f520
8 changed files with 188 additions and 288 deletions
+1 -1
View File
@@ -33,4 +33,4 @@ pdf\ modal.png
# Claude/AI development files # Claude/AI development files
.claude/ .claude/
CLAUDE.md CLAUDE.md
agents.md agents.mdcoverage/
+1
View File
@@ -77,6 +77,7 @@
"marked": "^17.0.3", "marked": "^17.0.3",
"marked-footnote": "^1.4.0", "marked-footnote": "^1.4.0",
"marked-highlight": "^2.2.3", "marked-highlight": "^2.2.3",
"mermaid": "^11.12.3",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"pdfjs-dist": "^5.5.207", "pdfjs-dist": "^5.5.207",
"pdfkit": "^0.17.2", "pdfkit": "^0.17.2",
+12 -15
View File
@@ -10,11 +10,8 @@ const {
} = require('@codemirror/view'); } = require('@codemirror/view');
const { EditorState } = require('@codemirror/state'); const { EditorState } = require('@codemirror/state');
const { markdown, markdownLanguage } = require('@codemirror/lang-markdown'); const { markdown, markdownLanguage } = require('@codemirror/lang-markdown');
const { javascript } = require('@codemirror/lang-javascript'); // Language extensions loaded lazily on first use
const { html } = require('@codemirror/lang-html'); let _javascript, _html, _css, _json, _python;
const { css } = require('@codemirror/lang-css');
const { json } = require('@codemirror/lang-json');
const { python } = require('@codemirror/lang-python');
const { const {
defaultKeymap, defaultKeymap,
history, history,
@@ -108,19 +105,19 @@ function createEditor(parentElement, options = {}) {
* @returns {Extension} CodeMirror language extension * @returns {Extension} CodeMirror language extension
*/ */
function getLanguageExtension(lang) { function getLanguageExtension(lang) {
const languages = { const loaders = {
javascript, javascript: () => { if (!_javascript) _javascript = require('@codemirror/lang-javascript').javascript; return _javascript(); },
js: javascript, html: () => { if (!_html) _html = require('@codemirror/lang-html').html; return _html(); },
html, css: () => { if (!_css) _css = require('@codemirror/lang-css').css; return _css(); },
css, json: () => { if (!_json) _json = require('@codemirror/lang-json').json; return _json(); },
json, python: () => { if (!_python) _python = require('@codemirror/lang-python').python; return _python(); },
python,
py: python,
markdown: () => markdown({ base: markdownLanguage }), markdown: () => markdown({ base: markdownLanguage }),
}; };
loaders.js = loaders.javascript;
loaders.py = loaders.python;
const factory = languages[lang]; const loader = loaders[lang];
return factory ? factory() : markdown({ base: markdownLanguage }); return loader ? loader() : markdown({ base: markdownLanguage });
} }
module.exports = { createEditor, getLanguageExtension }; module.exports = { createEditor, getLanguageExtension };
+1 -6
View File
@@ -11,9 +11,7 @@
<link rel="stylesheet" href="styles-concreteinfo.css"> <link rel="stylesheet" href="styles-concreteinfo.css">
<link rel="stylesheet" href="styles-sidebar.css"> <link rel="stylesheet" href="styles-sidebar.css">
<link rel="stylesheet" href="styles-welcome.css"> <link rel="stylesheet" href="styles-welcome.css">
<!-- Load external resources asynchronously --> <link rel="stylesheet" href="../node_modules/highlight.js/styles/default.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" media="print" onload="this.media='all'">
<script defer src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
@@ -1433,9 +1431,6 @@
</div> </div>
<div class="pane-resizer" id="pane-resizer-1" title="Drag to resize"></div> <div class="pane-resizer" id="pane-resizer-1" title="Drag to resize"></div>
<div id="preview-pane-1" class="pane"> <div id="preview-pane-1" class="pane">
<div class="preview-header">
<button class="preview-popout-btn" id="preview-popout-1" title="Pop out preview in new window">&#x2B1C;</button>
</div>
<div id="preview-1" class="preview-content"></div> <div id="preview-1" class="preview-content"></div>
</div> </div>
</div> </div>
+91
View File
@@ -324,6 +324,7 @@ function createWindow() {
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
width: 1200, width: 1200,
height: 800, height: 800,
show: false,
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
contextIsolation: false, contextIsolation: false,
@@ -334,6 +335,11 @@ function createWindow() {
mainWindow.loadFile(path.join(__dirname, 'index.html')); mainWindow.loadFile(path.join(__dirname, 'index.html'));
// Show window only after content is ready — avoids blank flash
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});
createMenu(); createMenu();
mainWindow.on('closed', () => { mainWindow.on('closed', () => {
@@ -1821,6 +1827,91 @@ ipcMain.on('universal-convert', async (event, { tool, fromFormat, toFormat, file
} }
}); });
// Handle universal batch file conversion
ipcMain.on('universal-convert-batch', async (event, { tool, fromFormat, toFormat, inputFolder, outputFolder, includeSubfolders, advancedOptions }) => {
if (!conversionLimiter()) {
mainWindow.webContents.send('conversion-status', 'Please wait before converting again...');
return;
}
try {
const toolAvailable = await checkConverterAvailable(tool);
if (!toolAvailable) {
throw new Error(`${tool} is not installed or not found in PATH. Please install it first.`);
}
// Collect matching files
const files = [];
function collectFiles(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory() && includeSubfolders) {
collectFiles(fullPath);
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(`.${fromFormat}`)) {
files.push(fullPath);
}
}
}
collectFiles(inputFolder);
if (files.length === 0) {
mainWindow.webContents.send('conversion-complete', { success: false, error: `No .${fromFormat} files found in the selected folder.` });
return;
}
let completed = 0;
let failed = 0;
for (const filePath of files) {
const relativePath = path.relative(inputFolder, filePath);
const outputPath = path.join(outputFolder, relativePath.replace(/\.[^/.]+$/, `.${toFormat}`));
// Ensure output subdirectory exists
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
mainWindow.webContents.send('conversion-status', `Converting ${completed + 1}/${files.length}: ${path.basename(filePath)}`);
let conversionInfo;
switch (tool) {
case 'libreoffice': conversionInfo = convertWithLibreOffice(filePath, toFormat, outputPath); break;
case 'imagemagick': conversionInfo = convertWithImageMagick(filePath, outputPath); break;
case 'ffmpeg': conversionInfo = convertWithFFmpeg(filePath, outputPath); break;
case 'pandoc': conversionInfo = convertWithPandoc(filePath, outputPath); break;
default: throw new Error(`Unknown conversion tool: ${tool}`);
}
await new Promise((resolve) => {
execFile(conversionInfo.command, conversionInfo.args, (error) => {
if (error) { failed++; } else { completed++; }
resolve();
});
});
}
mainWindow.webContents.send('conversion-complete', {
success: true,
outputPath: outputFolder
});
dialog.showMessageBox(mainWindow, {
type: 'info',
title: 'Batch Conversion Complete',
message: `Batch conversion finished!`,
detail: `Converted: ${completed}/${files.length} files${failed > 0 ? ` (${failed} failed)` : ''}\nOutput: ${outputFolder}`,
buttons: ['OK']
});
} catch (error) {
mainWindow.webContents.send('conversion-complete', { success: false, error: error.message });
dialog.showMessageBox(mainWindow, {
type: 'error',
title: 'Batch Conversion Failed',
message: 'Batch conversion failed',
detail: error.message,
buttons: ['OK']
});
}
});
// LibreOffice conversion - returns {command, args} for execFile (safer than exec) // LibreOffice conversion - returns {command, args} for execFile (safer than exec)
function convertWithLibreOffice(inputFile, outputFormat, outputPath) { function convertWithLibreOffice(inputFile, outputFormat, outputPath) {
const outputDir = path.dirname(outputPath); const outputDir = path.dirname(outputPath);
+3
View File
@@ -215,6 +215,9 @@ const ALLOWED_RECEIVE_CHANNELS = [
// Insert content from generator windows // Insert content from generator windows
'insert-content', 'insert-content',
// Batch converter
'show-batch-converter',
// v4 menu-triggered events // v4 menu-triggered events
'load-template-menu', 'load-template-menu',
'toggle-command-palette', 'toggle-command-palette',
+79 -238
View File
@@ -10,15 +10,19 @@ const DOMPurify = require('dompurify');
const hljs = require('highlight.js'); const hljs = require('highlight.js');
const { createEditor } = require('./editor/codemirror-setup'); const { createEditor } = require('./editor/codemirror-setup');
const { undo, redo } = require('@codemirror/commands'); const { undo, redo } = require('@codemirror/commands');
const { SidebarManager } = require('./sidebar/sidebar-manager'); // Lazy-loaded modules — defer heavy imports until first use
const { renderTemplatesPanel } = require('./sidebar/templates-panel'); let _SidebarManager, _renderTemplatesPanel, _renderExplorerPanel, _renderGitPanel, _renderSnippetsPanel;
const { renderExplorerPanel } = require('./sidebar/explorer-panel'); let _ReplPanel, _CommandPalette, _PrintPreview, _createWelcomeContent;
const { renderGitPanel } = require('./sidebar/git-panel');
const { renderSnippetsPanel } = require('./sidebar/snippets-panel'); function getSidebarManager() { if (!_SidebarManager) _SidebarManager = require('./sidebar/sidebar-manager').SidebarManager; return _SidebarManager; }
const { ReplPanel } = require('./repl/repl-panel'); function getRenderTemplatesPanel() { if (!_renderTemplatesPanel) _renderTemplatesPanel = require('./sidebar/templates-panel').renderTemplatesPanel; return _renderTemplatesPanel; }
const { CommandPalette } = require('./command-palette'); function getRenderExplorerPanel() { if (!_renderExplorerPanel) _renderExplorerPanel = require('./sidebar/explorer-panel').renderExplorerPanel; return _renderExplorerPanel; }
const { PrintPreview } = require('./print-preview'); function getRenderGitPanel() { if (!_renderGitPanel) _renderGitPanel = require('./sidebar/git-panel').renderGitPanel; return _renderGitPanel; }
const { createWelcomeContent } = require('./welcome'); function getRenderSnippetsPanel() { if (!_renderSnippetsPanel) _renderSnippetsPanel = require('./sidebar/snippets-panel').renderSnippetsPanel; return _renderSnippetsPanel; }
function getReplPanel() { if (!_ReplPanel) _ReplPanel = require('./repl/repl-panel').ReplPanel; return _ReplPanel; }
function getCommandPalette() { if (!_CommandPalette) _CommandPalette = require('./command-palette').CommandPalette; return _CommandPalette; }
function getPrintPreview() { if (!_PrintPreview) _PrintPreview = require('./print-preview').PrintPreview; return _PrintPreview; }
function getCreateWelcomeContent() { if (!_createWelcomeContent) _createWelcomeContent = require('./welcome').createWelcomeContent; return _createWelcomeContent; }
// Configure marked with highlight extension // Configure marked with highlight extension
marked.use(markedHighlight({ marked.use(markedHighlight({
@@ -192,9 +196,6 @@ class TabManager {
</div> </div>
<div class="pane-resizer" id="pane-resizer-${tab.id}" title="Drag to resize"></div> <div class="pane-resizer" id="pane-resizer-${tab.id}" title="Drag to resize"></div>
<div id="preview-pane-${tab.id}" class="pane"> <div id="preview-pane-${tab.id}" class="pane">
<div class="preview-header">
<button class="preview-popout-btn" id="preview-popout-${tab.id}" title="Pop out preview in new window">⬜</button>
</div>
<div id="preview-${tab.id}" class="preview-content"></div> <div id="preview-${tab.id}" class="preview-content"></div>
</div> </div>
`; `;
@@ -433,38 +434,29 @@ class TabManager {
} }
} }
// Render Mermaid diagrams if Mermaid is available // Render Mermaid diagrams — lazy-load mermaid only when needed
if (window.mermaid) { const mermaidBlocks = preview.querySelectorAll('pre code.language-mermaid');
if (mermaidBlocks.length > 0) {
try { try {
// Find all code blocks with language-mermaid class // Lazy-load mermaid on first use
const mermaidBlocks = preview.querySelectorAll('pre code.language-mermaid'); if (!window.mermaid) {
mermaidBlocks.forEach((block, index) => { const mermaidModule = require('mermaid');
window.mermaid = mermaidModule.default || mermaidModule;
}
mermaidBlocks.forEach((block) => {
const code = block.textContent; const code = block.textContent;
const pre = block.parentElement; const pre = block.parentElement;
// Create a div for mermaid rendering
const mermaidDiv = document.createElement('div'); const mermaidDiv = document.createElement('div');
mermaidDiv.className = 'mermaid'; mermaidDiv.className = 'mermaid';
mermaidDiv.setAttribute('data-processed', 'true'); mermaidDiv.setAttribute('data-processed', 'true');
mermaidDiv.textContent = code; mermaidDiv.textContent = code;
// Replace the pre element with the mermaid div
pre.parentElement.replaceChild(mermaidDiv, pre); pre.parentElement.replaceChild(mermaidDiv, pre);
}); });
// Initialize Mermaid with dark theme support
const theme = document.body.className.includes('theme-dark') ? 'dark' : 'default'; const theme = document.body.className.includes('theme-dark') ? 'dark' : 'default';
mermaid.initialize({ window.mermaid.initialize({ startOnLoad: false, theme, securityLevel: 'loose' });
startOnLoad: false, window.mermaid.run({ querySelector: '.mermaid:not([data-rendered])' }).then(() => {
theme: theme,
securityLevel: 'loose'
});
// Render all mermaid diagrams
mermaid.run({
querySelector: '.mermaid:not([data-rendered])'
}).then(() => {
// Mark as rendered
preview.querySelectorAll('.mermaid').forEach(el => { preview.querySelectorAll('.mermaid').forEach(el => {
el.setAttribute('data-rendered', 'true'); el.setAttribute('data-rendered', 'true');
}); });
@@ -1147,15 +1139,17 @@ let replPanel;
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
tabManager = new TabManager(); tabManager = new TabManager();
const ReplPanel = getReplPanel();
replPanel = new ReplPanel(); replPanel = new ReplPanel();
// Initialize sidebar // Initialize sidebar
const SidebarManager = getSidebarManager();
const sidebarManager = new SidebarManager(); const sidebarManager = new SidebarManager();
let explorerCurrentDir = null; let explorerCurrentDir = null;
sidebarManager.registerPanel('explorer', { sidebarManager.registerPanel('explorer', {
title: 'Explorer', title: 'Explorer',
render: (container) => renderExplorerPanel(container, { render: (container) => getRenderExplorerPanel()(container, {
listDirectory: (dir) => ipcRenderer.invoke('list-directory', dir), listDirectory: (dir) => ipcRenderer.invoke('list-directory', dir),
onFileOpen: (filePath) => ipcRenderer.send('open-file-path', filePath), onFileOpen: (filePath) => ipcRenderer.send('open-file-path', filePath),
currentDir: explorerCurrentDir, currentDir: explorerCurrentDir,
@@ -1163,7 +1157,7 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
sidebarManager.registerPanel('git', { sidebarManager.registerPanel('git', {
title: 'Git', title: 'Git',
render: (container) => renderGitPanel(container, { render: (container) => getRenderGitPanel()(container, {
gitStatus: () => ipcRenderer.invoke('git-status'), gitStatus: () => ipcRenderer.invoke('git-status'),
gitDiff: (file) => ipcRenderer.invoke('git-diff', { file }), gitDiff: (file) => ipcRenderer.invoke('git-diff', { file }),
gitStage: (files) => ipcRenderer.invoke('git-stage', { files }), gitStage: (files) => ipcRenderer.invoke('git-stage', { files }),
@@ -1173,7 +1167,7 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
sidebarManager.registerPanel('snippets', { sidebarManager.registerPanel('snippets', {
title: 'Snippets', title: 'Snippets',
render: (container) => renderSnippetsPanel(container, { render: (container) => getRenderSnippetsPanel()(container, {
getSnippets: () => ipcRenderer.invoke('get-snippets'), getSnippets: () => ipcRenderer.invoke('get-snippets'),
saveSnippet: (s) => ipcRenderer.invoke('save-snippet', s), saveSnippet: (s) => ipcRenderer.invoke('save-snippet', s),
deleteSnippet: (id) => ipcRenderer.invoke('delete-snippet', id), deleteSnippet: (id) => ipcRenderer.invoke('delete-snippet', id),
@@ -1182,7 +1176,7 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
sidebarManager.registerPanel('templates', { sidebarManager.registerPanel('templates', {
title: 'Templates', title: 'Templates',
render: (container) => renderTemplatesPanel(container, async (file) => { render: (container) => getRenderTemplatesPanel()(container, async (file) => {
const templateContent = await ipcRenderer.invoke('load-template', file); const templateContent = await ipcRenderer.invoke('load-template', file);
if (templateContent) { if (templateContent) {
const content = templateContent.replace(/\{\{DATE\}\}/g, new Date().toISOString().split('T')[0]); const content = templateContent.replace(/\{\{DATE\}\}/g, new Date().toISOString().split('T')[0]);
@@ -1202,7 +1196,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Set welcome content in the first tab's preview // Set welcome content in the first tab's preview
const recentFiles = JSON.parse(localStorage.getItem('recentFiles') || '[]'); const recentFiles = JSON.parse(localStorage.getItem('recentFiles') || '[]');
const welcomeHtml = createWelcomeContent(recentFiles); const welcomeHtml = getCreateWelcomeContent()(recentFiles);
const tab = tabManager.tabs.get(tabManager.activeTabId); const tab = tabManager.tabs.get(tabManager.activeTabId);
if (tab) { if (tab) {
@@ -1287,9 +1281,11 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
// Initialize command palette // Initialize command palette
const CommandPalette = getCommandPalette();
const commandPalette = new CommandPalette(); const commandPalette = new CommandPalette();
// Initialize print preview // Initialize print preview
const PrintPreview = getPrintPreview();
const printPreview = new PrintPreview(); const printPreview = new PrintPreview();
// Register commands // Register commands
@@ -1297,10 +1293,7 @@ document.addEventListener('DOMContentLoaded', () => {
commandPalette.register('Open File', 'Ctrl+O', () => ipcRenderer.send('menu-open')); commandPalette.register('Open File', 'Ctrl+O', () => ipcRenderer.send('menu-open'));
commandPalette.register('Save', 'Ctrl+S', () => { commandPalette.register('Save', 'Ctrl+S', () => {
const content = tabManager.getCurrentContent(); const content = tabManager.getCurrentContent();
const filePath = tabManager.getCurrentFilePath(); ipcRenderer.send('save-current-file', content);
if (filePath) {
ipcRenderer.send('save-current-file', content);
}
}); });
commandPalette.register('Toggle Preview', '', () => { commandPalette.register('Toggle Preview', '', () => {
tabManager.isPreviewVisible = !tabManager.isPreviewVisible; tabManager.isPreviewVisible = !tabManager.isPreviewVisible;
@@ -1414,14 +1407,24 @@ ipcRenderer.on('file-opened', (event, data) => {
ipcRenderer.on('file-save', () => { ipcRenderer.on('file-save', () => {
const currentContent = tabManager.getCurrentContent(); const currentContent = tabManager.getCurrentContent();
const currentFilePath = tabManager.getCurrentFilePath(); const currentFilePath = tabManager.getCurrentFilePath();
if (currentFilePath) { // send to main process which will save or trigger save-as dialog
ipcRenderer.send('save-current-file', currentContent); ipcRenderer.send('save-current-file', currentContent);
}
}); });
ipcRenderer.on('get-content-for-save', (event, filePath) => { ipcRenderer.on('get-content-for-save', (event, filePath) => {
const currentContent = tabManager.getCurrentContent(); const currentContent = tabManager.getCurrentContent();
ipcRenderer.send('save-file', { path: filePath, content: currentContent }); ipcRenderer.send('save-file', { path: filePath, content: currentContent });
// Update the active tab's file path and title after save-as
const tab = tabManager.tabs.get(tabManager.activeTabId);
if (tab) {
tab.filePath = filePath;
tab.originalContent = currentContent;
tab.isDirty = false;
tab.title = filePath.split(/[/\\]/).pop();
tabManager.updateTabBar();
tabManager.updateFilePath();
tabManager.updateBreadcrumb();
}
}); });
ipcRenderer.on('get-content-for-spreadsheet', (event, format) => { ipcRenderer.on('get-content-for-spreadsheet', (event, format) => {
@@ -1527,7 +1530,8 @@ function openPrintPreviewDialog() {
return; return;
} }
const printPreviewInstance = new PrintPreview(); const PrintPreviewClass = getPrintPreview();
const printPreviewInstance = new PrintPreviewClass();
printPreviewInstance.open(previewContent.innerHTML); printPreviewInstance.open(previewContent.innerHTML);
} }
@@ -2001,6 +2005,25 @@ ipcRenderer.on('show-universal-converter-dialog', () => {
showUniversalConverterDialog(); showUniversalConverterDialog();
}); });
// Batch converter menu items - open universal converter with batch mode and correct tool
ipcRenderer.on('show-batch-converter', (event, type) => {
showUniversalConverterDialog();
// Map batch type to the appropriate tool
const toolMap = { image: 'imagemagick', audio: 'ffmpeg', video: 'ffmpeg', pdf: 'libreoffice' };
const tool = toolMap[type] || 'libreoffice';
const toolSelect = document.getElementById('converter-tool');
if (toolSelect) {
toolSelect.value = tool;
updateConverterFormats(tool);
}
// Enable batch mode
const batchToggle = document.getElementById('converter-batch-mode');
if (batchToggle) {
batchToggle.checked = true;
batchToggle.dispatchEvent(new Event('change'));
}
});
ipcRenderer.on('conversion-status', (event, status) => { ipcRenderer.on('conversion-status', (event, status) => {
document.getElementById('converter-status').textContent = status; document.getElementById('converter-status').textContent = status;
}); });
@@ -4048,195 +4071,6 @@ document.addEventListener('mouseup', () => {
} }
}); });
// ============================================================================
// 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 = `
<!DOCTYPE html>
<html>
<head>
<title>Preview - PanConverter</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
padding: 20px;
background: #ffffff;
color: #24292f;
line-height: 1.6;
}
${getPreviewStyles()}
</style>
</head>
<body>
<div id="preview-content">
${previewContent.innerHTML}
</div>
<script>
// Update content when parent window updates
window.addEventListener('message', (event) => {
if (event.data.type === 'preview-update') {
document.getElementById('preview-content').innerHTML = event.data.content;
}
});
</script>
</body>
</html>
`;
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);
}
});
// ============================================ // ============================================
// Insert Content from Generator Windows // Insert Content from Generator Windows
@@ -4267,8 +4101,15 @@ let pdfFilePath = null;
let isPdfViewerActive = false; // Track if PDF viewer is currently shown let isPdfViewerActive = false; // Track if PDF viewer is currently shown
// Initialize PDF.js // Initialize PDF.js
const pdfjsLib = require('pdfjs-dist'); // Lazy-load pdfjs-dist only when PDF viewer is needed
pdfjsLib.GlobalWorkerOptions.workerSrc = require.resolve('pdfjs-dist/build/pdf.worker.mjs'); let _pdfjsLib;
function getPdfjsLib() {
if (!_pdfjsLib) {
_pdfjsLib = require('pdfjs-dist');
_pdfjsLib.GlobalWorkerOptions.workerSrc = require.resolve('pdfjs-dist/build/pdf.worker.mjs');
}
return _pdfjsLib;
}
// Open PDF file // Open PDF file
async function openPdfFile(filePath) { async function openPdfFile(filePath) {
@@ -4292,7 +4133,7 @@ async function openPdfFile(filePath) {
// Show loading state // Show loading state
document.getElementById('status-text').textContent = 'Loading PDF...'; document.getElementById('status-text').textContent = 'Loading PDF...';
const loadingTask = pdfjsLib.getDocument(filePath); const loadingTask = getPdfjsLib().getDocument(filePath);
pdfDoc = await loadingTask.promise; pdfDoc = await loadingTask.promise;
pdfFilePath = filePath; pdfFilePath = filePath;
pdfCurrentPage = 1; pdfCurrentPage = 1;
-28
View File
@@ -188,34 +188,6 @@ body {
background: #666; 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: none;
border-radius: 3px;
padding: 2px 6px;
cursor: pointer;
font-size: 11px;
color: #8c959f;
opacity: 0.5;
transition: all 0.2s;
}
.preview-popout-btn:hover {
background: rgba(0, 0, 0, 0.05);
opacity: 1;
color: #24292f;
}
.editor-textarea { .editor-textarea {
width: 100%; width: 100%;