feat(editor): implement next-gen editor with unique gutter drag handle, block actions menu, smart paste transclusion, and redesigned inline structured view block (US-NEXTGEN-EDITOR, US-4)
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
'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()
|
||||
}
|
||||
Reference in New Issue
Block a user