Skip to content

Commit 82e3ebb

Browse files
committed
fix: resolve build errors and implement VSCode-style status bar
- Fix JSX syntax error by wrapping return statement in React Fragment - Replace non-existent setImage() commands with insertContent() for image insertion - Simplify ReactNodeViewRenderer component props to resolve type conflicts - Remove unused addCommands method causing return type issues - Fix unused parameter warnings by prefixing with underscore - Add 'starred' option to SortOption type definition - Implement VSCode-style status bar with word/character counts and save status - Move word counts to left side of status bar for better UX
1 parent bd0527c commit 82e3ebb

File tree

7 files changed

+106
-49
lines changed

7 files changed

+106
-49
lines changed

.claude/settings.local.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(npm run build:*)"
5+
],
6+
"deny": [],
7+
"ask": []
8+
}
9+
}

src/components/editor/extensions/ImageUpload.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ export function ImageUpload({ editor }: ImageUploadProps) {
3535
editor
3636
.chain()
3737
.focus()
38-
.setImage({ src: result })
38+
.insertContent({
39+
type: 'image',
40+
attrs: { src: result }
41+
})
3942
.run();
4043
}
4144
};

src/components/editor/extensions/ResizableImage.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { Node, mergeAttributes } from '@tiptap/core';
2-
import { ReactNodeViewRenderer, NodeViewProps } from '@tiptap/react';
2+
import { ReactNodeViewRenderer } from '@tiptap/react';
33
import { NodeViewWrapper } from '@tiptap/react';
44
import { useState, useRef, useEffect } from 'react';
55
import { X, Maximize2 } from 'lucide-react';
66

7-
const ImageComponent = ({ node, deleteNode, updateAttributes }: NodeViewProps) => {
7+
8+
const ImageComponent = (props: any) => {
9+
const { node, deleteNode, updateAttributes } = props;
810
const [showControls, setShowControls] = useState(false);
911
const [isResizing, setIsResizing] = useState(false);
1012
const imageRef = useRef<HTMLImageElement>(null);
@@ -185,15 +187,4 @@ export const ResizableImage = Node.create({
185187
addNodeView() {
186188
return ReactNodeViewRenderer(ImageComponent);
187189
},
188-
189-
addCommands() {
190-
return {
191-
setImage: (options: { src: string; alt?: string; title?: string }) => ({ commands }) => {
192-
return commands.insertContent({
193-
type: this.name,
194-
attrs: options,
195-
});
196-
},
197-
};
198-
},
199190
});

src/components/editor/extensions/SlashCommands.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,10 @@ const commands: CommandItem[] = [
276276
editor
277277
.chain()
278278
.focus()
279-
.setImage({ src: result })
279+
.insertContent({
280+
type: 'image',
281+
attrs: { src: result }
282+
})
280283
.run();
281284
}
282285
};

src/components/editor/extensions/TableOfContents.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Node, mergeAttributes } from '@tiptap/core';
2-
import { ReactNodeViewRenderer, NodeViewProps } from '@tiptap/react';
2+
import { ReactNodeViewRenderer } from '@tiptap/react';
33
import { NodeViewWrapper } from '@tiptap/react';
4+
import type { Editor } from '@tiptap/react';
45
import { useEffect, useState, useCallback } from 'react';
56
import { ChevronRight, ChevronDown, FileText, X } from 'lucide-react';
67

@@ -10,7 +11,12 @@ interface Heading {
1011
id: string;
1112
}
1213

13-
const TableOfContentsComponent = ({ editor, deleteNode }: NodeViewProps) => {
14+
interface TableOfContentsComponentProps {
15+
editor: Editor;
16+
deleteNode: () => void;
17+
}
18+
19+
const TableOfContentsComponent = ({ editor, deleteNode }: TableOfContentsComponentProps) => {
1420
const [headings, setHeadings] = useState<Heading[]>([]);
1521
const [expanded, setExpanded] = useState(true);
1622

src/components/editor/index.tsx

Lines changed: 76 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ export default function Index({
5757
const [saveStatus, setSaveStatus] = useState<'saved' | 'saving' | 'error'>('saved');
5858
const saveTimeoutRef = useRef<NodeJS.Timeout | null>(null);
5959
const lastContentRef = useRef<string>('');
60+
const [wordCount, setWordCount] = useState(0);
61+
const [charCount, setCharCount] = useState(0);
6062

6163
const editor = useEditor(
6264
{
@@ -183,7 +185,7 @@ export default function Index({
183185

184186
return false;
185187
},
186-
handleDrop: (view, event, slice, moved) => {
188+
handleDrop: (view, event, _slice, moved) => {
187189
if (moved) return false;
188190

189191
const files = event.dataTransfer?.files;
@@ -227,6 +229,14 @@ export default function Index({
227229
},
228230
onUpdate: ({ editor }) => {
229231
if (!note) return;
232+
233+
// Update word and character counts
234+
const text = editor.state.doc.textContent;
235+
const words = text.trim() ? text.trim().split(/\s+/).length : 0;
236+
const chars = text.length;
237+
setWordCount(words);
238+
setCharCount(chars);
239+
230240
const html = editor.getHTML();
231241
if (html !== note.content && html !== lastContentRef.current) {
232242
lastContentRef.current = html;
@@ -262,6 +272,13 @@ export default function Index({
262272
editor.commands.setContent(note.content || '', false);
263273
lastContentRef.current = note.content || '';
264274

275+
// Update word and character counts
276+
const text = editor.state.doc.textContent;
277+
const words = text.trim() ? text.trim().split(/\s+/).length : 0;
278+
const chars = text.length;
279+
setWordCount(words);
280+
setCharCount(chars);
281+
265282
try {
266283
const docSize = editor.state.doc.content.size;
267284
if (from <= docSize && to <= docSize) {
@@ -446,7 +463,8 @@ export default function Index({
446463
const currentFolder = getCurrentFolder();
447464

448465
return (
449-
<div className="flex h-full flex-1 flex-col">
466+
<>
467+
<div className="flex h-full flex-1 flex-col">
450468
<div className="border-border flex-shrink-0 border-b p-4">
451469
<div className="mb-4 flex items-center justify-between">
452470
<div className="flex flex-1 items-center gap-3">
@@ -460,23 +478,6 @@ export default function Index({
460478
</div>
461479

462480
<div className="flex items-center gap-2">
463-
{/* Save status indicator */}
464-
<div className="flex items-center mr-2" title={
465-
saveStatus === 'saving' ? 'Saving' :
466-
saveStatus === 'saved' ? 'Saved' :
467-
'Error saving'
468-
}>
469-
{saveStatus === 'saving' && (
470-
<div className="h-3 w-3 rounded-full bg-orange-500 animate-pulse" />
471-
)}
472-
{saveStatus === 'saved' && (
473-
<div className="h-3 w-3 rounded-full bg-green-500" />
474-
)}
475-
{saveStatus === 'error' && (
476-
<div className="h-3 w-3 rounded-full bg-red-500" />
477-
)}
478-
</div>
479-
480481
<div className="relative">
481482
<Button
482483
variant="ghost"
@@ -610,18 +611,62 @@ export default function Index({
610611
/>
611612
</div>
612613

613-
<style dangerouslySetInnerHTML={{ __html: editorStyles }} />
614+
{/* Status Bar - VSCode Style */}
615+
<div className="border-t bg-primary/5 dark:bg-primary/10 px-3 py-0.5 flex items-center justify-between text-[11px] font-mono">
616+
{/* Left side - Word and character count */}
617+
<div className="flex items-center gap-3">
618+
{(wordCount > 0 || charCount > 0) && (
619+
<>
620+
<span className="hover:bg-muted px-1.5 py-0.5 rounded cursor-default">
621+
{wordCount} {wordCount === 1 ? 'word' : 'words'}
622+
</span>
623+
<span className="hover:bg-muted px-1.5 py-0.5 rounded cursor-default">
624+
{charCount} {charCount === 1 ? 'character' : 'characters'}
625+
</span>
626+
</>
627+
)}
628+
</div>
629+
630+
{/* Right side - Save status */}
631+
<div className="flex items-center gap-1 px-1.5 py-0.5 hover:bg-muted rounded cursor-default" title={
632+
saveStatus === 'saving' ? 'Saving changes...' :
633+
saveStatus === 'saved' ? 'All changes saved' :
634+
'Error saving changes'
635+
}>
636+
{saveStatus === 'saving' && (
637+
<>
638+
<div className="h-1.5 w-1.5 rounded-full bg-orange-500 animate-pulse" />
639+
<span>Saving</span>
640+
</>
641+
)}
642+
{saveStatus === 'saved' && (
643+
<>
644+
<div className="h-1.5 w-1.5 rounded-full bg-green-500" />
645+
<span>Saved</span>
646+
</>
647+
)}
648+
{saveStatus === 'error' && (
649+
<>
650+
<div className="h-1.5 w-1.5 rounded-full bg-red-500" />
651+
<span className="text-red-500">Error</span>
652+
</>
653+
)}
654+
</div>
655+
</div>
614656

615-
{note && (
616-
<MoveNoteModal
617-
isOpen={isMoveModalOpen}
618-
onClose={() => setIsMoveModalOpen(false)}
619-
onMove={handleMoveNote}
620-
folders={folders || []}
621-
currentFolderId={note.folderId}
622-
noteTitle={note.title}
623-
/>
624-
)}
625-
</div>
657+
<style dangerouslySetInnerHTML={{ __html: editorStyles }} />
658+
659+
{note && (
660+
<MoveNoteModal
661+
isOpen={isMoveModalOpen}
662+
onClose={() => setIsMoveModalOpen(false)}
663+
onMove={handleMoveNote}
664+
folders={folders || []}
665+
currentFolderId={note.folderId}
666+
noteTitle={note.title}
667+
/>
668+
)}
669+
</div>
670+
</>
626671
);
627672
}

src/components/notes/NotesPanel/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from '@/components/ui/dropdown-menu';
1212
import type { Note, Folder as FolderType, ViewMode } from '@/types/note.ts';
1313

14-
type SortOption = 'updated' | 'created' | 'title';
14+
type SortOption = 'updated' | 'created' | 'title' | 'starred';
1515

1616
interface SortConfig {
1717
option: SortOption;

0 commit comments

Comments
 (0)