feat: article editor core

This commit is contained in:
fsylvain committed 2026-09-27 02:11:38 +02:00
1 parent d493a181e1
commit a4734b2c91
55 files changed
+4186 -496

No files matched your search

+97
View File
@@ -0,0 +1,97 @@
import { Extension } from '@tiptap/core'
export const INDENT_MAX = 4
export const INDENT_TYPES = ['paragraph', 'heading']
declare module '@tiptap/core' {
interface Commands<ReturnType> {
articleIndent: {
indent: () => ReturnType
outdent: () => ReturnType
}
}
}
function currentIndentableNode(state: {
selection: { $from: { parent: unknown } }
}) {
return state.selection.$from.parent as {
type: { name: string }
attrs: { indent?: number }
}
}
export const ArticleIndent = Extension.create({
name: 'articleIndent',
addGlobalAttributes() {
return [
{
types: INDENT_TYPES,
attributes: {
indent: {
default: 0,
parseHTML: (element) =>
Number(element.getAttribute('data-indent')) || 0,
renderHTML: (attributes) => {
if (!attributes.indent) {
return {}
}
return { 'data-indent': String(attributes.indent) }
},
},
},
},
]
},
addCommands() {
return {
indent:
() =>
({ state, commands, tr, dispatch }) => {
if (commands.sinkListItem('listItem')) {
return true
}
const node = currentIndentableNode(state)
if (!INDENT_TYPES.includes(node.type.name)) {
return false
}
const level = Math.min(
INDENT_MAX,
(node.attrs.indent ?? 0) + 1,
)
if (dispatch) {
const { $from } = state.selection
tr.setNodeAttribute(
$from.before($from.depth),
'indent',
level,
)
dispatch(tr)
}
return true
},
outdent:
() =>
({ state, commands, tr, dispatch }) => {
if (commands.liftListItem('listItem')) {
return true
}
const node = currentIndentableNode(state)
if (!INDENT_TYPES.includes(node.type.name)) {
return false
}
const level = Math.max(0, (node.attrs.indent ?? 0) - 1)
if (dispatch) {
const { $from } = state.selection
tr.setNodeAttribute(
$from.before($from.depth),
'indent',
level,
)
dispatch(tr)
}
return true
},
}
},
})