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:
@@ -135,6 +135,16 @@ export interface Translations {
|
||||
viewTabsTooltip: string
|
||||
viewModeGroup: string
|
||||
reorderTabs: string
|
||||
noteType: string
|
||||
typeText: string
|
||||
typeMarkdown: string
|
||||
typeRichText: string
|
||||
typeChecklist: string
|
||||
richTextPlaceholder: string
|
||||
switchTypeTitle: string
|
||||
switchTypeWarning: string
|
||||
switchTypeContentPreserved: string
|
||||
switchType: string
|
||||
}
|
||||
pagination: {
|
||||
previous: string
|
||||
|
||||
@@ -44,6 +44,15 @@ export interface LinkMetadata {
|
||||
siteName?: string;
|
||||
}
|
||||
|
||||
export type NoteType = 'text' | 'markdown' | 'richtext' | 'checklist'
|
||||
|
||||
export const NOTE_TYPE_CONFIG: Record<NoteType, { icon: string; label: string }> = {
|
||||
text: { icon: 'AlignLeft', label: 'Text' },
|
||||
markdown: { icon: 'FileCode2', label: 'Markdown' },
|
||||
richtext: { icon: 'PenLine', label: 'Rich Text' },
|
||||
checklist: { icon: 'ListChecks', label: 'Checklist' },
|
||||
}
|
||||
|
||||
export interface Note {
|
||||
id: string;
|
||||
title: string | null;
|
||||
@@ -52,9 +61,9 @@ export interface Note {
|
||||
isPinned: boolean;
|
||||
isArchived: boolean;
|
||||
trashedAt?: Date | null;
|
||||
type: 'text' | 'checklist';
|
||||
type: NoteType;
|
||||
checkItems: CheckItem[] | null;
|
||||
labels: string[] | null; // DEPRECATED: Array of label names stored as JSON string
|
||||
labels: string[] | null;
|
||||
images: string[] | null;
|
||||
links: LinkMetadata[] | null;
|
||||
reminder: Date | null;
|
||||
@@ -70,13 +79,11 @@ export interface Note {
|
||||
contentUpdatedAt: Date;
|
||||
sharedWith?: string[];
|
||||
userId?: string | null;
|
||||
// Notebook relation (optional - null = "General Notes" / Inbox)
|
||||
notebookId?: string | null;
|
||||
notebook?: Notebook | null;
|
||||
autoGenerated?: boolean | null;
|
||||
aiProvider?: string | null;
|
||||
historyEnabled?: boolean;
|
||||
// Search result metadata (optional)
|
||||
matchType?: 'exact' | 'related' | null;
|
||||
searchScore?: number | null;
|
||||
}
|
||||
@@ -92,8 +99,7 @@ export interface NoteHistoryEntry {
|
||||
color: string;
|
||||
isPinned: boolean;
|
||||
isArchived: boolean;
|
||||
type: 'text' | 'checklist';
|
||||
checkItems: CheckItem[] | null;
|
||||
type: NoteType; checkItems: CheckItem[] | null;
|
||||
labels: string[] | null;
|
||||
images: string[] | null;
|
||||
links: LinkMetadata[] | null;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { LABEL_COLORS, LabelColorName, QueryType, Note } from "./types"
|
||||
import { LABEL_COLORS, LabelColorName, QueryType, Note, NoteType } from "./types"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
@@ -50,8 +50,14 @@ export function asArray<T = unknown>(val: unknown, fallback: T[] = []): T[] {
|
||||
* Guarantees array fields are always real arrays or null.
|
||||
*/
|
||||
export function parseNote(dbNote: any): Note {
|
||||
let noteType: NoteType = dbNote.type || 'text'
|
||||
if (noteType === 'text' && dbNote.isMarkdown) {
|
||||
noteType = 'markdown'
|
||||
}
|
||||
return {
|
||||
...dbNote,
|
||||
type: noteType,
|
||||
isMarkdown: noteType === 'markdown',
|
||||
checkItems: asArray(dbNote.checkItems, null as any) ?? null,
|
||||
labels: asArray(dbNote.labels) || null,
|
||||
images: asArray(dbNote.images) || null,
|
||||
|
||||
Reference in New Issue
Block a user