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>
This commit is contained in:
117
memento-note/components/tiptap-database-block-extension.tsx
Normal file
117
memento-note/components/tiptap-database-block-extension.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
'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()
|
||||
}
|
||||
Reference in New Issue
Block a user