Files
Momento/memento-note/components/tiptap-database-block-extension.tsx
Antigravity f46654f574 feat: editor improvements and architectural grid prototype
Multiple feature additions and improvements across the application:

- NextGen Editor: drag handles, smart paste, block actions
- Structured views: Kanban and table layouts for notes
- Architectural Grid: new brainstorming/agent interface prototype
- Flashcards: SM-2 revision algorithm with AI generation
- MCP server: robustness improvements
- Graph/PDF chat: fix click propagation and copy behavior
- Various UI/UX enhancements and bug fixes

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 19:45:15 +00:00

118 lines
3.9 KiB
TypeScript

'use client'
import { Node, mergeAttributes } from '@tiptap/core'
import { ReactNodeViewRenderer, NodeViewWrapper, type NodeViewProps } from '@tiptap/react'
import { useCallback } from 'react'
import type { Editor } from '@tiptap/core'
import type { Node as PMNode } from '@tiptap/pm/model'
import { DatabaseBlockEditor } from '@/components/database-block-editor'
import { NOTE_REQUEST_SAVE_EVENT } from '@/lib/note-change-sync'
import {
createDefaultDatabaseBlockData,
parseDatabaseBlockAttrs,
serializeDatabaseBlockData,
type DatabaseBlockData,
} from '@/lib/editor/database-block-types'
function DatabaseBlockView({ node, updateAttributes, editor }: NodeViewProps) {
const data = parseDatabaseBlockAttrs(node.attrs)
const requestSave = useCallback(() => {
const hostNoteId = editor.storage.liveBlock?.hostNoteId
if (hostNoteId) {
window.dispatchEvent(new CustomEvent(NOTE_REQUEST_SAVE_EVENT, {
detail: { noteId: hostNoteId, reason: 'database-block-mutation' },
}))
}
}, [editor])
const handleChange = useCallback((next: DatabaseBlockData) => {
updateAttributes(serializeDatabaseBlockData(next))
requestSave()
}, [requestSave, updateAttributes])
return (
<NodeViewWrapper className="database-block-wrapper" data-drag-handle contentEditable={false}>
<DatabaseBlockEditor data={data} onChange={handleChange} />
</NodeViewWrapper>
)
}
export const DatabaseBlockExtension = Node.create({
name: 'databaseBlock',
group: 'block',
atom: true,
draggable: true,
selectable: true,
addAttributes() {
return {
dbId: {
default: '',
parseHTML: (element) => element.getAttribute('data-db-id') || '',
renderHTML: (attributes) => (attributes.dbId ? { 'data-db-id': attributes.dbId } : {}),
},
dbView: {
default: 'table',
parseHTML: (element) => element.getAttribute('data-db-view') || 'table',
renderHTML: (attributes) => ({ 'data-db-view': attributes.dbView || 'table' }),
},
dbAuthorsJson: {
default: '[]',
parseHTML: (element) => element.getAttribute('data-db-authors') || '[]',
renderHTML: (attributes) => ({ 'data-db-authors': attributes.dbAuthorsJson || '[]' }),
},
dbBooksJson: {
default: '[]',
parseHTML: (element) => element.getAttribute('data-db-books') || '[]',
renderHTML: (attributes) => ({ 'data-db-books': attributes.dbBooksJson || '[]' }),
},
}
},
parseHTML() {
return [{ tag: 'div[data-database-block]' }]
},
renderHTML({ HTMLAttributes }) {
return ['div', mergeAttributes(HTMLAttributes, { 'data-database-block': 'true' })]
},
addNodeView() {
return ReactNodeViewRenderer(DatabaseBlockView)
},
})
export function insertDatabaseBlockAtSelection(editor: Editor): boolean {
const type = editor.schema.nodes.databaseBlock
if (!type) return false
const attrs = serializeDatabaseBlockData(createDefaultDatabaseBlockData())
const { empty, $from } = editor.state.selection
const parent = $from.parent
if (empty && parent.type.name === 'paragraph' && parent.content.size === 0) {
const pos = $from.before()
return editor
.chain()
.focus()
.command(({ tr, dispatch }) => {
tr.replaceWith(pos, pos + parent.nodeSize, type.create(attrs))
if (dispatch) dispatch(tr)
return true
})
.run()
}
return editor.chain().focus().insertContent({ type: 'databaseBlock', attrs }).run()
}
export function replaceBlockWithDatabase(editor: Editor, blockPos: number, blockNode: PMNode): void {
const type = editor.schema.nodes.databaseBlock
if (!type || blockPos < 0 || !blockNode) return
const attrs = serializeDatabaseBlockData(createDefaultDatabaseBlockData())
const dbNode = type.create(attrs)
editor.view.dispatch(editor.state.tr.replaceWith(blockPos, blockPos + blockNode.nodeSize, dbNode))
editor.commands.focus()
}