mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 18:10:18 +05:30
Compare commits
5
Commits
v4.4.3
...
v4.4.2-stable
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88e9a5290d | ||
|
|
fff15d8d3e | ||
|
|
c574d77c20 | ||
|
|
272215f9af | ||
|
|
0192590567 |
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<!-- CSP: unsafe-inline/unsafe-eval required for marked.js extensions and Mermaid -->
|
<!-- CSP: unsafe-inline/unsafe-eval required for marked.js extensions and Mermaid -->
|
||||||
<!-- TODO: Migrate to nonce-based CSP for better security -->
|
<!-- TODO: Migrate to nonce-based CSP for better security -->
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://www.plantuml.com;">
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data: blob:; font-src 'self' data: https://cdn.jsdelivr.net; connect-src 'self' https://www.plantuml.com;">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>MarkdownConverter</title>
|
<title>MarkdownConverter</title>
|
||||||
<!-- Design tokens - loaded first for CSS variable availability -->
|
<!-- Design tokens - loaded first for CSS variable availability -->
|
||||||
|
|||||||
+3
-1
@@ -242,7 +242,9 @@ function runPandoc(args, callback) {
|
|||||||
function runPandocCmd(cmdString, callback) {
|
function runPandocCmd(cmdString, callback) {
|
||||||
const parsed = parseCommand(cmdString);
|
const parsed = parseCommand(cmdString);
|
||||||
// Skip 'pandoc' if it's the first element (command itself)
|
// Skip 'pandoc' if it's the first element (command itself)
|
||||||
const args = parsed.command === 'pandoc' ? parsed.args : [parsed.command, ...parsed.args];
|
// Use path.basename to handle full paths like bin/linux/pandoc
|
||||||
|
const cmdBase = path.basename(parsed.command).replace(/\.exe$/i, '');
|
||||||
|
const args = cmdBase === 'pandoc' ? parsed.args : [parsed.command, ...parsed.args];
|
||||||
const pandocPath = getPandocPath();
|
const pandocPath = getPandocPath();
|
||||||
execFile(pandocPath, args, { maxBuffer: 10 * 1024 * 1024 }, callback);
|
execFile(pandocPath, args, { maxBuffer: 10 * 1024 * 1024 }, callback);
|
||||||
}
|
}
|
||||||
|
|||||||
+61
-61
@@ -4,6 +4,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const { ipcRenderer } = require('electron');
|
const { ipcRenderer } = require('electron');
|
||||||
|
|
||||||
|
// Shim window.electronAPI for main window which uses nodeIntegration
|
||||||
|
// without preload script (window.electronAPI is normally set by preload.js).
|
||||||
|
if (typeof window !== 'undefined' && !window.electronAPI) {
|
||||||
|
window.electronAPI = {
|
||||||
|
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
|
||||||
|
invoke: (channel, data) => ipcRenderer.invoke(channel, data),
|
||||||
|
on: (channel, callback) => ipcRenderer.on(channel, (event, ...args) => callback(...args))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const marked = require('marked');
|
const marked = require('marked');
|
||||||
const { markedHighlight } = require('marked-highlight');
|
const { markedHighlight } = require('marked-highlight');
|
||||||
const createDOMPurify = require('dompurify');
|
const createDOMPurify = require('dompurify');
|
||||||
@@ -11,13 +22,10 @@ const DOMPurify = createDOMPurify(window);
|
|||||||
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');
|
||||||
// Use window.ModalManager if already set by script tag, otherwise require it.
|
// ModalManager is loaded via <script src="utils/ModalManager.js"> in index.html.
|
||||||
// This prevents "Identifier 'ModalManager' has already been declared" when
|
// It already exists in the global scope — re-declaring with let/const causes
|
||||||
// both the script tag in index.html and CommonJS require() declare it.
|
// SyntaxError: "Identifier 'ModalManager' has already been declared".
|
||||||
let ModalManager;
|
if (typeof ModalManager === 'undefined') {
|
||||||
if (typeof window !== 'undefined' && window.ModalManager) {
|
|
||||||
ModalManager = window.ModalManager;
|
|
||||||
} else {
|
|
||||||
const result = require('./utils/ModalManager');
|
const result = require('./utils/ModalManager');
|
||||||
ModalManager = result.ModalManager || result;
|
ModalManager = result.ModalManager || result;
|
||||||
}
|
}
|
||||||
@@ -482,38 +490,7 @@ class TabManager {
|
|||||||
document.querySelector('.editor-container').appendChild(tabContent);
|
document.querySelector('.editor-container').appendChild(tabContent);
|
||||||
|
|
||||||
// Initialize CodeMirror editor
|
// Initialize CodeMirror editor
|
||||||
const editorContainer = document.getElementById(`editor-cm-${tab.id}`);
|
this.ensureEditor(tab);
|
||||||
if (editorContainer) {
|
|
||||||
const isDark = document.body.className.includes('dark');
|
|
||||||
tab.editorView = createEditor(editorContainer, {
|
|
||||||
content: tab.content,
|
|
||||||
onChange: (newContent) => {
|
|
||||||
tab.content = newContent;
|
|
||||||
tab.isDirty = true;
|
|
||||||
// Dynamically enable/disable Large File Mode on edit
|
|
||||||
if (newContent.length > 1024 * 1024) {
|
|
||||||
if (!tab.largeFileMode) {
|
|
||||||
tab.largeFileMode = true;
|
|
||||||
this.isPreviewVisible = false;
|
|
||||||
this.updatePreviewVisibility();
|
|
||||||
notifyUser('Large content detected (>1MB). Large File Mode enabled to maintain peak responsiveness. Live preview auto-render is disabled.', 'warning');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tab.largeFileMode = false;
|
|
||||||
}
|
|
||||||
this.updatePreview(tab.id);
|
|
||||||
this.updateWordCount();
|
|
||||||
this.updateTabBar();
|
|
||||||
if (outlinePanelContainer?._refreshOutline) outlinePanelContainer._refreshOutline();
|
|
||||||
},
|
|
||||||
onUpdate: (view) => {
|
|
||||||
this.updateCursorPosition(view);
|
|
||||||
if (outlinePanelContainer?._setActiveHeading) outlinePanelContainer._setActiveHeading(view.state.doc.lineAt(view.state.selection.main.head).number);
|
|
||||||
},
|
|
||||||
isDark,
|
|
||||||
showLineNumbers: this.showLineNumbers,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switchToTab(tabId) {
|
switchToTab(tabId) {
|
||||||
@@ -1484,9 +1461,47 @@ class TabManager {
|
|||||||
return tab?.content || '';
|
return tab?.content || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure a tab has a CodeMirror editor (lazy creation fallback)
|
||||||
|
ensureEditor(tab) {
|
||||||
|
if (tab.editorView) return true;
|
||||||
|
const editorContainer = document.getElementById(`editor-cm-${tab.id}`);
|
||||||
|
if (!editorContainer) {
|
||||||
|
console.error(`[ensureEditor] editor-cm-${tab.id} not found in DOM`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const isDark = document.body.className.includes('dark');
|
||||||
|
tab.editorView = createEditor(editorContainer, {
|
||||||
|
content: tab.content,
|
||||||
|
onChange: (newContent) => {
|
||||||
|
tab.content = newContent;
|
||||||
|
tab.isDirty = true;
|
||||||
|
this.updatePreview(tab.id);
|
||||||
|
this.updateWordCount();
|
||||||
|
this.updateTabBar();
|
||||||
|
if (outlinePanelContainer?._refreshOutline) outlinePanelContainer._refreshOutline();
|
||||||
|
},
|
||||||
|
onUpdate: (view) => {
|
||||||
|
this.updateCursorPosition(view);
|
||||||
|
if (outlinePanelContainer?._setActiveHeading) outlinePanelContainer._setActiveHeading(view.state.doc.lineAt(view.state.selection.main.head).number);
|
||||||
|
},
|
||||||
|
isDark,
|
||||||
|
showLineNumbers: this.showLineNumbers,
|
||||||
|
});
|
||||||
|
console.log(`[ensureEditor] Created editor for tab ${tab.id}`);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[ensureEditor] Failed to create editor for tab ${tab.id}:`, err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set content in editor
|
// Set content in editor
|
||||||
setEditorContent(tabId, content) {
|
setEditorContent(tabId, content) {
|
||||||
const tab = this.tabs.get(tabId);
|
const tab = this.tabs.get(tabId);
|
||||||
|
if (!tab?.editorView) {
|
||||||
|
this.ensureEditor(tab);
|
||||||
|
}
|
||||||
if (tab?.editorView) {
|
if (tab?.editorView) {
|
||||||
tab.editorView.dispatch({
|
tab.editorView.dispatch({
|
||||||
changes: { from: 0, to: tab.editorView.state.doc.length, insert: content }
|
changes: { from: 0, to: tab.editorView.state.doc.length, insert: content }
|
||||||
@@ -1730,7 +1745,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
const welcomeHtml = getCreateWelcomeContent()(recentFiles, appVersion);
|
const welcomeHtml = getCreateWelcomeContent()(recentFiles, appVersion);
|
||||||
|
|
||||||
const tab = tabManager.tabs.get(tabManager.activeTabId);
|
const tab = tabManager.tabs.get(tabManager.activeTabId);
|
||||||
if (tab) {
|
// Only show welcome if no file was opened while we were awaiting
|
||||||
|
if (tab && !tab.filePath && tab.content === '') {
|
||||||
tab.title = 'Welcome';
|
tab.title = 'Welcome';
|
||||||
tab.content = '';
|
tab.content = '';
|
||||||
const preview = document.getElementById(`preview-${tab.id}`);
|
const preview = document.getElementById(`preview-${tab.id}`);
|
||||||
@@ -1899,27 +1915,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Initialize CodeMirror for the initial tab (tab 1)
|
// Initialize CodeMirror for the initial tab (tab 1)
|
||||||
const initialEditorContainer = document.getElementById('editor-cm-1');
|
const initialTab = tabManager.tabs.get(1);
|
||||||
if (initialEditorContainer) {
|
if (initialTab) {
|
||||||
const tab = tabManager.tabs.get(1);
|
tabManager.ensureEditor(initialTab);
|
||||||
const isDark = document.body.className.includes('dark');
|
|
||||||
tab.editorView = createEditor(initialEditorContainer, {
|
|
||||||
content: tab.content,
|
|
||||||
onChange: (newContent) => {
|
|
||||||
tab.content = newContent;
|
|
||||||
tab.isDirty = true;
|
|
||||||
tabManager.updatePreview(tab.id);
|
|
||||||
tabManager.updateWordCount();
|
|
||||||
tabManager.updateTabBar();
|
|
||||||
if (outlinePanelContainer?._refreshOutline) outlinePanelContainer._refreshOutline();
|
|
||||||
},
|
|
||||||
onUpdate: (view) => {
|
|
||||||
tabManager.updateCursorPosition(view);
|
|
||||||
if (outlinePanelContainer?._setActiveHeading) outlinePanelContainer._setActiveHeading(view.state.doc.lineAt(view.state.selection.main.head).number);
|
|
||||||
},
|
|
||||||
isDark,
|
|
||||||
showLineNumbers: tabManager.showLineNumbers,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request current theme
|
// Request current theme
|
||||||
@@ -2041,6 +2039,8 @@ ipcRenderer.on('redo', () => {
|
|||||||
redo(tab.editorView);
|
redo(tab.editorView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Custom Preview CSS event handlers and trigger helpers
|
// Custom Preview CSS event handlers and trigger helpers
|
||||||
function applyCustomPreviewCSS(cssContent) {
|
function applyCustomPreviewCSS(cssContent) {
|
||||||
let styleTag = document.getElementById('custom-preview-style');
|
let styleTag = document.getElementById('custom-preview-style');
|
||||||
|
|||||||
Reference in New Issue
Block a user