Skip to content

Commit 1458a65

Browse files
authored
Merge pull request #22 from typelets/fix/ui-improvements
fix(notes): improve NEW badge UX and add star button loading state
2 parents f9a3443 + 2adb5b0 commit 1458a65

11 files changed

Lines changed: 205 additions & 79 deletions

File tree

src/components/editor/hooks/useEditorEffects.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export function useEditorEffects({
6363

6464
// Initialize word count when editor is ready
6565
useEffect(() => {
66-
if (!editor || !editor.view) return;
66+
if (!editor || !editor.view || !editor.view.dom) return;
6767

6868
// Calculate initial word count
6969
const text = editor.state.doc.textContent;
@@ -72,11 +72,11 @@ export function useEditorEffects({
7272

7373
// Track scroll percentage
7474
useEffect(() => {
75-
if (!editor || !editor.view) return;
75+
if (!editor || !editor.view || !editor.view.dom) return;
7676

7777
const updateScrollPercentage = () => {
7878
const editorView = editor.view;
79-
if (!editorView.dom) return;
79+
if (!editorView || !editorView.dom) return;
8080

8181
const { scrollTop, scrollHeight, clientHeight } = editorView.dom;
8282
const maxScroll = scrollHeight - clientHeight;
@@ -102,7 +102,7 @@ export function useEditorEffects({
102102

103103
// Store the original font size when editor is first created
104104
useEffect(() => {
105-
if (!editor || !editor.view || baseFontSize) return;
105+
if (!editor || !editor.view || !editor.view.dom || baseFontSize) return;
106106

107107
const editorElement = editor.view.dom as HTMLElement;
108108
const computedStyle = window.getComputedStyle(editorElement);
@@ -112,7 +112,7 @@ export function useEditorEffects({
112112

113113
// Apply zoom level to editor
114114
useEffect(() => {
115-
if (!editor || !editor.view || !baseFontSize) return;
115+
if (!editor || !editor.view || !editor.view.dom || !baseFontSize) return;
116116

117117
const editorElement = editor.view.dom as HTMLElement;
118118

src/components/editor/index.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ interface NoteEditorProps {
7070
onDeleteNote: (noteId: string) => void;
7171
onArchiveNote: (noteId: string) => void;
7272
onToggleStar: (noteId: string) => void;
73+
starringStar?: boolean;
7374
onHideNote: (noteId: string) => void;
75+
hidingNote?: boolean;
7476
onUnhideNote: (noteId: string) => void;
7577
onRefreshNote?: (noteId: string) => void;
7678
userId?: string;
@@ -92,7 +94,9 @@ export default function Index({
9294
onDeleteNote,
9395
onArchiveNote,
9496
onToggleStar,
97+
starringStar = false,
9598
onHideNote,
99+
hidingNote = false,
96100
onUnhideNote,
97101
onRefreshNote,
98102
userId = 'current-user',
@@ -671,10 +675,15 @@ export default function Index({
671675
note.starred ? 'text-yellow-500' : 'text-muted-foreground'
672676
}
673677
title={note.starred ? 'Remove from starred' : 'Add to starred'}
678+
disabled={starringStar}
674679
>
675-
<Star
676-
className={`h-4 w-4 ${note.starred ? 'fill-current' : ''}`}
677-
/>
680+
{starringStar ? (
681+
<div className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
682+
) : (
683+
<Star
684+
className={`h-4 w-4 ${note.starred ? 'fill-current' : ''}`}
685+
/>
686+
)}
678687
</Button>
679688

680689
<Button
@@ -687,8 +696,11 @@ export default function Index({
687696
note.hidden ? 'text-primary' : 'text-muted-foreground'
688697
}
689698
title={note.hidden ? 'Unhide note' : 'Hide note'}
699+
disabled={hidingNote}
690700
>
691-
{note.hidden ? (
701+
{hidingNote ? (
702+
<div className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
703+
) : note.hidden ? (
692704
<EyeOff className="h-4 w-4" />
693705
) : (
694706
<Eye className="h-4 w-4" />

src/components/layout/MainLayout.tsx

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,24 @@ export default function MainLayout() {
5151
expandedFolders,
5252
updateFolder,
5353
createNote,
54+
creatingNote,
5455
createFolder,
5556
deleteFolder,
5657
updateNote,
5758
deleteNote,
5859
toggleStar,
60+
starringStar,
5961
archiveNote,
6062
hideNote,
63+
hidingNote,
6164
unhideNote,
6265
toggleFolderExpansion,
6366
reorderFolders,
6467
setSelectedNote,
6568
setSelectedFolder,
6669
setCurrentView,
6770
setSearchQuery,
71+
setNotes,
6872
refetch,
6973
reinitialize,
7074
webSocket,
@@ -137,17 +141,43 @@ export default function MainLayout() {
137141
const handleRefreshNote = useCallback(async (noteId: string) => {
138142
try {
139143
const apiNote = await api.getNote(noteId);
140-
const note: Note = {
144+
const refreshedNote: Note = {
141145
...apiNote,
142146
createdAt: new Date(apiNote.createdAt),
143147
updatedAt: new Date(apiNote.updatedAt),
144148
hiddenAt: apiNote.hiddenAt ? new Date(apiNote.hiddenAt) : null,
145149
};
146-
setSelectedNote(note);
150+
151+
// Update the note in the notes list
152+
setNotes((prev) =>
153+
prev.map((note) => {
154+
if (note.id === noteId) {
155+
// Preserve attachments and folder from local state
156+
return {
157+
...refreshedNote,
158+
attachments: note.attachments,
159+
folder: note.folder,
160+
};
161+
}
162+
return note;
163+
})
164+
);
165+
166+
// Update selected note
167+
setSelectedNote((prev) => {
168+
if (prev?.id === noteId) {
169+
return {
170+
...refreshedNote,
171+
attachments: prev.attachments,
172+
folder: prev.folder,
173+
};
174+
}
175+
return prev;
176+
});
147177
} catch (error) {
148178
console.error('Failed to refresh note:', error);
149179
}
150-
}, [setSelectedNote]);
180+
}, [setSelectedNote, setNotes]);
151181

152182
const handleMasterPasswordUnlock = useCallback(() => {
153183
handleUnlockSuccess();
@@ -194,6 +224,7 @@ export default function MainLayout() {
194224
onCreateNote: handleCreateNote,
195225
onToggleFolderPanel: handleToggleFolderPanel,
196226
onEmptyTrash: handleEmptyTrash,
227+
creatingNote,
197228
};
198229

199230
const editorProps: EditorProps = {
@@ -203,7 +234,9 @@ export default function MainLayout() {
203234
onDeleteNote: deleteNote,
204235
onArchiveNote: archiveNote,
205236
onToggleStar: toggleStar,
237+
starringStar,
206238
onHideNote: hideNote,
239+
hidingNote,
207240
onUnhideNote: unhideNote,
208241
onRefreshNote: handleRefreshNote,
209242
userId,

src/components/notes/NotesPanel/NoteCard.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ function NoteCard({
124124
{note.title || 'Untitled'}
125125
</h3>
126126
<div className="flex items-center gap-1">
127+
{note.isNew && (
128+
<span className="mr-1 rounded-full bg-orange-500 px-1 py-0 text-[9px] font-semibold text-white">
129+
NEW
130+
</span>
131+
)}
127132
{hasExecutableCode && (
128133
<div className="flex items-center text-xs" title="Contains executable code">
129134
<Codesandbox className="h-3.5 w-3.5 text-purple-500" />

src/components/notes/NotesPanel/index.tsx

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ interface FilesPanelProps {
4545
onCreateNote: (templateContent?: { title: string; content: string }) => void;
4646
onToggleFolderPanel: () => void;
4747
onEmptyTrash?: () => Promise<void>;
48+
creatingNote?: boolean;
4849
isMobile?: boolean;
4950
onClose?: () => void;
5051
}
@@ -62,6 +63,7 @@ export default function FilesPanel({
6263
onCreateNote,
6364
onToggleFolderPanel,
6465
onEmptyTrash,
66+
creatingNote = false,
6567
isMobile = false,
6668
onClose,
6769
}: FilesPanelProps) {
@@ -110,19 +112,11 @@ export default function FilesPanel({
110112

111113
const filterNotes = (notes: Note[], config: FilterConfig): Note[] => {
112114
return notes.filter((note) => {
113-
if (
114-
config.showAttachmentsOnly &&
115-
(!note.attachments || note.attachments.length === 0)
116-
) {
117-
return false;
118-
}
119-
if (config.showStarredOnly && !note.starred) {
120-
return false;
121-
}
122-
if (config.showHiddenOnly && !note.hidden) {
123-
return false;
124-
}
125-
return true;
115+
return !(
116+
(config.showAttachmentsOnly && (!note.attachments || note.attachments.length === 0)) ||
117+
(config.showStarredOnly && !note.starred) ||
118+
(config.showHiddenOnly && !note.hidden)
119+
);
126120
});
127121
};
128122

@@ -366,9 +360,16 @@ export default function FilesPanel({
366360
size="sm"
367361
className={`flex items-center justify-center gap-1 ${isMobile ? 'h-9 touch-manipulation px-3' : 'h-6 px-2'}`}
368362
title="Create new note from template"
363+
disabled={creatingNote}
369364
>
370-
<Plus className={isMobile ? 'h-4 w-4' : 'h-3 w-3'} />
371-
{!isMobile && <ChevronDown className="h-2 w-2" />}
365+
{creatingNote ? (
366+
<div className={`${isMobile ? 'h-4 w-4' : 'h-3 w-3'} animate-spin rounded-full border-2 border-current border-t-transparent`} />
367+
) : (
368+
<>
369+
<Plus className={isMobile ? 'h-4 w-4' : 'h-3 w-3'} />
370+
{!isMobile && <ChevronDown className="h-2 w-2" />}
371+
</>
372+
)}
372373
</Button>
373374
</DropdownMenuTrigger>
374375
<DropdownMenuContent align="end" className="w-64" sideOffset={8}>

0 commit comments

Comments
 (0)