mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 10:00:17 +05:30
feat: enhanced status bar with word count, char count, line/col, encoding
- Restructured status bar into left/right sections with separators - Added character count, cursor line/column position, encoding, and language mode indicators - Added cursor position tracking via CodeMirror onUpdate callback - Added file path display that updates on tab switch - Simplified word count display for cleaner status bar layout
This commit is contained in:
@@ -43,6 +43,7 @@ const { oneDark } = require('@codemirror/theme-one-dark');
|
|||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
* @param {string} options.content - initial document content (default '')
|
* @param {string} options.content - initial document content (default '')
|
||||||
* @param {Function} options.onChange - called with new content string on every doc change
|
* @param {Function} options.onChange - called with new content string on every doc change
|
||||||
|
* @param {Function} options.onUpdate - called with the EditorView on every update (selection, doc change, etc.)
|
||||||
* @param {boolean} options.isDark - apply oneDark theme when true (default false)
|
* @param {boolean} options.isDark - apply oneDark theme when true (default false)
|
||||||
* @param {boolean} options.showLineNumbers - show line-number gutter (default true)
|
* @param {boolean} options.showLineNumbers - show line-number gutter (default true)
|
||||||
* @returns {EditorView} the created editor view
|
* @returns {EditorView} the created editor view
|
||||||
@@ -51,6 +52,7 @@ function createEditor(parentElement, options = {}) {
|
|||||||
const {
|
const {
|
||||||
content = '',
|
content = '',
|
||||||
onChange = () => {},
|
onChange = () => {},
|
||||||
|
onUpdate = null,
|
||||||
isDark = false,
|
isDark = false,
|
||||||
showLineNumbers = true,
|
showLineNumbers = true,
|
||||||
} = options;
|
} = options;
|
||||||
@@ -76,6 +78,9 @@ function createEditor(parentElement, options = {}) {
|
|||||||
if (update.docChanged) {
|
if (update.docChanged) {
|
||||||
onChange(update.state.doc.toString());
|
onChange(update.state.doc.toString());
|
||||||
}
|
}
|
||||||
|
if (onUpdate && (update.docChanged || update.selectionSet)) {
|
||||||
|
onUpdate(update.view);
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
EditorView.lineWrapping,
|
EditorView.lineWrapping,
|
||||||
];
|
];
|
||||||
|
|||||||
+33
-20
@@ -165,6 +165,9 @@ class TabManager {
|
|||||||
this.updateWordCount();
|
this.updateWordCount();
|
||||||
this.updateTabBar();
|
this.updateTabBar();
|
||||||
},
|
},
|
||||||
|
onUpdate: (view) => {
|
||||||
|
this.updateCursorPosition(view);
|
||||||
|
},
|
||||||
isDark,
|
isDark,
|
||||||
showLineNumbers: this.showLineNumbers,
|
showLineNumbers: this.showLineNumbers,
|
||||||
});
|
});
|
||||||
@@ -183,7 +186,14 @@ class TabManager {
|
|||||||
this.updateUI();
|
this.updateUI();
|
||||||
this.restoreTabState(tabId);
|
this.restoreTabState(tabId);
|
||||||
this.focusActiveEditor();
|
this.focusActiveEditor();
|
||||||
|
this.updateFilePath();
|
||||||
|
|
||||||
|
// Update cursor position for the newly active tab
|
||||||
|
const tabForCursor = this.tabs.get(tabId);
|
||||||
|
if (tabForCursor?.editorView) {
|
||||||
|
this.updateCursorPosition(tabForCursor.editorView);
|
||||||
|
}
|
||||||
|
|
||||||
// Notify main process about current file for exports
|
// Notify main process about current file for exports
|
||||||
const tab = this.tabs.get(tabId);
|
const tab = this.tabs.get(tabId);
|
||||||
if (tab?.filePath) {
|
if (tab?.filePath) {
|
||||||
@@ -417,29 +427,28 @@ class TabManager {
|
|||||||
const content = tab.content;
|
const content = tab.content;
|
||||||
const words = content.trim() ? content.trim().split(/\s+/).filter(word => word.length > 0).length : 0;
|
const words = content.trim() ? content.trim().split(/\s+/).filter(word => word.length > 0).length : 0;
|
||||||
const chars = content.length;
|
const chars = content.length;
|
||||||
const charsNoSpaces = content.replace(/\s/g, '').length;
|
|
||||||
|
|
||||||
// Enhanced statistics
|
const wordEl = document.getElementById('word-count');
|
||||||
const lines = content.split('\n').length;
|
const charEl = document.getElementById('char-count');
|
||||||
const paragraphs = content.split(/\n\s*\n/).filter(p => p.trim()).length;
|
if (wordEl) wordEl.textContent = `Words: ${words}`;
|
||||||
const readingTime = Math.ceil(words / 200); // Average reading speed: 200 words/minute
|
if (charEl) charEl.textContent = `Chars: ${chars}`;
|
||||||
const sentences = content.split(/[.!?]+/).filter(s => s.trim()).length;
|
}
|
||||||
|
|
||||||
// Update the word count display with enhanced stats
|
updateCursorPosition(view) {
|
||||||
const basicStats = `Words: ${words} | Characters: ${chars} (${charsNoSpaces} no spaces)`;
|
if (!view) return;
|
||||||
const enhancedStats = `Lines: ${lines} | Paragraphs: ${paragraphs} | Sentences: ${sentences} | Reading time: ${readingTime} min`;
|
const pos = view.state.selection.main.head;
|
||||||
|
const line = view.state.doc.lineAt(pos);
|
||||||
|
const lineCol = document.getElementById('line-col');
|
||||||
|
if (lineCol) lineCol.textContent = `Ln ${line.number}, Col ${pos - line.from + 1}`;
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('word-count').textContent = basicStats;
|
updateFilePath() {
|
||||||
|
const tab = this.tabs.get(this.activeTabId);
|
||||||
// Add enhanced stats to a separate element
|
const el = document.getElementById('status-file-path');
|
||||||
let enhancedEl = document.getElementById('enhanced-stats');
|
if (el && tab) {
|
||||||
if (!enhancedEl) {
|
el.textContent = tab.filePath || 'Untitled';
|
||||||
enhancedEl = document.createElement('div');
|
el.title = tab.filePath || '';
|
||||||
enhancedEl.id = 'enhanced-stats';
|
|
||||||
enhancedEl.className = 'enhanced-stats';
|
|
||||||
document.querySelector('.status-bar').appendChild(enhancedEl);
|
|
||||||
}
|
}
|
||||||
enhancedEl.textContent = enhancedStats;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupEditorEvents() {
|
setupEditorEvents() {
|
||||||
@@ -931,6 +940,7 @@ class TabManager {
|
|||||||
this.startAutoSave();
|
this.startAutoSave();
|
||||||
this.addToRecentFiles(filePath);
|
this.addToRecentFiles(filePath);
|
||||||
this.updateTabBar();
|
this.updateTabBar();
|
||||||
|
this.updateFilePath();
|
||||||
|
|
||||||
// Notify main process about current file for exports
|
// Notify main process about current file for exports
|
||||||
ipcRenderer.send('set-current-file', filePath);
|
ipcRenderer.send('set-current-file', filePath);
|
||||||
@@ -1040,6 +1050,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
tabManager.updateWordCount();
|
tabManager.updateWordCount();
|
||||||
tabManager.updateTabBar();
|
tabManager.updateTabBar();
|
||||||
},
|
},
|
||||||
|
onUpdate: (view) => {
|
||||||
|
tabManager.updateCursorPosition(view);
|
||||||
|
},
|
||||||
isDark,
|
isDark,
|
||||||
showLineNumbers: tabManager.showLineNumbers,
|
showLineNumbers: tabManager.showLineNumbers,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -323,10 +323,12 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 8px 20px;
|
padding: 2px 12px;
|
||||||
/* Background and color controlled by theme */
|
/* Background and color controlled by theme */
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
min-height: 24px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Preview Styles - Modern Typography */
|
/* Preview Styles - Modern Typography */
|
||||||
|
|||||||
+26
-4
@@ -139,11 +139,18 @@ body {
|
|||||||
background: #d0d0d0;
|
background: #d0d0d0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toolbar-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
.toolbar-separator {
|
.toolbar-separator {
|
||||||
width: 1px;
|
width: 1px;
|
||||||
height: 24px;
|
height: 20px;
|
||||||
background: #ccc;
|
background: rgba(0, 0, 0, 0.12);
|
||||||
margin: 0 8px;
|
margin: 0 6px;
|
||||||
|
align-self: center;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Editor Container */
|
/* Editor Container */
|
||||||
@@ -287,11 +294,26 @@ body {
|
|||||||
.status-bar {
|
.status-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 4px 12px;
|
align-items: center;
|
||||||
|
padding: 2px 12px;
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
border-top: 1px solid #ddd;
|
border-top: 1px solid #ddd;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
min-height: 24px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.status-bar-left, .status-bar-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.status-separator {
|
||||||
|
color: var(--gray-300, #d1d5db);
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
.status-item {
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Preview Styles */
|
/* Preview Styles */
|
||||||
|
|||||||
Reference in New Issue
Block a user