@portabletext/editor 1.1.5 → 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/lib/index.d.mts +403 -0
- package/lib/index.d.ts +403 -0
- package/lib/index.esm.js +220 -105
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +213 -98
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +220 -105
- package/lib/index.mjs.map +1 -1
- package/package.json +18 -18
- package/src/editor/Editable.tsx +5 -0
- package/src/editor/PortableTextEditor.tsx +15 -6
- package/src/editor/__tests__/self-solving.test.tsx +1 -1
- package/src/editor/behavior/behavior.actions.ts +39 -0
- package/src/editor/behavior/behavior.core.ts +37 -0
- package/src/editor/behavior/behavior.types.ts +106 -0
- package/src/editor/behavior/behavior.utils.ts +34 -0
- package/src/editor/editor-machine.ts +113 -1
- package/src/editor/plugins/createWithHotKeys.ts +0 -32
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +81 -109
- package/src/index.ts +10 -1
- package/src/utils/sibling-utils.ts +55 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -51,19 +51,19 @@
|
|
|
51
51
|
"xstate": "^5.18.2"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@babel/preset-env": "^7.25.
|
|
55
|
-
"@babel/preset-react": "^7.
|
|
54
|
+
"@babel/preset-env": "^7.25.9",
|
|
55
|
+
"@babel/preset-react": "^7.25.9",
|
|
56
56
|
"@jest/globals": "^29.7.0",
|
|
57
57
|
"@jest/types": "^29.6.3",
|
|
58
58
|
"@playwright/test": "1.48.1",
|
|
59
59
|
"@portabletext/toolkit": "^2.0.15",
|
|
60
|
-
"@sanity/block-tools": "^3.
|
|
60
|
+
"@sanity/block-tools": "^3.62.1",
|
|
61
61
|
"@sanity/diff-match-patch": "^3.1.1",
|
|
62
|
-
"@sanity/pkg-utils": "^6.11.
|
|
63
|
-
"@sanity/schema": "^3.
|
|
64
|
-
"@sanity/types": "^3.
|
|
65
|
-
"@sanity/ui": "^2.8.
|
|
66
|
-
"@sanity/util": "^3.
|
|
62
|
+
"@sanity/pkg-utils": "^6.11.4",
|
|
63
|
+
"@sanity/schema": "^3.62.1",
|
|
64
|
+
"@sanity/types": "^3.62.1",
|
|
65
|
+
"@sanity/ui": "^2.8.10",
|
|
66
|
+
"@sanity/util": "^3.62.1",
|
|
67
67
|
"@testing-library/dom": "^10.4.0",
|
|
68
68
|
"@testing-library/jest-dom": "^6.6.2",
|
|
69
69
|
"@testing-library/react": "^16.0.1",
|
|
@@ -71,17 +71,17 @@
|
|
|
71
71
|
"@types/debug": "^4.1.5",
|
|
72
72
|
"@types/express": "^4.17.21",
|
|
73
73
|
"@types/express-ws": "^3.0.5",
|
|
74
|
-
"@types/lodash": "^4.17.
|
|
74
|
+
"@types/lodash": "^4.17.12",
|
|
75
75
|
"@types/node": "^18.19.8",
|
|
76
76
|
"@types/node-ipc": "^9.2.3",
|
|
77
|
-
"@types/react": "^18.3.
|
|
77
|
+
"@types/react": "^18.3.12",
|
|
78
78
|
"@types/react-dom": "^18.3.1",
|
|
79
79
|
"@types/ws": "~8.5.12",
|
|
80
80
|
"@vitejs/plugin-react": "^4.3.3",
|
|
81
81
|
"@vitest/browser": "^2.1.3",
|
|
82
82
|
"@xstate/react": "^4.1.3",
|
|
83
83
|
"dotenv": "^16.4.5",
|
|
84
|
-
"express": "^4.
|
|
84
|
+
"express": "^4.21.1",
|
|
85
85
|
"express-ws": "^5.0.2",
|
|
86
86
|
"jest": "^29.7.0",
|
|
87
87
|
"jest-dev-server": "^10.1.1",
|
|
@@ -95,16 +95,16 @@
|
|
|
95
95
|
"styled-components": "^6.1.13",
|
|
96
96
|
"ts-node": "^10.9.2",
|
|
97
97
|
"typescript": "5.6.3",
|
|
98
|
-
"vite": "^5.4.
|
|
98
|
+
"vite": "^5.4.10",
|
|
99
99
|
"vitest": "^2.1.3",
|
|
100
|
-
"vitest-browser-react": "^0.0.
|
|
100
|
+
"vitest-browser-react": "^0.0.3",
|
|
101
101
|
"@sanity/gherkin-driver": "^0.0.1"
|
|
102
102
|
},
|
|
103
103
|
"peerDependencies": {
|
|
104
|
-
"@sanity/block-tools": "^3.
|
|
105
|
-
"@sanity/schema": "^3.
|
|
106
|
-
"@sanity/types": "^3.
|
|
107
|
-
"@sanity/util": "^3.
|
|
104
|
+
"@sanity/block-tools": "^3.62.1",
|
|
105
|
+
"@sanity/schema": "^3.62.1",
|
|
106
|
+
"@sanity/types": "^3.62.1",
|
|
107
|
+
"@sanity/util": "^3.62.1",
|
|
108
108
|
"react": "^16.9 || ^17 || ^18",
|
|
109
109
|
"rxjs": "^7.8.1",
|
|
110
110
|
"styled-components": "^6.1.13"
|
package/src/editor/Editable.tsx
CHANGED
|
@@ -634,6 +634,11 @@ export const PortableTextEditable = forwardRef<
|
|
|
634
634
|
props.onKeyDown(event)
|
|
635
635
|
}
|
|
636
636
|
if (!event.isDefaultPrevented()) {
|
|
637
|
+
editorActor.send({
|
|
638
|
+
type: 'key down',
|
|
639
|
+
nativeEvent: event.nativeEvent,
|
|
640
|
+
editor: slateEditor,
|
|
641
|
+
})
|
|
637
642
|
slateEditor.pteWithHotKeys(event)
|
|
638
643
|
}
|
|
639
644
|
},
|
|
@@ -24,6 +24,7 @@ import type {
|
|
|
24
24
|
import {debugWithName} from '../utils/debug'
|
|
25
25
|
import {getPortableTextMemberSchemaTypes} from '../utils/getPortableTextMemberSchemaTypes'
|
|
26
26
|
import {compileType} from '../utils/schema'
|
|
27
|
+
import {coreBehaviors} from './behavior/behavior.core'
|
|
27
28
|
import {SlateContainer} from './components/SlateContainer'
|
|
28
29
|
import {Synchronizer} from './components/Synchronizer'
|
|
29
30
|
import {editorMachine, type EditorActor} from './editor-machine'
|
|
@@ -122,18 +123,20 @@ export class PortableTextEditor extends Component<PortableTextEditorProps> {
|
|
|
122
123
|
)
|
|
123
124
|
}
|
|
124
125
|
|
|
126
|
+
this.schemaTypes = getPortableTextMemberSchemaTypes(
|
|
127
|
+
props.schemaType.hasOwnProperty('jsonType')
|
|
128
|
+
? props.schemaType
|
|
129
|
+
: compileType(props.schemaType),
|
|
130
|
+
)
|
|
131
|
+
|
|
125
132
|
this.editorActor = createActor(editorMachine, {
|
|
126
133
|
input: {
|
|
134
|
+
behaviors: coreBehaviors,
|
|
127
135
|
keyGenerator: props.keyGenerator || defaultKeyGenerator,
|
|
136
|
+
schema: this.schemaTypes,
|
|
128
137
|
},
|
|
129
138
|
})
|
|
130
139
|
this.editorActor.start()
|
|
131
|
-
|
|
132
|
-
this.schemaTypes = getPortableTextMemberSchemaTypes(
|
|
133
|
-
props.schemaType.hasOwnProperty('jsonType')
|
|
134
|
-
? props.schemaType
|
|
135
|
-
: compileType(props.schemaType),
|
|
136
|
-
)
|
|
137
140
|
}
|
|
138
141
|
|
|
139
142
|
componentDidUpdate(prevProps: PortableTextEditorProps) {
|
|
@@ -144,7 +147,13 @@ export class PortableTextEditor extends Component<PortableTextEditorProps> {
|
|
|
144
147
|
? this.props.schemaType
|
|
145
148
|
: compileType(this.props.schemaType),
|
|
146
149
|
)
|
|
150
|
+
|
|
151
|
+
this.editorActor.send({
|
|
152
|
+
type: 'update schema',
|
|
153
|
+
schema: this.schemaTypes,
|
|
154
|
+
})
|
|
147
155
|
}
|
|
156
|
+
|
|
148
157
|
if (this.props.editorRef !== prevProps.editorRef && this.props.editorRef) {
|
|
149
158
|
this.props.editorRef.current = this
|
|
150
159
|
}
|
|
@@ -4,7 +4,7 @@ import type {PortableTextBlock, PortableTextSpan} from '@sanity/types'
|
|
|
4
4
|
import {render, waitFor} from '@testing-library/react'
|
|
5
5
|
import {createRef, type ComponentProps, type RefObject} from 'react'
|
|
6
6
|
import {describe, expect, it, vi} from 'vitest'
|
|
7
|
-
import {getTextSelection} from '../../../gherkin-
|
|
7
|
+
import {getTextSelection} from '../../../gherkin-tests/gherkin-step-helpers'
|
|
8
8
|
import {PortableTextEditable} from '../Editable'
|
|
9
9
|
import {PortableTextEditor} from '../PortableTextEditor'
|
|
10
10
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {Editor} from 'slate'
|
|
2
|
+
import type {PortableTextMemberSchemaTypes} from '../../types/editor'
|
|
3
|
+
import type {BehaviorAction, PickFromUnion} from './behavior.types'
|
|
4
|
+
|
|
5
|
+
type BehaviorActionContext = {
|
|
6
|
+
keyGenerator: () => string
|
|
7
|
+
schema: PortableTextMemberSchemaTypes
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function inserText({
|
|
11
|
+
event,
|
|
12
|
+
}: {
|
|
13
|
+
context: BehaviorActionContext
|
|
14
|
+
event: PickFromUnion<BehaviorAction, 'type', 'insert text'>
|
|
15
|
+
}) {
|
|
16
|
+
Editor.insertText(event.editor, event.text)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function inserTextBlock({
|
|
20
|
+
context,
|
|
21
|
+
event,
|
|
22
|
+
}: {
|
|
23
|
+
context: BehaviorActionContext
|
|
24
|
+
event: PickFromUnion<BehaviorAction, 'type', 'insert text block'>
|
|
25
|
+
}) {
|
|
26
|
+
Editor.insertNode(event.editor, {
|
|
27
|
+
_key: context.keyGenerator(),
|
|
28
|
+
_type: context.schema.block.name,
|
|
29
|
+
style: context.schema.styles[0].value ?? 'normal',
|
|
30
|
+
markDefs: [],
|
|
31
|
+
children: [
|
|
32
|
+
{
|
|
33
|
+
_key: context.keyGenerator(),
|
|
34
|
+
_type: 'span',
|
|
35
|
+
text: '',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
})
|
|
39
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {isHotkey} from 'is-hotkey-esm'
|
|
2
|
+
import {defineBehavior} from './behavior.types'
|
|
3
|
+
import {getFocusBlockObject} from './behavior.utils'
|
|
4
|
+
|
|
5
|
+
const overwriteSoftReturn = defineBehavior({
|
|
6
|
+
on: 'key down',
|
|
7
|
+
guard: ({event}) => isHotkey('shift+enter', event.nativeEvent),
|
|
8
|
+
actions: [
|
|
9
|
+
({event}) => {
|
|
10
|
+
event.nativeEvent.preventDefault()
|
|
11
|
+
return {type: 'insert text', text: '\n'}
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const enterOnVoidBlock = defineBehavior({
|
|
17
|
+
on: 'key down',
|
|
18
|
+
guard: ({context, event}) => {
|
|
19
|
+
const isEnter = isHotkey('enter', event.nativeEvent)
|
|
20
|
+
|
|
21
|
+
if (!isEnter) {
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const focusBlockObject = getFocusBlockObject(context)
|
|
26
|
+
|
|
27
|
+
return !!focusBlockObject
|
|
28
|
+
},
|
|
29
|
+
actions: [
|
|
30
|
+
({event}) => {
|
|
31
|
+
event.nativeEvent.preventDefault()
|
|
32
|
+
return {type: 'insert text block', decorators: []}
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export const coreBehaviors = [overwriteSoftReturn, enterOnVoidBlock]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type {PortableTextBlock} from '@sanity/types'
|
|
2
|
+
import type {
|
|
3
|
+
EditorSelection,
|
|
4
|
+
PortableTextMemberSchemaTypes,
|
|
5
|
+
PortableTextSlateEditor,
|
|
6
|
+
} from '../../types/editor'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @alpha
|
|
10
|
+
*/
|
|
11
|
+
export type BehaviorContext = {
|
|
12
|
+
schema: PortableTextMemberSchemaTypes
|
|
13
|
+
value: Array<PortableTextBlock>
|
|
14
|
+
selection: NonNullable<EditorSelection>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @alpha
|
|
19
|
+
*/
|
|
20
|
+
export type BehaviorEvent = {
|
|
21
|
+
type: 'key down'
|
|
22
|
+
nativeEvent: KeyboardEvent
|
|
23
|
+
editor: PortableTextSlateEditor
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @alpha
|
|
28
|
+
*/
|
|
29
|
+
export type BehaviorGuard<
|
|
30
|
+
TBehaviorEvent extends BehaviorEvent,
|
|
31
|
+
TGuardResponse,
|
|
32
|
+
> = ({
|
|
33
|
+
context,
|
|
34
|
+
event,
|
|
35
|
+
}: {
|
|
36
|
+
event: TBehaviorEvent
|
|
37
|
+
context: BehaviorContext
|
|
38
|
+
}) => TGuardResponse | false
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @alpha
|
|
42
|
+
*/
|
|
43
|
+
export type BehaviorActionIntend =
|
|
44
|
+
| {
|
|
45
|
+
type: 'insert text'
|
|
46
|
+
text: string
|
|
47
|
+
}
|
|
48
|
+
| {
|
|
49
|
+
type: 'insert text block'
|
|
50
|
+
decorators: Array<string>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @alpha
|
|
55
|
+
*/
|
|
56
|
+
export type BehaviorAction = BehaviorActionIntend & {
|
|
57
|
+
editor: PortableTextSlateEditor
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @alpha
|
|
62
|
+
*/
|
|
63
|
+
export type Behavior<
|
|
64
|
+
TBehaviorEventType extends BehaviorEvent['type'] = BehaviorEvent['type'],
|
|
65
|
+
TGuardResponse = true,
|
|
66
|
+
> = {
|
|
67
|
+
on: TBehaviorEventType
|
|
68
|
+
guard?: BehaviorGuard<
|
|
69
|
+
PickFromUnion<BehaviorEvent, 'type', TBehaviorEventType>,
|
|
70
|
+
TGuardResponse
|
|
71
|
+
>
|
|
72
|
+
actions: Array<RaiseBehaviorActionIntend<TBehaviorEventType, TGuardResponse>>
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @alpha
|
|
77
|
+
*/
|
|
78
|
+
export type RaiseBehaviorActionIntend<
|
|
79
|
+
TBehaviorEventType extends BehaviorEvent['type'] = BehaviorEvent['type'],
|
|
80
|
+
TGuardResponse = true,
|
|
81
|
+
> = (
|
|
82
|
+
{
|
|
83
|
+
context,
|
|
84
|
+
event,
|
|
85
|
+
}: {
|
|
86
|
+
context: BehaviorContext
|
|
87
|
+
event: PickFromUnion<BehaviorEvent, 'type', TBehaviorEventType>
|
|
88
|
+
},
|
|
89
|
+
guardResponse: TGuardResponse,
|
|
90
|
+
) => BehaviorActionIntend | void
|
|
91
|
+
|
|
92
|
+
export function defineBehavior<
|
|
93
|
+
TBehaviorEventType extends BehaviorEvent['type'],
|
|
94
|
+
TGuardResponse = true,
|
|
95
|
+
>(behavior: Behavior<TBehaviorEventType, TGuardResponse>): Behavior {
|
|
96
|
+
return behavior as unknown as Behavior
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @alpha
|
|
101
|
+
*/
|
|
102
|
+
export type PickFromUnion<
|
|
103
|
+
TUnion,
|
|
104
|
+
TTagKey extends keyof TUnion,
|
|
105
|
+
TPickedTags extends TUnion[TTagKey],
|
|
106
|
+
> = TUnion extends Record<TTagKey, TPickedTags> ? TUnion : never
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isKeySegment,
|
|
3
|
+
isPortableTextTextBlock,
|
|
4
|
+
type KeyedSegment,
|
|
5
|
+
type PortableTextBlock,
|
|
6
|
+
type PortableTextObject,
|
|
7
|
+
} from '@sanity/types'
|
|
8
|
+
import type {BehaviorContext} from './behavior.types'
|
|
9
|
+
|
|
10
|
+
export function getFocusBlock(
|
|
11
|
+
context: BehaviorContext,
|
|
12
|
+
): {node: PortableTextBlock; path: [KeyedSegment]} | undefined {
|
|
13
|
+
const key = context.selection
|
|
14
|
+
? isKeySegment(context.selection.focus.path[0])
|
|
15
|
+
? context.selection.focus.path[0]._key
|
|
16
|
+
: undefined
|
|
17
|
+
: undefined
|
|
18
|
+
|
|
19
|
+
const node = key
|
|
20
|
+
? context.value.find((block) => block._key === key)
|
|
21
|
+
: undefined
|
|
22
|
+
|
|
23
|
+
return node && key ? {node, path: [{_key: key}]} : undefined
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getFocusBlockObject(
|
|
27
|
+
context: BehaviorContext,
|
|
28
|
+
): {node: PortableTextObject; path: [KeyedSegment]} | undefined {
|
|
29
|
+
const focusBlock = getFocusBlock(context)
|
|
30
|
+
|
|
31
|
+
return focusBlock && !isPortableTextTextBlock(focusBlock.node)
|
|
32
|
+
? {node: focusBlock.node, path: focusBlock.path}
|
|
33
|
+
: undefined
|
|
34
|
+
}
|
|
@@ -10,7 +10,21 @@ import {
|
|
|
10
10
|
setup,
|
|
11
11
|
type ActorRefFrom,
|
|
12
12
|
} from 'xstate'
|
|
13
|
-
import type {
|
|
13
|
+
import type {
|
|
14
|
+
EditorSelection,
|
|
15
|
+
InvalidValueResolution,
|
|
16
|
+
PortableTextMemberSchemaTypes,
|
|
17
|
+
} from '../types/editor'
|
|
18
|
+
import {toPortableTextRange} from '../utils/ranges'
|
|
19
|
+
import {fromSlateValue} from '../utils/values'
|
|
20
|
+
import {KEY_TO_VALUE_ELEMENT} from '../utils/weakMaps'
|
|
21
|
+
import {inserText, inserTextBlock} from './behavior/behavior.actions'
|
|
22
|
+
import type {
|
|
23
|
+
Behavior,
|
|
24
|
+
BehaviorAction,
|
|
25
|
+
BehaviorContext,
|
|
26
|
+
BehaviorEvent,
|
|
27
|
+
} from './behavior/behavior.types'
|
|
14
28
|
|
|
15
29
|
/**
|
|
16
30
|
* @internal
|
|
@@ -51,6 +65,12 @@ export type MutationEvent = {
|
|
|
51
65
|
type EditorEvent =
|
|
52
66
|
| {type: 'normalizing'}
|
|
53
67
|
| {type: 'done normalizing'}
|
|
68
|
+
| BehaviorEvent
|
|
69
|
+
| BehaviorAction
|
|
70
|
+
| {
|
|
71
|
+
type: 'update schema'
|
|
72
|
+
schema: PortableTextMemberSchemaTypes
|
|
73
|
+
}
|
|
54
74
|
| EditorEmittedEvent
|
|
55
75
|
|
|
56
76
|
type EditorEmittedEvent =
|
|
@@ -90,16 +110,34 @@ type EditorEmittedEvent =
|
|
|
90
110
|
export const editorMachine = setup({
|
|
91
111
|
types: {
|
|
92
112
|
context: {} as {
|
|
113
|
+
behaviors: Array<Behavior>
|
|
93
114
|
keyGenerator: () => string
|
|
94
115
|
pendingEvents: Array<PatchEvent | MutationEvent>
|
|
116
|
+
schema: PortableTextMemberSchemaTypes
|
|
95
117
|
},
|
|
96
118
|
events: {} as EditorEvent,
|
|
97
119
|
emitted: {} as EditorEmittedEvent,
|
|
98
120
|
input: {} as {
|
|
121
|
+
behaviors: Array<Behavior>
|
|
99
122
|
keyGenerator: () => string
|
|
123
|
+
schema: PortableTextMemberSchemaTypes
|
|
100
124
|
},
|
|
101
125
|
},
|
|
102
126
|
actions: {
|
|
127
|
+
'apply:insert text': ({context, event}) => {
|
|
128
|
+
assertEvent(event, 'insert text')
|
|
129
|
+
inserText({context, event})
|
|
130
|
+
},
|
|
131
|
+
'apply:insert text block': ({context, event}) => {
|
|
132
|
+
assertEvent(event, 'insert text block')
|
|
133
|
+
inserTextBlock({context, event})
|
|
134
|
+
},
|
|
135
|
+
'assign schema': assign({
|
|
136
|
+
schema: ({event}) => {
|
|
137
|
+
assertEvent(event, 'update schema')
|
|
138
|
+
return event.schema
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
103
141
|
'emit patch event': emit(({event}) => {
|
|
104
142
|
assertEvent(event, 'patch')
|
|
105
143
|
return event
|
|
@@ -122,6 +160,68 @@ export const editorMachine = setup({
|
|
|
122
160
|
'clear pending events': assign({
|
|
123
161
|
pendingEvents: [],
|
|
124
162
|
}),
|
|
163
|
+
'handle behavior event': enqueueActions(({context, event, enqueue}) => {
|
|
164
|
+
assertEvent(event, ['key down'])
|
|
165
|
+
|
|
166
|
+
const eventBehaviors = context.behaviors.filter(
|
|
167
|
+
(behavior) => behavior.on === event.type,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if (eventBehaviors.length === 0) {
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const value = fromSlateValue(
|
|
175
|
+
event.editor.children,
|
|
176
|
+
context.schema.block.name,
|
|
177
|
+
KEY_TO_VALUE_ELEMENT.get(event.editor),
|
|
178
|
+
)
|
|
179
|
+
const selection = toPortableTextRange(
|
|
180
|
+
value,
|
|
181
|
+
event.editor.selection,
|
|
182
|
+
context.schema,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if (!selection) {
|
|
186
|
+
console.warn(
|
|
187
|
+
`Unable to handle event ${event.type} due to missing selection`,
|
|
188
|
+
)
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const behaviorContext = {
|
|
193
|
+
schema: context.schema,
|
|
194
|
+
value,
|
|
195
|
+
selection,
|
|
196
|
+
} satisfies BehaviorContext
|
|
197
|
+
|
|
198
|
+
for (const eventBehavior of eventBehaviors) {
|
|
199
|
+
const shouldRun =
|
|
200
|
+
eventBehavior.guard?.({
|
|
201
|
+
context: behaviorContext,
|
|
202
|
+
event,
|
|
203
|
+
}) ?? true
|
|
204
|
+
|
|
205
|
+
if (!shouldRun) {
|
|
206
|
+
continue
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const actions = eventBehavior.actions.map((action) =>
|
|
210
|
+
action({context: behaviorContext, event}, shouldRun),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
for (const action of actions) {
|
|
214
|
+
if (typeof action !== 'object') {
|
|
215
|
+
continue
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
enqueue.raise({
|
|
219
|
+
...action,
|
|
220
|
+
editor: event.editor,
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}),
|
|
125
225
|
},
|
|
126
226
|
actors: {
|
|
127
227
|
networkLogic,
|
|
@@ -129,8 +229,10 @@ export const editorMachine = setup({
|
|
|
129
229
|
}).createMachine({
|
|
130
230
|
id: 'editor',
|
|
131
231
|
context: ({input}) => ({
|
|
232
|
+
behaviors: input.behaviors,
|
|
132
233
|
keyGenerator: input.keyGenerator,
|
|
133
234
|
pendingEvents: [],
|
|
235
|
+
schema: input.schema,
|
|
134
236
|
}),
|
|
135
237
|
invoke: {
|
|
136
238
|
id: 'networkLogic',
|
|
@@ -149,6 +251,16 @@ export const editorMachine = setup({
|
|
|
149
251
|
'offline': {actions: emit({type: 'offline'})},
|
|
150
252
|
'loading': {actions: emit({type: 'loading'})},
|
|
151
253
|
'done loading': {actions: emit({type: 'done loading'})},
|
|
254
|
+
'update schema': {actions: 'assign schema'},
|
|
255
|
+
'key down': {
|
|
256
|
+
actions: ['handle behavior event'],
|
|
257
|
+
},
|
|
258
|
+
'insert text': {
|
|
259
|
+
actions: ['apply:insert text'],
|
|
260
|
+
},
|
|
261
|
+
'insert text block': {
|
|
262
|
+
actions: ['apply:insert text block'],
|
|
263
|
+
},
|
|
152
264
|
},
|
|
153
265
|
initial: 'pristine',
|
|
154
266
|
states: {
|
|
@@ -186,38 +186,6 @@ export function createWithHotkeys(
|
|
|
186
186
|
return
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
|
-
// Block object enter key
|
|
190
|
-
if (focusBlock && Editor.isVoid(editor, focusBlock)) {
|
|
191
|
-
Editor.insertNode(editor, editor.pteCreateTextBlock({decorators: []}))
|
|
192
|
-
event.preventDefault()
|
|
193
|
-
editor.onChange()
|
|
194
|
-
return
|
|
195
|
-
}
|
|
196
|
-
// Default enter key behavior
|
|
197
|
-
event.preventDefault()
|
|
198
|
-
editor.insertBreak()
|
|
199
|
-
editor.onChange()
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Soft line breaks
|
|
203
|
-
if (isShiftEnter) {
|
|
204
|
-
event.preventDefault()
|
|
205
|
-
editor.insertText('\n')
|
|
206
|
-
return
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Undo/redo
|
|
210
|
-
if (isHotkey('mod+z', event.nativeEvent)) {
|
|
211
|
-
event.preventDefault()
|
|
212
|
-
editor.undo()
|
|
213
|
-
return
|
|
214
|
-
}
|
|
215
|
-
if (
|
|
216
|
-
isHotkey('mod+y', event.nativeEvent) ||
|
|
217
|
-
isHotkey('mod+shift+z', event.nativeEvent)
|
|
218
|
-
) {
|
|
219
|
-
event.preventDefault()
|
|
220
|
-
editor.redo()
|
|
221
189
|
}
|
|
222
190
|
}
|
|
223
191
|
return editor
|