style: run prettier formatter over src and tests

This commit is contained in:
2026-06-11 20:46:00 +05:30
parent 2389d4f297
commit 6b564a4569
211 changed files with 6134 additions and 4234 deletions
+62 -43
View File
@@ -4,11 +4,11 @@ const { PDFDocument, rgb, degrees, StandardFonts } = require('pdf-lib');
function parsePageRanges(rangeString, totalPages) {
const pages = [];
const ranges = rangeString.split(',').map(r => r.trim());
const ranges = rangeString.split(',').map((r) => r.trim());
for (const range of ranges) {
if (range.includes('-')) {
const [start, end] = range.split('-').map(n => parseInt(n.trim()));
const [start, end] = range.split('-').map((n) => parseInt(n.trim()));
for (let i = start; i <= end && i <= totalPages; i++) {
if (i > 0 && !pages.includes(i - 1)) {
pages.push(i - 1);
@@ -27,11 +27,13 @@ function parsePageRanges(rangeString, totalPages) {
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16) / 255,
g: parseInt(result[2], 16) / 255,
b: parseInt(result[3], 16) / 255
} : { r: 0, g: 0, b: 0 };
return result
? {
r: parseInt(result[1], 16) / 255,
g: parseInt(result[2], 16) / 255,
b: parseInt(result[3], 16) / 255,
}
: { r: 0, g: 0, b: 0 };
}
async function pdfMerge(data) {
@@ -42,7 +44,7 @@ async function pdfMerge(data) {
const pdfBytes = fs.readFileSync(filePath);
const pdf = await PDFDocument.load(pdfBytes);
const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
copiedPages.forEach(page => mergedPdf.addPage(page));
copiedPages.forEach((page) => mergedPdf.addPage(page));
}
const pdfBytes = await mergedPdf.save();
@@ -63,13 +65,13 @@ async function pdfSplit(data) {
const splits = [];
if (data.splitMode === 'pages') {
const ranges = data.pageRanges.split(',').map(r => r.trim());
const ranges = data.pageRanges.split(',').map((r) => r.trim());
for (let i = 0; i < ranges.length; i++) {
const range = ranges[i];
const pages = [];
if (range.includes('-')) {
const [start, end] = range.split('-').map(n => parseInt(n.trim()));
const [start, end] = range.split('-').map((n) => parseInt(n.trim()));
for (let p = start; p <= end && p <= totalPages; p++) {
pages.push(p - 1);
}
@@ -108,7 +110,7 @@ async function pdfSplit(data) {
for (const split of splits) {
const newPdf = await PDFDocument.create();
const copiedPages = await newPdf.copyPages(pdf, split.pages);
copiedPages.forEach(page => newPdf.addPage(page));
copiedPages.forEach((page) => newPdf.addPage(page));
const outputPath = path.join(data.outputFolder, `${baseName}_${split.name}.pdf`);
const newPdfBytes = await newPdf.save();
@@ -129,18 +131,18 @@ async function pdfCompress(data) {
const compressedPdfBytes = await pdf.save({
useObjectStreams: true,
addDefaultPage: false,
objectsPerTick: 50
objectsPerTick: 50,
});
fs.writeFileSync(data.outputPath, compressedPdfBytes);
const originalSize = fs.statSync(data.inputPath).size;
const compressedSize = fs.statSync(data.outputPath).size;
const savings = ((originalSize - compressedSize) / originalSize * 100).toFixed(1);
const savings = (((originalSize - compressedSize) / originalSize) * 100).toFixed(1);
return {
success: true,
message: `PDF compressed. Size reduced by ${savings}% (${(originalSize / 1024).toFixed(1)}KB → ${(compressedSize / 1024).toFixed(1)}KB)`
message: `PDF compressed. Size reduced by ${savings}% (${(originalSize / 1024).toFixed(1)}KB → ${(compressedSize / 1024).toFixed(1)}KB)`,
};
} catch (error) {
return { success: false, error: error.message };
@@ -160,7 +162,7 @@ async function pdfRotate(data) {
pagesToRotate = Array.from({ length: totalPages }, (_, i) => i);
}
pagesToRotate.forEach(pageIndex => {
pagesToRotate.forEach((pageIndex) => {
const page = pdf.getPage(pageIndex);
page.setRotation(degrees(data.angle));
});
@@ -170,7 +172,7 @@ async function pdfRotate(data) {
return {
success: true,
message: `Successfully rotated ${pagesToRotate.length} page(s) by ${data.angle}\u00B0`
message: `Successfully rotated ${pagesToRotate.length} page(s) by ${data.angle}\u00B0`,
};
} catch (error) {
return { success: false, error: error.message };
@@ -185,16 +187,18 @@ async function pdfDeletePages(data) {
const pagesToDelete = parsePageRanges(data.pages, totalPages);
pagesToDelete.sort((a, b) => b - a).forEach(pageIndex => {
pdf.removePage(pageIndex);
});
pagesToDelete
.sort((a, b) => b - a)
.forEach((pageIndex) => {
pdf.removePage(pageIndex);
});
const newPdfBytes = await pdf.save();
fs.writeFileSync(data.outputPath, newPdfBytes);
return {
success: true,
message: `Successfully deleted ${pagesToDelete.length} page(s). New PDF has ${totalPages - pagesToDelete.length} pages`
message: `Successfully deleted ${pagesToDelete.length} page(s). New PDF has ${totalPages - pagesToDelete.length} pages`,
};
} catch (error) {
return { success: false, error: error.message };
@@ -207,7 +211,7 @@ async function pdfReorder(data) {
const pdf = await PDFDocument.load(pdfBytes);
const totalPages = pdf.getPageCount();
const newOrder = data.newOrder.split(',').map(n => parseInt(n.trim()) - 1);
const newOrder = data.newOrder.split(',').map((n) => parseInt(n.trim()) - 1);
if (newOrder.length !== totalPages) {
return { success: false, error: `New order must include all ${totalPages} pages` };
@@ -215,7 +219,7 @@ async function pdfReorder(data) {
const newPdf = await PDFDocument.create();
const copiedPages = await newPdf.copyPages(pdf, newOrder);
copiedPages.forEach(page => newPdf.addPage(page));
copiedPages.forEach((page) => newPdf.addPage(page));
const reorderedPdfBytes = await newPdf.save();
fs.writeFileSync(data.outputPath, reorderedPdfBytes);
@@ -246,7 +250,9 @@ async function pdfWatermark(data) {
const page = pdf.getPage(pageIndex);
const { width, height } = page.getSize();
let x, y, rotation = 0;
let x,
y,
rotation = 0;
switch (data.position) {
case 'center':
@@ -294,7 +300,7 @@ async function pdfWatermark(data) {
font,
color: rgb(color.r, color.g, color.b),
opacity: data.opacity,
rotate: degrees(rotation)
rotate: degrees(rotation),
});
}
@@ -303,7 +309,7 @@ async function pdfWatermark(data) {
return {
success: true,
message: `Successfully added watermark to ${pagesToWatermark.length} page(s)`
message: `Successfully added watermark to ${pagesToWatermark.length} page(s)`,
};
} catch (error) {
return { success: false, error: error.message };
@@ -325,8 +331,8 @@ async function pdfEncrypt(data) {
annotating: data.permissions.annotating,
fillingForms: data.permissions.fillingForms,
contentAccessibility: data.permissions.contentAccessibility,
documentAssembly: data.permissions.documentAssembly
}
documentAssembly: data.permissions.documentAssembly,
},
});
fs.writeFileSync(data.outputPath, encryptedPdfBytes);
@@ -336,7 +342,8 @@ async function pdfEncrypt(data) {
if (error.message.includes('encrypt') || error.message.includes('password')) {
return {
success: false,
error: 'PDF encryption requires pdf-lib with encryption support. This feature may not be available in the current version.'
error:
'PDF encryption requires pdf-lib with encryption support. This feature may not be available in the current version.',
};
}
return { success: false, error: error.message };
@@ -375,8 +382,8 @@ async function pdfSetPermissions(data) {
annotating: data.permissions.annotating,
fillingForms: data.permissions.fillingForms,
contentAccessibility: data.permissions.contentAccessibility,
documentAssembly: data.permissions.documentAssembly
}
documentAssembly: data.permissions.documentAssembly,
},
});
fs.writeFileSync(data.outputPath, newPdfBytes);
@@ -386,7 +393,8 @@ async function pdfSetPermissions(data) {
if (error.message.includes('encrypt') || error.message.includes('permission')) {
return {
success: false,
error: 'PDF permissions require pdf-lib with encryption support. This feature may not be available in the current version.'
error:
'PDF permissions require pdf-lib with encryption support. This feature may not be available in the current version.',
};
}
return { success: false, error: error.message };
@@ -395,17 +403,28 @@ async function pdfSetPermissions(data) {
function executeOperation(operation, data) {
switch (operation) {
case 'merge': return pdfMerge(data);
case 'split': return pdfSplit(data);
case 'compress': return pdfCompress(data);
case 'rotate': return pdfRotate(data);
case 'delete': return pdfDeletePages(data);
case 'reorder': return pdfReorder(data);
case 'watermark': return pdfWatermark(data);
case 'encrypt': return pdfEncrypt(data);
case 'decrypt': return pdfDecrypt(data);
case 'permissions': return pdfSetPermissions(data);
default: return Promise.resolve({ success: false, error: `Unknown operation: ${operation}` });
case 'merge':
return pdfMerge(data);
case 'split':
return pdfSplit(data);
case 'compress':
return pdfCompress(data);
case 'rotate':
return pdfRotate(data);
case 'delete':
return pdfDeletePages(data);
case 'reorder':
return pdfReorder(data);
case 'watermark':
return pdfWatermark(data);
case 'encrypt':
return pdfEncrypt(data);
case 'decrypt':
return pdfDecrypt(data);
case 'permissions':
return pdfSetPermissions(data);
default:
return Promise.resolve({ success: false, error: `Unknown operation: ${operation}` });
}
}
@@ -429,5 +448,5 @@ module.exports = {
pdfDecrypt,
pdfSetPermissions,
executeOperation,
getPageCount
getPageCount,
};
+1 -1
View File
@@ -10,4 +10,4 @@ function register() {
});
}
module.exports = { register };
module.exports = { register };
+13 -5
View File
@@ -5,24 +5,32 @@ const GitOperations = require('../GitOperations');
function register(currentFileRef) {
ipcMain.handle('git-status', async () => {
const dir = currentFileRef.current ? require('path').dirname(currentFileRef.current) : process.cwd();
const dir = currentFileRef.current
? require('path').dirname(currentFileRef.current)
: process.cwd();
return GitOperations.getStatus(dir);
});
ipcMain.handle('git-stage', async (_event, { files }) => {
const dir = currentFileRef.current ? require('path').dirname(currentFileRef.current) : process.cwd();
const dir = currentFileRef.current
? require('path').dirname(currentFileRef.current)
: process.cwd();
return GitOperations.stage(dir, files);
});
ipcMain.handle('git-commit', async (_event, { message }) => {
const dir = currentFileRef.current ? require('path').dirname(currentFileRef.current) : process.cwd();
const dir = currentFileRef.current
? require('path').dirname(currentFileRef.current)
: process.cwd();
return GitOperations.commit(dir, message);
});
ipcMain.handle('git-log', async () => {
const dir = currentFileRef.current ? require('path').dirname(currentFileRef.current) : process.cwd();
const dir = currentFileRef.current
? require('path').dirname(currentFileRef.current)
: process.cwd();
return GitOperations.log(dir);
});
}
module.exports = { register };
module.exports = { register };
+8 -2
View File
@@ -6,7 +6,13 @@ const path = require('path');
const { register: registerGit } = require('./git');
const { register: registerBinary } = require('./binary');
function register({ validatePath, resolveWritablePath, isPathAccessible, currentFileRef, mainWindow }) {
function register({
validatePath,
resolveWritablePath,
isPathAccessible,
currentFileRef,
mainWindow,
}) {
// pick-folder
ipcMain.handle('pick-folder', async () => {
const result = await dialog.showOpenDialog(mainWindow, {
@@ -154,4 +160,4 @@ function register({ validatePath, resolveWritablePath, isPathAccessible, current
registerBinary();
}
module.exports = { register };
module.exports = { register };
+701 -524
View File
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -10,7 +10,10 @@ function register({ crash, getMainWindow }) {
});
ipcMain.handle('crash:delete', (_event, filename) => {
if (typeof filename === 'string' && /^\d+(-\d+)?-(uncaughtException|unhandledRejection)\.json$/.test(filename)) {
if (
typeof filename === 'string' &&
/^\d+(-\d+)?-(uncaughtException|unhandledRejection)\.json$/.test(filename)
) {
crash.delete(filename);
return true;
}
+3 -3
View File
@@ -10,7 +10,7 @@ const {
convertItems,
pdfEditorItems,
toolsItems,
helpItems
helpItems,
} = require('./items');
function buildMenu(mainWindow) {
@@ -22,7 +22,7 @@ function buildMenu(mainWindow) {
{ label: '&Convert', submenu: convertItems(mainWindow) },
{ label: 'PDF Editor', submenu: pdfEditorItems(mainWindow) },
{ label: '&Tools', submenu: toolsItems(mainWindow) },
{ label: '&Help', submenu: helpItems(mainWindow) }
{ label: '&Help', submenu: helpItems(mainWindow) },
];
return Menu.buildFromTemplate(template);
}
@@ -31,4 +31,4 @@ function register(mainWindow) {
Menu.setApplicationMenu(buildMenu(mainWindow));
}
module.exports = { register, buildMenu };
module.exports = { register, buildMenu };
+404 -133
View File
@@ -11,14 +11,14 @@ function buildRecentFilesMenu(mainWindow) {
const recentFilesPath = path.join(app.getPath('userData'), 'recent-files.json');
if (!fs.existsSync(recentFilesPath)) return [{ label: 'No Recent Files', enabled: false }];
const recentFiles = JSON.parse(fs.readFileSync(recentFilesPath, 'utf-8'));
const existing = recentFiles.filter(file => fs.existsSync(file));
const existing = recentFiles.filter((file) => fs.existsSync(file));
if (existing.length === 0) return [{ label: 'No Recent Files', enabled: false }];
const items = existing.map(file => ({
const items = existing.map((file) => ({
label: path.basename(file),
click: () => {
const { openFileFromPath } = require('../index');
openFileFromPath(file);
}
},
}));
items.push(
{ type: 'separator' },
@@ -28,7 +28,7 @@ function buildRecentFilesMenu(mainWindow) {
if (mainWindow) {
mainWindow.webContents.send('clear-recent-files');
}
}
},
}
);
return items;
@@ -42,7 +42,7 @@ function fileItems(mainWindow) {
{
label: 'New',
accelerator: 'CmdOrCtrl+N',
click: () => mainWindow.webContents.send('file-new')
click: () => mainWindow.webContents.send('file-new'),
},
{
label: 'Open',
@@ -50,7 +50,7 @@ function fileItems(mainWindow) {
click: () => {
const { openFile } = require('../index');
openFile();
}
},
},
{
label: 'Open PDF',
@@ -58,12 +58,12 @@ function fileItems(mainWindow) {
click: () => {
const { openPdfFile } = require('../index');
openPdfFile();
}
},
},
{
label: 'Save',
accelerator: 'CmdOrCtrl+S',
click: () => mainWindow.webContents.send('file-save')
click: () => mainWindow.webContents.send('file-save'),
},
{
label: 'Save As',
@@ -71,30 +71,60 @@ function fileItems(mainWindow) {
click: () => {
const { saveAsFile } = require('../index');
saveAsFile();
}
},
},
{ type: 'separator' },
// NOTE: Print Preview submenu removed — handled by React <PrintPreview> overlay
{ type: 'separator' },
{
label: 'Recent Files',
submenu: buildRecentFilesMenu(mainWindow)
submenu: buildRecentFilesMenu(mainWindow),
},
{ type: 'separator' },
{
label: 'New from Template',
submenu: [
{ label: 'Blog Post', click: () => mainWindow.webContents.send('load-template-menu', 'blog-post.md') },
{ label: 'Meeting Notes', click: () => mainWindow.webContents.send('load-template-menu', 'meeting-notes.md') },
{ label: 'Technical Spec', click: () => mainWindow.webContents.send('load-template-menu', 'technical-spec.md') },
{ label: 'Changelog', click: () => mainWindow.webContents.send('load-template-menu', 'changelog.md') },
{ label: 'README', click: () => mainWindow.webContents.send('load-template-menu', 'readme.md') },
{ label: 'Project Plan', click: () => mainWindow.webContents.send('load-template-menu', 'project-plan.md') },
{ label: 'API Documentation', click: () => mainWindow.webContents.send('load-template-menu', 'api-docs.md') },
{ label: 'Tutorial', click: () => mainWindow.webContents.send('load-template-menu', 'tutorial.md') },
{ label: 'Release Notes', click: () => mainWindow.webContents.send('load-template-menu', 'release-notes.md') },
{ label: 'Comparison', click: () => mainWindow.webContents.send('load-template-menu', 'comparison.md') }
]
{
label: 'Blog Post',
click: () => mainWindow.webContents.send('load-template-menu', 'blog-post.md'),
},
{
label: 'Meeting Notes',
click: () => mainWindow.webContents.send('load-template-menu', 'meeting-notes.md'),
},
{
label: 'Technical Spec',
click: () => mainWindow.webContents.send('load-template-menu', 'technical-spec.md'),
},
{
label: 'Changelog',
click: () => mainWindow.webContents.send('load-template-menu', 'changelog.md'),
},
{
label: 'README',
click: () => mainWindow.webContents.send('load-template-menu', 'readme.md'),
},
{
label: 'Project Plan',
click: () => mainWindow.webContents.send('load-template-menu', 'project-plan.md'),
},
{
label: 'API Documentation',
click: () => mainWindow.webContents.send('load-template-menu', 'api-docs.md'),
},
{
label: 'Tutorial',
click: () => mainWindow.webContents.send('load-template-menu', 'tutorial.md'),
},
{
label: 'Release Notes',
click: () => mainWindow.webContents.send('load-template-menu', 'release-notes.md'),
},
{
label: 'Comparison',
click: () => mainWindow.webContents.send('load-template-menu', 'comparison.md'),
},
],
},
{ type: 'separator' },
{
@@ -103,58 +133,137 @@ function fileItems(mainWindow) {
click: () => {
const { importDocument } = require('../index');
importDocument();
}
},
},
{
label: 'Export',
submenu: [
{
label: 'HTML', click: () => {
label: 'HTML',
click: () => {
const { exportFile } = require('../index');
exportFile('html');
}
},
},
{
label: 'PDF', click: () => {
label: 'PDF',
click: () => {
const { exportFile } = require('../index');
exportFile('pdf');
}
},
},
{
label: 'PDF (Enhanced)', click: () => {
label: 'PDF (Enhanced)',
click: () => {
const { exportPDFViaWordTemplate } = require('../index');
exportPDFViaWordTemplate();
}, accelerator: 'Ctrl+Shift+P'
},
accelerator: 'Ctrl+Shift+P',
},
{
label: 'DOCX', click: () => {
label: 'DOCX',
click: () => {
const { exportFile } = require('../index');
exportFile('docx');
}
},
},
{
label: 'DOCX (Enhanced)', click: () => {
label: 'DOCX (Enhanced)',
click: () => {
const { exportWordWithTemplate } = require('../index');
exportWordWithTemplate();
}, accelerator: 'Ctrl+Shift+W'
},
accelerator: 'Ctrl+Shift+W',
},
{
label: 'LaTeX',
click: () => {
const { exportFile } = require('../index');
exportFile('latex');
},
},
{
label: 'RTF',
click: () => {
const { exportFile } = require('../index');
exportFile('rtf');
},
},
{
label: 'ODT',
click: () => {
const { exportFile } = require('../index');
exportFile('odt');
},
},
{
label: 'EPUB',
click: () => {
const { exportFile } = require('../index');
exportFile('epub');
},
},
{ label: 'LaTeX', click: () => { const { exportFile } = require('../index'); exportFile('latex'); } },
{ label: 'RTF', click: () => { const { exportFile } = require('../index'); exportFile('rtf'); } },
{ label: 'ODT', click: () => { const { exportFile } = require('../index'); exportFile('odt'); } },
{ label: 'EPUB', click: () => { const { exportFile } = require('../index'); exportFile('epub'); } },
{ type: 'separator' },
{ label: 'PowerPoint (PPTX)', click: () => { const { exportFile } = require('../index'); exportFile('pptx'); } },
{ label: 'OpenDocument Presentation (ODP)', click: () => { const { exportFile } = require('../index'); exportFile('odp'); } },
{
label: 'PowerPoint (PPTX)',
click: () => {
const { exportFile } = require('../index');
exportFile('pptx');
},
},
{
label: 'OpenDocument Presentation (ODP)',
click: () => {
const { exportFile } = require('../index');
exportFile('odp');
},
},
{ type: 'separator' },
{ label: 'CSV (Tables)', click: () => { const { exportSpreadsheet } = require('../index'); exportSpreadsheet('csv'); } },
{
label: 'CSV (Tables)',
click: () => {
const { exportSpreadsheet } = require('../index');
exportSpreadsheet('csv');
},
},
{ type: 'separator' },
{ label: 'JSON (.json)', click: () => { const { exportFile } = require('../index'); exportFile('json'); } },
{ label: 'YAML (.yaml)', click: () => { const { exportFile } = require('../index'); exportFile('yaml'); } },
{ label: 'XML (.xml)', click: () => { const { exportFile } = require('../index'); exportFile('xml'); } },
{
label: 'JSON (.json)',
click: () => {
const { exportFile } = require('../index');
exportFile('json');
},
},
{
label: 'YAML (.yaml)',
click: () => {
const { exportFile } = require('../index');
exportFile('yaml');
},
},
{
label: 'XML (.xml)',
click: () => {
const { exportFile } = require('../index');
exportFile('xml');
},
},
{ type: 'separator' },
{ label: 'Confluence Wiki (.txt)', click: () => { const { exportFile } = require('../index'); exportFile('confluence'); } },
{ label: 'MOBI E-book (.mobi)', click: () => { const { exportFile } = require('../index'); exportFile('mobi'); } }
]
{
label: 'Confluence Wiki (.txt)',
click: () => {
const { exportFile } = require('../index');
exportFile('confluence');
},
},
{
label: 'MOBI E-book (.mobi)',
click: () => {
const { exportFile } = require('../index');
exportFile('mobi');
},
},
],
},
{ type: 'separator' },
{
@@ -162,14 +271,14 @@ function fileItems(mainWindow) {
click: () => {
const { selectWordTemplate } = require('../index');
selectWordTemplate();
}
},
},
{
label: 'Template Settings...',
click: () => {
const { showTemplateSettings } = require('../index');
showTemplateSettings();
}
},
},
{
label: 'Header & Footer Settings...',
@@ -177,14 +286,14 @@ function fileItems(mainWindow) {
if (mainWindow) {
mainWindow.webContents.send('open-header-footer-dialog');
}
}
},
},
{ type: 'separator' },
{
label: 'Quit',
accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q',
click: () => app.quit()
}
click: () => app.quit(),
},
];
}
@@ -193,12 +302,12 @@ function editItems(mainWindow) {
{
label: 'Undo',
accelerator: 'CmdOrCtrl+Z',
click: () => mainWindow.webContents.send('undo')
click: () => mainWindow.webContents.send('undo'),
},
{
label: 'Redo',
accelerator: 'CmdOrCtrl+Shift+Z',
click: () => mainWindow.webContents.send('redo')
click: () => mainWindow.webContents.send('redo'),
},
{ type: 'separator' },
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' },
@@ -209,8 +318,8 @@ function editItems(mainWindow) {
{
label: 'Find & Replace',
accelerator: 'CmdOrCtrl+F',
click: () => mainWindow.webContents.send('toggle-find')
}
click: () => mainWindow.webContents.send('toggle-find'),
},
];
}
@@ -219,60 +328,222 @@ function viewItems(mainWindow) {
{
label: 'Toggle Preview',
accelerator: 'CmdOrCtrl+Shift+V',
click: () => mainWindow.webContents.send('toggle-preview')
click: () => mainWindow.webContents.send('toggle-preview'),
},
{
label: 'Writing Analytics',
accelerator: 'CmdOrCtrl+Shift+A',
click: () => mainWindow.webContents.send('show-analytics-dialog')
click: () => mainWindow.webContents.send('show-analytics-dialog'),
},
// NOTE: Command Palette removed — handled by useCommandStore
{ type: 'separator' },
{
label: 'Sidebar',
submenu: [
{ label: 'Files', click: () => mainWindow.webContents.send('toggle-sidebar-panel', 'explorer') },
{ label: 'Outline', click: () => mainWindow.webContents.send('toggle-sidebar-panel', 'outline') },
{ label: 'Snippets', click: () => mainWindow.webContents.send('toggle-sidebar-panel', 'snippets') },
{ label: 'Templates', click: () => mainWindow.webContents.send('toggle-sidebar-panel', 'templates') },
{ label: 'Git', click: () => mainWindow.webContents.send('toggle-sidebar-panel', 'git') }
]
{
label: 'Files',
click: () => mainWindow.webContents.send('toggle-sidebar-panel', 'explorer'),
},
{
label: 'Outline',
click: () => mainWindow.webContents.send('toggle-sidebar-panel', 'outline'),
},
{
label: 'Snippets',
click: () => mainWindow.webContents.send('toggle-sidebar-panel', 'snippets'),
},
{
label: 'Templates',
click: () => mainWindow.webContents.send('toggle-sidebar-panel', 'templates'),
},
{ label: 'Git', click: () => mainWindow.webContents.send('toggle-sidebar-panel', 'git') },
],
},
{
label: 'Bottom Panel (REPL)',
click: () => mainWindow.webContents.send('toggle-bottom-panel')
click: () => mainWindow.webContents.send('toggle-bottom-panel'),
},
{ type: 'separator' },
{
label: 'Theme',
submenu: [
{ label: 'Atom One Light (Default)', click: () => { const { setTheme } = require('../index'); setTheme('atomonelight'); } },
{ label: 'GitHub Light', click: () => { const { setTheme } = require('../index'); setTheme('github'); } },
{ label: 'Light', click: () => { const { setTheme } = require('../index'); setTheme('light'); } },
{ label: 'Solarized Light', click: () => { const { setTheme } = require('../index'); setTheme('solarized'); } },
{ label: 'Gruvbox Light', click: () => { const { setTheme } = require('../index'); setTheme('gruvbox-light'); } },
{ label: 'Ayu Light', click: () => { const { setTheme } = require('../index'); setTheme('ayu-light'); } },
{ label: 'Sepia', click: () => { const { setTheme } = require('../index'); setTheme('sepia'); } },
{ label: 'Paper', click: () => { const { setTheme } = require('../index'); setTheme('paper'); } },
{ label: 'Rose Pine Dawn', click: () => { const { setTheme } = require('../index'); setTheme('rosepine-dawn'); } },
{ label: 'Concrete Light', click: () => { const { setTheme } = require('../index'); setTheme('concrete-light'); } },
{
label: 'Atom One Light (Default)',
click: () => {
const { setTheme } = require('../index');
setTheme('atomonelight');
},
},
{
label: 'GitHub Light',
click: () => {
const { setTheme } = require('../index');
setTheme('github');
},
},
{
label: 'Light',
click: () => {
const { setTheme } = require('../index');
setTheme('light');
},
},
{
label: 'Solarized Light',
click: () => {
const { setTheme } = require('../index');
setTheme('solarized');
},
},
{
label: 'Gruvbox Light',
click: () => {
const { setTheme } = require('../index');
setTheme('gruvbox-light');
},
},
{
label: 'Ayu Light',
click: () => {
const { setTheme } = require('../index');
setTheme('ayu-light');
},
},
{
label: 'Sepia',
click: () => {
const { setTheme } = require('../index');
setTheme('sepia');
},
},
{
label: 'Paper',
click: () => {
const { setTheme } = require('../index');
setTheme('paper');
},
},
{
label: 'Rose Pine Dawn',
click: () => {
const { setTheme } = require('../index');
setTheme('rosepine-dawn');
},
},
{
label: 'Concrete Light',
click: () => {
const { setTheme } = require('../index');
setTheme('concrete-light');
},
},
{ type: 'separator' },
{ label: 'Dark', click: () => { const { setTheme } = require('../index'); setTheme('dark'); } },
{ label: 'One Dark', click: () => { const { setTheme } = require('../index'); setTheme('onedark'); } },
{ label: 'Dracula', click: () => { const { setTheme } = require('../index'); setTheme('dracula'); } },
{ label: 'Nord', click: () => { const { setTheme } = require('../index'); setTheme('nord'); } },
{ label: 'Monokai', click: () => { const { setTheme } = require('../index'); setTheme('monokai'); } },
{ label: 'Material', click: () => { const { setTheme } = require('../index'); setTheme('material'); } },
{ label: 'Gruvbox Dark', click: () => { const { setTheme } = require('../index'); setTheme('gruvbox-dark'); } },
{ label: 'Tokyo Night', click: () => { const { setTheme } = require('../index'); setTheme('tokyonight'); } },
{ label: 'Palenight', click: () => { const { setTheme } = require('../index'); setTheme('palenight'); } },
{ label: 'Ayu Dark', click: () => { const { setTheme } = require('../index'); setTheme('ayu-dark'); } },
{ label: 'Ayu Mirage', click: () => { const { setTheme } = require('../index'); setTheme('ayu-mirage'); } },
{ label: 'Oceanic Next', click: () => { const { setTheme } = require('../index'); setTheme('oceanic-next'); } },
{ label: 'Cobalt2', click: () => { const { setTheme } = require('../index'); setTheme('cobalt2'); } },
{ label: 'Concrete Dark', click: () => { const { setTheme } = require('../index'); setTheme('concrete-dark'); } },
{ label: 'Concrete Warm', click: () => { const { setTheme } = require('../index'); setTheme('concrete-warm'); } }
]
{
label: 'Dark',
click: () => {
const { setTheme } = require('../index');
setTheme('dark');
},
},
{
label: 'One Dark',
click: () => {
const { setTheme } = require('../index');
setTheme('onedark');
},
},
{
label: 'Dracula',
click: () => {
const { setTheme } = require('../index');
setTheme('dracula');
},
},
{
label: 'Nord',
click: () => {
const { setTheme } = require('../index');
setTheme('nord');
},
},
{
label: 'Monokai',
click: () => {
const { setTheme } = require('../index');
setTheme('monokai');
},
},
{
label: 'Material',
click: () => {
const { setTheme } = require('../index');
setTheme('material');
},
},
{
label: 'Gruvbox Dark',
click: () => {
const { setTheme } = require('../index');
setTheme('gruvbox-dark');
},
},
{
label: 'Tokyo Night',
click: () => {
const { setTheme } = require('../index');
setTheme('tokyonight');
},
},
{
label: 'Palenight',
click: () => {
const { setTheme } = require('../index');
setTheme('palenight');
},
},
{
label: 'Ayu Dark',
click: () => {
const { setTheme } = require('../index');
setTheme('ayu-dark');
},
},
{
label: 'Ayu Mirage',
click: () => {
const { setTheme } = require('../index');
setTheme('ayu-mirage');
},
},
{
label: 'Oceanic Next',
click: () => {
const { setTheme } = require('../index');
setTheme('oceanic-next');
},
},
{
label: 'Cobalt2',
click: () => {
const { setTheme } = require('../index');
setTheme('cobalt2');
},
},
{
label: 'Concrete Dark',
click: () => {
const { setTheme } = require('../index');
setTheme('concrete-dark');
},
},
{
label: 'Concrete Warm',
click: () => {
const { setTheme } = require('../index');
setTheme('concrete-warm');
},
},
],
},
{ type: 'separator' },
{
@@ -281,19 +552,19 @@ function viewItems(mainWindow) {
{
label: 'Increase Font Size',
accelerator: 'CmdOrCtrl+Shift+Plus',
click: () => mainWindow.webContents.send('adjust-font-size', 'increase')
click: () => mainWindow.webContents.send('adjust-font-size', 'increase'),
},
{
label: 'Decrease Font Size',
accelerator: 'CmdOrCtrl+Shift+-',
click: () => mainWindow.webContents.send('adjust-font-size', 'decrease')
click: () => mainWindow.webContents.send('adjust-font-size', 'decrease'),
},
{
label: 'Reset Font Size',
accelerator: 'CmdOrCtrl+Shift+0',
click: () => mainWindow.webContents.send('adjust-font-size', 'reset')
}
]
click: () => mainWindow.webContents.send('adjust-font-size', 'reset'),
},
],
},
{ type: 'separator' },
{
@@ -302,7 +573,7 @@ function viewItems(mainWindow) {
checked: true,
click: (menuItem) => {
mainWindow.webContents.session.setSpellCheckerEnabled(menuItem.checked);
}
},
},
{ type: 'separator' },
{
@@ -310,13 +581,13 @@ function viewItems(mainWindow) {
submenu: [
{
label: 'Load Custom Preview CSS...',
click: () => mainWindow.webContents.send('load-custom-css')
click: () => mainWindow.webContents.send('load-custom-css'),
},
{
label: 'Clear Custom Preview CSS',
click: () => mainWindow.webContents.send('clear-custom-css')
}
]
click: () => mainWindow.webContents.send('clear-custom-css'),
},
],
},
{ type: 'separator' },
{ label: 'Reload', accelerator: 'CmdOrCtrl+R', role: 'reload' },
@@ -324,7 +595,7 @@ function viewItems(mainWindow) {
{ type: 'separator' },
{ label: 'Zoom In', accelerator: 'CmdOrCtrl+Plus', role: 'zoomIn' },
{ label: 'Zoom Out', accelerator: 'CmdOrCtrl+-', role: 'zoomOut' },
{ label: 'Reset Zoom', accelerator: 'CmdOrCtrl+0', role: 'resetZoom' }
{ label: 'Reset Zoom', accelerator: 'CmdOrCtrl+0', role: 'resetZoom' },
];
}
@@ -335,25 +606,25 @@ function batchItems(mainWindow) {
click: () => {
const { showBatchConversionDialog } = require('../index');
showBatchConversionDialog();
}
},
},
{ type: 'separator' },
{
label: 'Batch Image Conversion...',
click: () => mainWindow.webContents.send('show-batch-converter', 'image')
click: () => mainWindow.webContents.send('show-batch-converter', 'image'),
},
{
label: 'Batch Audio Conversion...',
click: () => mainWindow.webContents.send('show-batch-converter', 'audio')
click: () => mainWindow.webContents.send('show-batch-converter', 'audio'),
},
{
label: 'Batch Video Conversion...',
click: () => mainWindow.webContents.send('show-batch-converter', 'video')
click: () => mainWindow.webContents.send('show-batch-converter', 'video'),
},
{
label: 'Batch PDF Conversion...',
click: () => mainWindow.webContents.send('show-batch-converter', 'pdf')
}
click: () => mainWindow.webContents.send('show-batch-converter', 'pdf'),
},
];
}
@@ -365,8 +636,8 @@ function convertItems(mainWindow) {
click: () => {
const { showUniversalConverterDialog } = require('../index');
showUniversalConverterDialog();
}
}
},
},
];
}
@@ -378,7 +649,7 @@ function pdfEditorItems(mainWindow) {
click: () => {
const { openPdfFile } = require('../index');
openPdfFile();
}
},
},
{ type: 'separator' },
{
@@ -386,21 +657,21 @@ function pdfEditorItems(mainWindow) {
click: () => {
const { showPDFEditorDialog } = require('../index');
showPDFEditorDialog('merge');
}
},
},
{
label: 'Split PDF...',
click: () => {
const { showPDFEditorDialog } = require('../index');
showPDFEditorDialog('split');
}
},
},
{
label: 'Compress PDF...',
click: () => {
const { showPDFEditorDialog } = require('../index');
showPDFEditorDialog('compress');
}
},
},
{ type: 'separator' },
{
@@ -408,21 +679,21 @@ function pdfEditorItems(mainWindow) {
click: () => {
const { showPDFEditorDialog } = require('../index');
showPDFEditorDialog('rotate');
}
},
},
{
label: 'Delete Pages...',
click: () => {
const { showPDFEditorDialog } = require('../index');
showPDFEditorDialog('delete');
}
},
},
{
label: 'Reorder Pages...',
click: () => {
const { showPDFEditorDialog } = require('../index');
showPDFEditorDialog('reorder');
}
},
},
{ type: 'separator' },
{
@@ -430,7 +701,7 @@ function pdfEditorItems(mainWindow) {
click: () => {
const { showPDFEditorDialog } = require('../index');
showPDFEditorDialog('watermark');
}
},
},
{ type: 'separator' },
{
@@ -441,23 +712,23 @@ function pdfEditorItems(mainWindow) {
click: () => {
const { showPDFEditorDialog } = require('../index');
showPDFEditorDialog('encrypt');
}
},
},
{
label: 'Remove Password...',
click: () => {
const { showPDFEditorDialog } = require('../index');
showPDFEditorDialog('decrypt');
}
},
},
{
label: 'Set Permissions...',
click: () => {
const { showPDFEditorDialog } = require('../index');
showPDFEditorDialog('permissions');
}
}
]
},
},
],
},
{ type: 'separator' },
{
@@ -465,8 +736,8 @@ function pdfEditorItems(mainWindow) {
click: () => {
const { showAboutDialog } = require('../index');
showAboutDialog();
}
}
},
},
];
}
@@ -478,8 +749,8 @@ function toolsItems(mainWindow) {
{ type: 'separator' },
{
label: 'Document Compare',
click: () => mainWindow.webContents.send('show-document-compare')
}
click: () => mainWindow.webContents.send('show-document-compare'),
},
];
}
@@ -490,7 +761,7 @@ function helpItems(mainWindow) {
click: () => {
const { showAboutDialog } = require('../index');
showAboutDialog();
}
},
},
{ type: 'separator' },
{
@@ -498,21 +769,21 @@ function helpItems(mainWindow) {
click: () => {
const { showDependenciesDialog } = require('../index');
showDependenciesDialog();
}
},
},
{ type: 'separator' },
{
label: 'Documentation',
click: () => shell.openExternal('https://github.com/amitwh/markdown-converter')
click: () => shell.openExternal('https://github.com/amitwh/markdown-converter'),
},
{
label: 'Report Issue',
click: () => shell.openExternal('https://github.com/amitwh/markdown-converter/issues')
click: () => shell.openExternal('https://github.com/amitwh/markdown-converter/issues'),
},
{
label: 'Check for Updates',
click: () => shell.openExternal('https://github.com/amitwh/markdown-converter/releases')
}
click: () => shell.openExternal('https://github.com/amitwh/markdown-converter/releases'),
},
];
}
@@ -524,5 +795,5 @@ module.exports = {
convertItems,
pdfEditorItems,
toolsItems,
helpItems
};
helpItems,
};
+1 -1
View File
@@ -25,7 +25,7 @@ const store = {
} catch {}
settings[key] = value;
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
}
},
};
module.exports = store;
+8 -3
View File
@@ -29,13 +29,18 @@ class CrashWriter {
const files = fs.readdirSync(this.dir).sort();
while (files.length > MAX_DUMPS) {
const oldest = files.shift();
try { fs.unlinkSync(path.join(this.dir, oldest)); } catch (_) { /* ignore */ }
try {
fs.unlinkSync(path.join(this.dir, oldest));
} catch (_) {
/* ignore */
}
}
}
list() {
if (!fs.existsSync(this.dir)) return [];
return fs.readdirSync(this.dir)
return fs
.readdirSync(this.dir)
.filter((f) => f.endsWith('.json'))
.sort()
.reverse()
@@ -56,4 +61,4 @@ class CrashWriter {
}
}
module.exports = { CrashWriter };
module.exports = { CrashWriter };
+39 -35
View File
@@ -2,42 +2,46 @@
// Kept in sync manually; if the renderer transform changes, update this too.
const { z } = require('zod');
const v4SettingsSchema = z.object({
theme: z.enum(['light', 'dark', 'auto']).default('auto'),
customCss: z.string().optional().nullable(),
recentFiles: z.array(z.string()).default([]),
editorFontSize: z.number().min(10).max(28).default(14),
keyBindings: z.record(z.string(), z.string()).optional(),
snippets: z.array(z.unknown()).default([]),
}).passthrough();
const v4SettingsSchema = z
.object({
theme: z.enum(['light', 'dark', 'auto']).default('auto'),
customCss: z.string().optional().nullable(),
recentFiles: z.array(z.string()).default([]),
editorFontSize: z.number().min(10).max(28).default(14),
keyBindings: z.record(z.string(), z.string()).optional(),
snippets: z.array(z.unknown()).default([]),
})
.passthrough();
const v5SettingsSchema = z.object({
fontSize: z.number().default(14),
tabSize: z.number().default(4),
lineNumbers: z.boolean().default(true),
wordWrap: z.boolean().default(true),
minimap: z.boolean().default(true),
theme: z.enum(['light', 'dark', 'system']).default('system'),
accentColor: z.string().default('brand'),
fontFamily: z.string().default('system'),
pdfFormat: z.string().default('a4'),
pdfMargins: z.string().default('normal'),
pdfEmbedFonts: z.boolean().default(true),
docxTemplate: z.string().default('standard'),
docxCustomTemplatePath: z.string().nullable().default(null),
replOpen: z.boolean().default(false),
breadcrumbSymbols: z.boolean().default(true),
htmlHighlightStyle: z.string().default('github'),
renderTablesAsAscii: z.boolean().default(false),
welcomeDismissed: z.boolean().default(false),
editorFontSize: z.number().default(14),
customCssPath: z.string().nullable().default(null),
userBindings: z.record(z.string(), z.string()).default({}),
updateChannel: z.enum(['github', 'concreteinfo']).default('github'),
autoCheckUpdates: z.boolean().default(true),
firstRun: z.boolean().default(true),
'migration.version': z.literal(5).optional(),
}).passthrough();
const v5SettingsSchema = z
.object({
fontSize: z.number().default(14),
tabSize: z.number().default(4),
lineNumbers: z.boolean().default(true),
wordWrap: z.boolean().default(true),
minimap: z.boolean().default(true),
theme: z.enum(['light', 'dark', 'system']).default('system'),
accentColor: z.string().default('brand'),
fontFamily: z.string().default('system'),
pdfFormat: z.string().default('a4'),
pdfMargins: z.string().default('normal'),
pdfEmbedFonts: z.boolean().default(true),
docxTemplate: z.string().default('standard'),
docxCustomTemplatePath: z.string().nullable().default(null),
replOpen: z.boolean().default(false),
breadcrumbSymbols: z.boolean().default(true),
htmlHighlightStyle: z.string().default('github'),
renderTablesAsAscii: z.boolean().default(false),
welcomeDismissed: z.boolean().default(false),
editorFontSize: z.number().default(14),
customCssPath: z.string().nullable().default(null),
userBindings: z.record(z.string(), z.string()).default({}),
updateChannel: z.enum(['github', 'concreteinfo']).default('github'),
autoCheckUpdates: z.boolean().default(true),
firstRun: z.boolean().default(true),
'migration.version': z.literal(5).optional(),
})
.passthrough();
const v5OnlyFields = ['updateChannel', 'autoCheckUpdates', 'firstRun'];
const v5ThemeValues = ['light', 'dark', 'system'];
+3 -2
View File
@@ -18,7 +18,8 @@ class UpdaterService extends EventEmitter {
au.on('update-downloaded', (info) => this._emit({ state: 'ready', version: info.version }));
au.on('update-not-available', () => this._emit({ state: 'idle' }));
au.on('error', (err) => {
const code = err && /ENOTFOUND|ETIMEDOUT|ECONNREFUSED/.test(err.message) ? 'NETWORK' : 'UNKNOWN';
const code =
err && /ENOTFOUND|ETIMEDOUT|ECONNREFUSED/.test(err.message) ? 'NETWORK' : 'UNKNOWN';
this._emit({ state: 'error', code });
});
}
@@ -39,4 +40,4 @@ class UpdaterService extends EventEmitter {
}
}
module.exports = { UpdaterService };
module.exports = { UpdaterService };
+8 -4
View File
@@ -10,7 +10,7 @@ function getAllowedDirectories() {
app.getPath('desktop'),
app.getPath('downloads'),
app.getPath('home'),
process.cwd() // Current working directory
process.cwd(), // Current working directory
].filter(Boolean); // Remove any undefined paths
return dirs;
}
@@ -88,9 +88,13 @@ function resolveWritablePath(filePath) {
function isPathAccessible(resolvedPath) {
// Block access to sensitive system directories
const blockedPaths = [
'/etc/passwd', '/etc/shadow', '/root',
'C:\\Windows\\System32', 'C:\\Windows\\System',
'/System', '/private/etc'
'/etc/passwd',
'/etc/shadow',
'/root',
'C:\\Windows\\System32',
'C:\\Windows\\System',
'/System',
'/private/etc',
];
const normalizedPath = resolvedPath.toLowerCase();
+16 -11
View File
@@ -25,9 +25,9 @@ function createMainWindow() {
// on load and the renderer never gets the IPC bridge.
contextIsolation: true,
nodeIntegration: false,
spellcheck: true
spellcheck: true,
},
icon: path.join(__dirname, '../../../assets/icon.png')
icon: path.join(__dirname, '../../../assets/icon.png'),
});
// Dev (Vite): load the running dev server so .tsx is transformed on the fly.
@@ -54,7 +54,7 @@ function createMainWindow() {
console.error(
'[WINDOW] Renderer not found at',
rendererIndex,
'— did you run `npm run build:renderer` before packaging?',
'— did you run `npm run build:renderer` before packaging?'
);
}
}
@@ -84,18 +84,23 @@ function createMainWindow() {
// Add spell check suggestions
if (params.misspelledWord) {
for (const suggestion of params.dictionarySuggestions) {
ctxMenu.append(new MenuItem({
label: suggestion,
click: () => win.webContents.replaceMisspelling(suggestion)
}));
ctxMenu.append(
new MenuItem({
label: suggestion,
click: () => win.webContents.replaceMisspelling(suggestion),
})
);
}
if (params.dictionarySuggestions.length > 0) {
ctxMenu.append(new MenuItem({ type: 'separator' }));
}
ctxMenu.append(new MenuItem({
label: 'Add to Dictionary',
click: () => win.webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord)
}));
ctxMenu.append(
new MenuItem({
label: 'Add to Dictionary',
click: () =>
win.webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord),
})
);
ctxMenu.append(new MenuItem({ type: 'separator' }));
}
File diff suppressed because it is too large Load Diff