# Rich Text Study Notes - Implementation Plan ## 📋 Overview Implement a comprehensive rich text note-taking system allowing users to create detailed, formatted study notes with images, links, and advanced organization features for deep Bible study. **Status:** Planning Phase **Priority:** 🟡 Medium **Estimated Time:** 2 weeks (80 hours) **Target Completion:** TBD --- ## 🎯 Goals & Objectives ### Primary Goals 1. Provide rich text editing capabilities for study notes 2. Enable advanced formatting (bold, italic, lists, headers) 3. Support multimedia content (images, links, videos) 4. Organize notes with folders and tags 5. Enable search and filtering across all notes ### User Value Proposition - **For students**: Comprehensive study journal - **For scholars**: Research documentation - **For teachers**: Lesson planning and preparation - **For small groups**: Collaborative study materials - **For personal growth**: Spiritual reflection journal --- ## ✨ Feature Specifications ### 1. Note Data Model ```typescript interface StudyNote { id: string userId: string // Content title: string content: string // Rich text (HTML or JSON) contentType: 'html' | 'json' | 'markdown' plainText: string // For search indexing // References verseReferences: VerseReference[] relatedNotes: string[] // Note IDs // Organization folderId: string | null tags: string[] color: string // For visual organization isPinned: boolean isFavorite: boolean // Collaboration visibility: 'private' | 'shared' | 'public' sharedWith: string[] // User IDs // Metadata createdAt: Date updatedAt: Date lastViewedAt: Date version: number // For version history wordCount: number readingTime: number // minutes } interface NoteFolder { id: string userId: string name: string description?: string parentId: string | null // For nested folders color: string icon: string order: number createdAt: Date updatedAt: Date } interface VerseReference { book: string chapter: number verse: number endVerse?: number context?: string // Surrounding text snippet } ``` ### 2. Rich Text Editor (TipTap) ```typescript import { useEditor, EditorContent } from '@tiptap/react' import StarterKit from '@tiptap/starter-kit' import Highlight from '@tiptap/extension-highlight' import Typography from '@tiptap/extension-typography' import Link from '@tiptap/extension-link' import Image from '@tiptap/extension-image' import TaskList from '@tiptap/extension-task-list' import TaskItem from '@tiptap/extension-task-item' import Table from '@tiptap/extension-table' import TableRow from '@tiptap/extension-table-row' import TableCell from '@tiptap/extension-table-cell' import TableHeader from '@tiptap/extension-table-header' import Placeholder from '@tiptap/extension-placeholder' // Custom verse reference extension const VerseReference = Node.create({ name: 'verseReference', group: 'inline', inline: true, atom: true, addAttributes() { return { book: { default: null }, chapter: { default: null }, verse: { default: null }, text: { default: null } } }, parseHTML() { return [{ tag: 'span[data-verse-ref]' }] }, renderHTML({ node, HTMLAttributes }) { return [ 'span', { ...HTMLAttributes, 'data-verse-ref': true, class: 'verse-reference-chip', contenteditable: 'false' }, node.attrs.text || `${node.attrs.book} ${node.attrs.chapter}:${node.attrs.verse}` ] } }) interface NoteEditorProps { note: StudyNote onSave: (content: string) => void autoSave?: boolean readOnly?: boolean } export const NoteEditor: React.FC = ({ note, onSave, autoSave = true, readOnly = false }) => { const editor = useEditor({ extensions: [ StarterKit.configure({ heading: { levels: [1, 2, 3, 4] }, code: { HTMLAttributes: { class: 'code-block' } } }), Highlight.configure({ multicolor: true }), Typography, Link.configure({ openOnClick: false, HTMLAttributes: { class: 'prose-link' } }), Image.configure({ inline: true, HTMLAttributes: { class: 'note-image' } }), TaskList, TaskItem.configure({ nested: true }), Table.configure({ resizable: true }), TableRow, TableCell, TableHeader, Placeholder.configure({ placeholder: 'Start writing your study notes...', showOnlyWhenEditable: true }), VerseReference ], content: note.content, editable: !readOnly, autofocus: !readOnly, onUpdate: ({ editor }) => { if (autoSave) { debouncedSave(editor.getHTML()) } } }) const debouncedSave = useDebounce((content: string) => { onSave(content) }, 1000) if (!editor) return null return ( {!readOnly && } ) } ``` ### 3. Editor Toolbar ```typescript const EditorToolbar: React.FC<{ editor: Editor }> = ({ editor }) => { const [linkDialogOpen, setLinkDialogOpen] = useState(false) const [imageDialogOpen, setImageDialogOpen] = useState(false) const [verseRefDialogOpen, setVerseRefDialogOpen] = useState(false) return ( {/* Text Formatting */} editor.chain().focus().toggleBold().run()} color={editor.isActive('bold') ? 'primary' : 'default'} title="Bold (Ctrl+B)" > editor.chain().focus().toggleItalic().run()} color={editor.isActive('italic') ? 'primary' : 'default'} title="Italic (Ctrl+I)" > editor.chain().focus().toggleUnderline().run()} color={editor.isActive('underline') ? 'primary' : 'default'} title="Underline (Ctrl+U)" > editor.chain().focus().toggleStrike().run()} color={editor.isActive('strike') ? 'primary' : 'default'} title="Strikethrough" > {/* Headings */} {/* Lists */} editor.chain().focus().toggleBulletList().run()} color={editor.isActive('bulletList') ? 'primary' : 'default'} title="Bullet List" > editor.chain().focus().toggleOrderedList().run()} color={editor.isActive('orderedList') ? 'primary' : 'default'} title="Numbered List" > editor.chain().focus().toggleTaskList().run()} color={editor.isActive('taskList') ? 'primary' : 'default'} title="Task List" > {/* Alignment */} editor.chain().focus().setTextAlign('left').run()} color={editor.isActive({ textAlign: 'left' }) ? 'primary' : 'default'} title="Align Left" > editor.chain().focus().setTextAlign('center').run()} color={editor.isActive({ textAlign: 'center' }) ? 'primary' : 'default'} title="Align Center" > editor.chain().focus().setTextAlign('right').run()} color={editor.isActive({ textAlign: 'right' }) ? 'primary' : 'default'} title="Align Right" > {/* Highlights */} { editor.chain().focus().toggleHighlight({ color }).run() }} /> {/* Media & References */} setLinkDialogOpen(true)} color={editor.isActive('link') ? 'primary' : 'default'} title="Insert Link" > setImageDialogOpen(true)} title="Insert Image" > setVerseRefDialogOpen(true)} title="Insert Verse Reference" > editor.chain().focus().toggleCodeBlock().run()} color={editor.isActive('codeBlock') ? 'primary' : 'default'} title="Code Block" > {/* Undo/Redo */} editor.chain().focus().undo().run()} disabled={!editor.can().undo()} title="Undo (Ctrl+Z)" > editor.chain().focus().redo().run()} disabled={!editor.can().redo()} title="Redo (Ctrl+Y)" > {/* Dialogs */} setLinkDialogOpen(false)} onInsert={(url, text) => { editor.chain().focus().setLink({ href: url }).insertContent(text).run() }} /> setImageDialogOpen(false)} onInsert={(url, alt) => { editor.chain().focus().setImage({ src: url, alt }).run() }} /> setVerseRefDialogOpen(false)} onInsert={(ref) => { editor.chain().focus().insertContent({ type: 'verseReference', attrs: { book: ref.book, chapter: ref.chapter, verse: ref.verse, text: `${ref.book} ${ref.chapter}:${ref.verse}` } }).run() }} /> ) } ``` ### 4. Notes List & Organization ```typescript const NotesPage: React.FC = () => { const [notes, setNotes] = useState([]) const [folders, setFolders] = useState([]) const [selectedFolder, setSelectedFolder] = useState(null) const [searchQuery, setSearchQuery] = useState('') const [sortBy, setSortBy] = useState<'updated' | 'created' | 'title'>('updated') const [viewMode, setViewMode] = useState<'list' | 'grid' | 'compact'>('list') // Load notes useEffect(() => { loadNotes() }, [selectedFolder, searchQuery, sortBy]) const loadNotes = async () => { const params = new URLSearchParams({ ...(selectedFolder && { folderId: selectedFolder }), ...(searchQuery && { search: searchQuery }), sortBy }) const response = await fetch(`/api/notes?${params}`) const data = await response.json() setNotes(data.notes) } return ( {/* Sidebar - Folders */} Study Notes setSelectedFolder(null)} > Folders {folders.map(folder => ( setSelectedFolder(folder.id)} sx={{ pl: 3 }} > ))} createFolder()}> {/* Main Content - Notes List */} {/* Toolbar */} setSearchQuery(e.target.value)} size="small" fullWidth InputProps={{ startAdornment: }} /> value && setViewMode(value)} size="small" > {/* Notes Display */} {viewMode === 'grid' ? ( {notes.map(note => ( openNote(note)} /> ))} ) : ( {notes.map(note => ( openNote(note)} /> ))} )} ) } ``` ### 5. Note Templates ```typescript const NOTE_TEMPLATES = [ { id: 'sermon-notes', name: 'Sermon Notes', icon: '📝', content: `

Sermon Notes

Date:

Speaker:

Topic:

Main Points

Key Verses

Personal Application

Prayer Points

` }, { id: 'bible-study', name: 'Bible Study', icon: '📖', content: `

Bible Study

Passage

Context

Historical Context:

Literary Context:

Observation

  • What does the text say?

Interpretation

  • What does it mean?

Application

  • How does this apply to my life?
` }, { id: 'character-study', name: 'Character Study', icon: '👤', content: `

Character Study: [Name]

Background

Family:

Occupation:

Time Period:

Key Events

Character Traits

  • Strengths:
  • Weaknesses:

Lessons Learned

` }, { id: 'topical-study', name: 'Topical Study', icon: '🏷️', content: `

Topical Study: [Topic]

Definition

Key Verses

What the Bible Says

Practical Application

` } ] const TemplateSelector: React.FC<{ onSelect: (template: string) => void }> = ({ onSelect }) => { return ( {NOTE_TEMPLATES.map(template => ( onSelect(template.content)} > {template.icon} {template.name} ))} ) } ``` ### 6. Full-Text Search ```typescript // API endpoint with PostgreSQL full-text search export async function POST(request: Request) { const { query } = await request.json() const userId = await getUserIdFromAuth(request) const notes = await prisma.$queryRaw` SELECT id, title, "plainText", ts_rank(to_tsvector('english', title || ' ' || "plainText"), plainto_tsquery('english', ${query})) AS rank FROM "StudyNote" WHERE "userId" = ${userId} AND to_tsvector('english', title || ' ' || "plainText") @@ plainto_tsquery('english', ${query}) ORDER BY rank DESC LIMIT 50 ` return NextResponse.json({ notes }) } ``` --- ## 🗄️ Database Schema ```prisma model StudyNote { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id]) title String content String @db.Text contentType String @default("html") plainText String @db.Text // For search folderId String? folder NoteFolder? @relation(fields: [folderId], references: [id]) tags String[] color String? isPinned Boolean @default(false) isFavorite Boolean @default(false) visibility String @default("private") sharedWith String[] wordCount Int @default(0) readingTime Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt lastViewedAt DateTime @default(now()) version Int @default(1) verseReferences NoteVerseReference[] @@index([userId, updatedAt]) @@index([userId, folderId]) @@index([userId, isPinned]) } model NoteFolder { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id]) name String description String? parentId String? parent NoteFolder? @relation("FolderHierarchy", fields: [parentId], references: [id]) children NoteFolder[] @relation("FolderHierarchy") color String @default("#1976d2") icon String @default("folder") order Int @default(0) notes StudyNote[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId, parentId]) } model NoteVerseReference { id String @id @default(cuid()) noteId String note StudyNote @relation(fields: [noteId], references: [id], onDelete: Cascade) book String chapter Int verse Int endVerse Int? context String? @@index([noteId]) @@index([book, chapter, verse]) } ``` --- ## 📅 Implementation Timeline ### Week 1 **Day 1-2:** Setup & Editor - [ ] Create database schema - [ ] Set up TipTap editor - [ ] Build basic toolbar **Day 3-4:** Core Features - [ ] Implement save/autosave - [ ] Add formatting options - [ ] Build media insertion **Day 5:** Organization - [ ] Create folders system - [ ] Add tags support - [ ] Implement search ### Week 2 **Day 1-2:** Advanced Features - [ ] Build templates - [ ] Add verse references - [ ] Implement version history **Day 3-4:** Polish - [ ] Mobile optimization - [ ] Performance tuning - [ ] UI refinement **Day 5:** Testing & Launch - [ ] Bug fixes - [ ] Documentation - [ ] Deployment --- **Document Version:** 1.0 **Last Updated:** 2025-10-13 **Status:** Ready for Implementation