mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 10:00:17 +05:30
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:
+1
-1
@@ -33,4 +33,4 @@ pdf\ modal.png
|
||||
# Claude/AI development files
|
||||
.claude/
|
||||
CLAUDE.md
|
||||
agents.md
|
||||
agents.mdcoverage/
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
"marked": "^17.0.3",
|
||||
"marked-footnote": "^1.4.0",
|
||||
"marked-highlight": "^2.2.3",
|
||||
"mermaid": "^11.12.3",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pdfjs-dist": "^5.5.207",
|
||||
"pdfkit": "^0.17.2",
|
||||
|
||||
@@ -10,11 +10,8 @@ const {
|
||||
} = require('@codemirror/view');
|
||||
const { EditorState } = require('@codemirror/state');
|
||||
const { markdown, markdownLanguage } = require('@codemirror/lang-markdown');
|
||||
const { javascript } = require('@codemirror/lang-javascript');
|
||||
const { html } = require('@codemirror/lang-html');
|
||||
const { css } = require('@codemirror/lang-css');
|
||||
const { json } = require('@codemirror/lang-json');
|
||||
const { python } = require('@codemirror/lang-python');
|
||||
// Language extensions loaded lazily on first use
|
||||
let _javascript, _html, _css, _json, _python;
|
||||
const {
|
||||
defaultKeymap,
|
||||
history,
|
||||
@@ -108,19 +105,19 @@ function createEditor(parentElement, options = {}) {
|
||||
* @returns {Extension} CodeMirror language extension
|
||||
*/
|
||||
function getLanguageExtension(lang) {
|
||||
const languages = {
|
||||
javascript,
|
||||
js: javascript,
|
||||
html,
|
||||
css,
|
||||
json,
|
||||
python,
|
||||
py: python,
|
||||
const loaders = {
|
||||
javascript: () => { if (!_javascript) _javascript = require('@codemirror/lang-javascript').javascript; return _javascript(); },
|
||||
html: () => { if (!_html) _html = require('@codemirror/lang-html').html; return _html(); },
|
||||
css: () => { if (!_css) _css = require('@codemirror/lang-css').css; return _css(); },
|
||||
json: () => { if (!_json) _json = require('@codemirror/lang-json').json; return _json(); },
|
||||
python: () => { if (!_python) _python = require('@codemirror/lang-python').python; return _python(); },
|
||||
markdown: () => markdown({ base: markdownLanguage }),
|
||||
};
|
||||
loaders.js = loaders.javascript;
|
||||
loaders.py = loaders.python;
|
||||
|
||||
const factory = languages[lang];
|
||||
return factory ? factory() : markdown({ base: markdownLanguage });
|
||||
const loader = loaders[lang];
|
||||
return loader ? loader() : markdown({ base: markdownLanguage });
|
||||
}
|
||||
|
||||
module.exports = { createEditor, getLanguageExtension };
|
||||
|
||||
+1
-6
@@ -11,9 +11,7 @@
|
||||
<link rel="stylesheet" href="styles-concreteinfo.css">
|
||||
<link rel="stylesheet" href="styles-sidebar.css">
|
||||
<link rel="stylesheet" href="styles-welcome.css">
|
||||
<!-- Load external resources asynchronously -->
|
||||
<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>
|
||||
<link rel="stylesheet" href="../node_modules/highlight.js/styles/default.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
@@ -1433,9 +1431,6 @@
|
||||
</div>
|
||||
<div class="pane-resizer" id="pane-resizer-1" title="Drag to resize"></div>
|
||||
<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">⬜</button>
|
||||
</div>
|
||||
<div id="preview-1" class="preview-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+91
@@ -324,6 +324,7 @@ function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
@@ -334,6 +335,11 @@ function createWindow() {
|
||||
|
||||
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();
|
||||
|
||||
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)
|
||||
function convertWithLibreOffice(inputFile, outputFormat, outputPath) {
|
||||
const outputDir = path.dirname(outputPath);
|
||||
|
||||
@@ -215,6 +215,9 @@ const ALLOWED_RECEIVE_CHANNELS = [
|
||||
// Insert content from generator windows
|
||||
'insert-content',
|
||||
|
||||
// Batch converter
|
||||
'show-batch-converter',
|
||||
|
||||
// v4 menu-triggered events
|
||||
'load-template-menu',
|
||||
'toggle-command-palette',
|
||||
|
||||
+79
-238
@@ -10,15 +10,19 @@ const DOMPurify = require('dompurify');
|
||||
const hljs = require('highlight.js');
|
||||
const { createEditor } = require('./editor/codemirror-setup');
|
||||
const { undo, redo } = require('@codemirror/commands');
|
||||
const { SidebarManager } = require('./sidebar/sidebar-manager');
|
||||
const { renderTemplatesPanel } = require('./sidebar/templates-panel');
|
||||
const { renderExplorerPanel } = require('./sidebar/explorer-panel');
|
||||
const { renderGitPanel } = require('./sidebar/git-panel');
|
||||
const { renderSnippetsPanel } = require('./sidebar/snippets-panel');
|
||||
const { ReplPanel } = require('./repl/repl-panel');
|
||||
const { CommandPalette } = require('./command-palette');
|
||||
const { PrintPreview } = require('./print-preview');
|
||||
const { createWelcomeContent } = require('./welcome');
|
||||
// Lazy-loaded modules — defer heavy imports until first use
|
||||
let _SidebarManager, _renderTemplatesPanel, _renderExplorerPanel, _renderGitPanel, _renderSnippetsPanel;
|
||||
let _ReplPanel, _CommandPalette, _PrintPreview, _createWelcomeContent;
|
||||
|
||||
function getSidebarManager() { if (!_SidebarManager) _SidebarManager = require('./sidebar/sidebar-manager').SidebarManager; return _SidebarManager; }
|
||||
function getRenderTemplatesPanel() { if (!_renderTemplatesPanel) _renderTemplatesPanel = require('./sidebar/templates-panel').renderTemplatesPanel; return _renderTemplatesPanel; }
|
||||
function getRenderExplorerPanel() { if (!_renderExplorerPanel) _renderExplorerPanel = require('./sidebar/explorer-panel').renderExplorerPanel; return _renderExplorerPanel; }
|
||||
function getRenderGitPanel() { if (!_renderGitPanel) _renderGitPanel = require('./sidebar/git-panel').renderGitPanel; return _renderGitPanel; }
|
||||
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
|
||||
marked.use(markedHighlight({
|
||||
@@ -192,9 +196,6 @@ class TabManager {
|
||||
</div>
|
||||
<div class="pane-resizer" id="pane-resizer-${tab.id}" title="Drag to resize"></div>
|
||||
<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>
|
||||
`;
|
||||
@@ -433,38 +434,29 @@ class TabManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Render Mermaid diagrams if Mermaid is available
|
||||
if (window.mermaid) {
|
||||
// Render Mermaid diagrams — lazy-load mermaid only when needed
|
||||
const mermaidBlocks = preview.querySelectorAll('pre code.language-mermaid');
|
||||
if (mermaidBlocks.length > 0) {
|
||||
try {
|
||||
// Find all code blocks with language-mermaid class
|
||||
const mermaidBlocks = preview.querySelectorAll('pre code.language-mermaid');
|
||||
mermaidBlocks.forEach((block, index) => {
|
||||
// Lazy-load mermaid on first use
|
||||
if (!window.mermaid) {
|
||||
const mermaidModule = require('mermaid');
|
||||
window.mermaid = mermaidModule.default || mermaidModule;
|
||||
}
|
||||
|
||||
mermaidBlocks.forEach((block) => {
|
||||
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
|
||||
window.mermaid.initialize({ startOnLoad: false, theme, securityLevel: 'loose' });
|
||||
window.mermaid.run({ querySelector: '.mermaid:not([data-rendered])' }).then(() => {
|
||||
preview.querySelectorAll('.mermaid').forEach(el => {
|
||||
el.setAttribute('data-rendered', 'true');
|
||||
});
|
||||
@@ -1147,15 +1139,17 @@ let replPanel;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
tabManager = new TabManager();
|
||||
const ReplPanel = getReplPanel();
|
||||
replPanel = new ReplPanel();
|
||||
|
||||
// Initialize sidebar
|
||||
const SidebarManager = getSidebarManager();
|
||||
const sidebarManager = new SidebarManager();
|
||||
let explorerCurrentDir = null;
|
||||
|
||||
sidebarManager.registerPanel('explorer', {
|
||||
title: 'Explorer',
|
||||
render: (container) => renderExplorerPanel(container, {
|
||||
render: (container) => getRenderExplorerPanel()(container, {
|
||||
listDirectory: (dir) => ipcRenderer.invoke('list-directory', dir),
|
||||
onFileOpen: (filePath) => ipcRenderer.send('open-file-path', filePath),
|
||||
currentDir: explorerCurrentDir,
|
||||
@@ -1163,7 +1157,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
sidebarManager.registerPanel('git', {
|
||||
title: 'Git',
|
||||
render: (container) => renderGitPanel(container, {
|
||||
render: (container) => getRenderGitPanel()(container, {
|
||||
gitStatus: () => ipcRenderer.invoke('git-status'),
|
||||
gitDiff: (file) => ipcRenderer.invoke('git-diff', { file }),
|
||||
gitStage: (files) => ipcRenderer.invoke('git-stage', { files }),
|
||||
@@ -1173,7 +1167,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
sidebarManager.registerPanel('snippets', {
|
||||
title: 'Snippets',
|
||||
render: (container) => renderSnippetsPanel(container, {
|
||||
render: (container) => getRenderSnippetsPanel()(container, {
|
||||
getSnippets: () => ipcRenderer.invoke('get-snippets'),
|
||||
saveSnippet: (s) => ipcRenderer.invoke('save-snippet', s),
|
||||
deleteSnippet: (id) => ipcRenderer.invoke('delete-snippet', id),
|
||||
@@ -1182,7 +1176,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
sidebarManager.registerPanel('templates', {
|
||||
title: 'Templates',
|
||||
render: (container) => renderTemplatesPanel(container, async (file) => {
|
||||
render: (container) => getRenderTemplatesPanel()(container, async (file) => {
|
||||
const templateContent = await ipcRenderer.invoke('load-template', file);
|
||||
if (templateContent) {
|
||||
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
|
||||
const recentFiles = JSON.parse(localStorage.getItem('recentFiles') || '[]');
|
||||
const welcomeHtml = createWelcomeContent(recentFiles);
|
||||
const welcomeHtml = getCreateWelcomeContent()(recentFiles);
|
||||
|
||||
const tab = tabManager.tabs.get(tabManager.activeTabId);
|
||||
if (tab) {
|
||||
@@ -1287,9 +1281,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
// Initialize command palette
|
||||
const CommandPalette = getCommandPalette();
|
||||
const commandPalette = new CommandPalette();
|
||||
|
||||
// Initialize print preview
|
||||
const PrintPreview = getPrintPreview();
|
||||
const printPreview = new PrintPreview();
|
||||
|
||||
// Register commands
|
||||
@@ -1297,10 +1293,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
commandPalette.register('Open File', 'Ctrl+O', () => ipcRenderer.send('menu-open'));
|
||||
commandPalette.register('Save', 'Ctrl+S', () => {
|
||||
const content = tabManager.getCurrentContent();
|
||||
const filePath = tabManager.getCurrentFilePath();
|
||||
if (filePath) {
|
||||
ipcRenderer.send('save-current-file', content);
|
||||
}
|
||||
ipcRenderer.send('save-current-file', content);
|
||||
});
|
||||
commandPalette.register('Toggle Preview', '', () => {
|
||||
tabManager.isPreviewVisible = !tabManager.isPreviewVisible;
|
||||
@@ -1414,14 +1407,24 @@ ipcRenderer.on('file-opened', (event, data) => {
|
||||
ipcRenderer.on('file-save', () => {
|
||||
const currentContent = tabManager.getCurrentContent();
|
||||
const currentFilePath = tabManager.getCurrentFilePath();
|
||||
if (currentFilePath) {
|
||||
ipcRenderer.send('save-current-file', currentContent);
|
||||
}
|
||||
// send to main process which will save or trigger save-as dialog
|
||||
ipcRenderer.send('save-current-file', currentContent);
|
||||
});
|
||||
|
||||
ipcRenderer.on('get-content-for-save', (event, filePath) => {
|
||||
const currentContent = tabManager.getCurrentContent();
|
||||
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) => {
|
||||
@@ -1527,7 +1530,8 @@ function openPrintPreviewDialog() {
|
||||
return;
|
||||
}
|
||||
|
||||
const printPreviewInstance = new PrintPreview();
|
||||
const PrintPreviewClass = getPrintPreview();
|
||||
const printPreviewInstance = new PrintPreviewClass();
|
||||
printPreviewInstance.open(previewContent.innerHTML);
|
||||
}
|
||||
|
||||
@@ -2001,6 +2005,25 @@ ipcRenderer.on('show-universal-converter-dialog', () => {
|
||||
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) => {
|
||||
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
|
||||
@@ -4267,8 +4101,15 @@ let pdfFilePath = null;
|
||||
let isPdfViewerActive = false; // Track if PDF viewer is currently shown
|
||||
|
||||
// Initialize PDF.js
|
||||
const pdfjsLib = require('pdfjs-dist');
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = require.resolve('pdfjs-dist/build/pdf.worker.mjs');
|
||||
// Lazy-load pdfjs-dist only when PDF viewer is needed
|
||||
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
|
||||
async function openPdfFile(filePath) {
|
||||
@@ -4292,7 +4133,7 @@ async function openPdfFile(filePath) {
|
||||
// Show loading state
|
||||
document.getElementById('status-text').textContent = 'Loading PDF...';
|
||||
|
||||
const loadingTask = pdfjsLib.getDocument(filePath);
|
||||
const loadingTask = getPdfjsLib().getDocument(filePath);
|
||||
pdfDoc = await loadingTask.promise;
|
||||
pdfFilePath = filePath;
|
||||
pdfCurrentPage = 1;
|
||||
|
||||
@@ -188,34 +188,6 @@ body {
|
||||
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 {
|
||||
width: 100%;
|
||||
|
||||
Reference in New Issue
Block a user