Skip to content

Commit 46289c8

Browse files
authored
Merge pull request #5 from typelets/feature/hidden-notes-v2
feat: implement hidden notes filtering and improve type safety
2 parents 2338198 + 920bb11 commit 46289c8

13 files changed

Lines changed: 241 additions & 167 deletions

File tree

src/components/editor/extensions/SlashCommands.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
Image,
2727
} from 'lucide-react';
2828

29-
interface CommandItem {
29+
export interface CommandItem {
3030
title: string;
3131
command: ({ editor, range }: { editor: Editor; range: Range }) => void;
3232
icon: React.ReactNode;

src/components/editor/extensions/TableOfContents.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,8 @@ const TableOfContentsComponent = ({ editor, deleteNode }: TableOfContentsCompone
5555
editor.state.doc.descendants((node: any, pos: number) => { // eslint-disable-line @typescript-eslint/no-explicit-any
5656
if (node.type.name === 'heading') {
5757
if (currentIndex === index) {
58-
// Focus the editor and set cursor position
5958
editor.commands.focus();
6059
editor.commands.setTextSelection(pos + 1);
61-
62-
// Scroll to the heading
6360
setTimeout(() => {
6461
const element = editor.view.domAtPos(pos + 1);
6562
if (element && element.node) {

src/components/editor/index.tsx

Lines changed: 84 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
FolderInput,
88
Paperclip,
99
Printer,
10+
EyeOff,
11+
Eye,
1012
} from 'lucide-react';
1113
import { Button } from '@/components/ui/button';
1214
import {
@@ -16,8 +18,9 @@ import {
1618
DropdownMenuSeparator,
1719
DropdownMenuTrigger,
1820
} from '@/components/ui/dropdown-menu';
19-
import { useEditor, EditorContent } from '@tiptap/react';
21+
import { useEditor, EditorContent, Editor } from '@tiptap/react';
2022
import ReactDOM from 'react-dom/client';
23+
import type { Root } from 'react-dom/client';
2124
import tippy from 'tippy.js';
2225
import { createEditorExtensions } from './config/editor-config';
2326
import { SlashCommands } from './extensions/SlashCommandsExtension';
@@ -33,13 +36,38 @@ import { useEditorEffects } from '@/components/editor/hooks/useEditorEffects';
3336
import { fileService } from '@/services/fileService';
3437
import type { Note, Folder as FolderType, FileAttachment } from '@/types/note';
3538

39+
// Type definitions for TipTap Suggestion
40+
// Import CommandItem type from SlashCommands
41+
import type { CommandItem } from './extensions/SlashCommands';
42+
43+
interface SuggestionProps {
44+
editor: Editor;
45+
range: { from: number; to: number };
46+
query: string;
47+
text: string;
48+
command: (item: CommandItem) => void;
49+
clientRect?: () => DOMRect | null;
50+
decorationNode?: Element | null;
51+
virtualNode?: Element | null;
52+
}
53+
54+
interface SuggestionKeyDownProps {
55+
event: KeyboardEvent;
56+
}
57+
58+
interface SlashCommandsHandle {
59+
onKeyDown: (props: SuggestionKeyDownProps) => boolean;
60+
}
61+
3662
interface NoteEditorProps {
3763
note: Note | null;
3864
folders?: FolderType[];
3965
onUpdateNote: (noteId: string, updates: Partial<Note>) => void;
4066
onDeleteNote: (noteId: string) => void;
4167
onArchiveNote: (noteId: string) => void;
4268
onToggleStar: (noteId: string) => void;
69+
onHideNote: (noteId: string) => void;
70+
onUnhideNote: (noteId: string) => void;
4371
userId?: string;
4472
}
4573

@@ -50,6 +78,8 @@ export default function Index({
5078
onDeleteNote,
5179
onArchiveNote,
5280
onToggleStar,
81+
onHideNote,
82+
onUnhideNote,
5383
userId = 'current-user',
5484
}: NoteEditorProps) {
5585
const [isMoveModalOpen, setIsMoveModalOpen] = useState(false);
@@ -80,6 +110,7 @@ export default function Index({
80110

81111
const editor = useEditor(
82112
{
113+
editable: !note?.hidden,
83114
extensions: [
84115
...createEditorExtensions(),
85116
SlashCommands.configure({
@@ -88,23 +119,20 @@ export default function Index({
88119
return [];
89120
},
90121
render: () => {
91-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
92-
let component: any;
93-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
94-
let popup: any;
95-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
96-
let root: any;
122+
let component: SlashCommandsHandle | null = null;
123+
let popup: ReturnType<typeof tippy>[0] | null = null;
124+
let root: Root | null = null;
97125

98126
return {
99-
onStart: (props: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
127+
onStart: (props: SuggestionProps) => {
100128
if (!props.clientRect) {
101129
return;
102130
}
103131

104132
const container = document.createElement('div');
105133

106134
popup = tippy('body', {
107-
getReferenceClientRect: props.clientRect,
135+
getReferenceClientRect: props.clientRect as () => DOMRect,
108136
appendTo: () => document.body,
109137
content: container,
110138
showOnCreate: true,
@@ -115,35 +143,23 @@ export default function Index({
115143

116144
root = ReactDOM.createRoot(container);
117145

118-
const commandsList = document.createElement('div');
119-
component = ReactDOM.createRoot(commandsList);
120-
121-
component.render(
122-
<SlashCommandsList
123-
ref={(ref: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
124-
component = ref;
125-
}}
126-
command={props.command}
127-
/>
128-
);
129-
130146
root.render(
131147
<SlashCommandsList
132-
ref={(ref: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
148+
ref={(ref: SlashCommandsHandle | null) => {
133149
component = ref;
134150
}}
135151
command={props.command}
136152
/>
137153
);
138154
},
139155

140-
onUpdate: (props: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
156+
onUpdate: (props: SuggestionProps) => {
141157
popup?.setProps({
142-
getReferenceClientRect: props.clientRect,
158+
getReferenceClientRect: props.clientRect as () => DOMRect,
143159
});
144160
},
145161

146-
onKeyDown: (props: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
162+
onKeyDown: (props: SuggestionKeyDownProps) => {
147163
if (props.event.key === 'Escape') {
148164
popup?.hide();
149165
return true;
@@ -161,7 +177,7 @@ export default function Index({
161177
},
162178
}),
163179
],
164-
content: note?.content || '',
180+
content: note?.hidden ? '[HIDDEN]' : (note?.content || ''),
165181
editorProps: {
166182
attributes: {
167183
class:
@@ -246,7 +262,7 @@ export default function Index({
246262
},
247263
},
248264
onUpdate: ({ editor }) => {
249-
if (!note) return;
265+
if (!note || note.hidden) return; // Prevent auto-save for hidden notes
250266

251267
// Update word and character counts
252268
const text = editor.state.doc.textContent;
@@ -275,7 +291,7 @@ export default function Index({
275291
}
276292
},
277293
},
278-
[note?.id, updateCounts]
294+
[note?.id, note?.hidden, updateCounts]
279295
);
280296

281297
// Use custom hook for editor effects
@@ -292,6 +308,22 @@ export default function Index({
292308

293309

294310

311+
// Update editor when note hidden state changes
312+
useEffect(() => {
313+
if (editor && note) {
314+
const currentContent = editor.getHTML();
315+
316+
// Only update if content actually changed
317+
if (note.hidden && currentContent !== '[HIDDEN]') {
318+
editor.commands.setContent('[HIDDEN]');
319+
editor.setEditable(false);
320+
} else if (!note.hidden && currentContent === '[HIDDEN]') {
321+
editor.commands.setContent(note.content || '');
322+
editor.setEditable(true);
323+
}
324+
}
325+
}, [editor, note]);
326+
295327
// Cleanup timeout on unmount
296328
useEffect(() => {
297329
return () => {
@@ -476,6 +508,7 @@ export default function Index({
476508
onChange={handleTitleChange}
477509
className="text-foreground placeholder-muted-foreground min-w-0 flex-1 border-none bg-transparent text-2xl font-bold outline-none"
478510
placeholder="Untitled Note"
511+
disabled={note.hidden}
479512
/>
480513
</div>
481514

@@ -508,12 +541,29 @@ export default function Index({
508541
className={
509542
note.starred ? 'text-yellow-500' : 'text-muted-foreground'
510543
}
544+
title={note.starred ? 'Remove from starred' : 'Add to starred'}
511545
>
512546
<Star
513547
className={`h-4 w-4 ${note.starred ? 'fill-current' : ''}`}
514548
/>
515549
</Button>
516550

551+
<Button
552+
variant="ghost"
553+
size="sm"
554+
onClick={() => note.hidden ? onUnhideNote(note.id) : onHideNote(note.id)}
555+
className={
556+
note.hidden ? 'text-primary' : 'text-muted-foreground'
557+
}
558+
title={note.hidden ? 'Unhide note' : 'Hide note'}
559+
>
560+
{note.hidden ? (
561+
<EyeOff className="h-4 w-4" />
562+
) : (
563+
<Eye className="h-4 w-4" />
564+
)}
565+
</Button>
566+
517567
<DropdownMenu>
518568
<DropdownMenuTrigger asChild>
519569
<Button variant="ghost" size="sm">
@@ -523,7 +573,7 @@ export default function Index({
523573
<DropdownMenuContent align="end">
524574
<DropdownMenuItem onClick={() => onToggleStar(note.id)}>
525575
<Star className="mr-2 h-4 w-4" />
526-
{note.starred ? 'Remove from Starred' : 'Star'}
576+
{note.starred ? 'Unstar' : 'Star'}
527577
</DropdownMenuItem>
528578
<DropdownMenuItem onClick={() => setIsMoveModalOpen(true)}>
529579
<FolderInput className="mr-2 h-4 w-4" />
@@ -585,9 +635,11 @@ export default function Index({
585635
</div>
586636
</div>
587637

588-
<div className="flex-shrink-0">
589-
<Toolbar editor={editor} />
590-
</div>
638+
{!note.hidden && (
639+
<div className="flex-shrink-0">
640+
<Toolbar editor={editor} />
641+
</div>
642+
)}
591643

592644
{showAttachments && (
593645
<>

src/components/folders/QuickAccessSection.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface QuickAccessSectionProps {
1111
starredCount: number;
1212
archivedCount: number;
1313
trashedCount: number;
14+
hiddenCount: number;
1415
onViewChange: (view: ViewMode) => void;
1516
onFolderSelect: (folder: Folder | null) => void;
1617
}
@@ -22,6 +23,7 @@ export default function QuickAccessSection({
2223
starredCount,
2324
archivedCount,
2425
trashedCount,
26+
hiddenCount,
2527
onViewChange,
2628
onFolderSelect,
2729
}: QuickAccessSectionProps) {
@@ -31,8 +33,9 @@ export default function QuickAccessSection({
3133
starred: starredCount,
3234
archived: archivedCount,
3335
trash: trashedCount,
36+
hidden: hiddenCount,
3437
}),
35-
[notesCount, starredCount, archivedCount, trashedCount]
38+
[notesCount, starredCount, archivedCount, trashedCount, hiddenCount]
3639
);
3740

3841
const handleViewSelect = (view: ViewMode) => {

src/components/folders/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ interface FolderPanelProps {
3131
starredCount: number;
3232
archivedCount: number;
3333
trashedCount: number;
34+
hiddenCount: number;
3435
expandedFolders: Set<string>;
3536
onCreateNote: (templateContent?: { title: string; content: string }) => void;
3637
onCreateFolder: (name: string, color: string, parentId?: string) => void;
@@ -62,6 +63,7 @@ export default function FolderPanel({
6263
starredCount,
6364
archivedCount,
6465
trashedCount,
66+
hiddenCount,
6567
expandedFolders,
6668
onCreateFolder,
6769
onUpdateFolder,
@@ -235,6 +237,7 @@ export default function FolderPanel({
235237
starredCount={starredCount}
236238
archivedCount={archivedCount}
237239
trashedCount={trashedCount}
240+
hiddenCount={hiddenCount}
238241
onViewChange={onViewChange}
239242
onFolderSelect={onFolderSelect}
240243
/>

src/components/layout/MainLayout.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export default function MainLayout() {
2727
starredCount,
2828
archivedCount,
2929
trashedCount,
30+
hiddenCount,
3031
expandedFolders,
3132
updateFolder,
3233
createNote,
@@ -36,6 +37,8 @@ export default function MainLayout() {
3637
deleteNote,
3738
toggleStar,
3839
archiveNote,
40+
hideNote,
41+
unhideNote,
3942
toggleFolderExpansion,
4043
reorderFolders,
4144
setSelectedNote,
@@ -98,7 +101,7 @@ export default function MainLayout() {
98101
}, [handleUnlockSuccess, reinitialize]);
99102

100103
const folderPanelProps: FolderPanelProps = {
101-
currentView, folders, selectedFolder, searchQuery, notesCount, starredCount, archivedCount, trashedCount, expandedFolders,
104+
currentView, folders, selectedFolder, searchQuery, notesCount, starredCount, archivedCount, trashedCount, hiddenCount, expandedFolders,
102105
onUpdateFolder: (id, name, color) => updateFolder(id, { name, color }),
103106
onUpdateFolderParent: (id, parentId) => updateFolder(id, { parentId }),
104107
onCreateNote: handleCreateNote,
@@ -128,6 +131,8 @@ export default function MainLayout() {
128131
onDeleteNote: deleteNote,
129132
onArchiveNote: archiveNote,
130133
onToggleStar: toggleStar,
134+
onHideNote: hideNote,
135+
onUnhideNote: unhideNote,
131136
userId,
132137
};
133138

src/components/notes/NotesPanel/NoteCard.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,13 @@ function NoteCard({
3434
}
3535
};
3636

37+
const formattedDate = useMemo(() => {
38+
return note?.createdAt ? formatDateTime(note.createdAt) : 'No date';
39+
}, [note?.createdAt]);
40+
3741
const previewText = useMemo(() => {
42+
if (note?.hidden) return '[HIDDEN]';
43+
3844
if (!note?.content) return 'No additional text';
3945

4046
const temp = document.createElement('div');
@@ -50,11 +56,7 @@ function NoteCard({
5056
}
5157

5258
return text || 'No additional text';
53-
}, [note?.content]);
54-
55-
const formattedDate = useMemo(() => {
56-
return note?.updatedAt ? formatDateTime(note.updatedAt) : 'No date';
57-
}, [note?.updatedAt]);
59+
}, [note?.content, note?.hidden]);
5860

5961
const folder = useMemo(() => {
6062
// First check if note has embedded folder data

0 commit comments

Comments
 (0)