mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-03 02:11:07 +05:30
feat: enhance WelcomeDialog, export dialogs, GitStatusPanel, add CommandPalette, FindReplaceBar, and new converter dialogs
- WelcomeDialog: add quick start, feature showcase, keyboard shortcuts, recent files, version display - ExportDocxDialog/ExportHtmlDialog/ExportPdfDialog: add header/footer, paper size, margin controls - GitStatusPanel: full staging/unstaging, discard, commit workflow - CommandPalette: fuzzy command search with keyboard navigation - FindReplaceBar: find/replace with regex, case, whole-word options - New dialogs: BatchMediaConverterDialog, HeaderFooterDialog, PdfEditorDialog, UniversalConverterDialog - Tests: comprehensive coverage for all new/updated components
This commit is contained in:
@@ -8,7 +8,7 @@ async function getStatus(dir) {
|
||||
try {
|
||||
const git = getGitInstance(dir);
|
||||
return await git.status();
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
return { error: 'Not a git repository' };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/main/files/index.js
|
||||
// File ops facade — registers all file-related IPC handlers
|
||||
const { ipcMain, dialog, shell } = require('electron');
|
||||
const { ipcMain, dialog } = require('electron');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { register: registerGit } = require('./git');
|
||||
|
||||
+33
-28
@@ -1,4 +1,4 @@
|
||||
const { app, BrowserWindow, Menu, dialog, ipcMain, shell } = require('electron');
|
||||
const { app, BrowserWindow, dialog, ipcMain, shell } = require('electron');
|
||||
const { autoUpdater } = require('electron-updater');
|
||||
const { UpdaterService } = require('./updater/updater-service');
|
||||
const { CrashWriter } = require('./updater/crash-writer');
|
||||
@@ -7,16 +7,10 @@ const updaterHandlers = require('./ipc/updater-handlers');
|
||||
const crashHandlers = require('./ipc/crash-handlers');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { exec, execFile, spawn } = require('child_process');
|
||||
const { execFile } = require('child_process');
|
||||
const WordTemplateExporter = require('./word-template');
|
||||
const PDFOperations = require('./PDFOperations');
|
||||
const GitOperations = require('./GitOperations');
|
||||
const {
|
||||
getAllowedDirectories,
|
||||
validatePath,
|
||||
resolveWritablePath,
|
||||
isPathAccessible,
|
||||
} = require('./utils/paths');
|
||||
const { validatePath, resolveWritablePath, isPathAccessible } = require('./utils/paths');
|
||||
const fileOps = require('./files');
|
||||
const menu = require('./menu');
|
||||
const { createMainWindow } = require('./window');
|
||||
@@ -57,13 +51,13 @@ function getFFmpegPath() {
|
||||
ffmpegPath = ffmpegPath.replace('app.asar' + path.sep, 'app.asar.unpacked' + path.sep);
|
||||
}
|
||||
if (fs.existsSync(ffmpegPath)) return ffmpegPath;
|
||||
} catch (_) {
|
||||
} catch (_ffmpegErr) {
|
||||
/* ffmpeg-static not available */
|
||||
}
|
||||
return process.platform === 'win32' ? 'ffmpeg' : 'ffmpeg';
|
||||
}
|
||||
|
||||
// Check if Pandoc is available
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function checkPandocAvailable() {
|
||||
return new Promise((resolve) => {
|
||||
const pandocPath = getPandocPath();
|
||||
@@ -80,6 +74,7 @@ function checkPandocAvailable() {
|
||||
* @param {object} options - Options for execFile
|
||||
* @returns {Promise<{stdout: string, stderr: string}>}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function safeExecFile(command, args, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile(
|
||||
@@ -144,6 +139,7 @@ function convertDataToMarkdown(content, format) {
|
||||
* @param {string[]} args - Pandoc arguments array
|
||||
* @param {Function} callback - Callback function (error, stdout, stderr)
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function runPandoc(args, callback) {
|
||||
const pandocPath = getPandocPath();
|
||||
execFile(pandocPath, args, { maxBuffer: 10 * 1024 * 1024 }, callback);
|
||||
@@ -373,7 +369,7 @@ function checkPandocAvailability() {
|
||||
return;
|
||||
}
|
||||
|
||||
execFile('pandoc', ['--version'], (error, stdout, stderr) => {
|
||||
execFile('pandoc', ['--version'], (error, _stdout, _stderr) => {
|
||||
pandocAvailable = !error;
|
||||
resolve(pandocAvailable);
|
||||
});
|
||||
@@ -597,7 +593,7 @@ function showDependenciesDialog() {
|
||||
depsWindow.setMenuBarVisibility(false);
|
||||
}
|
||||
|
||||
// Open PDF File for viewing/editing
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function openPDFFile() {
|
||||
const files = dialog.showOpenDialogSync(mainWindow, {
|
||||
properties: ['openFile'],
|
||||
@@ -1198,7 +1194,7 @@ async function exportPDFViaWordTemplate() {
|
||||
const outputDir = path.dirname(result.filePath);
|
||||
const sofficeArgs = ['--headless', '--convert-to', 'pdf', '--outdir', outputDir, tempDocxPath];
|
||||
|
||||
execFile(soffice, sofficeArgs, (error, stdout, stderr) => {
|
||||
execFile(soffice, sofficeArgs, (error, _stdout, _stderr) => {
|
||||
// Clean up temporary DOCX file
|
||||
try {
|
||||
fs.unlinkSync(tempDocxPath);
|
||||
@@ -1290,7 +1286,7 @@ function checkConverterAvailable(tool) {
|
||||
}
|
||||
|
||||
// Handle universal file conversion
|
||||
ipcMain.on('universal-convert', async (event, { tool, fromFormat, toFormat, filePath }) => {
|
||||
ipcMain.on('universal-convert', async (_event, { tool, _fromFormat, toFormat, filePath }) => {
|
||||
if (!conversionLimiter()) {
|
||||
mainWindow.webContents.send('conversion-status', 'Please wait before converting again...');
|
||||
return;
|
||||
@@ -1378,7 +1374,15 @@ ipcMain.on(
|
||||
'universal-convert-batch',
|
||||
async (
|
||||
event,
|
||||
{ tool, fromFormat, toFormat, inputFolder, outputFolder, includeSubfolders, advancedOptions }
|
||||
{
|
||||
tool,
|
||||
fromFormat,
|
||||
toFormat,
|
||||
inputFolder,
|
||||
outputFolder,
|
||||
includeSubfolders,
|
||||
advancedOptions: _advancedOptions,
|
||||
}
|
||||
) => {
|
||||
if (!conversionLimiter()) {
|
||||
mainWindow.webContents.send('conversion-status', 'Please wait before converting again...');
|
||||
@@ -1776,7 +1780,7 @@ function performExportWithOptions(format, options) {
|
||||
// Clean up intermediate EPUB
|
||||
try {
|
||||
fs.unlinkSync(epubFile);
|
||||
} catch (e) {
|
||||
} catch (_e) {
|
||||
/* ignore */
|
||||
}
|
||||
showExportSuccess(outputFile);
|
||||
@@ -1797,7 +1801,7 @@ function performExportWithOptions(format, options) {
|
||||
});
|
||||
}
|
||||
|
||||
function tryPdfFallback(inputFile, outputFile, engines, index, options, lastError) {
|
||||
function tryPdfFallback(inputFile, outputFile, engines, index, options, _lastError) {
|
||||
if (index >= engines.length) {
|
||||
// All Pandoc PDF engines failed, fallback to Electron's built-in PDF export
|
||||
console.log('All Pandoc PDF engines failed, falling back to Electron PDF export');
|
||||
@@ -1966,13 +1970,14 @@ function exportWithPandoc(pandocCmd, outputFile, format) {
|
||||
}
|
||||
|
||||
// Helper function to export PDF with pandoc (with fallbacks) - uses runPandocCmd for safety
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function exportWithPandocPDF(pandocCmd, outputFile) {
|
||||
runPandocCmd(pandocCmd, (error, stdout, stderr) => {
|
||||
runPandocCmd(pandocCmd, (error, _stdout, _stderr) => {
|
||||
if (error) {
|
||||
console.log('XeLaTeX failed, trying PDFLaTeX...');
|
||||
// Fallback to pdflatex
|
||||
const fallbackCmd = pandocCmd.replace('--pdf-engine=xelatex', '--pdf-engine=pdflatex');
|
||||
runPandocCmd(fallbackCmd, (fallbackError, fallbackStdout, fallbackStderr) => {
|
||||
runPandocCmd(fallbackCmd, (fallbackError, _fallbackStdout, _fallbackStderr) => {
|
||||
if (fallbackError) {
|
||||
console.log('PDFLaTeX failed, trying Electron PDF...');
|
||||
// Final fallback to Electron PDF
|
||||
@@ -2337,7 +2342,7 @@ function importDocument() {
|
||||
// Convert to markdown using pandoc (using runPandocCmd for safety)
|
||||
const pandocCmd = `${getPandocPath()} "${inputFile}" -t markdown ${additionalOptions} -o "${outputFile}"`;
|
||||
|
||||
runPandocCmd(pandocCmd, (error, stdout, stderr) => {
|
||||
runPandocCmd(pandocCmd, (error, _stdout, _stderr) => {
|
||||
if (error) {
|
||||
dialog.showErrorBox(
|
||||
'Import Error',
|
||||
@@ -2457,7 +2462,7 @@ ipcMain.on('do-print-with-options', (event, options) => {
|
||||
});
|
||||
|
||||
// Handle renderer ready for file association
|
||||
ipcMain.on('renderer-ready', (event) => {
|
||||
ipcMain.on('renderer-ready', (_event) => {
|
||||
console.log('[MAIN] renderer-ready received, rendererReady was:', rendererReady);
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.executeJavaScript(
|
||||
@@ -2675,7 +2680,7 @@ function performBatchConversion(inputFolder, outputFolder, format, options) {
|
||||
|
||||
const inputFile = markdownFiles[index];
|
||||
const relativePath = path.relative(inputFolder, inputFile);
|
||||
const baseName = path.basename(relativePath, path.extname(relativePath));
|
||||
const _baseName = path.basename(relativePath, path.extname(relativePath));
|
||||
let outputExtension = format;
|
||||
if (format === 'docx-enhanced') outputExtension = 'docx';
|
||||
if (format === 'pdf-enhanced') outputExtension = 'pdf';
|
||||
@@ -2709,7 +2714,7 @@ function performBatchConversion(inputFolder, outputFolder, format, options) {
|
||||
|
||||
// Process next file
|
||||
processNextFile(index + 1);
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
// Update progress with error
|
||||
mainWindow.webContents.send('batch-progress', {
|
||||
completed: index + 1,
|
||||
@@ -2750,7 +2755,7 @@ function performBatchConversion(inputFolder, outputFolder, format, options) {
|
||||
tempDocxPath,
|
||||
];
|
||||
|
||||
execFile(soffice, sofficeArgs, (error, stdout, stderr) => {
|
||||
execFile(soffice, sofficeArgs, (error, _stdout, _stderr) => {
|
||||
// Clean up temporary DOCX file
|
||||
try {
|
||||
fs.unlinkSync(tempDocxPath);
|
||||
@@ -2795,7 +2800,7 @@ function performBatchConversion(inputFolder, outputFolder, format, options) {
|
||||
// Process next file
|
||||
processNextFile(index + 1);
|
||||
});
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
// Update progress with error
|
||||
mainWindow.webContents.send('batch-progress', {
|
||||
completed: index + 1,
|
||||
@@ -2904,7 +2909,7 @@ function performBatchConversion(inputFolder, outputFolder, format, options) {
|
||||
}
|
||||
|
||||
// Execute conversion (using runPandocCmd for safety)
|
||||
runPandocCmd(pandocCmd, async (error, stdout, stderr) => {
|
||||
runPandocCmd(pandocCmd, async (error, _stdout, _stderr) => {
|
||||
if (!error) {
|
||||
// Add headers/footers to DOCX if enabled
|
||||
if (format === 'docx' && headerFooterSettings.enabled) {
|
||||
@@ -3530,7 +3535,7 @@ ipcMain.handle('list-directory', async (event, dirPath) => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('select-custom-css', async (event) => {
|
||||
ipcMain.handle('select-custom-css', async (_event) => {
|
||||
const result = dialog.showOpenDialogSync(mainWindow, {
|
||||
title: 'Select Custom Preview CSS',
|
||||
properties: ['openFile'],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const { ipcMain, shell } = require('electron');
|
||||
|
||||
function register({ crash, getMainWindow }) {
|
||||
function register({ crash, getMainWindow: _getMainWindow }) {
|
||||
ipcMain.handle('crash:read', () => {
|
||||
return crash.list();
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/main/menu/items.js
|
||||
// Individual menu items — pure functions that take (mainWindow) and return menu item arrays
|
||||
|
||||
const { app, dialog, shell } = require('electron');
|
||||
const { app, shell } = require('electron');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
@@ -32,7 +32,7 @@ function buildRecentFilesMenu(mainWindow) {
|
||||
}
|
||||
);
|
||||
return items;
|
||||
} catch (e) {
|
||||
} catch (_e) {
|
||||
return [{ label: 'No Recent Files', enabled: false }];
|
||||
}
|
||||
}
|
||||
@@ -628,7 +628,7 @@ function batchItems(mainWindow) {
|
||||
];
|
||||
}
|
||||
|
||||
function convertItems(mainWindow) {
|
||||
function convertItems(_mainWindow) {
|
||||
return [
|
||||
{
|
||||
label: 'Universal File Converter...',
|
||||
@@ -641,7 +641,7 @@ function convertItems(mainWindow) {
|
||||
];
|
||||
}
|
||||
|
||||
function pdfEditorItems(mainWindow) {
|
||||
function pdfEditorItems(_mainWindow) {
|
||||
return [
|
||||
{
|
||||
label: 'Open PDF File...',
|
||||
@@ -754,7 +754,7 @@ function toolsItems(mainWindow) {
|
||||
];
|
||||
}
|
||||
|
||||
function helpItems(mainWindow) {
|
||||
function helpItems(_mainWindow) {
|
||||
return [
|
||||
{
|
||||
label: 'About MarkdownConverter',
|
||||
|
||||
@@ -31,7 +31,7 @@ class CrashWriter {
|
||||
const oldest = files.shift();
|
||||
try {
|
||||
fs.unlinkSync(path.join(this.dir, oldest));
|
||||
} catch (_) {
|
||||
} catch (_unlinkErr) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ function validatePath(filePath) {
|
||||
let resolved;
|
||||
try {
|
||||
resolved = path.resolve(filePath);
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
return { valid: false, resolved: '', error: 'Invalid path format' };
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ function resolveWritablePath(filePath) {
|
||||
let resolved;
|
||||
try {
|
||||
resolved = path.normalize(path.resolve(filePath));
|
||||
} catch (err) {
|
||||
} catch (_err2) {
|
||||
return { valid: false, resolved: '', error: 'Invalid path format' };
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const PizZip = require('pizzip');
|
||||
const Docx = require('docx4js').default;
|
||||
|
||||
class WordTemplateExporter {
|
||||
constructor(templatePath, startPage = 3, pageSettings = null) {
|
||||
@@ -432,7 +431,7 @@ class WordTemplateExporter {
|
||||
rowCells.push('');
|
||||
}
|
||||
|
||||
rowCells.forEach((cellText, colIndex) => {
|
||||
rowCells.forEach((cellText, _colIndex) => {
|
||||
tableXml += '<w:tc>';
|
||||
tableXml += '<w:tcPr>';
|
||||
|
||||
@@ -715,7 +714,7 @@ class WordTemplateExporter {
|
||||
*/
|
||||
parseInlineFormatting(text) {
|
||||
let xml = '';
|
||||
const pos = 0;
|
||||
const _pos = 0;
|
||||
|
||||
// Patterns for inline formatting
|
||||
const patterns = [
|
||||
|
||||
Reference in New Issue
Block a user