2023-08-11 07:44:46 +08:00
|
|
|
import { Extension } from '@tiptap/core'
|
|
|
|
|
|
|
|
import { handleBackspace, handleDelete } from './listHelpers/index.js'
|
|
|
|
|
|
|
|
export type ListKeymapOptions = {
|
2024-05-11 20:30:44 +08:00
|
|
|
/**
|
|
|
|
* An array of list types. This is used for item and wrapper list matching.
|
|
|
|
* @default []
|
|
|
|
* @example [{ itemName: 'listItem', wrapperNames: ['bulletList', 'orderedList'] }]
|
|
|
|
*/
|
2023-08-11 07:44:46 +08:00
|
|
|
listTypes: Array<{
|
|
|
|
itemName: string,
|
|
|
|
wrapperNames: string[],
|
|
|
|
}>
|
|
|
|
}
|
|
|
|
|
2024-05-11 20:30:44 +08:00
|
|
|
/**
|
|
|
|
* This extension registers custom keymaps to change the behaviour of the backspace and delete keys.
|
|
|
|
* By default Prosemirror keyhandling will always lift or sink items so paragraphs are joined into
|
|
|
|
* the adjacent or previous list item. This extension will prevent this behaviour and instead will
|
|
|
|
* try to join paragraphs from two list items into a single list item.
|
|
|
|
* @see https://www.tiptap.dev/api/extensions/list-keymap
|
|
|
|
*/
|
2023-08-11 07:44:46 +08:00
|
|
|
export const ListKeymap = Extension.create<ListKeymapOptions>({
|
|
|
|
name: 'listKeymap',
|
|
|
|
|
|
|
|
addOptions() {
|
|
|
|
return {
|
|
|
|
listTypes: [
|
|
|
|
{
|
|
|
|
itemName: 'listItem',
|
|
|
|
wrapperNames: ['bulletList', 'orderedList'],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
itemName: 'taskItem',
|
|
|
|
wrapperNames: ['taskList'],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
addKeyboardShortcuts() {
|
|
|
|
return {
|
|
|
|
Delete: ({ editor }) => {
|
|
|
|
let handled = false
|
|
|
|
|
|
|
|
this.options.listTypes.forEach(({ itemName }) => {
|
|
|
|
if (editor.state.schema.nodes[itemName] === undefined) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (handleDelete(editor, itemName)) {
|
|
|
|
handled = true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return handled
|
|
|
|
},
|
|
|
|
'Mod-Delete': ({ editor }) => {
|
|
|
|
let handled = false
|
|
|
|
|
|
|
|
this.options.listTypes.forEach(({ itemName }) => {
|
|
|
|
if (editor.state.schema.nodes[itemName] === undefined) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (handleDelete(editor, itemName)) {
|
|
|
|
handled = true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return handled
|
|
|
|
},
|
|
|
|
Backspace: ({ editor }) => {
|
|
|
|
let handled = false
|
|
|
|
|
|
|
|
this.options.listTypes.forEach(({ itemName, wrapperNames }) => {
|
|
|
|
if (editor.state.schema.nodes[itemName] === undefined) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (handleBackspace(editor, itemName, wrapperNames)) {
|
|
|
|
handled = true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return handled
|
|
|
|
},
|
|
|
|
'Mod-Backspace': ({ editor }) => {
|
|
|
|
let handled = false
|
|
|
|
|
|
|
|
this.options.listTypes.forEach(({ itemName, wrapperNames }) => {
|
|
|
|
if (editor.state.schema.nodes[itemName] === undefined) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (handleBackspace(editor, itemName, wrapperNames)) {
|
|
|
|
handled = true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return handled
|
|
|
|
},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|