Files
Momento/memento-note/components/tiptap-structured-view-block-extension.tsx

159 lines
5.3 KiB
TypeScript

'use client'
import { Node, mergeAttributes } from '@tiptap/core'
import { ReactNodeViewRenderer, NodeViewWrapper, type NodeViewProps } from '@tiptap/react'
import type { Editor } from '@tiptap/core'
import type { Node as PMNode } from '@tiptap/pm/model'
import { StructuredViewBlockEmbed } from '@/components/structured-view-block-embed'
function StructuredViewBlockView(props: NodeViewProps) {
return (
<NodeViewWrapper className="structured-view-block-wrapper" data-drag-handle contentEditable={false}>
<StructuredViewBlockEmbed
notebookId={props.node.attrs.notebookId}
displayMode={props.node.attrs.displayMode}
filterJson={props.node.attrs.filterJson}
isLocal={props.node.attrs.isLocal}
localColumnsJson={props.node.attrs.localColumnsJson}
localRowsJson={props.node.attrs.localRowsJson}
updateAttributes={props.updateAttributes}
editor={props.editor}
/>
</NodeViewWrapper>
)
}
const defaultColumns = [
{ id: 'col-title', name: 'Nom', type: 'text' },
{ id: 'col-done', name: 'Fait', type: 'checkbox' },
{ id: 'col-status', name: 'Statut', type: 'select', options: ['À faire', 'En cours', 'Terminé'] }
]
const defaultRows = [
{ id: 'row-1', values: { 'col-title': '', 'col-done': false, 'col-status': 'À faire' } },
{ id: 'row-2', values: { 'col-title': '', 'col-done': false, 'col-status': 'À faire' } }
]
export const StructuredViewBlockExtension = Node.create({
name: 'structuredViewBlock',
group: 'block',
atom: true,
draggable: true,
selectable: true,
addAttributes() {
return {
notebookId: {
default: null,
parseHTML: (el) => el.getAttribute('data-sv-notebook-id'),
renderHTML: (attrs) => attrs.notebookId
? { 'data-sv-notebook-id': attrs.notebookId }
: {},
},
displayMode: {
default: 'table',
parseHTML: (el) => el.getAttribute('data-sv-mode') || 'table',
renderHTML: (attrs) => ({ 'data-sv-mode': attrs.displayMode || 'table' }),
},
filterJson: {
default: '{}',
parseHTML: (el) => el.getAttribute('data-sv-filter') || '{}',
renderHTML: (attrs) => ({ 'data-sv-filter': attrs.filterJson || '{}' }),
},
isLocal: {
default: true, // Par défaut, on crée une base de données locale autonome
parseHTML: (el) => el.getAttribute('data-sv-is-local') === 'true',
renderHTML: (attrs) => attrs.isLocal
? { 'data-sv-is-local': 'true' }
: { 'data-sv-is-local': 'false' },
},
localColumnsJson: {
default: JSON.stringify(defaultColumns),
parseHTML: (el) => el.getAttribute('data-sv-local-cols') || '[]',
renderHTML: (attrs) => ({ 'data-sv-local-cols': attrs.localColumnsJson || '[]' }),
},
localRowsJson: {
default: JSON.stringify(defaultRows),
parseHTML: (el) => el.getAttribute('data-sv-local-rows') || '[]',
renderHTML: (attrs) => ({ 'data-sv-local-rows': attrs.localRowsJson || '[]' }),
},
}
},
parseHTML() {
return [
{ tag: 'div[data-structured-view-block]' },
{
tag: 'div[data-database-block]',
getAttrs: () => ({
isLocal: true,
localColumnsJson: JSON.stringify(defaultColumns),
localRowsJson: JSON.stringify(defaultRows),
})
}
]
},
renderHTML({ HTMLAttributes }) {
return ['div', mergeAttributes(HTMLAttributes, { 'data-structured-view-block': 'true' })]
},
addNodeView() {
return ReactNodeViewRenderer(StructuredViewBlockView)
},
})
export function insertStructuredViewBlockAtSelection(editor: Editor, notebookId?: string | null): boolean {
const type = editor.schema.nodes.structuredViewBlock
if (!type) return false
// Par défaut, si la note a un carnet et qu'on n'est pas dans l'inbox, on peut l'insérer liée au carnet.
// Mais si aucun notebookId n'est passé ou si on veut favoriser la base locale autonome Notion-like par défaut :
// On l'insère en base locale autonome par défaut !
const attrs = {
notebookId: null,
displayMode: 'table',
filterJson: '{}',
isLocal: true,
localColumnsJson: JSON.stringify(defaultColumns),
localRowsJson: JSON.stringify(defaultRows)
}
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: 'structuredViewBlock', attrs }).run()
}
export function replaceBlockWithStructuredView(
editor: Editor,
blockPos: number,
blockNode: PMNode,
notebookId?: string | null
): void {
const type = editor.schema.nodes.structuredViewBlock
if (!type || blockPos < 0 || !blockNode) return
const attrs = {
notebookId: null,
displayMode: 'table',
filterJson: '{}',
isLocal: true,
localColumnsJson: JSON.stringify(defaultColumns),
localRowsJson: JSON.stringify(defaultRows)
}
const svNode = type.create(attrs)
editor.view.dispatch(editor.state.tr.replaceWith(blockPos, blockPos + blockNode.nodeSize, svNode))
editor.commands.focus()
}