Skip to content

Commit 5871f93

Browse files
committed
major vim mode improvements
1 parent 31fb0dd commit 5871f93

File tree

4 files changed

+442
-188
lines changed

4 files changed

+442
-188
lines changed

docs/vim.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ When Vim mode is enabled, a status line appears at the bottom of the editor show
2929

3030
| Command | Description |
3131
|---------|-------------|
32+
| `Ctrl +` | Enter/Exit Vim Mode
3233
| `Escape` | Switch to Normal mode from any other mode |
3334
| `i` | Enter Insert mode at cursor position |
3435
| `a` | Enter Insert mode after cursor position |

src/components/NoteEditor.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,15 @@ export function NoteEditor({ note, isMobile = false, onBack, loadNotes }: NoteEd
162162
variant="unstyled"
163163
size="md"
164164
onBlur={saveNote}
165+
onKeyDown={(e) => {
166+
console.log('Mobile title keydown:', e.key);
167+
if (e.key === 'Enter') {
168+
e.preventDefault();
169+
console.log('Enter pressed in mobile title, saving note...');
170+
e.currentTarget.blur();
171+
saveNote();
172+
}
173+
}}
165174
style={{
166175
flex: 1
167176
}}
@@ -264,6 +273,15 @@ export function NoteEditor({ note, isMobile = false, onBack, loadNotes }: NoteEd
264273
variant="unstyled"
265274
size="xl"
266275
onBlur={saveNote}
276+
onKeyDown={(e) => {
277+
console.log('Desktop title keydown:', e.key);
278+
if (e.key === 'Enter') {
279+
e.preventDefault();
280+
console.log('Enter pressed in desktop title, saving note...');
281+
e.currentTarget.blur();
282+
saveNote();
283+
}
284+
}}
267285
className="modern-editor-title"
268286
style={{
269287
flex: 1,

src/components/RichTextEditor.tsx

Lines changed: 61 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -134,23 +134,48 @@ export function RichTextEditor({ content, onChange, isMobile }: RichTextEditorPr
134134
}
135135
}, [vimModeEnabled, editor]);
136136

137+
useEffect(() => {
138+
const handleKeyDown = (event: KeyboardEvent) => {
139+
if (event.ctrlKey && event.key === ';') {
140+
event.preventDefault();
141+
handleVimModeToggle();
142+
}
143+
};
144+
145+
document.addEventListener('keydown', handleKeyDown);
146+
147+
return () => {
148+
document.removeEventListener('keydown', handleKeyDown);
149+
};
150+
}, []);
151+
137152
if (!editor) {
138153
return null;
139154
}
140155

141-
const runCommand = (callback: (chain: ReturnType<NonNullable<typeof editor>['chain']>) => void) => {
142-
if (editor) {
143-
callback(editor.chain().focus());
144-
}
145-
};
146-
147156
const isTableSelected = () => {
148157
return editor.isActive('table');
149158
};
150159

151160
const handleVimModeToggle = () => {
152161
const isEnabled = toggleVimMode();
153162
setVimModeEnabled(isEnabled);
163+
164+
if (window.vimMode && window.vimMode.statusElement) {
165+
const statusMessage = isEnabled ? 'Vim mode enabled' : 'Vim mode disabled';
166+
window.vimMode.statusElement.textContent = statusMessage;
167+
window.vimMode.statusElement.style.display = 'block';
168+
169+
setTimeout(() => {
170+
if (window.vimMode && window.vimMode.statusElement) {
171+
if (isEnabled) {
172+
window.vimMode.statusElement.textContent = window.vimMode.mode.toUpperCase();
173+
} else {
174+
window.vimMode.statusElement.style.display = 'none';
175+
}
176+
}
177+
}, 1500);
178+
}
154179
};
155180

156181
const ToolbarButton = ({
@@ -171,6 +196,7 @@ export function RichTextEditor({ content, onChange, isMobile }: RichTextEditorPr
171196
variant={isActive ? 'filled' : 'subtle'}
172197
color={isActive ? 'blue' : 'gray'}
173198
onClick={onClick}
199+
onMouseDown={(e) => e.preventDefault()}
174200
disabled={disabled}
175201
size={isMobile ? 'sm' : 'md'}
176202
className="modern-toolbar-button"
@@ -254,13 +280,13 @@ export function RichTextEditor({ content, onChange, isMobile }: RichTextEditorPr
254280
icon={<IconBold size={isMobile ? 14 : 16} />}
255281
label="Bold"
256282
isActive={editor?.isActive('bold') ?? false}
257-
onClick={() => runCommand(chain => chain.toggleBold().run())}
283+
onClick={() => editor?.chain().focus().toggleBold().run()}
258284
/>
259285
<ToolbarButton
260286
icon={<IconItalic size={isMobile ? 14 : 16} />}
261287
label="Italic"
262288
isActive={editor?.isActive('italic') ?? false}
263-
onClick={() => runCommand(chain => chain.toggleItalic().run())}
289+
onClick={() => editor?.chain().focus().toggleItalic().run()}
264290
/>
265291
</ToolbarGroup>
266292

@@ -272,13 +298,13 @@ export function RichTextEditor({ content, onChange, isMobile }: RichTextEditorPr
272298
icon={<IconH1 size={isMobile ? 14 : 16} />}
273299
label="Heading 1"
274300
isActive={editor?.isActive('heading', { level: 1 }) ?? false}
275-
onClick={() => runCommand(chain => chain.toggleHeading({ level: 1 }).run())}
301+
onClick={() => editor?.chain().focus().toggleHeading({ level: 1 }).run()}
276302
/>
277303
<ToolbarButton
278304
icon={<IconH2 size={isMobile ? 14 : 16} />}
279305
label="Heading 2"
280306
isActive={editor?.isActive('heading', { level: 2 }) ?? false}
281-
onClick={() => runCommand(chain => chain.toggleHeading({ level: 2 }).run())}
307+
onClick={() => editor?.chain().focus().toggleHeading({ level: 2 }).run()}
282308
/>
283309
</ToolbarGroup>
284310

@@ -290,13 +316,13 @@ export function RichTextEditor({ content, onChange, isMobile }: RichTextEditorPr
290316
icon={<IconList size={isMobile ? 14 : 16} />}
291317
label="Bullet List"
292318
isActive={editor?.isActive('bulletList') ?? false}
293-
onClick={() => runCommand(chain => chain.toggleBulletList().run())}
319+
onClick={() => editor?.chain().focus().toggleBulletList().run()}
294320
/>
295321
<ToolbarButton
296322
icon={<IconListNumbers size={isMobile ? 14 : 16} />}
297323
label="Numbered List"
298324
isActive={editor?.isActive('orderedList') ?? false}
299-
onClick={() => runCommand(chain => chain.toggleOrderedList().run())}
325+
onClick={() => editor?.chain().focus().toggleOrderedList().run()}
300326
/>
301327
</ToolbarGroup>
302328

@@ -308,14 +334,14 @@ export function RichTextEditor({ content, onChange, isMobile }: RichTextEditorPr
308334
icon={<IconQuote size={isMobile ? 14 : 16} />}
309335
label="Quote"
310336
isActive={editor?.isActive('blockquote') ?? false}
311-
onClick={() => runCommand(chain => chain.toggleBlockquote().run())}
337+
onClick={() => editor?.chain().focus().toggleBlockquote().run()}
312338
/>
313339
{!isMobile && (
314340
<ToolbarButton
315341
icon={<IconCode size={16} />}
316342
label="Code Block"
317343
isActive={editor?.isActive('codeBlock') ?? false}
318-
onClick={() => runCommand(chain => chain.toggleCodeBlock().run())}
344+
onClick={() => editor?.chain().focus().toggleCodeBlock().run()}
319345
/>
320346
)}
321347
</ToolbarGroup>
@@ -328,7 +354,7 @@ export function RichTextEditor({ content, onChange, isMobile }: RichTextEditorPr
328354
<ToolbarButton
329355
icon={<IconTable size={16} />}
330356
label="Insert Table"
331-
onClick={() => runCommand(chain => chain.insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run())}
357+
onClick={() => editor?.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()}
332358
/>
333359
</ToolbarGroup>
334360
</>
@@ -342,17 +368,17 @@ export function RichTextEditor({ content, onChange, isMobile }: RichTextEditorPr
342368
<ToolbarButton
343369
icon={<IconRowInsertBottom size={isMobile ? 14 : 16} />}
344370
label="Add Row"
345-
onClick={() => runCommand(chain => chain.addRowAfter().run())}
371+
onClick={() => editor?.chain().focus().addRowAfter().run()}
346372
/>
347373
<ToolbarButton
348374
icon={<IconColumnInsertRight size={isMobile ? 14 : 16} />}
349375
label="Add Column"
350-
onClick={() => runCommand(chain => chain.addColumnAfter().run())}
376+
onClick={() => editor?.chain().focus().addColumnAfter().run()}
351377
/>
352378
<ToolbarButton
353379
icon={<IconTrash size={isMobile ? 14 : 16} />}
354380
label="Delete Table"
355-
onClick={() => runCommand(chain => chain.deleteTable().run())}
381+
onClick={() => editor?.chain().focus().deleteTable().run()}
356382
/>
357383
</ToolbarGroup>
358384
</>
@@ -367,19 +393,19 @@ export function RichTextEditor({ content, onChange, isMobile }: RichTextEditorPr
367393
icon={<IconAlignLeft size={16} />}
368394
label="Align Left"
369395
isActive={editor?.isActive({ textAlign: 'left' }) ?? false}
370-
onClick={() => runCommand(chain => chain.setTextAlign('left').run())}
396+
onClick={() => editor?.chain().focus().setTextAlign('left').run()}
371397
/>
372398
<ToolbarButton
373399
icon={<IconAlignCenter size={16} />}
374400
label="Align Center"
375401
isActive={editor?.isActive({ textAlign: 'center' }) ?? false}
376-
onClick={() => runCommand(chain => chain.setTextAlign('center').run())}
402+
onClick={() => editor?.chain().focus().setTextAlign('center').run()}
377403
/>
378404
<ToolbarButton
379405
icon={<IconAlignRight size={16} />}
380406
label="Align Right"
381407
isActive={editor?.isActive({ textAlign: 'right' }) ?? false}
382-
onClick={() => runCommand(chain => chain.setTextAlign('right').run())}
408+
onClick={() => editor?.chain().focus().setTextAlign('right').run()}
383409
/>
384410
</ToolbarGroup>
385411
</>
@@ -389,18 +415,20 @@ export function RichTextEditor({ content, onChange, isMobile }: RichTextEditorPr
389415
{!isMobile && (
390416
<>
391417
<ToolbarDivider />
392-
<Switch
393-
label="Vim"
394-
checked={vimModeEnabled}
395-
onChange={handleVimModeToggle}
396-
size="sm"
397-
color="blue"
398-
thumbIcon={vimModeEnabled ? <IconKeyboard size={12} /> : null}
399-
styles={{
400-
root: { alignItems: 'center' },
401-
label: { fontSize: '0.875rem', fontWeight: 500 }
402-
}}
403-
/>
418+
<Tooltip label="Vim Mode (Ctrl+;)" position="bottom">
419+
<Switch
420+
label="Vim"
421+
checked={vimModeEnabled}
422+
onChange={handleVimModeToggle}
423+
size="sm"
424+
color="blue"
425+
thumbIcon={vimModeEnabled ? <IconKeyboard size={12} /> : null}
426+
styles={{
427+
root: { alignItems: 'center' },
428+
label: { fontSize: '0.875rem', fontWeight: 500 }
429+
}}
430+
/>
431+
</Tooltip>
404432
</>
405433
)}
406434
</Group>

0 commit comments

Comments
 (0)