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:
2026-06-13 19:34:42 +05:30
parent 6b564a4569
commit f2398e6e1a
52 changed files with 4309 additions and 250 deletions
+1 -1
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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();
});
+5 -5
View File
@@ -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',
+1 -1
View File
@@ -31,7 +31,7 @@ class CrashWriter {
const oldest = files.shift();
try {
fs.unlinkSync(path.join(this.dir, oldest));
} catch (_) {
} catch (_unlinkErr) {
/* ignore */
}
}
+2 -2
View File
@@ -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' };
}
+2 -3
View File
@@ -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 = [