Files
RenewCMS/web/src/lib/articleIndent.ts
T

84 lines
1.9 KiB
TypeScript

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
},
}
},
})