feature(core): add exit handling for marks (#2925)
* feat(core): add exit handling for marks * docs(core): add information about exitable marks
This commit is contained in:
@@ -272,6 +272,17 @@ Mark.create({
|
||||
})
|
||||
```
|
||||
|
||||
#### Exitable
|
||||
By default a mark will "trap" the cursor meaning the cursor can't get out of the mark except by moving the cursor left to right into text without a mark.
|
||||
If this is set to true, the mark will be exitable when the mark is at the end of a node. This is handy for example code marks.
|
||||
|
||||
```js
|
||||
Mark.create({
|
||||
// make this mark exitable - default is false
|
||||
exitable: true,
|
||||
})
|
||||
```
|
||||
|
||||
#### Group
|
||||
Add this mark to a group of extensions, which can be referred to in the content attribute of the schema.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Node as ProsemirrorNode, Schema } from 'prosemirror-model'
|
||||
import { Plugin } from 'prosemirror-state'
|
||||
import { Decoration, EditorView } from 'prosemirror-view'
|
||||
|
||||
import { NodeConfig } from '.'
|
||||
import { Mark, NodeConfig } from '.'
|
||||
import { Editor } from './Editor'
|
||||
import { getAttributesFromExtensions } from './helpers/getAttributesFromExtensions'
|
||||
import { getExtensionField } from './helpers/getExtensionField'
|
||||
@@ -252,6 +252,13 @@ export class ExtensionManager {
|
||||
context,
|
||||
)
|
||||
|
||||
let defaultBindings: Record<string, () => boolean> = {}
|
||||
|
||||
// bind exit handling
|
||||
if (extension.type === 'mark' && extension.config.exitable) {
|
||||
defaultBindings.ArrowRight = () => Mark.handleExit({ editor, mark: (extension as Mark) })
|
||||
}
|
||||
|
||||
if (addKeyboardShortcuts) {
|
||||
const bindings = Object.fromEntries(
|
||||
Object
|
||||
@@ -261,11 +268,13 @@ export class ExtensionManager {
|
||||
}),
|
||||
)
|
||||
|
||||
const keyMapPlugin = keymap(bindings)
|
||||
|
||||
plugins.push(keyMapPlugin)
|
||||
defaultBindings = { ...defaultBindings, ...bindings }
|
||||
}
|
||||
|
||||
const keyMapPlugin = keymap(defaultBindings)
|
||||
|
||||
plugins.push(keyMapPlugin)
|
||||
|
||||
const addInputRules = getExtensionField<AnyConfig['addInputRules']>(
|
||||
extension,
|
||||
'addInputRules',
|
||||
|
||||
@@ -304,6 +304,11 @@ declare module '@tiptap/core' {
|
||||
parent: ParentConfig<MarkConfig<Options, Storage>>['excludes'],
|
||||
}) => MarkSpec['excludes']),
|
||||
|
||||
/**
|
||||
* Marks this Mark as exitable
|
||||
*/
|
||||
exitable?: boolean | (() => boolean),
|
||||
|
||||
/**
|
||||
* Group
|
||||
*/
|
||||
@@ -486,4 +491,38 @@ export class Mark<Options = any, Storage = any> {
|
||||
|
||||
return extension
|
||||
}
|
||||
|
||||
static handleExit({
|
||||
editor,
|
||||
mark,
|
||||
}: {
|
||||
editor: Editor
|
||||
mark: Mark
|
||||
}) {
|
||||
const { tr } = editor.state
|
||||
const currentPos = editor.state.selection.$from
|
||||
const isAtEnd = currentPos.pos === currentPos.end()
|
||||
|
||||
if (isAtEnd) {
|
||||
const currentMarks = currentPos.marks()
|
||||
const isInMark = !!currentMarks.find(m => m?.type.name === mark.name)
|
||||
|
||||
if (!isInMark) {
|
||||
return false
|
||||
}
|
||||
|
||||
const removeMark = currentMarks.find(m => m?.type.name === mark.name)
|
||||
|
||||
if (removeMark) {
|
||||
tr.removeStoredMark(removeMark)
|
||||
}
|
||||
tr.insertText(' ', currentPos.pos)
|
||||
|
||||
editor.view.dispatch(tr)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ export const Code = Mark.create<CodeOptions>({
|
||||
|
||||
code: true,
|
||||
|
||||
exitable: true,
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{ tag: 'code' },
|
||||
|
||||
Reference in New Issue
Block a user