mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 10:00:17 +05:30
feat: add developer format support (JSON, YAML, XML, TOML)
This commit is contained in:
+186
-5
@@ -75,6 +75,23 @@ function createRateLimiter(minIntervalMs = 2000) {
|
|||||||
}
|
}
|
||||||
const conversionLimiter = createRateLimiter(2000);
|
const conversionLimiter = createRateLimiter(2000);
|
||||||
|
|
||||||
|
// Convert structured data formats to markdown code blocks
|
||||||
|
function convertDataToMarkdown(content, format) {
|
||||||
|
switch (format) {
|
||||||
|
case 'json':
|
||||||
|
return '```json\n' + content + '\n```';
|
||||||
|
case 'yaml':
|
||||||
|
case 'yml':
|
||||||
|
return '```yaml\n' + content + '\n```';
|
||||||
|
case 'xml':
|
||||||
|
return '```xml\n' + content + '\n```';
|
||||||
|
case 'toml':
|
||||||
|
return '```toml\n' + content + '\n```';
|
||||||
|
default:
|
||||||
|
return '```\n' + content + '\n```';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run Pandoc command safely with execFile
|
* Run Pandoc command safely with execFile
|
||||||
* @param {string[]} args - Pandoc arguments array
|
* @param {string[]} args - Pandoc arguments array
|
||||||
@@ -309,7 +326,8 @@ function createWindow() {
|
|||||||
height: 800,
|
height: 800,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
contextIsolation: false
|
contextIsolation: false,
|
||||||
|
spellcheck: true
|
||||||
},
|
},
|
||||||
icon: path.join(__dirname, '../assets/icon.png')
|
icon: path.join(__dirname, '../assets/icon.png')
|
||||||
});
|
});
|
||||||
@@ -322,6 +340,38 @@ function createWindow() {
|
|||||||
mainWindow = null;
|
mainWindow = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Spell check context menu
|
||||||
|
mainWindow.webContents.on('context-menu', (event, params) => {
|
||||||
|
const { Menu, MenuItem } = require('electron');
|
||||||
|
const menu = new Menu();
|
||||||
|
|
||||||
|
// Add spell check suggestions
|
||||||
|
if (params.misspelledWord) {
|
||||||
|
for (const suggestion of params.dictionarySuggestions) {
|
||||||
|
menu.append(new MenuItem({
|
||||||
|
label: suggestion,
|
||||||
|
click: () => mainWindow.webContents.replaceMisspelling(suggestion)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (params.dictionarySuggestions.length > 0) {
|
||||||
|
menu.append(new MenuItem({ type: 'separator' }));
|
||||||
|
}
|
||||||
|
menu.append(new MenuItem({
|
||||||
|
label: 'Add to Dictionary',
|
||||||
|
click: () => mainWindow.webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord)
|
||||||
|
}));
|
||||||
|
menu.append(new MenuItem({ type: 'separator' }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard context menu items
|
||||||
|
menu.append(new MenuItem({ role: 'cut' }));
|
||||||
|
menu.append(new MenuItem({ role: 'copy' }));
|
||||||
|
menu.append(new MenuItem({ role: 'paste' }));
|
||||||
|
menu.append(new MenuItem({ role: 'selectAll' }));
|
||||||
|
|
||||||
|
menu.popup();
|
||||||
|
});
|
||||||
|
|
||||||
// Wait for the page to fully load before sending file data
|
// Wait for the page to fully load before sending file data
|
||||||
mainWindow.webContents.on('did-finish-load', () => {
|
mainWindow.webContents.on('did-finish-load', () => {
|
||||||
console.log('Window finished loading');
|
console.log('Window finished loading');
|
||||||
@@ -455,6 +505,17 @@ function createMenu() {
|
|||||||
{ label: 'OpenDocument Presentation (ODP)', click: () => exportFile('odp') },
|
{ label: 'OpenDocument Presentation (ODP)', click: () => exportFile('odp') },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ label: 'CSV (Tables)', click: () => exportSpreadsheet('csv') },
|
{ label: 'CSV (Tables)', click: () => exportSpreadsheet('csv') },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'JSON (.json)', click: () => exportFile('json') },
|
||||||
|
{ label: 'YAML (.yaml)', click: () => exportFile('yaml') },
|
||||||
|
{ label: 'XML (.xml)', click: () => exportFile('xml') },
|
||||||
|
{ label: 'TOML (.toml)', click: () => exportFile('toml') },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'Reveal.js Slides (.html)', click: () => exportFile('revealjs') },
|
||||||
|
{ label: 'Beamer Slides (.pdf)', click: () => exportFile('beamer') },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'Confluence Wiki (.txt)', click: () => exportFile('confluence') },
|
||||||
|
{ label: 'MOBI E-book (.mobi)', click: () => exportFile('mobi') },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
@@ -571,6 +632,15 @@ function createMenu() {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
label: 'Spell Check',
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: true,
|
||||||
|
click: (menuItem) => {
|
||||||
|
mainWindow.webContents.session.setSpellCheckerEnabled(menuItem.checked);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
{ label: 'Reload', accelerator: 'CmdOrCtrl+R', role: 'reload' },
|
{ label: 'Reload', accelerator: 'CmdOrCtrl+R', role: 'reload' },
|
||||||
{ label: 'Toggle DevTools', accelerator: 'F12', role: 'toggleDevTools' },
|
{ label: 'Toggle DevTools', accelerator: 'F12', role: 'toggleDevTools' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
@@ -989,6 +1059,7 @@ function openFile() {
|
|||||||
properties: ['openFile'],
|
properties: ['openFile'],
|
||||||
filters: [
|
filters: [
|
||||||
{ name: 'Markdown', extensions: ['md', 'markdown'] },
|
{ name: 'Markdown', extensions: ['md', 'markdown'] },
|
||||||
|
{ name: 'Developer Formats', extensions: ['json', 'yaml', 'yml', 'xml', 'toml'] },
|
||||||
{ name: 'All Files', extensions: ['*'] }
|
{ name: 'All Files', extensions: ['*'] }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -1000,7 +1071,15 @@ function openFile() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentFile = files[0];
|
currentFile = files[0];
|
||||||
const content = fs.readFileSync(currentFile, 'utf-8');
|
const ext = path.extname(currentFile).toLowerCase().slice(1);
|
||||||
|
let content = fs.readFileSync(currentFile, 'utf-8');
|
||||||
|
|
||||||
|
// Wrap developer format files in code blocks for markdown display
|
||||||
|
const devFormats = ['json', 'yaml', 'yml', 'xml', 'toml'];
|
||||||
|
if (devFormats.includes(ext)) {
|
||||||
|
content = convertDataToMarkdown(content, ext);
|
||||||
|
}
|
||||||
|
|
||||||
mainWindow.webContents.send('file-opened', { path: currentFile, content });
|
mainWindow.webContents.send('file-opened', { path: currentFile, content });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1766,10 +1845,19 @@ function convertWithPandoc(inputFile, outputPath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function performExportWithOptions(format, options) {
|
function performExportWithOptions(format, options) {
|
||||||
|
// Map format names to file extensions
|
||||||
|
const formatExtMap = {
|
||||||
|
revealjs: 'html',
|
||||||
|
beamer: 'pdf',
|
||||||
|
confluence: 'txt',
|
||||||
|
jira: 'txt',
|
||||||
|
};
|
||||||
|
const fileExt = formatExtMap[format] || format;
|
||||||
|
|
||||||
const outputFile = dialog.showSaveDialogSync(mainWindow, {
|
const outputFile = dialog.showSaveDialogSync(mainWindow, {
|
||||||
defaultPath: currentFile.replace(/\.[^/.]+$/, `.${format}`),
|
defaultPath: currentFile.replace(/\.[^/.]+$/, `.${fileExt}`),
|
||||||
filters: [
|
filters: [
|
||||||
{ name: format.toUpperCase(), extensions: [format] }
|
{ name: format.toUpperCase(), extensions: [fileExt] }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1900,6 +1988,52 @@ function performExportWithOptions(format, options) {
|
|||||||
pandocCmd += ` --variable footer="${footerText}"`;
|
pandocCmd += ` --variable footer="${footerText}"`;
|
||||||
}
|
}
|
||||||
exportWithPandoc(pandocCmd, outputFile, format);
|
exportWithPandoc(pandocCmd, outputFile, format);
|
||||||
|
} else if (format === 'json') {
|
||||||
|
pandocCmd = `${getPandocPath()} "${currentFile}" -t json -o "${outputFile}"`;
|
||||||
|
exportWithPandoc(pandocCmd, outputFile, format);
|
||||||
|
} else if (format === 'yaml' || format === 'xml' || format === 'toml') {
|
||||||
|
// For YAML/XML/TOML, save the raw markdown content with the new extension
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(currentFile, 'utf-8');
|
||||||
|
fs.writeFileSync(outputFile, content, 'utf-8');
|
||||||
|
showExportSuccess(outputFile);
|
||||||
|
} catch (err) {
|
||||||
|
dialog.showErrorBox('Export Error', sanitizeErrorMessage(`Failed to export: ${err.message}`));
|
||||||
|
}
|
||||||
|
} else if (format === 'revealjs') {
|
||||||
|
pandocCmd = `${getPandocPath()} "${currentFile}" -t revealjs -s -o "${outputFile}" --slide-level=2`;
|
||||||
|
exportWithPandoc(pandocCmd, outputFile, format);
|
||||||
|
} else if (format === 'beamer') {
|
||||||
|
pandocCmd = `${getPandocPath()} "${currentFile}" -t beamer -o "${outputFile}"`;
|
||||||
|
exportWithPandoc(pandocCmd, outputFile, format);
|
||||||
|
} else if (format === 'confluence' || format === 'jira') {
|
||||||
|
pandocCmd = `${getPandocPath()} "${currentFile}" -t jira -o "${outputFile}"`;
|
||||||
|
exportWithPandoc(pandocCmd, outputFile, format);
|
||||||
|
} else if (format === 'mobi') {
|
||||||
|
// First export to EPUB, then try ebook-convert if available
|
||||||
|
const epubFile = outputFile.replace(/\.mobi$/i, '.epub');
|
||||||
|
pandocCmd = `${getPandocPath()} "${currentFile}" -o "${epubFile}"`;
|
||||||
|
runPandocCmd(pandocCmd, (error) => {
|
||||||
|
if (error) {
|
||||||
|
dialog.showErrorBox('Export Error', sanitizeErrorMessage(`Failed to export EPUB intermediate: ${error.message}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Try ebook-convert (Calibre) for MOBI
|
||||||
|
execFile('ebook-convert', [epubFile, outputFile], (ebookError) => {
|
||||||
|
if (ebookError) {
|
||||||
|
dialog.showMessageBox(mainWindow, {
|
||||||
|
type: 'warning',
|
||||||
|
title: 'MOBI Export - Partial',
|
||||||
|
message: `Calibre's ebook-convert was not found. The file has been exported as EPUB instead.\n\nEPUB saved to: ${epubFile}\n\nTo get MOBI output, install Calibre from: https://calibre-ebook.com/`,
|
||||||
|
buttons: ['OK']
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Clean up intermediate EPUB
|
||||||
|
try { fs.unlinkSync(epubFile); } catch (e) { /* ignore */ }
|
||||||
|
showExportSuccess(outputFile);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// Generic export for other formats
|
// Generic export for other formats
|
||||||
exportWithPandoc(pandocCmd, outputFile, format);
|
exportWithPandoc(pandocCmd, outputFile, format);
|
||||||
@@ -2372,7 +2506,7 @@ function importDocument() {
|
|||||||
{ name: 'Web Formats', extensions: ['html', 'htm', 'xhtml'] },
|
{ name: 'Web Formats', extensions: ['html', 'htm', 'xhtml'] },
|
||||||
{ name: 'Wiki Formats', extensions: ['mediawiki', 'dokuwiki', 'tikiwiki', 'twiki'] },
|
{ name: 'Wiki Formats', extensions: ['mediawiki', 'dokuwiki', 'tikiwiki', 'twiki'] },
|
||||||
{ name: 'CSV/TSV', extensions: ['csv', 'tsv'] },
|
{ name: 'CSV/TSV', extensions: ['csv', 'tsv'] },
|
||||||
{ name: 'JSON', extensions: ['json'] },
|
{ name: 'Developer Formats', extensions: ['json', 'yaml', 'yml', 'xml', 'toml'] },
|
||||||
{ name: 'All Files', extensions: ['*'] }
|
{ name: 'All Files', extensions: ['*'] }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -2405,6 +2539,23 @@ function importDocument() {
|
|||||||
additionalOptions = '--from=json -t markdown';
|
additionalOptions = '--from=json -t markdown';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For YAML, XML, TOML - wrap content in code blocks directly
|
||||||
|
if (['yaml', 'yml', 'xml', 'toml'].includes(ext)) {
|
||||||
|
const rawContent = fs.readFileSync(inputFile, 'utf-8');
|
||||||
|
const mdContent = convertDataToMarkdown(rawContent, ext);
|
||||||
|
const mdOutputFile = inputFile.replace(/\.[^/.]+$/, '.md');
|
||||||
|
fs.writeFileSync(mdOutputFile, mdContent, 'utf-8');
|
||||||
|
currentFile = mdOutputFile;
|
||||||
|
mainWindow.webContents.send('file-opened', { path: mdOutputFile, content: mdContent });
|
||||||
|
dialog.showMessageBox(mainWindow, {
|
||||||
|
type: 'info',
|
||||||
|
title: 'Import Complete',
|
||||||
|
message: `Document imported successfully as ${path.basename(mdOutputFile)}\n\nOriginal format: ${ext.toUpperCase()}\nConverted to: Markdown`,
|
||||||
|
buttons: ['OK']
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Convert to markdown using pandoc (using runPandocCmd for safety)
|
// Convert to markdown using pandoc (using runPandocCmd for safety)
|
||||||
const pandocCmd = `${getPandocPath()} "${inputFile}" -t markdown ${additionalOptions} -o "${outputFile}"`;
|
const pandocCmd = `${getPandocPath()} "${inputFile}" -t markdown ${additionalOptions} -o "${outputFile}"`;
|
||||||
|
|
||||||
@@ -3165,6 +3316,36 @@ function buildPandocCommand(content, format, outputPath) {
|
|||||||
command += ` --variable footer="${footerText}"`;
|
command += ` --variable footer="${footerText}"`;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'json':
|
||||||
|
command = `pandoc "${inputFile}" -t json -o "${outputPath}"`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'yaml':
|
||||||
|
command = `pandoc "${inputFile}" -t markdown -o "${outputPath}"`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'xml':
|
||||||
|
command = `pandoc "${inputFile}" -t jats -o "${outputPath}"`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'toml':
|
||||||
|
// TOML: save raw markdown content with .toml extension
|
||||||
|
command = `pandoc "${inputFile}" -t markdown -o "${outputPath}"`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'revealjs':
|
||||||
|
command = `pandoc "${inputFile}" -t revealjs -s -o "${outputPath}" --slide-level=2`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'beamer':
|
||||||
|
command = `pandoc "${inputFile}" -t beamer -o "${outputPath}"`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'confluence':
|
||||||
|
case 'jira':
|
||||||
|
command = `pandoc "${inputFile}" -t jira -o "${outputPath}"`;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return command;
|
return command;
|
||||||
|
|||||||
Reference in New Issue
Block a user