feat: Notion-like rich text editor with TipTap, 4 note types, slash commands & bubble menu
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 1m33s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 1m33s
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { Note, NOTE_COLORS, NoteColor } from '@/lib/types'
|
||||
import { Note, NOTE_COLORS, NoteColor, NoteType } from '@/lib/types'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import { Pin, Bell, GripVertical, X, Link2, FolderOpen, StickyNote, LucideIcon, Folder, Briefcase, FileText, Zap, BarChart3, Globe, Sparkles, Book, Heart, Crown, Music, Building2, LogOut, Trash2 } from 'lucide-react'
|
||||
import { Pin, Bell, GripVertical, X, Link2, FolderOpen, StickyNote, LucideIcon, Folder, Briefcase, FileText, Zap, BarChart3, Globe, Sparkles, Book, Heart, Crown, Music, Building2, LogOut, Trash2, AlignLeft, FileCode2, PenLine, ListChecks } from 'lucide-react'
|
||||
import { useState, useEffect, useTransition, useOptimistic, memo } from 'react'
|
||||
import { useSession } from 'next-auth/react'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
@@ -83,6 +83,13 @@ function getDateLocale(language: string): Locale {
|
||||
return localeMap[language] || enUS
|
||||
}
|
||||
|
||||
const NOTE_TYPE_ICONS: Record<NoteType, LucideIcon> = {
|
||||
text: AlignLeft,
|
||||
markdown: FileCode2,
|
||||
richtext: PenLine,
|
||||
checklist: ListChecks,
|
||||
}
|
||||
|
||||
// Map icon names to lucide-react components
|
||||
const ICON_MAP: Record<string, LucideIcon> = {
|
||||
'folder': Folder,
|
||||
@@ -536,8 +543,12 @@ export const NoteCard = memo(function NoteCard({
|
||||
|
||||
{/* Title */}
|
||||
{optimisticNote.title && (
|
||||
<h3 dir="auto" className="text-lg font-heading font-semibold mb-2 pr-20 text-foreground leading-tight tracking-tight">
|
||||
{optimisticNote.title}
|
||||
<h3 dir="auto" className="text-lg font-heading font-semibold mb-2 pr-20 text-foreground leading-tight tracking-tight flex items-center gap-2">
|
||||
{(() => {
|
||||
const TypeIcon = NOTE_TYPE_ICONS[optimisticNote.type] || AlignLeft
|
||||
return <TypeIcon className="h-4 w-4 shrink-0 text-muted-foreground/50" />
|
||||
})()}
|
||||
<span className="min-w-0 truncate">{optimisticNote.title}</span>
|
||||
</h3>
|
||||
)}
|
||||
|
||||
@@ -608,18 +619,20 @@ export const NoteCard = memo(function NoteCard({
|
||||
)}
|
||||
|
||||
{/* Content */}
|
||||
{optimisticNote.type === 'text' ? (
|
||||
{optimisticNote.type === 'checklist' ? (
|
||||
<NoteChecklist
|
||||
items={optimisticNote.checkItems || []}
|
||||
onToggleItem={handleCheckItem}
|
||||
/>
|
||||
) : optimisticNote.type === 'richtext' ? (
|
||||
<div className="text-sm text-foreground line-clamp-10 rt-preview" dangerouslySetInnerHTML={{ __html: optimisticNote.content || '' }} />
|
||||
) : (
|
||||
<div className="text-sm text-foreground line-clamp-10">
|
||||
<MarkdownContent
|
||||
content={optimisticNote.content}
|
||||
className="prose-h1:text-xl prose-h1:font-semibold prose-h1:leading-snug prose-h1:mt-1 prose-h1:mb-2 prose-h2:text-lg prose-h2:font-medium prose-h3:text-base prose-p:text-sm prose-p:leading-relaxed"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<NoteChecklist
|
||||
items={optimisticNote.checkItems || []}
|
||||
onToggleItem={handleCheckItem}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Labels - using shared LabelBadge component */}
|
||||
|
||||
Reference in New Issue
Block a user