From d705cfc30bde9f0ea69a02a1b1b5d598d9b47529 Mon Sep 17 00:00:00 2001 From: Amit Haridas Date: Tue, 30 Jun 2026 12:56:33 +0530 Subject: [PATCH] fix(batch): resolve pandoc path handling and include-subfolders option - Normalize pandoc command parsing with path.basename() to support bundled binary paths - Use bundled pandoc binary in convertWithPandoc instead of relying on PATH - Forward includeSubfolders checkbox state from renderer to main process - Add pandoc availability check before batch conversion - Re-enable Start button when batch conversion completes - Clean up obsolete dist build artifact causing test snapshot warning - Bump version to 4.4.4 --- README.md | 2 +- package-lock.json | 4 +- package.json | 2 +- src/adapters/electron/fs.js | 178 +- src/adapters/types.js | 2 +- src/analytics/analytics-panel.js | 72 +- src/analytics/writing-analytics.js | 281 +- src/ascii-generator.html | 1201 ++- src/command-palette.js | 178 +- src/editor/codemirror-setup.js | 65 +- src/fonts.css | 90 +- src/index.html | 3975 ++++--- src/main.js | 2795 +++-- src/main/GitOperations.js | 2 +- src/main/PDFOperations.js | 105 +- src/plugins/built-in/_sample/manifest.json | 4 +- src/plugins/built-in/writing-studio/index.js | 108 +- .../built-in/writing-studio/manifest.json | 25 +- .../writing-studio/panels/goals-panel.js | 5 +- .../writing-studio/panels/manuscript-panel.js | 3 +- .../writing-studio/panels/proofread-panel.js | 7 +- .../writing-studio/panels/snapshots-panel.js | 6 +- .../writing-studio/project-manager.js | 4 +- .../writing-studio/snapshot-manager.js | 14 +- .../built-in/writing-studio/sprint-engine.js | 4 +- src/plugins/plugin-context.js | 25 +- src/plugins/plugin-loader.js | 7 +- src/plugins/plugin-registry.js | 2 +- src/plugins/settings-store.js | 2 +- src/preload.js | 43 +- src/print-preview.js | 180 +- src/renderer.js | 9582 +++++++++-------- src/repl/repl-panel.js | 68 +- src/sidebar/explorer-panel.js | 106 +- src/sidebar/git-panel.js | 100 +- src/sidebar/outline-panel.js | 152 +- src/sidebar/sidebar-manager.js | 82 +- src/sidebar/snippets-panel.js | 92 +- src/sidebar/templates-panel.js | 43 +- src/styles-concreteinfo.css | 27 +- src/styles-modern.css | 528 +- src/styles-sidebar.css | 611 +- src/styles-welcome.css | 145 +- src/styles-zen.css | 96 +- src/styles.css | 3811 +++---- src/styles/modal.css | 252 +- src/styles/tokens.css | 66 +- src/table-generator.html | 961 +- src/templates/api-docs.md | 20 +- src/templates/blog-post.md | 2 +- src/templates/changelog.md | 5 + src/templates/comparison.md | 36 +- src/templates/meeting-notes.md | 1 + src/templates/project-plan.md | 29 +- src/templates/readme.md | 6 +- src/templates/technical-spec.md | 14 +- src/templates/tutorial.md | 4 +- src/utils/ModalManager.js | 407 +- src/welcome.js | 16 +- src/wordTemplateExporter.js | 1157 +- src/zen-mode.js | 442 +- tests/bundled-tools.test.js | 28 + tests/command-palette.test.js | 220 +- tests/event-bus.test.js | 4 +- tests/git-operations.test.js | 28 +- tests/goal-tracker.test.js | 4 +- tests/lint-quality.test.js | 18 + tests/main-utils.test.js | 153 +- tests/markdown-extensions.test.js | 458 +- tests/modal-manager.test.js | 468 +- tests/no-diagnostic-logs.test.js | 19 + tests/pdf-operations.test.js | 23 +- tests/plugin-context.test.js | 19 +- tests/plugin-loader.test.js | 28 +- tests/plugin-registry.test.js | 103 +- tests/preload.test.js | 4 +- tests/print-preview.test.js | 152 +- tests/project-manager.test.js | 66 +- tests/project-meta.test.js | 21 + tests/security-path-handling.test.js | 45 +- tests/settings-store.test.js | 4 +- tests/setup.js | 30 +- tests/sidebar.test.js | 179 +- tests/snapshot-manager.test.js | 4 +- tests/sprint-engine.test.js | 4 +- tests/utils.test.js | 18 +- 86 files changed, 16876 insertions(+), 13476 deletions(-) create mode 100644 tests/bundled-tools.test.js create mode 100644 tests/lint-quality.test.js create mode 100644 tests/no-diagnostic-logs.test.js create mode 100644 tests/project-meta.test.js diff --git a/README.md b/README.md index 19eae7e..c08b818 100644 --- a/README.md +++ b/README.md @@ -162,4 +162,4 @@ Amit Haridas (amit.wh@gmail.com) ## Version -v4.1.0 +v4.4.4 diff --git a/package-lock.json b/package-lock.json index 30ec880..fb6405d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "markdown-converter", - "version": "4.4.1", + "version": "4.4.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "markdown-converter", - "version": "4.4.1", + "version": "4.4.4", "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.20.1", diff --git a/package.json b/package.json index fc43524..7ab1c49 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "markdown-converter", - "version": "4.4.3", + "version": "4.4.4", "description": "Professional Markdown editor and universal file converter with PDF editing, batch processing, and syntax highlighting", "main": "src/main.js", "scripts": { diff --git a/src/adapters/electron/fs.js b/src/adapters/electron/fs.js index 29813f6..639f078 100644 --- a/src/adapters/electron/fs.js +++ b/src/adapters/electron/fs.js @@ -12,100 +12,100 @@ * @type {import('../types').FileSystemAdapter} */ const electronFsAdapter = { - /** - * Read file content - * @param {string} path - File path - * @returns {Promise} File content - */ - async readFile(path) { - return await window.electronAPI.file.read(path); - }, + /** + * Read file content + * @param {string} path - File path + * @returns {Promise} File content + */ + async readFile(path) { + return await window.electronAPI.file.read(path); + }, - /** - * Write content to file - * @param {string} path - File path - * @param {string} content - File content - * @returns {Promise} - */ - async writeFile(path, content) { - return await window.electronAPI.file.write(path, content); - }, + /** + * Write content to file + * @param {string} path - File path + * @param {string} content - File content + * @returns {Promise} + */ + async writeFile(path, content) { + return await window.electronAPI.file.write(path, content); + }, - /** - * Delete file - * @param {string} path - File path - * @returns {Promise} - */ - async deleteFile(path) { - return await window.electronAPI.file.delete(path); - }, + /** + * Delete file + * @param {string} path - File path + * @returns {Promise} + */ + async deleteFile(path) { + return await window.electronAPI.file.delete(path); + }, - /** - * Ensure directory exists - * @param {string} path - Directory path - * @returns {Promise} - */ - async ensureDir(path) { - return await window.electronAPI.file.ensureDir(path); - }, + /** + * Ensure directory exists + * @param {string} path - Directory path + * @returns {Promise} + */ + async ensureDir(path) { + return await window.electronAPI.file.ensureDir(path); + }, - /** - * List directory contents - * @param {string} path - Directory path - * @returns {Promise>} - */ - async listDirectory(path) { - const result = await window.electronAPI.invoke('list-directory', path); - if (!result?.entries) { - return []; - } - - return result.entries.map((entry) => ({ - name: entry.name, - isDir: entry.isDirectory, - size: entry.size ?? 0, - modified: entry.modified ?? 0, - path: entry.path - })); - }, - - /** - * Check if path exists - * @param {string} path - Path to check - * @returns {Promise} - */ - async exists(path) { - return await window.electronAPI.file.exists(path); - }, - - /** - * Check if path is a directory - * @param {string} path - Path to check - * @returns {Promise} - */ - async isDirectory(path) { - return await window.electronAPI.file.isDirectory(path); - }, - - /** - * Copy file or directory - * @param {string} source - Source path - * @param {string} dest - Destination path - * @returns {Promise} - */ - async copy(source, dest) { - return await window.electronAPI.file.copy(source, dest); - }, - - /** - * Move file or directory - * @param {string} source - Source path - * @param {string} dest - Destination path - * @returns {Promise} - */ - async move(source, dest) { - return await window.electronAPI.file.move(source, dest); + /** + * List directory contents + * @param {string} path - Directory path + * @returns {Promise>} + */ + async listDirectory(path) { + const result = await window.electronAPI.invoke('list-directory', path); + if (!result?.entries) { + return []; } + + return result.entries.map((entry) => ({ + name: entry.name, + isDir: entry.isDirectory, + size: entry.size ?? 0, + modified: entry.modified ?? 0, + path: entry.path, + })); + }, + + /** + * Check if path exists + * @param {string} path - Path to check + * @returns {Promise} + */ + async exists(path) { + return await window.electronAPI.file.exists(path); + }, + + /** + * Check if path is a directory + * @param {string} path - Path to check + * @returns {Promise} + */ + async isDirectory(path) { + return await window.electronAPI.file.isDirectory(path); + }, + + /** + * Copy file or directory + * @param {string} source - Source path + * @param {string} dest - Destination path + * @returns {Promise} + */ + async copy(source, dest) { + return await window.electronAPI.file.copy(source, dest); + }, + + /** + * Move file or directory + * @param {string} source - Source path + * @param {string} dest - Destination path + * @returns {Promise} + */ + async move(source, dest) { + return await window.electronAPI.file.move(source, dest); + }, }; module.exports = { electronFsAdapter }; diff --git a/src/adapters/types.js b/src/adapters/types.js index 065fb2e..124bf24 100644 --- a/src/adapters/types.js +++ b/src/adapters/types.js @@ -129,5 +129,5 @@ */ module.exports = { - // Type definitions are JSDoc only, no runtime exports needed + // Type definitions are JSDoc only, no runtime exports needed }; diff --git a/src/analytics/analytics-panel.js b/src/analytics/analytics-panel.js index 180a923..0a9afa3 100644 --- a/src/analytics/analytics-panel.js +++ b/src/analytics/analytics-panel.js @@ -5,19 +5,19 @@ const { analyze } = require('./writing-analytics'); function showAnalyticsModal(tabManager) { - const existing = document.getElementById('analytics-modal'); - if (existing) existing.remove(); + const existing = document.getElementById('analytics-modal'); + if (existing) existing.remove(); - const content = tabManager.getEditorContent(); - const metrics = analyze(content); + const content = tabManager.getEditorContent(); + const metrics = analyze(content); - const overlay = document.createElement('div'); - overlay.id = 'analytics-modal'; - overlay.className = 'analytics-overlay'; + const overlay = document.createElement('div'); + overlay.id = 'analytics-modal'; + overlay.className = 'analytics-overlay'; - const maxCount = metrics.topWords.length > 0 ? metrics.topWords[0].count : 1; + const maxCount = metrics.topWords.length > 0 ? metrics.topWords[0].count : 1; - overlay.innerHTML = ` + overlay.innerHTML = `

Writing Analytics

@@ -61,11 +61,15 @@ function showAnalyticsModal(tabManager) { Avg Sentence ${metrics.avgSentenceLength} words
- ${metrics.longestSentenceLength > 0 ? ` + ${ + metrics.longestSentenceLength > 0 + ? `
Longest (${metrics.longestSentenceLength} words) ${escapeHtml(metrics.longestSentence)} -
` : ''} +
` + : '' + }
@@ -74,39 +78,45 @@ function showAnalyticsModal(tabManager) { Unique ${metrics.uniqueWordCount} / ${metrics.wordCount}${metrics.lexicalDiversity}%
- ${metrics.topWords.length > 0 ? ` + ${ + metrics.topWords.length > 0 + ? `
- ${metrics.topWords.map(w => { + ${metrics.topWords + .map((w) => { const scale = 13 + Math.round((w.count / maxCount) * 3); return `${escapeHtml(w.word)}${w.count}`; - }).join('')} -
` : ''} + }) + .join('')} + ` + : '' + } `; - const closeBtn = overlay.querySelector('.analytics-close'); - closeBtn.addEventListener('click', () => overlay.remove()); - overlay.addEventListener('click', (e) => { - if (e.target === overlay) overlay.remove(); - }); + const closeBtn = overlay.querySelector('.analytics-close'); + closeBtn.addEventListener('click', () => overlay.remove()); + overlay.addEventListener('click', (e) => { + if (e.target === overlay) overlay.remove(); + }); - const escHandler = (e) => { - if (e.key === 'Escape') { - overlay.remove(); - document.removeEventListener('keydown', escHandler); - } - }; - document.addEventListener('keydown', escHandler); + const escHandler = (e) => { + if (e.key === 'Escape') { + overlay.remove(); + document.removeEventListener('keydown', escHandler); + } + }; + document.addEventListener('keydown', escHandler); - document.body.appendChild(overlay); + document.body.appendChild(overlay); } function escapeHtml(str) { - const div = document.createElement('div'); - div.textContent = str; - return div.innerHTML; + const div = document.createElement('div'); + div.textContent = str; + return div.innerHTML; } module.exports = { showAnalyticsModal }; diff --git a/src/analytics/writing-analytics.js b/src/analytics/writing-analytics.js index 8b6ca08..7c83f73 100644 --- a/src/analytics/writing-analytics.js +++ b/src/analytics/writing-analytics.js @@ -4,124 +4,197 @@ */ const STOP_WORDS = new Set([ - 'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', - 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', - 'could', 'should', 'to', 'of', 'in', 'for', 'on', 'with', - 'at', 'by', 'from', 'as', 'and', 'or', 'but', 'if', 'it', - 'its', 'this', 'that', 'these', 'those', 'i', 'me', 'my', - 'we', 'our', 'you', 'your', 'he', 'him', 'his', 'she', 'her', - 'they', 'them', 'their', 'not', 'no', 'so', 'than', 'too', - 'very', 'also', 'just', 'about', 'up', 'out', 'what', 'which', 'who' + 'the', + 'a', + 'an', + 'is', + 'are', + 'was', + 'were', + 'be', + 'been', + 'have', + 'has', + 'had', + 'do', + 'does', + 'did', + 'will', + 'would', + 'could', + 'should', + 'to', + 'of', + 'in', + 'for', + 'on', + 'with', + 'at', + 'by', + 'from', + 'as', + 'and', + 'or', + 'but', + 'if', + 'it', + 'its', + 'this', + 'that', + 'these', + 'those', + 'i', + 'me', + 'my', + 'we', + 'our', + 'you', + 'your', + 'he', + 'him', + 'his', + 'she', + 'her', + 'they', + 'them', + 'their', + 'not', + 'no', + 'so', + 'than', + 'too', + 'very', + 'also', + 'just', + 'about', + 'up', + 'out', + 'what', + 'which', + 'who', ]); function countSyllables(word) { - word = word.toLowerCase().replace(/(?:[^laeiouy]es|ed|[^laeiouy]e)$/, ''); - word = word.replace(/^y/, ''); - return word.match(/[aeiouy]{1,2}/gi)?.length || 1; + word = word.toLowerCase().replace(/(?:[^laeiouy]es|ed|[^laeiouy]e)$/, ''); + word = word.replace(/^y/, ''); + return word.match(/[aeiouy]{1,2}/gi)?.length || 1; } function extractWords(text) { - return text.match(/[a-zA-Z]+(?:['-][a-zA-Z]+)*/g) || []; + return text.match(/[a-zA-Z]+(?:['-][a-zA-Z]+)*/g) || []; } function getReadabilityLabel(score) { - if (score >= 90) return 'Very Easy'; - if (score >= 70) return 'Easy'; - if (score >= 50) return 'Standard'; - if (score >= 30) return 'Difficult'; - return 'Very Difficult'; + if (score >= 90) return 'Very Easy'; + if (score >= 70) return 'Easy'; + if (score >= 50) return 'Standard'; + if (score >= 30) return 'Difficult'; + return 'Very Difficult'; } function analyze(text) { - if (!text || !text.trim()) { - return { - wordCount: 0, - sentenceCount: 0, - paragraphCount: 0, - fleschEase: 0, - fleschGrade: 0, - readabilityLabel: 'N/A', - readingTime: 0, - speakingTime: 0, - uniqueWordCount: 0, - lexicalDiversity: 0, - avgSentenceLength: 0, - longestSentence: '', - longestSentenceLength: 0, - topWords: [] - }; - } - - const words = extractWords(text); - const wordCount = words.length; - - const sentences = text.split(/[.!?]+/).map(s => s.trim()).filter(Boolean); - const sentenceCount = Math.max(sentences.length, 1); - - const paragraphs = text.split(/\n\s*\n/).map(p => p.trim()).filter(Boolean); - const paragraphCount = Math.max(paragraphs.length, 1); - - let totalSyllables = 0; - for (const w of words) { - totalSyllables += countSyllables(w); - } - - const fleschEase = Math.round((206.835 - 1.015 * (wordCount / sentenceCount) - 84.6 * (totalSyllables / wordCount)) * 10) / 10; - const fleschGrade = Math.round((0.39 * (wordCount / sentenceCount) + 11.8 * (totalSyllables / wordCount) - 15.59) * 10) / 10; - const readabilityLabel = getReadabilityLabel(fleschEase); - - const readingTime = Math.ceil(wordCount / 200); - const speakingTime = Math.ceil(wordCount / 130); - - const uniqueWords = new Set(words.map(w => w.toLowerCase())); - const uniqueWordCount = uniqueWords.size; - const lexicalDiversity = wordCount > 0 ? Math.round((uniqueWordCount / wordCount) * 1000) / 10 : 0; - - const avgSentenceLength = Math.round((wordCount / sentenceCount) * 10) / 10; - - let longestSentence = ''; - let longestSentenceLength = 0; - for (const s of sentences) { - const sWords = extractWords(s); - if (sWords.length > longestSentenceLength) { - longestSentenceLength = sWords.length; - longestSentence = s.trim(); - } - } - - if (longestSentence.length > 80) { - longestSentence = longestSentence.substring(0, 80) + '...'; - } - - const wordFreq = {}; - for (const w of words) { - const lower = w.toLowerCase(); - if (!STOP_WORDS.has(lower) && lower.length > 1) { - wordFreq[lower] = (wordFreq[lower] || 0) + 1; - } - } - - const topWords = Object.entries(wordFreq) - .sort((a, b) => b[1] - a[1]) - .slice(0, 10) - .map(([word, count]) => ({ word, count })); - + if (!text || !text.trim()) { return { - wordCount, - sentenceCount, - paragraphCount, - fleschEase, - fleschGrade, - readabilityLabel, - readingTime, - speakingTime, - uniqueWordCount, - lexicalDiversity, - avgSentenceLength, - longestSentence, - longestSentenceLength, - topWords + wordCount: 0, + sentenceCount: 0, + paragraphCount: 0, + fleschEase: 0, + fleschGrade: 0, + readabilityLabel: 'N/A', + readingTime: 0, + speakingTime: 0, + uniqueWordCount: 0, + lexicalDiversity: 0, + avgSentenceLength: 0, + longestSentence: '', + longestSentenceLength: 0, + topWords: [], }; + } + + const words = extractWords(text); + const wordCount = words.length; + + const sentences = text + .split(/[.!?]+/) + .map((s) => s.trim()) + .filter(Boolean); + const sentenceCount = Math.max(sentences.length, 1); + + const paragraphs = text + .split(/\n\s*\n/) + .map((p) => p.trim()) + .filter(Boolean); + const paragraphCount = Math.max(paragraphs.length, 1); + + let totalSyllables = 0; + for (const w of words) { + totalSyllables += countSyllables(w); + } + + const fleschEase = + Math.round( + (206.835 - 1.015 * (wordCount / sentenceCount) - 84.6 * (totalSyllables / wordCount)) * 10 + ) / 10; + const fleschGrade = + Math.round( + (0.39 * (wordCount / sentenceCount) + 11.8 * (totalSyllables / wordCount) - 15.59) * 10 + ) / 10; + const readabilityLabel = getReadabilityLabel(fleschEase); + + const readingTime = Math.ceil(wordCount / 200); + const speakingTime = Math.ceil(wordCount / 130); + + const uniqueWords = new Set(words.map((w) => w.toLowerCase())); + const uniqueWordCount = uniqueWords.size; + const lexicalDiversity = + wordCount > 0 ? Math.round((uniqueWordCount / wordCount) * 1000) / 10 : 0; + + const avgSentenceLength = Math.round((wordCount / sentenceCount) * 10) / 10; + + let longestSentence = ''; + let longestSentenceLength = 0; + for (const s of sentences) { + const sWords = extractWords(s); + if (sWords.length > longestSentenceLength) { + longestSentenceLength = sWords.length; + longestSentence = s.trim(); + } + } + + if (longestSentence.length > 80) { + longestSentence = longestSentence.substring(0, 80) + '...'; + } + + const wordFreq = {}; + for (const w of words) { + const lower = w.toLowerCase(); + if (!STOP_WORDS.has(lower) && lower.length > 1) { + wordFreq[lower] = (wordFreq[lower] || 0) + 1; + } + } + + const topWords = Object.entries(wordFreq) + .sort((a, b) => b[1] - a[1]) + .slice(0, 10) + .map(([word, count]) => ({ word, count })); + + return { + wordCount, + sentenceCount, + paragraphCount, + fleschEase, + fleschGrade, + readabilityLabel, + readingTime, + speakingTime, + uniqueWordCount, + lexicalDiversity, + avgSentenceLength, + longestSentence, + longestSentenceLength, + topWords, + }; } module.exports = { analyze }; diff --git a/src/ascii-generator.html b/src/ascii-generator.html index 2543ae1..52fdf32 100644 --- a/src/ascii-generator.html +++ b/src/ascii-generator.html @@ -1,601 +1,750 @@ - + - - - + + + ASCII Art Generator - MarkdownConverter - + - - + +
-

ASCII Art Generator

+

ASCII Art Generator

-
-
- - - -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
Arrows & Flow
-
- - - - -
-
-
-
Diagrams
-
- - - - -
-
-
-
Boxes & Containers
-
- - - - -
-
-
-
Decorative
-
- - - - -
-
-
+
+
+ + +
-
-
Preview
-
-
-
+ +
+
+ + +
+
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
Arrows & Flow
+
+ + + + +
+
+
+
Diagrams
+
+ + + + +
+
+
+
Boxes & Containers
+
+ + + + +
+
+
+
Decorative
+
+ + + + +
+
+
+
+ +
+
Preview
+
+
+
+
- + diff --git a/src/command-palette.js b/src/command-palette.js index faa25f1..06d193a 100644 --- a/src/command-palette.js +++ b/src/command-palette.js @@ -1,109 +1,113 @@ class CommandPalette { - constructor() { - this.overlay = document.getElementById('command-palette-overlay'); - this.input = document.getElementById('command-palette-input'); - this.results = document.getElementById('command-palette-results'); - this.commands = []; - this.selectedIndex = 0; - this.filteredCommands = []; - this.setupEventListeners(); - } + constructor() { + this.overlay = document.getElementById('command-palette-overlay'); + this.input = document.getElementById('command-palette-input'); + this.results = document.getElementById('command-palette-results'); + this.commands = []; + this.selectedIndex = 0; + this.filteredCommands = []; + this.setupEventListeners(); + } - register(label, shortcut, action) { - this.commands.push({ label, shortcut, action }); - } + register(label, shortcut, action) { + this.commands.push({ label, shortcut, action }); + } - open() { - this.overlay.classList.remove('hidden'); - this.input.value = ''; - this.input.focus(); - this.selectedIndex = 0; - this.renderResults(''); - } + open() { + this.overlay.classList.remove('hidden'); + this.input.value = ''; + this.input.focus(); + this.selectedIndex = 0; + this.renderResults(''); + } - close() { - this.overlay.classList.add('hidden'); - } + close() { + this.overlay.classList.add('hidden'); + } - isOpen() { - return !this.overlay.classList.contains('hidden'); - } + isOpen() { + return !this.overlay.classList.contains('hidden'); + } - setupEventListeners() { - this.input.addEventListener('input', () => { - this.selectedIndex = 0; - this.renderResults(this.input.value); - }); + setupEventListeners() { + this.input.addEventListener('input', () => { + this.selectedIndex = 0; + this.renderResults(this.input.value); + }); - this.overlay.addEventListener('click', (e) => { - if (e.target === this.overlay) this.close(); - }); + this.overlay.addEventListener('click', (e) => { + if (e.target === this.overlay) this.close(); + }); - this.input.addEventListener('keydown', (e) => { - if (e.key === 'Escape') { - e.preventDefault(); - this.close(); - } else if (e.key === 'Enter') { - e.preventDefault(); - this.executeSelected(); - } else if (e.key === 'ArrowDown') { - e.preventDefault(); - this.selectedIndex = Math.min(this.selectedIndex + 1, this.filteredCommands.length - 1); - this.updateSelection(); - } else if (e.key === 'ArrowUp') { - e.preventDefault(); - this.selectedIndex = Math.max(this.selectedIndex - 1, 0); - this.updateSelection(); - } - }); - } + this.input.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + e.preventDefault(); + this.close(); + } else if (e.key === 'Enter') { + e.preventDefault(); + this.executeSelected(); + } else if (e.key === 'ArrowDown') { + e.preventDefault(); + this.selectedIndex = Math.min(this.selectedIndex + 1, this.filteredCommands.length - 1); + this.updateSelection(); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + this.selectedIndex = Math.max(this.selectedIndex - 1, 0); + this.updateSelection(); + } + }); + } - renderResults(query) { - this.filteredCommands = query - ? this.commands.filter(cmd => cmd.label.toLowerCase().includes(query.toLowerCase())) - : [...this.commands]; + renderResults(query) { + this.filteredCommands = query + ? this.commands.filter((cmd) => cmd.label.toLowerCase().includes(query.toLowerCase())) + : [...this.commands]; - this.results.innerHTML = this.filteredCommands.map((cmd, i) => ` + this.results.innerHTML = this.filteredCommands + .map( + (cmd, i) => `
${this.highlightMatch(cmd.label, query)} ${cmd.shortcut ? `${cmd.shortcut}` : ''}
- `).join(''); + ` + ) + .join(''); - this.results.querySelectorAll('.command-item').forEach((el) => { - el.addEventListener('click', () => { - const idx = parseInt(el.dataset.index); - this.filteredCommands[idx].action(); - this.close(); - }); - el.addEventListener('mouseenter', () => { - this.selectedIndex = parseInt(el.dataset.index); - this.updateSelection(); - }); - }); - } + this.results.querySelectorAll('.command-item').forEach((el) => { + el.addEventListener('click', () => { + const idx = parseInt(el.dataset.index); + this.filteredCommands[idx].action(); + this.close(); + }); + el.addEventListener('mouseenter', () => { + this.selectedIndex = parseInt(el.dataset.index); + this.updateSelection(); + }); + }); + } - highlightMatch(text, query) { - if (!query) return text; - const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'); - return text.replace(regex, '$1'); - } + highlightMatch(text, query) { + if (!query) return text; + const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'); + return text.replace(regex, '$1'); + } - updateSelection() { - this.results.querySelectorAll('.command-item').forEach((el, i) => { - el.classList.toggle('selected', i === this.selectedIndex); - }); - // Scroll selected into view - const selected = this.results.querySelector('.command-item.selected'); - if (selected) selected.scrollIntoView({ block: 'nearest' }); - } + updateSelection() { + this.results.querySelectorAll('.command-item').forEach((el, i) => { + el.classList.toggle('selected', i === this.selectedIndex); + }); + // Scroll selected into view + const selected = this.results.querySelector('.command-item.selected'); + if (selected) selected.scrollIntoView({ block: 'nearest' }); + } - executeSelected() { - if (this.filteredCommands[this.selectedIndex]) { - this.filteredCommands[this.selectedIndex].action(); - this.close(); - } + executeSelected() { + if (this.filteredCommands[this.selectedIndex]) { + this.filteredCommands[this.selectedIndex].action(); + this.close(); } + } } module.exports = { CommandPalette }; diff --git a/src/editor/codemirror-setup.js b/src/editor/codemirror-setup.js index a16f5e7..cd44a52 100644 --- a/src/editor/codemirror-setup.js +++ b/src/editor/codemirror-setup.js @@ -12,38 +12,23 @@ const { EditorState } = require('@codemirror/state'); const { markdown, markdownLanguage } = require('@codemirror/lang-markdown'); // Language extensions loaded lazily on first use let _javascript, _html, _css, _json, _python; -const { - defaultKeymap, - history, - historyKeymap, - indentWithTab, -} = require('@codemirror/commands'); -const { - searchKeymap, - highlightSelectionMatches, -} = require('@codemirror/search'); -const { - autocompletion, - completionKeymap, -} = require('@codemirror/autocomplete'); -const { - bracketMatching, - foldGutter, - indentOnInput, -} = require('@codemirror/language'); +const { defaultKeymap, history, historyKeymap, indentWithTab } = require('@codemirror/commands'); +const { searchKeymap, highlightSelectionMatches } = require('@codemirror/search'); +const { autocompletion, completionKeymap } = require('@codemirror/autocomplete'); +const { bracketMatching, foldGutter, indentOnInput } = require('@codemirror/language'); const { oneDark } = require('@codemirror/theme-one-dark'); // Custom theme for JetBrains Mono font const jetBrainsMonoTheme = EditorView.theme({ '&': { - fontFamily: "'JetBrains Mono', 'Fira Code', 'SF Mono', Monaco, 'Courier New', monospace" + fontFamily: "'JetBrains Mono', 'Fira Code', 'SF Mono', Monaco, 'Courier New', monospace", }, '.cm-content': { - fontFamily: 'inherit' + fontFamily: 'inherit', }, '.cm-scroller': { - fontFamily: 'inherit' - } + fontFamily: 'inherit', + }, }); /** @@ -59,7 +44,14 @@ const jetBrainsMonoTheme = EditorView.theme({ * @returns {EditorView} the created editor view */ function createEditor(parentElement, options = {}) { - console.log('[createEditor] Called with parentElement:', parentElement?.id, 'dimensions:', parentElement?.clientWidth, 'x', parentElement?.clientHeight); + console.log( + '[createEditor] Called with parentElement:', + parentElement?.id, + 'dimensions:', + parentElement?.clientWidth, + 'x', + parentElement?.clientHeight + ); if (!parentElement) { console.error('[createEditor] ERROR: parentElement is null or undefined!'); return null; @@ -125,11 +117,26 @@ function createEditor(parentElement, options = {}) { */ function getLanguageExtension(lang) { const loaders = { - javascript: () => { if (!_javascript) _javascript = require('@codemirror/lang-javascript').javascript; return _javascript(); }, - html: () => { if (!_html) _html = require('@codemirror/lang-html').html; return _html(); }, - css: () => { if (!_css) _css = require('@codemirror/lang-css').css; return _css(); }, - json: () => { if (!_json) _json = require('@codemirror/lang-json').json; return _json(); }, - python: () => { if (!_python) _python = require('@codemirror/lang-python').python; return _python(); }, + javascript: () => { + if (!_javascript) _javascript = require('@codemirror/lang-javascript').javascript; + return _javascript(); + }, + html: () => { + if (!_html) _html = require('@codemirror/lang-html').html; + return _html(); + }, + css: () => { + if (!_css) _css = require('@codemirror/lang-css').css; + return _css(); + }, + json: () => { + if (!_json) _json = require('@codemirror/lang-json').json; + return _json(); + }, + python: () => { + if (!_python) _python = require('@codemirror/lang-python').python; + return _python(); + }, markdown: () => markdown({ base: markdownLanguage }), }; loaders.js = loaders.javascript; diff --git a/src/fonts.css b/src/fonts.css index c312c87..311a2da 100644 --- a/src/fonts.css +++ b/src/fonts.css @@ -2,74 +2,74 @@ /* Inter Font Family */ @font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 300; - font-display: swap; - src: url('../assets/fonts/Inter-Light.woff2') format('woff2'); + font-family: 'Inter'; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url('../assets/fonts/Inter-Light.woff2') format('woff2'); } @font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url('../assets/fonts/Inter-Regular.woff2') format('woff2'); + font-family: 'Inter'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('../assets/fonts/Inter-Regular.woff2') format('woff2'); } @font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 500; - font-display: swap; - src: url('../assets/fonts/Inter-Medium.woff2') format('woff2'); + font-family: 'Inter'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url('../assets/fonts/Inter-Medium.woff2') format('woff2'); } @font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 600; - font-display: swap; - src: url('../assets/fonts/Inter-SemiBold.woff2') format('woff2'); + font-family: 'Inter'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url('../assets/fonts/Inter-SemiBold.woff2') format('woff2'); } @font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 700; - font-display: swap; - src: url('../assets/fonts/Inter-Bold.woff2') format('woff2'); + font-family: 'Inter'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('../assets/fonts/Inter-Bold.woff2') format('woff2'); } /* JetBrains Mono Font Family - For code, markdown editor, and ASCII art */ @font-face { - font-family: 'JetBrains Mono'; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url('../assets/fonts/JetBrainsMono-Regular.woff2') format('woff2'); + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('../assets/fonts/JetBrainsMono-Regular.woff2') format('woff2'); } @font-face { - font-family: 'JetBrains Mono'; - font-style: normal; - font-weight: 500; - font-display: swap; - src: url('../assets/fonts/JetBrainsMono-Medium.woff2') format('woff2'); + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url('../assets/fonts/JetBrainsMono-Medium.woff2') format('woff2'); } @font-face { - font-family: 'JetBrains Mono'; - font-style: normal; - font-weight: 600; - font-display: swap; - src: url('../assets/fonts/JetBrainsMono-SemiBold.woff2') format('woff2'); + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url('../assets/fonts/JetBrainsMono-SemiBold.woff2') format('woff2'); } @font-face { - font-family: 'JetBrains Mono'; - font-style: normal; - font-weight: 700; - font-display: swap; - src: url('../assets/fonts/JetBrainsMono-Bold.woff2') format('woff2'); + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('../assets/fonts/JetBrainsMono-Bold.woff2') format('woff2'); } diff --git a/src/index.html b/src/index.html index c79af69..1481af0 100644 --- a/src/index.html +++ b/src/index.html @@ -1,1667 +1,2454 @@ - + - - + + - - + + MarkdownConverter - - - - - - - - - - - - - + + + + + + + + + + + + +
- -
-
- - MarkdownConverter -
-
- -
+ +
+
+ + MarkdownConverter
-
- - +
+
-
-
- -
+
+ + +
+
+
+ + + + +
+
+
+ + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + +