Files
Momento/memento-note/components/tiptap-columns-extension.tsx
Antigravity 299836bd74
All checks were successful
CI / Lint, Unit Tests & Build (push) Successful in 5m0s
CI / Deploy production (on server) (push) Successful in 1m2s
cleanup: audit complet — code mort supprimé, erreurs TS corrigées, i18n wizard ajouté
Supprimé:
- memento-note/memento-note/ (dossier fantôme, 7 erreurs TS)
- tiptap-subpage-extension.tsx + toutes ses références (feature retirée)

Corrigé:
- tiptap-columns-extension.tsx: PMNode import type → import value
- study-plan/route.ts: title null → string conversion
- csv/route.ts: paramètre implicit any

Ajouté:
- Section wizard.* complète (33 clés) dans fr.json + en.json
- generalContinue + structuredViewsTagApplied dans fr/en
2026-06-19 21:53:10 +00:00

160 lines
4.3 KiB
TypeScript

'use client'
import { Node, mergeAttributes, findParentNode } from '@tiptap/core'
import { TextSelection } from '@tiptap/pm/state'
import type { EditorState, Transaction } from '@tiptap/pm/state'
import { Node as PMNode } from '@tiptap/pm/model'
export const ColumnNode = Node.create({
name: 'column',
content: 'block+',
isolating: true,
defining: true,
addAttributes() {
return {
index: {
default: 0,
parseHTML: (el) => Number(el.getAttribute('index')) || 0,
renderHTML: (attrs) => ({ index: attrs.index }),
},
}
},
parseHTML() {
return [{ tag: 'div[data-type="column"]' }]
},
renderHTML({ HTMLAttributes }) {
return [
'div',
mergeAttributes(HTMLAttributes, {
'data-type': 'column',
class: 'tiptap-column',
}),
0,
]
},
})
export const ColumnsExtension = Node.create({
name: 'columns',
group: 'block',
content: 'column{1,}',
isolating: true,
defining: true,
allowGapCursor: false,
addAttributes() {
return {
cols: {
default: 2,
parseHTML: (el) => Number(el.getAttribute('cols')) || 2,
renderHTML: (attrs) => ({ cols: attrs.cols }),
},
}
},
parseHTML() {
return [{ tag: 'div[data-type="columns"]' }]
},
renderHTML({ HTMLAttributes }) {
return [
'div',
mergeAttributes(HTMLAttributes, {
'data-type': 'columns',
class: 'tiptap-columns',
}),
0,
]
},
addCommands() {
return {
addColumnAfter: () => ({ state, dispatch }: { state: EditorState; dispatch?: (tr: Transaction) => void }) => {
return addOrDeleteCol(state, dispatch, 'addAfter')
},
addColumnBefore: () => ({ state, dispatch }: { state: EditorState; dispatch?: (tr: Transaction) => void }) => {
return addOrDeleteCol(state, dispatch, 'addBefore')
},
deleteColumn: () => ({ state, dispatch }: { state: EditorState; dispatch?: (tr: Transaction) => void }) => {
return addOrDeleteCol(state, dispatch, 'delete')
},
}
},
addKeyboardShortcuts() {
return {
'Mod-Shift-L': () => this.editor.commands.insertContent({
type: this.name,
attrs: { cols: 2 },
content: [
{ type: 'column', attrs: { index: 0 }, content: [{ type: 'paragraph' }] },
{ type: 'column', attrs: { index: 1 }, content: [{ type: 'paragraph' }] },
],
}),
}
},
})
function addOrDeleteCol(state: EditorState, dispatch?: (tr: Transaction) => void, type?: 'addBefore' | 'addAfter' | 'delete'): boolean {
const maybeColumns = findParentNode((node: PMNode) => node.type.name === 'columns')(state.selection)
const maybeColumn = findParentNode((node: PMNode) => node.type.name === 'column')(state.selection)
if (!maybeColumns || !maybeColumn) return false
const cols = maybeColumns.node
const colIndex = maybeColumn.node.attrs.index
const colsJSON = cols.toJSON()
let nextIndex = colIndex
if (type === 'delete') {
if (colsJSON.content.length <= 1) return false
nextIndex = Math.max(0, colIndex - 1)
colsJSON.content.splice(colIndex, 1)
} else {
nextIndex = type === 'addBefore' ? colIndex : colIndex + 1
colsJSON.content.splice(nextIndex, 0, {
type: 'column',
attrs: { index: colIndex },
content: [{ type: 'paragraph' }],
})
}
colsJSON.attrs.cols = colsJSON.content.length
colsJSON.content.forEach((colJSON: any, index: number) => {
colJSON.attrs.index = index
})
const nextCols = PMNode.fromJSON(state.schema, colsJSON)
let nextSelectPos = maybeColumns.pos + 1
nextCols.content.forEach((col: PMNode, _pos: number, index: number) => {
if (index < nextIndex) nextSelectPos += col.nodeSize
})
if (dispatch) {
const tr = state.tr
tr.replaceWith(maybeColumns.pos, maybeColumns.pos + maybeColumns.node.nodeSize, nextCols)
tr.setSelection(TextSelection.near(tr.doc.resolve(nextSelectPos + 1)))
dispatch(tr)
}
return true
}
export function insertColumnsBlock(editor: any, columns: number = 2) {
const content = Array.from({ length: columns }, (_, i) => ({
type: 'column',
attrs: { index: i },
content: [{ type: 'paragraph' }],
}))
editor.chain().focus().insertContent({
type: 'columns',
attrs: { cols: columns },
content,
}).run()
}