159 lines
5.3 KiB
TypeScript
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()
|
|
}
|