mirror of
https://github.com/ueberdosis/tiptap.git
synced 2025-01-18 22:36:14 +08:00
Fix List issues & add support for Mod keys (#4210)
* add support for mod-delete and mod-backspace * fix backspace not working right behind a list * move list helpers to core, add support for task lists * add option to check for node in isAtEndOfNode --------- Co-authored-by: bdbch <dominik@bdbch.com>
This commit is contained in:
parent
b581fa6523
commit
d1e879dfab
@ -1,3 +1,5 @@
|
||||
import { TextSelection } from '@tiptap/pm/state'
|
||||
|
||||
import { RawCommands } from '../types.js'
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
@ -11,17 +13,17 @@ declare module '@tiptap/core' {
|
||||
}
|
||||
}
|
||||
|
||||
export const cut: RawCommands['cut'] = (originRange, targetPos) => ({ editor }) => {
|
||||
export const cut: RawCommands['cut'] = (originRange, targetPos) => ({ editor, tr }) => {
|
||||
const { state } = editor
|
||||
|
||||
const contentSlice = state.doc.slice(originRange.from, originRange.to)
|
||||
|
||||
return editor
|
||||
.chain()
|
||||
.deleteRange(originRange)
|
||||
.command(({ commands, tr }) => {
|
||||
return commands.insertContentAt(tr.mapping.map(targetPos), contentSlice.content.toJSON())
|
||||
})
|
||||
.focus()
|
||||
.run()
|
||||
tr.deleteRange(originRange.from, originRange.to)
|
||||
const newPos = tr.mapping.map(targetPos)
|
||||
|
||||
tr.insert(newPos, contentSlice.content)
|
||||
|
||||
tr.setSelection(new TextSelection(tr.doc.resolve(newPos - 1)))
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ export * from './forEach.js'
|
||||
export * from './insertContent.js'
|
||||
export * from './insertContentAt.js'
|
||||
export * from './join.js'
|
||||
export * from './joinItemBackward.js'
|
||||
export * from './joinItemForward.js'
|
||||
export * from './keyboardShortcut.js'
|
||||
export * from './lift.js'
|
||||
export * from './liftEmptyBlock.js'
|
||||
|
@ -1,7 +1,19 @@
|
||||
import { RawCommands } from '@tiptap/core'
|
||||
import { joinPoint } from '@tiptap/pm/transform'
|
||||
|
||||
export const joinListItemBackward: RawCommands['splitListItem'] = () => ({
|
||||
import { RawCommands } from '../types.js'
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
joinItemBackward: {
|
||||
/**
|
||||
* Join two nodes Forwards.
|
||||
*/
|
||||
joinItemBackward: () => ReturnType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const joinItemBackward: RawCommands['joinItemBackward'] = () => ({
|
||||
tr, state, dispatch,
|
||||
}) => {
|
||||
try {
|
@ -1,7 +1,19 @@
|
||||
import { RawCommands } from '@tiptap/core'
|
||||
import { joinPoint } from '@tiptap/pm/transform'
|
||||
|
||||
export const joinListItemForward: RawCommands['splitListItem'] = () => ({
|
||||
import { RawCommands } from '../types.js'
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
joinItemForward: {
|
||||
/**
|
||||
* Join two nodes Forwards.
|
||||
*/
|
||||
joinItemForward: () => ReturnType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const joinItemForward: RawCommands['joinItemForward'] = () => ({
|
||||
state,
|
||||
dispatch,
|
||||
tr,
|
@ -12,6 +12,7 @@ export const Keymap = Extension.create({
|
||||
addKeyboardShortcuts() {
|
||||
const handleBackspace = () => this.editor.commands.first(({ commands }) => [
|
||||
() => commands.undoInputRule(),
|
||||
|
||||
// maybe convert first text block node to default node
|
||||
() => commands.command(({ tr }) => {
|
||||
const { selection, doc } = tr
|
||||
@ -32,6 +33,7 @@ export const Keymap = Extension.create({
|
||||
|
||||
return commands.clearNodes()
|
||||
}),
|
||||
|
||||
() => commands.deleteSelection(),
|
||||
() => commands.joinBackward(),
|
||||
() => commands.selectNodeBackward(),
|
||||
|
@ -44,6 +44,7 @@ export * from './isNodeActive.js'
|
||||
export * from './isNodeEmpty.js'
|
||||
export * from './isNodeSelection.js'
|
||||
export * from './isTextSelection.js'
|
||||
export * from './listHelpers/index.js'
|
||||
export * from './posToDOMRect.js'
|
||||
export * from './resolveFocusPosition.js'
|
||||
export * from './selectionToInsertionEnd.js'
|
||||
|
@ -1,7 +1,25 @@
|
||||
import { EditorState } from '@tiptap/pm/state'
|
||||
|
||||
export const istAtEndOfNode = (state: EditorState) => {
|
||||
const { $from, $to } = state.selection
|
||||
import { findParentNode } from './findParentNode.js'
|
||||
|
||||
export const isAtEndOfNode = (state: EditorState, nodeType?: string) => {
|
||||
const { $from, $to, $anchor } = state.selection
|
||||
|
||||
if (nodeType) {
|
||||
const parentNode = findParentNode(node => node.type.name === nodeType)(state.selection)
|
||||
|
||||
if (!parentNode) {
|
||||
return false
|
||||
}
|
||||
|
||||
const $parentPos = state.doc.resolve(parentNode.pos + 1)
|
||||
|
||||
if ($anchor.pos + 1 === $parentPos.end()) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if ($to.parentOffset < $to.parent.nodeSize - 2 || $from.pos !== $to.pos) {
|
||||
return false
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { getNodeType } from '@tiptap/core'
|
||||
import { NodeType } from '@tiptap/pm/model'
|
||||
import { EditorState } from '@tiptap/pm/state'
|
||||
import { ResolvedPos } from 'prosemirror-model'
|
||||
|
||||
export const getCurrentListItemPos = (typeOrName: string, state: EditorState): ResolvedPos | undefined => {
|
||||
import { getNodeType } from '../getNodeType.js'
|
||||
|
||||
export const findListItemPos = (typeOrName: string | NodeType, state: EditorState) => {
|
||||
const { $from } = state.selection
|
||||
const nodeType = getNodeType(typeOrName, state.schema)
|
||||
|
||||
@ -23,8 +24,8 @@ export const getCurrentListItemPos = (typeOrName: string, state: EditorState): R
|
||||
}
|
||||
|
||||
if (targetDepth === null) {
|
||||
return
|
||||
return null
|
||||
}
|
||||
|
||||
return state.doc.resolve(currentPos)
|
||||
return { $pos: state.doc.resolve(currentPos), depth: targetDepth }
|
||||
}
|
16
packages/core/src/helpers/listHelpers/getNextListDepth.ts
Normal file
16
packages/core/src/helpers/listHelpers/getNextListDepth.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { EditorState } from '@tiptap/pm/state'
|
||||
|
||||
import { getNodeAtPosition } from '../getNodeAtPosition.js'
|
||||
import { findListItemPos } from './findListItemPos.js'
|
||||
|
||||
export const getNextListDepth = (typeOrName: string, state: EditorState) => {
|
||||
const listItemPos = findListItemPos(typeOrName, state)
|
||||
|
||||
if (!listItemPos) {
|
||||
return false
|
||||
}
|
||||
|
||||
const [, depth] = getNodeAtPosition(state, typeOrName, listItemPos.$pos.pos + 4)
|
||||
|
||||
return depth
|
||||
}
|
76
packages/core/src/helpers/listHelpers/handleBackspace.ts
Normal file
76
packages/core/src/helpers/listHelpers/handleBackspace.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { Node } from '@tiptap/pm/model'
|
||||
|
||||
import { Editor } from '../../Editor.js'
|
||||
import { isAtStartOfNode } from '../isAtStartOfNode.js'
|
||||
import { isNodeActive } from '../isNodeActive.js'
|
||||
import { findListItemPos } from './findListItemPos.js'
|
||||
import { hasListBefore } from './hasListBefore.js'
|
||||
import { hasListItemBefore } from './hasListItemBefore.js'
|
||||
import { listItemHasSubList } from './listItemHasSubList.js'
|
||||
|
||||
export const handleBackspace = (editor: Editor, name: string, parentListTypes: string[]) => {
|
||||
// this is required to still handle the undo handling
|
||||
if (editor.commands.undoInputRule()) {
|
||||
return true
|
||||
}
|
||||
|
||||
// if the current item is NOT inside a list item &
|
||||
// the previous item is a list (orderedList or bulletList)
|
||||
// move the cursor into the list and delete the current item
|
||||
if (!isNodeActive(editor.state, name) && hasListBefore(editor.state, name, parentListTypes)) {
|
||||
const { $anchor } = editor.state.selection
|
||||
|
||||
const $listPos = editor.state.doc.resolve($anchor.before() - 1)
|
||||
|
||||
const listDescendants: Array<{ node: Node, pos: number }> = []
|
||||
|
||||
$listPos.node().descendants((node, pos) => {
|
||||
if (node.type.name === name) {
|
||||
listDescendants.push({ node, pos })
|
||||
}
|
||||
})
|
||||
|
||||
const lastItem = listDescendants.at(-1)
|
||||
|
||||
if (!lastItem) {
|
||||
return false
|
||||
}
|
||||
|
||||
const $lastItemPos = editor.state.doc.resolve($listPos.start() + lastItem.pos + 1)
|
||||
|
||||
return editor.chain().cut({ from: $anchor.start() - 1, to: $anchor.end() + 1 }, $lastItemPos.end()).joinForward().run()
|
||||
}
|
||||
|
||||
// if the cursor is not inside the current node type
|
||||
// do nothing and proceed
|
||||
if (!isNodeActive(editor.state, name)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// if the cursor is not at the start of a node
|
||||
// do nothing and proceed
|
||||
if (!isAtStartOfNode(editor.state)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const listItemPos = findListItemPos(name, editor.state)
|
||||
|
||||
if (!listItemPos) {
|
||||
return false
|
||||
}
|
||||
|
||||
const $prev = editor.state.doc.resolve(listItemPos.$pos.pos - 2)
|
||||
const prevNode = $prev.node(listItemPos.depth)
|
||||
|
||||
const previousListItemHasSubList = listItemHasSubList(name, editor.state, prevNode)
|
||||
|
||||
// if the previous item is a list item and doesn't have a sublist, join the list items
|
||||
if (hasListItemBefore(name, editor.state) && !previousListItemHasSubList) {
|
||||
return editor.commands.joinItemBackward()
|
||||
}
|
||||
|
||||
// otherwise in the end, a backspace should
|
||||
// always just lift the list item if
|
||||
// joining / merging is not possible
|
||||
return editor.chain().liftListItem(name).run()
|
||||
}
|
38
packages/core/src/helpers/listHelpers/handleDelete.ts
Normal file
38
packages/core/src/helpers/listHelpers/handleDelete.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { Editor } from '../../Editor.js'
|
||||
import { isAtEndOfNode } from '../isAtEndOfNode.js'
|
||||
import { isNodeActive } from '../isNodeActive.js'
|
||||
import { nextListIsDeeper } from './nextListIsDeeper.js'
|
||||
import { nextListIsHigher } from './nextListIsHigher.js'
|
||||
|
||||
export const handleDelete = (editor: Editor, name: string) => {
|
||||
// if the cursor is not inside the current node type
|
||||
// do nothing and proceed
|
||||
if (!isNodeActive(editor.state, name)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// if the cursor is not at the end of a node
|
||||
// do nothing and proceed
|
||||
if (!isAtEndOfNode(editor.state, name)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if the next node is a list with a deeper depth
|
||||
if (nextListIsDeeper(name, editor.state)) {
|
||||
return editor
|
||||
.chain()
|
||||
.focus(editor.state.selection.from + 4)
|
||||
.lift(name)
|
||||
.joinBackward()
|
||||
.run()
|
||||
}
|
||||
|
||||
if (nextListIsHigher(name, editor.state)) {
|
||||
return editor.chain()
|
||||
.joinForward()
|
||||
.joinBackward()
|
||||
.run()
|
||||
}
|
||||
|
||||
return editor.commands.joinItemForward()
|
||||
}
|
15
packages/core/src/helpers/listHelpers/hasListBefore.ts
Normal file
15
packages/core/src/helpers/listHelpers/hasListBefore.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { EditorState } from '@tiptap/pm/state'
|
||||
|
||||
export const hasListBefore = (editorState: EditorState, name: string, parentListTypes: string[]) => {
|
||||
const { $anchor } = editorState.selection
|
||||
|
||||
const previousNodePos = Math.max(0, $anchor.pos - 2)
|
||||
|
||||
const previousNode = editorState.doc.resolve(previousNodePos).node()
|
||||
|
||||
if (!previousNode || !parentListTypes.includes(previousNode.type.name)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
10
packages/core/src/helpers/listHelpers/index.ts
Normal file
10
packages/core/src/helpers/listHelpers/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export * from './findListItemPos.js'
|
||||
export * from './getNextListDepth.js'
|
||||
export * from './handleBackspace.js'
|
||||
export * from './handleDelete.js'
|
||||
export * from './hasListBefore.js'
|
||||
export * from './hasListItemAfter.js'
|
||||
export * from './hasListItemBefore.js'
|
||||
export * from './listItemHasSubList.js'
|
||||
export * from './nextListIsDeeper.js'
|
||||
export * from './nextListIsHigher.js'
|
@ -1,7 +1,8 @@
|
||||
import { getNodeType } from '@tiptap/core'
|
||||
import { Node } from '@tiptap/pm/model'
|
||||
import { EditorState } from '@tiptap/pm/state'
|
||||
|
||||
import { getNodeType } from '../getNodeType.js'
|
||||
|
||||
export const listItemHasSubList = (typeOrName: string, state: EditorState, node?: Node) => {
|
||||
if (!node) {
|
||||
return false
|
19
packages/core/src/helpers/listHelpers/nextListIsDeeper.ts
Normal file
19
packages/core/src/helpers/listHelpers/nextListIsDeeper.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { EditorState } from '@tiptap/pm/state'
|
||||
|
||||
import { findListItemPos } from './findListItemPos.js'
|
||||
import { getNextListDepth } from './getNextListDepth.js'
|
||||
|
||||
export const nextListIsDeeper = (typeOrName: string, state: EditorState) => {
|
||||
const listDepth = getNextListDepth(typeOrName, state)
|
||||
const listItemPos = findListItemPos(typeOrName, state)
|
||||
|
||||
if (!listItemPos || !listDepth) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (listDepth > listItemPos.depth) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
19
packages/core/src/helpers/listHelpers/nextListIsHigher.ts
Normal file
19
packages/core/src/helpers/listHelpers/nextListIsHigher.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { EditorState } from '@tiptap/pm/state'
|
||||
|
||||
import { findListItemPos } from './findListItemPos.js'
|
||||
import { getNextListDepth } from './getNextListDepth.js'
|
||||
|
||||
export const nextListIsHigher = (typeOrName: string, state: EditorState) => {
|
||||
const listDepth = getNextListDepth(typeOrName, state)
|
||||
const listItemPos = findListItemPos(typeOrName, state)
|
||||
|
||||
if (!listItemPos || !listDepth) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (listDepth < listItemPos.depth) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
import { getNodeAtPosition, getNodeType } from '@tiptap/core'
|
||||
import { NodeType } from '@tiptap/pm/model'
|
||||
import { EditorState } from '@tiptap/pm/state'
|
||||
|
||||
export * from './hasListItemAfter.js'
|
||||
export * from './hasListItemBefore.js'
|
||||
export * from './listItemhasSublist.js'
|
||||
|
||||
export const findListItemPos = (typeOrName: string | NodeType, state: EditorState) => {
|
||||
const { $from } = state.selection
|
||||
const nodeType = getNodeType(typeOrName, state.schema)
|
||||
|
||||
let currentNode = null
|
||||
let currentDepth = $from.depth
|
||||
let currentPos = $from.pos
|
||||
let targetDepth: number | null = null
|
||||
|
||||
while (currentDepth > 0 && targetDepth === null) {
|
||||
currentNode = $from.node(currentDepth)
|
||||
|
||||
if (currentNode.type === nodeType) {
|
||||
targetDepth = currentDepth
|
||||
} else {
|
||||
currentDepth -= 1
|
||||
currentPos -= 1
|
||||
}
|
||||
}
|
||||
|
||||
if (targetDepth === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return { $pos: state.doc.resolve(currentPos), depth: targetDepth }
|
||||
}
|
||||
|
||||
export const getNextListDepth = (typeOrName: string, state: EditorState) => {
|
||||
const listItemPos = findListItemPos(typeOrName, state)
|
||||
|
||||
if (!listItemPos) {
|
||||
return false
|
||||
}
|
||||
|
||||
const [, depth] = getNodeAtPosition(state, typeOrName, listItemPos.$pos.pos + 4)
|
||||
|
||||
return depth
|
||||
}
|
||||
|
||||
export const nextListIsDeeper = (typeOrName: string, state: EditorState) => {
|
||||
const listDepth = getNextListDepth(typeOrName, state)
|
||||
const listItemPos = findListItemPos(typeOrName, state)
|
||||
|
||||
if (!listItemPos || !listDepth) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (listDepth > listItemPos.depth) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export const nextListIsHigher = (typeOrName: string, state: EditorState) => {
|
||||
const listDepth = getNextListDepth(typeOrName, state)
|
||||
const listItemPos = findListItemPos(typeOrName, state)
|
||||
|
||||
if (!listItemPos || !listDepth) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (listDepth < listItemPos.depth) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
@ -1,28 +1,12 @@
|
||||
import {
|
||||
isAtStartOfNode, isNodeActive, istAtEndOfNode, mergeAttributes, Node,
|
||||
handleBackspace, handleDelete,
|
||||
mergeAttributes, Node,
|
||||
} from '@tiptap/core'
|
||||
import { NodeType } from '@tiptap/pm/model'
|
||||
|
||||
import { joinListItemBackward } from './commands/joinListItemBackward.js'
|
||||
import { joinListItemForward } from './commands/joinListItemForward.js'
|
||||
import {
|
||||
findListItemPos, hasListItemBefore, listItemHasSubList, nextListIsDeeper, nextListIsHigher,
|
||||
} from './helpers/index.js'
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
listItem: {
|
||||
/**
|
||||
* Lift the list item into a wrapping list.
|
||||
*/
|
||||
joinListItemForward: (typeOrName: string | NodeType) => ReturnType
|
||||
joinListItemBackward: (typeOrName: string | NodeType) => ReturnType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface ListItemOptions {
|
||||
HTMLAttributes: Record<string, any>,
|
||||
bulletListTypeName: string
|
||||
orderedListTypeName: string
|
||||
}
|
||||
|
||||
export const ListItem = Node.create<ListItemOptions>({
|
||||
@ -31,6 +15,8 @@ export const ListItem = Node.create<ListItemOptions>({
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
bulletListTypeName: 'bulletList',
|
||||
orderedListTypeName: 'orderedList',
|
||||
}
|
||||
},
|
||||
|
||||
@ -50,90 +36,15 @@ export const ListItem = Node.create<ListItemOptions>({
|
||||
return ['li', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
return {
|
||||
joinListItemForward,
|
||||
joinListItemBackward,
|
||||
}
|
||||
},
|
||||
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
Enter: () => this.editor.commands.splitListItem(this.name),
|
||||
Tab: () => this.editor.commands.sinkListItem(this.name),
|
||||
'Shift-Tab': () => this.editor.commands.liftListItem(this.name),
|
||||
Delete: ({ editor }) => {
|
||||
// if the cursor is not inside the current node type
|
||||
// do nothing and proceed
|
||||
if (!isNodeActive(editor.state, this.name)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// if the cursor is not at the end of a node
|
||||
// do nothing and proceed
|
||||
if (!istAtEndOfNode(editor.state)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if the next node is a list with a deeper depth
|
||||
if (nextListIsDeeper(this.name, editor.state)) {
|
||||
return editor
|
||||
.chain()
|
||||
.focus(editor.state.selection.from + 4)
|
||||
.lift(this.name)
|
||||
.joinBackward()
|
||||
.run()
|
||||
}
|
||||
|
||||
if (nextListIsHigher(this.name, editor.state)) {
|
||||
return editor.chain()
|
||||
.joinForward()
|
||||
.joinBackward()
|
||||
.run()
|
||||
}
|
||||
|
||||
// check if the next node is also a listItem
|
||||
return editor.commands.joinListItemForward(this.name)
|
||||
},
|
||||
Backspace: ({ editor }) => {
|
||||
// this is required to still handle the undo handling
|
||||
if (this.editor.commands.undoInputRule()) {
|
||||
return true
|
||||
}
|
||||
|
||||
// if the cursor is not inside the current node type
|
||||
// do nothing and proceed
|
||||
if (!isNodeActive(editor.state, this.name)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// if the cursor is not at the start of a node
|
||||
// do nothing and proceed
|
||||
if (!isAtStartOfNode(editor.state)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const listItemPos = findListItemPos(this.name, editor.state)
|
||||
|
||||
if (!listItemPos) {
|
||||
return false
|
||||
}
|
||||
|
||||
const $prev = editor.state.doc.resolve(listItemPos.$pos.pos - 2)
|
||||
const prevNode = $prev.node(listItemPos.depth)
|
||||
|
||||
const previousListItemHasSubList = listItemHasSubList(this.name, editor.state, prevNode)
|
||||
|
||||
// if the previous item is a list item and doesn't have a sublist, join the list items
|
||||
if (hasListItemBefore(this.name, editor.state) && !previousListItemHasSubList) {
|
||||
return editor.commands.joinListItemBackward(this.name)
|
||||
}
|
||||
|
||||
// otherwise in the end, a backspace should
|
||||
// always just lift the list item if
|
||||
// joining / merging is not possible
|
||||
return editor.chain().liftListItem(this.name).run()
|
||||
},
|
||||
Delete: ({ editor }) => handleDelete(editor, this.name),
|
||||
'Mod-Delete': ({ editor }) => handleDelete(editor, this.name),
|
||||
Backspace: ({ editor }) => handleBackspace(editor, this.name, [this.options.bulletListTypeName, this.options.orderedListTypeName]),
|
||||
'Mod-Backspace': ({ editor }) => handleBackspace(editor, this.name, [this.options.bulletListTypeName, this.options.orderedListTypeName]),
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -1,10 +1,13 @@
|
||||
import { mergeAttributes, Node, wrappingInputRule } from '@tiptap/core'
|
||||
import {
|
||||
handleBackspace, handleDelete, KeyboardShortcutCommand, mergeAttributes, Node, wrappingInputRule,
|
||||
} from '@tiptap/core'
|
||||
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
|
||||
|
||||
export interface TaskItemOptions {
|
||||
onReadOnlyChecked?: (node: ProseMirrorNode, checked: boolean) => boolean
|
||||
nested: boolean
|
||||
HTMLAttributes: Record<string, any>
|
||||
taskListTypeName: string
|
||||
}
|
||||
|
||||
export const inputRegex = /^\s*(\[([( |x])?\])\s$/
|
||||
@ -16,6 +19,7 @@ export const TaskItem = Node.create<TaskItemOptions>({
|
||||
return {
|
||||
nested: false,
|
||||
HTMLAttributes: {},
|
||||
taskListTypeName: 'taskList',
|
||||
}
|
||||
},
|
||||
|
||||
@ -69,9 +73,15 @@ export const TaskItem = Node.create<TaskItemOptions>({
|
||||
},
|
||||
|
||||
addKeyboardShortcuts() {
|
||||
const shortcuts = {
|
||||
const shortcuts: {
|
||||
[key: string]: KeyboardShortcutCommand
|
||||
} = {
|
||||
Enter: () => this.editor.commands.splitListItem(this.name),
|
||||
'Shift-Tab': () => this.editor.commands.liftListItem(this.name),
|
||||
Delete: ({ editor }) => handleDelete(editor, this.name),
|
||||
'Mod-Delete': ({ editor }) => handleDelete(editor, this.name),
|
||||
Backspace: ({ editor }) => handleBackspace(editor, this.name, [this.options.taskListTypeName]),
|
||||
'Mod-Backspace': ({ editor }) => handleBackspace(editor, this.name, [this.options.taskListTypeName]),
|
||||
}
|
||||
|
||||
if (!this.options.nested) {
|
||||
|
Loading…
Reference in New Issue
Block a user