111 lines
3.6 KiB
TypeScript
111 lines
3.6 KiB
TypeScript
import { Plugin, PluginKey, Selection } from 'prosemirror-state'
|
||
import { createChainableState } from '../helpers/createChainableState'
|
||
import { CommandManager } from '../CommandManager'
|
||
import { Extension } from '../Extension'
|
||
|
||
export const Keymap = Extension.create({
|
||
name: 'keymap',
|
||
|
||
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
|
||
const { empty, $anchor } = selection
|
||
const { pos, parent } = $anchor
|
||
const isAtStart = Selection.atStart(doc).from === pos
|
||
|
||
if (
|
||
!empty
|
||
|| !isAtStart
|
||
|| !parent.type.isTextblock
|
||
|| parent.textContent.length
|
||
) {
|
||
return false
|
||
}
|
||
|
||
return commands.clearNodes()
|
||
}),
|
||
() => commands.deleteSelection(),
|
||
() => commands.joinBackward(),
|
||
() => commands.selectNodeBackward(),
|
||
])
|
||
|
||
const handleDelete = () => this.editor.commands.first(({ commands }) => [
|
||
() => commands.deleteSelection(),
|
||
() => commands.joinForward(),
|
||
() => commands.selectNodeForward(),
|
||
])
|
||
|
||
return {
|
||
Enter: () => this.editor.commands.first(({ commands }) => [
|
||
() => commands.newlineInCode(),
|
||
() => commands.createParagraphNear(),
|
||
() => commands.liftEmptyBlock(),
|
||
() => commands.splitBlock(),
|
||
]),
|
||
'Mod-Enter': () => this.editor.commands.exitCode(),
|
||
Backspace: handleBackspace,
|
||
'Mod-Backspace': handleBackspace,
|
||
'Shift-Backspace': handleBackspace,
|
||
Delete: handleDelete,
|
||
'Mod-Delete': handleDelete,
|
||
'Mod-a': () => this.editor.commands.selectAll(),
|
||
'Ctrl-a': () => this.editor.commands.selectTextblockStart(),
|
||
'Ctrl-e': () => this.editor.commands.selectTextblockEnd(),
|
||
Home: () => this.editor.commands.selectTextblockStart(),
|
||
End: () => this.editor.commands.selectTextblockStart(),
|
||
}
|
||
},
|
||
|
||
addProseMirrorPlugins() {
|
||
return [
|
||
// With this plugin we check if the whole document was selected and deleted.
|
||
// In this case we will additionally call `clearNodes()` to convert e.g. a heading
|
||
// to a paragraph if necessary.
|
||
// This is an alternative to ProseMirror's `AllSelection`, which doesn’t work well
|
||
// with many other commands.
|
||
new Plugin({
|
||
key: new PluginKey('clearDocument'),
|
||
appendTransaction: (transactions, oldState, newState) => {
|
||
const docChanges = transactions.some(transaction => transaction.docChanged)
|
||
&& !oldState.doc.eq(newState.doc)
|
||
|
||
if (!docChanges) {
|
||
return
|
||
}
|
||
|
||
const { empty, from, to } = oldState.selection
|
||
const allFrom = Selection.atStart(oldState.doc).from
|
||
const allEnd = Selection.atEnd(oldState.doc).to
|
||
const allWasSelected = from === allFrom && to === allEnd
|
||
const isEmpty = newState.doc.textBetween(0, newState.doc.content.size, ' ', ' ').length === 0
|
||
|
||
if (empty || !allWasSelected || !isEmpty) {
|
||
return
|
||
}
|
||
|
||
const tr = newState.tr
|
||
const state = createChainableState({
|
||
state: newState,
|
||
transaction: tr,
|
||
})
|
||
const { commands } = new CommandManager({
|
||
editor: this.editor,
|
||
state,
|
||
})
|
||
|
||
commands.clearNodes()
|
||
|
||
if (!tr.steps.length) {
|
||
return
|
||
}
|
||
|
||
return tr
|
||
},
|
||
}),
|
||
]
|
||
},
|
||
})
|