tiptap/packages/core/src/commands/splitBlock.ts

129 lines
2.9 KiB
TypeScript
Raw Normal View History

2020-11-05 05:38:52 +08:00
import { canSplit } from 'prosemirror-transform'
import { ContentMatch, Fragment } from 'prosemirror-model'
import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state'
2021-02-10 16:59:35 +08:00
import { Command, Commands } from '../types'
2021-01-29 16:33:42 +08:00
import getSplittedAttributes from '../helpers/getSplittedAttributes'
2020-11-05 05:38:52 +08:00
function defaultBlockAt(match: ContentMatch) {
for (let i = 0; i < match.edgeCount; i + 1) {
const { type } = match.edge(i)
2020-11-11 21:00:56 +08:00
if (type.isTextblock && !type.hasRequiredAttrs()) {
return type
}
2020-11-05 05:38:52 +08:00
}
return null
}
2021-02-11 01:05:02 +08:00
function ensureMarks(state: EditorState) {
2020-11-05 05:38:52 +08:00
const marks = state.storedMarks
|| (state.selection.$to.parentOffset && state.selection.$from.marks())
if (marks) {
state.tr.ensureMarks(marks)
}
}
2021-02-11 01:05:02 +08:00
declare module '@tiptap/core' {
2021-02-16 18:27:58 +08:00
interface AllCommands {
splitBlock: {
/**
* Forks a new node from an existing node.
*/
splitBlock: (options?: { keepMarks?: boolean }) => Command,
}
2021-02-11 01:05:02 +08:00
}
}
export const splitBlock: Commands['splitBlock'] = ({ keepMarks = true } = {}) => ({
2021-01-29 02:56:35 +08:00
tr,
state,
dispatch,
editor,
}) => {
2020-11-05 05:38:52 +08:00
const { selection, doc } = tr
const { $from, $to } = selection
2021-01-29 02:56:35 +08:00
const extensionAttributes = editor.extensionManager.attributes
2021-01-29 16:33:42 +08:00
const newAttributes = getSplittedAttributes(
extensionAttributes,
$from.node().type.name,
$from.node().attrs,
)
2021-01-29 02:56:35 +08:00
2020-11-05 05:38:52 +08:00
if (selection instanceof NodeSelection && selection.node.isBlock) {
if (!$from.parentOffset || !canSplit(doc, $from.pos)) {
return false
}
if (dispatch) {
2021-02-11 01:05:02 +08:00
if (keepMarks) {
ensureMarks(state)
2020-11-05 05:38:52 +08:00
}
tr.split($from.pos).scrollIntoView()
}
return true
}
if (!$from.parent.isBlock) {
return false
}
if (dispatch) {
const atEnd = $to.parentOffset === $to.parent.content.size
if (selection instanceof TextSelection) {
tr.deleteSelection()
}
const deflt = $from.depth === 0
? undefined
: defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1)))
let types = atEnd && deflt
? [{
type: deflt,
2021-01-29 02:56:35 +08:00
attrs: newAttributes,
2020-11-05 05:38:52 +08:00
}]
: undefined
let can = canSplit(tr.doc, tr.mapping.map($from.pos), 1, types)
if (
!types
&& !can
&& canSplit(tr.doc, tr.mapping.map($from.pos), 1, deflt ? [{ type: deflt }] : undefined)
) {
can = true
types = deflt
? [{
type: deflt,
2021-01-29 02:56:35 +08:00
attrs: newAttributes,
2020-11-05 05:38:52 +08:00
}]
: undefined
}
if (can) {
tr.split(tr.mapping.map($from.pos), 1, types)
if (
!atEnd
&& !$from.parentOffset
&& $from.parent.type !== deflt
&& $from.node(-1).canReplace($from.index(-1), $from.indexAfter(-1), Fragment.from(deflt?.create()))
) {
tr.setNodeMarkup(tr.mapping.map($from.before()), deflt || undefined)
}
}
2021-02-11 01:05:02 +08:00
if (keepMarks) {
ensureMarks(state)
2020-11-05 05:38:52 +08:00
}
tr.scrollIntoView()
}
return true
}