Skip to content

Commit 163100e

Browse files
committed
feat: add document templates for quick note creation
- Add 5 pre-built note templates (Meeting Notes, Project Plan, Daily Notes, Research Notes, Blank) - Implement template dropdown in note creation button - Update note creation flow to accept template content
1 parent 57c330e commit 163100e

7 files changed

Lines changed: 117 additions & 23 deletions

File tree

src/components/editor/Editor/Toolbar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export function Toolbar({ editor }: ToolbarProps) {
8383
};
8484

8585
return (
86-
<div className="border-border bg-muted flex flex-wrap items-center gap-1 border-b p-2">
86+
<div className="border-border bg-muted dark:bg-gray-800 flex flex-wrap items-center gap-1 border-b p-2">
8787
<Button
8888
variant="ghost"
8989
size="sm"
@@ -297,6 +297,7 @@ export function Toolbar({ editor }: ToolbarProps) {
297297
>
298298
<Minus className="h-4 w-4" />
299299
</Button>
300+
300301
</div>
301302
);
302303
}

src/components/folders/index.tsx

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ interface FolderPanelProps {
3232
archivedCount: number;
3333
trashedCount: number;
3434
expandedFolders: Set<string>;
35-
onCreateNote: () => void;
35+
onCreateNote: (templateContent?: { title: string; content: string }) => void;
3636
onCreateFolder: (name: string, color: string, parentId?: string) => void;
3737
onUpdateFolder: (
3838
folderId: string,
@@ -338,7 +338,7 @@ export default function FolderPanel({
338338
}
339339
onClick={() => window.open('https://github.com/typelets/typelets-app/issues', '_blank')}
340340
/>
341-
{hasPassword && (
341+
{hasPassword ? (
342342
<UserButton.Action
343343
label="Change Master Password"
344344
labelIcon={
@@ -366,6 +366,34 @@ export default function FolderPanel({
366366
}
367367
onClick={() => setShowChangePassword(true)}
368368
/>
369+
) : (
370+
<UserButton.Action
371+
label="Setup Master Password"
372+
labelIcon={
373+
<svg
374+
xmlns="http://www.w3.org/2000/svg"
375+
width="16"
376+
height="16"
377+
viewBox="0 0 24 24"
378+
fill="none"
379+
stroke="currentColor"
380+
strokeWidth="2"
381+
strokeLinecap="round"
382+
strokeLinejoin="round"
383+
>
384+
<rect
385+
x="3"
386+
y="11"
387+
width="18"
388+
height="11"
389+
rx="2"
390+
ry="2"
391+
></rect>
392+
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
393+
</svg>
394+
}
395+
onClick={() => setShowChangePassword(true)}
396+
/>
369397
)}
370398
</UserButton.MenuItems>
371399
</UserButton>

src/components/layout/MainLayout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ export default function MainLayout() {
4545
refetch,
4646
} = useNotes();
4747

48-
const handleCreateNote = useCallback(() => {
49-
void createNote();
48+
const handleCreateNote = useCallback((templateContent?: { title: string; content: string }) => {
49+
void createNote(undefined, templateContent);
5050
if (!filesPanelOpen) setFilesPanelOpen(true);
5151
}, [createNote, filesPanelOpen]);
5252

src/components/notes/NotesPanel/index.tsx

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState } from 'react';
2-
import { Plus, PanelLeftClose, PanelLeftOpen, Filter, FilterX } from 'lucide-react';
2+
import { Plus, PanelLeftClose, PanelLeftOpen, Filter, FilterX, ChevronDown } from 'lucide-react';
33
import NotesList from '@/components/notes/NotesPanel/NotesList.tsx';
44
import { Button } from '@/components/ui/button.tsx';
55
import {
@@ -9,6 +9,7 @@ import {
99
DropdownMenuSeparator,
1010
DropdownMenuTrigger,
1111
} from '@/components/ui/dropdown-menu';
12+
import { NOTE_TEMPLATES } from '@/constants/templates';
1213
import type { Note, Folder as FolderType, ViewMode } from '@/types/note.ts';
1314

1415
type SortOption = 'updated' | 'created' | 'title' | 'starred';
@@ -33,7 +34,7 @@ interface FilesPanelProps {
3334
isFolderPanelOpen: boolean;
3435
onSelectNote: (note: Note) => void;
3536
onToggleStar: (noteId: string) => void;
36-
onCreateNote: () => void;
37+
onCreateNote: (templateContent?: { title: string; content: string }) => void;
3738
onToggleFolderPanel: () => void;
3839
onEmptyTrash?: () => Promise<void>;
3940
isMobile?: boolean;
@@ -207,7 +208,7 @@ export default function FilesPanel({
207208
style={{ backgroundColor: selectedFolder.color ?? '#6b7280' }}
208209
/>
209210
)}
210-
<span className="text-xs opacity-80">
211+
<span className="text-[11px] opacity-80">
211212
{sortedNotes.length} note{sortedNotes.length !== 1 ? 's' : ''}
212213
{sortedNotes.length !== notes.length && ` (${notes.length} total)`}
213214
</span>
@@ -231,7 +232,7 @@ export default function FilesPanel({
231232
)}
232233
</Button>
233234
</DropdownMenuTrigger>
234-
<DropdownMenuContent align="end" className="w-52">
235+
<DropdownMenuContent align="end" className="w-52" sideOffset={8}>
235236
<div className="px-2 py-1.5 text-xs font-medium text-muted-foreground">
236237
FILTER
237238
</div>
@@ -290,15 +291,34 @@ export default function FilesPanel({
290291
</DropdownMenu>
291292

292293
{!isTrashView && (
293-
<Button
294-
variant="outline"
295-
size="sm"
296-
onClick={onCreateNote}
297-
className={`flex items-center justify-center ${isMobile ? 'h-9 w-9 touch-manipulation p-0' : 'h-6 w-6 p-0'} `}
298-
title="Create new note"
299-
>
300-
<Plus className={isMobile ? 'h-4 w-4' : 'h-3 w-3'} />
301-
</Button>
294+
<DropdownMenu>
295+
<DropdownMenuTrigger asChild>
296+
<Button
297+
variant="outline"
298+
size="sm"
299+
className={`flex items-center justify-center gap-1 ${isMobile ? 'h-9 px-3 touch-manipulation' : 'h-6 px-2'}`}
300+
title="Create new note from template"
301+
>
302+
<Plus className={isMobile ? 'h-4 w-4' : 'h-3 w-3'} />
303+
{!isMobile && <ChevronDown className="h-2 w-2" />}
304+
</Button>
305+
</DropdownMenuTrigger>
306+
<DropdownMenuContent align="end" className="w-64" sideOffset={8}>
307+
<div className="px-2 py-1.5 text-xs font-medium text-muted-foreground">
308+
CREATE FROM TEMPLATE
309+
</div>
310+
{NOTE_TEMPLATES.map((template, index) => (
311+
<DropdownMenuItem
312+
key={template.id}
313+
onClick={() => onCreateNote({ title: template.title, content: template.content })}
314+
className="flex flex-col items-start gap-1 py-2"
315+
>
316+
<div className="font-medium">{template.name}</div>
317+
<div className="text-xs text-muted-foreground">{template.description}</div>
318+
</DropdownMenuItem>
319+
))}
320+
</DropdownMenuContent>
321+
</DropdownMenu>
302322
)}
303323
</div>
304324
</div>

src/constants/templates.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
export interface NoteTemplate {
2+
id: string;
3+
name: string;
4+
description: string;
5+
title: string;
6+
content: string;
7+
}
8+
9+
export const NOTE_TEMPLATES: NoteTemplate[] = [
10+
{
11+
id: 'blank',
12+
name: 'Blank Note',
13+
description: 'Start with a clean slate',
14+
title: 'Untitled Note',
15+
content: '',
16+
},
17+
{
18+
id: 'meeting-notes',
19+
name: 'Meeting Notes',
20+
description: 'Structured template for meetings',
21+
title: 'Meeting Notes - [Date]',
22+
content: `<h1>Meeting: [Title]</h1><p><strong>Date:</strong> ${new Date().toLocaleDateString()}<br><strong>Attendees:</strong></p><ul data-type="taskList"><li data-type="taskItem" data-checked="false"><label><input type="checkbox"><span></span></label><div><p></p></div></li><li data-type="taskItem" data-checked="false"><label><input type="checkbox"><span></span></label><div><p></p></div></li></ul><h2>Agenda</h2><ol><li><p></p></li><li><p></p></li><li><p></p></li></ol><h2>Discussion</h2><p></p><h2>Action Items</h2><ul data-type="taskList"><li data-type="taskItem" data-checked="false"><label><input type="checkbox"><span></span></label><div><p>Task 1 - @person - Due: [date]</p></div></li><li data-type="taskItem" data-checked="false"><label><input type="checkbox"><span></span></label><div><p>Task 2 - @person - Due: [date]</p></div></li></ul><h2>Next Steps</h2><p></p>`,
23+
},
24+
{
25+
id: 'project-plan',
26+
name: 'Project Plan',
27+
description: 'Plan and track project progress',
28+
title: 'Project: [Name]',
29+
content: `<h1>Project: [Name]</h1><h2>Overview</h2><p>Brief description of the project...</p><h2>Goals &amp; Objectives</h2><ul data-type="taskList"><li data-type="taskItem" data-checked="false"><label><input type="checkbox"><span></span></label><div><p>Primary goal 1</p></div></li><li data-type="taskItem" data-checked="false"><label><input type="checkbox"><span></span></label><div><p>Primary goal 2</p></div></li><li data-type="taskItem" data-checked="false"><label><input type="checkbox"><span></span></label><div><p>Secondary goal 1</p></div></li></ul><h2>Timeline</h2><table><tbody><tr><th><p>Phase</p></th><th><p>Start Date</p></th><th><p>End Date</p></th><th><p>Status</p></th></tr><tr><td><p>Planning</p></td><td><p></p></td><td><p></p></td><td><p>⏳ Planned</p></td></tr><tr><td><p>Development</p></td><td><p></p></td><td><p></p></td><td><p>⏳ Planned</p></td></tr><tr><td><p>Testing</p></td><td><p></p></td><td><p></p></td><td><p>⏳ Planned</p></td></tr><tr><td><p>Launch</p></td><td><p></p></td><td><p></p></td><td><p>⏳ Planned</p></td></tr></tbody></table><h2>Resources</h2><ul><li><p><strong>Budget:</strong></p></li><li><p><strong>Team Members:</strong></p></li><li><p><strong>Tools/Software:</strong></p></li></ul><h2>Risks &amp; Mitigation</h2><ul><li><p><strong>Risk 1:</strong><br><em>Mitigation:</em></p></li><li><p><strong>Risk 2:</strong><br><em>Mitigation:</em></p></li></ul><h2>Next Actions</h2><ul data-type="taskList"><li data-type="taskItem" data-checked="false"><label><input type="checkbox"><span></span></label><div><p>Action item 1</p></div></li><li data-type="taskItem" data-checked="false"><label><input type="checkbox"><span></span></label><div><p>Action item 2</p></div></li></ul>`,
30+
},
31+
{
32+
id: 'daily-notes',
33+
name: 'Daily Notes',
34+
description: 'Daily journal and task tracking',
35+
title: `Daily Notes - ${new Date().toLocaleDateString()}`,
36+
content: `<h1>${new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}</h1><h2>Today's Focus</h2><p><em>What are the 3 most important things to accomplish today?</em></p><ol><li><p></p></li><li><p></p></li><li><p></p></li></ol><h2>Tasks</h2><ul data-type="taskList"><li data-type="taskItem" data-checked="false"><label><input type="checkbox"><span></span></label><div><p></p></div></li><li data-type="taskItem" data-checked="false"><label><input type="checkbox"><span></span></label><div><p></p></div></li><li data-type="taskItem" data-checked="false"><label><input type="checkbox"><span></span></label><div><p></p></div></li></ul><h2>Notes &amp; Thoughts</h2><p></p><h2>Wins &amp; Progress</h2><ul><li><p></p></li><li><p></p></li></ul><h2>Tomorrow's Priorities</h2><ul data-type="taskList"><li data-type="taskItem" data-checked="false"><label><input type="checkbox"><span></span></label><div><p></p></div></li><li data-type="taskItem" data-checked="false"><label><input type="checkbox"><span></span></label><div><p></p></div></li></ul>`,
37+
},
38+
{
39+
id: 'research-notes',
40+
name: 'Research Notes',
41+
description: 'Structured research documentation',
42+
title: 'Research: [Topic]',
43+
content: `<h1>Research: [Topic]</h1><h2>Research Question</h2><p><em>What am I trying to find out?</em></p><p></p><h2>Sources</h2><ul><li><p></p></li><li><p></p></li><li><p></p></li></ul><h2>Key Findings</h2><h3>Finding 1</h3><p></p><h3>Finding 2</h3><p></p><h3>Finding 3</h3><p></p><h2>Summary &amp; Conclusions</h2><p></p><h2>Next Steps</h2><ul data-type="taskList"><li data-type="taskItem" data-checked="false"><label><input type="checkbox"><span></span></label><div><p></p></div></li><li data-type="taskItem" data-checked="false"><label><input type="checkbox"><span></span></label><div><p></p></div></li></ul><hr><p><em>Research conducted on: ${new Date().toLocaleDateString()}</em></p>`,
44+
},
45+
];

src/hooks/useNotes.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export function useNotes() {
159159
});
160160
}, [notes, searchQuery, currentView, selectedFolder, folders]);
161161

162-
const createNote = async (folderId?: string) => {
162+
const createNote = async (folderId?: string, templateContent?: { title: string; content: string }) => {
163163
try {
164164
if (!encryptionReady) {
165165
throw new Error(
@@ -168,8 +168,8 @@ export function useNotes() {
168168
}
169169

170170
const apiNote = await api.createNote({
171-
title: 'Untitled Note',
172-
content: '',
171+
title: templateContent?.title || 'Untitled Note',
172+
content: templateContent?.content || '',
173173
folderId: folderId ?? selectedFolder?.id ?? null,
174174
starred: false,
175175
tags: [],

src/types/layout.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export interface FolderPanelProps {
1919
folderId: string,
2020
parentId: string | null
2121
) => Promise<void>;
22-
onCreateNote: () => void;
22+
onCreateNote: (templateContent?: { title: string; content: string }) => void;
2323
onCreateFolder: (name: string, color: string, parentId?: string) => void;
2424
onDeleteFolder: (folderId: string) => Promise<void>;
2525
onReorderFolders: (folderId: string, newIndex: number) => Promise<void>;
@@ -38,7 +38,7 @@ export interface FilesPanelProps {
3838
isFolderPanelOpen: boolean;
3939
onSelectNote: (note: Note) => void;
4040
onToggleStar: (noteId: string) => Promise<void>;
41-
onCreateNote: () => void;
41+
onCreateNote: (templateContent?: { title: string; content: string }) => void;
4242
onToggleFolderPanel: () => void;
4343
onEmptyTrash: () => Promise<void>;
4444
isMobile?: boolean;

0 commit comments

Comments
 (0)