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:
Dominik
2022-08-22 15:23:44 +02:00
committed by GitHub
parent f558417a25
commit 5fed0f2fc6
4 changed files with 65 additions and 4 deletions

View File

@@ -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 #### Group
Add this mark to a group of extensions, which can be referred to in the content attribute of the schema. Add this mark to a group of extensions, which can be referred to in the content attribute of the schema.

View File

@@ -3,7 +3,7 @@ import { Node as ProsemirrorNode, Schema } from 'prosemirror-model'
import { Plugin } from 'prosemirror-state' import { Plugin } from 'prosemirror-state'
import { Decoration, EditorView } from 'prosemirror-view' import { Decoration, EditorView } from 'prosemirror-view'
import { NodeConfig } from '.' import { Mark, NodeConfig } from '.'
import { Editor } from './Editor' import { Editor } from './Editor'
import { getAttributesFromExtensions } from './helpers/getAttributesFromExtensions' import { getAttributesFromExtensions } from './helpers/getAttributesFromExtensions'
import { getExtensionField } from './helpers/getExtensionField' import { getExtensionField } from './helpers/getExtensionField'
@@ -252,6 +252,13 @@ export class ExtensionManager {
context, 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) { if (addKeyboardShortcuts) {
const bindings = Object.fromEntries( const bindings = Object.fromEntries(
Object Object
@@ -261,10 +268,12 @@ export class ExtensionManager {
}), }),
) )
const keyMapPlugin = keymap(bindings) defaultBindings = { ...defaultBindings, ...bindings }
}
const keyMapPlugin = keymap(defaultBindings)
plugins.push(keyMapPlugin) plugins.push(keyMapPlugin)
}
const addInputRules = getExtensionField<AnyConfig['addInputRules']>( const addInputRules = getExtensionField<AnyConfig['addInputRules']>(
extension, extension,

View File

@@ -304,6 +304,11 @@ declare module '@tiptap/core' {
parent: ParentConfig<MarkConfig<Options, Storage>>['excludes'], parent: ParentConfig<MarkConfig<Options, Storage>>['excludes'],
}) => MarkSpec['excludes']), }) => MarkSpec['excludes']),
/**
* Marks this Mark as exitable
*/
exitable?: boolean | (() => boolean),
/** /**
* Group * Group
*/ */
@@ -486,4 +491,38 @@ export class Mark<Options = any, Storage = any> {
return extension 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
}
} }

View File

@@ -44,6 +44,8 @@ export const Code = Mark.create<CodeOptions>({
code: true, code: true,
exitable: true,
parseHTML() { parseHTML() {
return [ return [
{ tag: 'code' }, { tag: 'code' },