@portabletext/editor 1.1.7 → 1.1.8
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/lib/index.d.mts +450 -38
- package/lib/index.d.ts +450 -38
- package/lib/index.esm.js +109 -102
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +108 -101
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +109 -102
- package/lib/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/editor/Editable.tsx +15 -3
- package/src/editor/PortableTextEditor.tsx +33 -31
- package/src/editor/behavior/behavior.actions.ts +45 -25
- package/src/editor/behavior/behavior.types.ts +24 -6
- package/src/editor/behavior/behavior.utils.ts +68 -0
- package/src/editor/components/Leaf.tsx +17 -17
- package/src/editor/components/SlateContainer.tsx +5 -1
- package/src/editor/editor-actor-context.ts +4 -0
- package/src/editor/editor-machine.ts +13 -12
- package/src/editor/hooks/useSyncValue.ts +1 -1
- package/src/editor/plugins/createWithHotKeys.ts +1 -24
- package/src/editor/plugins/createWithInsertBreak.ts +16 -28
- package/src/editor/plugins/index.ts +3 -2
- package/src/types/options.ts +2 -0
- package/src/utils/__tests__/operationToPatches.test.ts +15 -1
- package/src/utils/__tests__/patchToOperations.test.ts +11 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"@babel/preset-react": "^7.25.9",
|
|
56
56
|
"@jest/globals": "^29.7.0",
|
|
57
57
|
"@jest/types": "^29.6.3",
|
|
58
|
-
"@playwright/test": "1.48.
|
|
58
|
+
"@playwright/test": "1.48.2",
|
|
59
59
|
"@portabletext/toolkit": "^2.0.15",
|
|
60
60
|
"@sanity/block-tools": "^3.62.2",
|
|
61
61
|
"@sanity/diff-match-patch": "^3.1.1",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"jest-environment-node": "^29.7.0",
|
|
89
89
|
"jsdom": "^25.0.1",
|
|
90
90
|
"node-ipc": "npm:@node-ipc/compat@9.2.5",
|
|
91
|
-
"playwright": "^1.48.
|
|
91
|
+
"playwright": "^1.48.2",
|
|
92
92
|
"react": "^18.3.1",
|
|
93
93
|
"react-dom": "^18.3.1",
|
|
94
94
|
"rxjs": "^7.8.1",
|
package/src/editor/Editable.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import {isEqual, noop} from 'lodash'
|
|
|
3
3
|
import {
|
|
4
4
|
forwardRef,
|
|
5
5
|
useCallback,
|
|
6
|
+
useContext,
|
|
6
7
|
useEffect,
|
|
7
8
|
useImperativeHandle,
|
|
8
9
|
useMemo,
|
|
@@ -63,6 +64,7 @@ import {
|
|
|
63
64
|
} from '../utils/values'
|
|
64
65
|
import {Element} from './components/Element'
|
|
65
66
|
import {Leaf} from './components/Leaf'
|
|
67
|
+
import {EditorActorContext} from './editor-actor-context'
|
|
66
68
|
import {usePortableTextEditor} from './hooks/usePortableTextEditor'
|
|
67
69
|
import {usePortableTextEditorReadOnlyStatus} from './hooks/usePortableTextReadOnly'
|
|
68
70
|
import {createWithHotkeys, createWithInsertData} from './plugins'
|
|
@@ -155,7 +157,8 @@ export const PortableTextEditable = forwardRef<
|
|
|
155
157
|
|
|
156
158
|
const rangeDecorationsRef = useRef(rangeDecorations)
|
|
157
159
|
|
|
158
|
-
const
|
|
160
|
+
const editorActor = useContext(EditorActorContext)
|
|
161
|
+
const {schemaTypes} = portableTextEditor
|
|
159
162
|
const slateEditor = useSlate()
|
|
160
163
|
|
|
161
164
|
const blockTypeName = schemaTypes.block.name
|
|
@@ -166,8 +169,8 @@ export const PortableTextEditable = forwardRef<
|
|
|
166
169
|
[editorActor, schemaTypes],
|
|
167
170
|
)
|
|
168
171
|
const withHotKeys = useMemo(
|
|
169
|
-
() => createWithHotkeys(
|
|
170
|
-
[hotkeys, portableTextEditor
|
|
172
|
+
() => createWithHotkeys(portableTextEditor, hotkeys),
|
|
173
|
+
[hotkeys, portableTextEditor],
|
|
171
174
|
)
|
|
172
175
|
|
|
173
176
|
// Output a minimal React editor inside Editable when in readOnly mode.
|
|
@@ -216,6 +219,7 @@ export const PortableTextEditable = forwardRef<
|
|
|
216
219
|
let rendered = (
|
|
217
220
|
<Leaf
|
|
218
221
|
{...lProps}
|
|
222
|
+
editorActor={editorActor}
|
|
219
223
|
schemaTypes={schemaTypes}
|
|
220
224
|
renderAnnotation={renderAnnotation}
|
|
221
225
|
renderChild={renderChild}
|
|
@@ -546,6 +550,14 @@ export const PortableTextEditable = forwardRef<
|
|
|
546
550
|
if (onBeforeInput) {
|
|
547
551
|
onBeforeInput(event)
|
|
548
552
|
}
|
|
553
|
+
|
|
554
|
+
if (!event.defaultPrevented && event.inputType === 'insertText') {
|
|
555
|
+
editorActor.send({
|
|
556
|
+
type: 'before insert text',
|
|
557
|
+
nativeEvent: event,
|
|
558
|
+
editor: slateEditor,
|
|
559
|
+
})
|
|
560
|
+
}
|
|
549
561
|
},
|
|
550
562
|
[onBeforeInput],
|
|
551
563
|
)
|
|
@@ -27,6 +27,7 @@ import {compileType} from '../utils/schema'
|
|
|
27
27
|
import {coreBehaviors} from './behavior/behavior.core'
|
|
28
28
|
import {SlateContainer} from './components/SlateContainer'
|
|
29
29
|
import {Synchronizer} from './components/Synchronizer'
|
|
30
|
+
import {EditorActorContext} from './editor-actor-context'
|
|
30
31
|
import {editorMachine, type EditorActor} from './editor-machine'
|
|
31
32
|
import {PortableTextEditorContext} from './hooks/usePortableTextEditor'
|
|
32
33
|
import {PortableTextEditorSelectionProvider} from './hooks/usePortableTextEditorSelection'
|
|
@@ -92,11 +93,6 @@ export type PortableTextEditorProps = PropsWithChildren<{
|
|
|
92
93
|
* @public
|
|
93
94
|
*/
|
|
94
95
|
export class PortableTextEditor extends Component<PortableTextEditorProps> {
|
|
95
|
-
/**
|
|
96
|
-
* @internal
|
|
97
|
-
* Don't use this API directly. It's subject to change.
|
|
98
|
-
*/
|
|
99
|
-
public editorActor: EditorActor
|
|
100
96
|
/**
|
|
101
97
|
* An observable of all the editor changes.
|
|
102
98
|
*/
|
|
@@ -109,6 +105,7 @@ export class PortableTextEditor extends Component<PortableTextEditorProps> {
|
|
|
109
105
|
* The editor API (currently implemented with Slate).
|
|
110
106
|
*/
|
|
111
107
|
private editable?: EditableAPI
|
|
108
|
+
private editorActor: EditorActor
|
|
112
109
|
|
|
113
110
|
constructor(props: PortableTextEditorProps) {
|
|
114
111
|
super(props)
|
|
@@ -183,33 +180,38 @@ export class PortableTextEditor extends Component<PortableTextEditorProps> {
|
|
|
183
180
|
const readOnly = Boolean(this.props.readOnly)
|
|
184
181
|
|
|
185
182
|
return (
|
|
186
|
-
<
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
183
|
+
<EditorActorContext.Provider value={this.editorActor}>
|
|
184
|
+
<SlateContainer
|
|
185
|
+
editorActor={this.editorActor}
|
|
186
|
+
maxBlocks={maxBlocks}
|
|
187
|
+
patches$={_patches$}
|
|
188
|
+
portableTextEditor={this}
|
|
189
|
+
readOnly={readOnly}
|
|
190
|
+
>
|
|
191
|
+
<PortableTextEditorContext.Provider value={this}>
|
|
192
|
+
<PortableTextEditorReadOnlyContext.Provider value={readOnly}>
|
|
193
|
+
<PortableTextEditorSelectionProvider
|
|
196
194
|
editorActor={this.editorActor}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
this.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
195
|
+
>
|
|
196
|
+
<Synchronizer
|
|
197
|
+
editorActor={this.editorActor}
|
|
198
|
+
getValue={this.getValue}
|
|
199
|
+
onChange={(change) => {
|
|
200
|
+
this.props.onChange(change)
|
|
201
|
+
/**
|
|
202
|
+
* For backwards compatibility, we relay all changes to the
|
|
203
|
+
* `change$` Subject as well.
|
|
204
|
+
*/
|
|
205
|
+
this.change$.next(change)
|
|
206
|
+
}}
|
|
207
|
+
value={value}
|
|
208
|
+
/>
|
|
209
|
+
{children}
|
|
210
|
+
</PortableTextEditorSelectionProvider>
|
|
211
|
+
</PortableTextEditorReadOnlyContext.Provider>
|
|
212
|
+
</PortableTextEditorContext.Provider>
|
|
213
|
+
</SlateContainer>
|
|
214
|
+
</EditorActorContext.Provider>
|
|
213
215
|
)
|
|
214
216
|
}
|
|
215
217
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {Editor} from 'slate'
|
|
1
|
+
import {Editor, Transforms} from 'slate'
|
|
2
2
|
import type {PortableTextMemberSchemaTypes} from '../../types/editor'
|
|
3
|
+
import {toSlateRange} from '../../utils/ranges'
|
|
3
4
|
import type {BehaviorAction, PickFromUnion} from './behavior.types'
|
|
4
5
|
|
|
5
6
|
type BehaviorActionContext = {
|
|
@@ -7,33 +8,52 @@ type BehaviorActionContext = {
|
|
|
7
8
|
schema: PortableTextMemberSchemaTypes
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
type BehaviourActionImplementation<TBehaviorAction extends BehaviorAction> = ({
|
|
12
|
+
context,
|
|
11
13
|
event,
|
|
12
14
|
}: {
|
|
13
15
|
context: BehaviorActionContext
|
|
14
|
-
event:
|
|
15
|
-
})
|
|
16
|
-
|
|
16
|
+
event: TBehaviorAction
|
|
17
|
+
}) => void
|
|
18
|
+
|
|
19
|
+
type BehaviourActionImplementations = {
|
|
20
|
+
[TBehaviorActionType in BehaviorAction['type']]: BehaviourActionImplementation<
|
|
21
|
+
PickFromUnion<BehaviorAction, 'type', TBehaviorActionType>
|
|
22
|
+
>
|
|
17
23
|
}
|
|
18
24
|
|
|
19
|
-
export
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
})
|
|
25
|
+
export const behaviorActionImplementations: BehaviourActionImplementations = {
|
|
26
|
+
'apply block style': ({event}) => {
|
|
27
|
+
for (const path of event.paths) {
|
|
28
|
+
const at = toSlateRange(
|
|
29
|
+
{anchor: {path, offset: 0}, focus: {path, offset: 0}},
|
|
30
|
+
event.editor,
|
|
31
|
+
)!
|
|
32
|
+
|
|
33
|
+
Transforms.setNodes(event.editor, {style: event.style}, {at})
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
'delete text': ({event}) => {
|
|
37
|
+
Transforms.delete(event.editor, {
|
|
38
|
+
at: toSlateRange(event.selection, event.editor)!,
|
|
39
|
+
})
|
|
40
|
+
},
|
|
41
|
+
'insert text': ({event}) => {
|
|
42
|
+
Editor.insertText(event.editor, event.text)
|
|
43
|
+
},
|
|
44
|
+
'insert text block': ({context, event}) => {
|
|
45
|
+
Editor.insertNode(event.editor, {
|
|
46
|
+
_key: context.keyGenerator(),
|
|
47
|
+
_type: context.schema.block.name,
|
|
48
|
+
style: context.schema.styles[0].value ?? 'normal',
|
|
49
|
+
markDefs: [],
|
|
50
|
+
children: [
|
|
51
|
+
{
|
|
52
|
+
_key: context.keyGenerator(),
|
|
53
|
+
_type: 'span',
|
|
54
|
+
text: '',
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
})
|
|
58
|
+
},
|
|
39
59
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {PortableTextBlock} from '@sanity/types'
|
|
1
|
+
import type {KeyedSegment, PortableTextBlock} from '@sanity/types'
|
|
2
2
|
import type {
|
|
3
3
|
EditorSelection,
|
|
4
4
|
PortableTextMemberSchemaTypes,
|
|
@@ -17,11 +17,17 @@ export type BehaviorContext = {
|
|
|
17
17
|
/**
|
|
18
18
|
* @alpha
|
|
19
19
|
*/
|
|
20
|
-
export type BehaviorEvent =
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
export type BehaviorEvent =
|
|
21
|
+
| {
|
|
22
|
+
type: 'key down'
|
|
23
|
+
nativeEvent: KeyboardEvent
|
|
24
|
+
editor: PortableTextSlateEditor
|
|
25
|
+
}
|
|
26
|
+
| {
|
|
27
|
+
type: 'before insert text'
|
|
28
|
+
nativeEvent: InputEvent
|
|
29
|
+
editor: PortableTextSlateEditor
|
|
30
|
+
}
|
|
25
31
|
|
|
26
32
|
/**
|
|
27
33
|
* @alpha
|
|
@@ -49,6 +55,15 @@ export type BehaviorActionIntend =
|
|
|
49
55
|
type: 'insert text block'
|
|
50
56
|
decorators: Array<string>
|
|
51
57
|
}
|
|
58
|
+
| {
|
|
59
|
+
type: 'apply block style'
|
|
60
|
+
paths: Array<[KeyedSegment]>
|
|
61
|
+
style: string
|
|
62
|
+
}
|
|
63
|
+
| {
|
|
64
|
+
type: 'delete text'
|
|
65
|
+
selection: NonNullable<EditorSelection>
|
|
66
|
+
}
|
|
52
67
|
|
|
53
68
|
/**
|
|
54
69
|
* @alpha
|
|
@@ -89,6 +104,9 @@ export type RaiseBehaviorActionIntend<
|
|
|
89
104
|
guardResponse: TGuardResponse,
|
|
90
105
|
) => BehaviorActionIntend | void
|
|
91
106
|
|
|
107
|
+
/**
|
|
108
|
+
* @alpha
|
|
109
|
+
*/
|
|
92
110
|
export function defineBehavior<
|
|
93
111
|
TBehaviorEventType extends BehaviorEvent['type'],
|
|
94
112
|
TGuardResponse = true,
|
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isKeySegment,
|
|
3
|
+
isPortableTextSpan,
|
|
3
4
|
isPortableTextTextBlock,
|
|
4
5
|
type KeyedSegment,
|
|
5
6
|
type PortableTextBlock,
|
|
6
7
|
type PortableTextObject,
|
|
8
|
+
type PortableTextSpan,
|
|
9
|
+
type PortableTextTextBlock,
|
|
7
10
|
} from '@sanity/types'
|
|
8
11
|
import type {BehaviorContext} from './behavior.types'
|
|
9
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Selection utilities
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export function selectionIsCollapsed(context: BehaviorContext) {
|
|
18
|
+
return (
|
|
19
|
+
context.selection?.anchor.path.join() ===
|
|
20
|
+
context.selection?.focus.path.join() &&
|
|
21
|
+
context.selection?.anchor.offset === context.selection?.focus.offset
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Value utilities
|
|
27
|
+
*/
|
|
28
|
+
|
|
10
29
|
export function getFocusBlock(
|
|
11
30
|
context: BehaviorContext,
|
|
12
31
|
): {node: PortableTextBlock; path: [KeyedSegment]} | undefined {
|
|
@@ -23,6 +42,16 @@ export function getFocusBlock(
|
|
|
23
42
|
return node && key ? {node, path: [{_key: key}]} : undefined
|
|
24
43
|
}
|
|
25
44
|
|
|
45
|
+
export function getFocusTextBlock(
|
|
46
|
+
context: BehaviorContext,
|
|
47
|
+
): {node: PortableTextTextBlock; path: [KeyedSegment]} | undefined {
|
|
48
|
+
const focusBlock = getFocusBlock(context)
|
|
49
|
+
|
|
50
|
+
return focusBlock && isPortableTextTextBlock(focusBlock.node)
|
|
51
|
+
? {node: focusBlock.node, path: focusBlock.path}
|
|
52
|
+
: undefined
|
|
53
|
+
}
|
|
54
|
+
|
|
26
55
|
export function getFocusBlockObject(
|
|
27
56
|
context: BehaviorContext,
|
|
28
57
|
): {node: PortableTextObject; path: [KeyedSegment]} | undefined {
|
|
@@ -32,3 +61,42 @@ export function getFocusBlockObject(
|
|
|
32
61
|
? {node: focusBlock.node, path: focusBlock.path}
|
|
33
62
|
: undefined
|
|
34
63
|
}
|
|
64
|
+
|
|
65
|
+
export function getFocusChild(context: BehaviorContext):
|
|
66
|
+
| {
|
|
67
|
+
node: PortableTextObject | PortableTextSpan
|
|
68
|
+
path: [KeyedSegment, 'children', KeyedSegment]
|
|
69
|
+
}
|
|
70
|
+
| undefined {
|
|
71
|
+
const focusBlock = getFocusTextBlock(context)
|
|
72
|
+
|
|
73
|
+
if (!focusBlock) {
|
|
74
|
+
return undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const key = context.selection
|
|
78
|
+
? isKeySegment(context.selection.focus.path[2])
|
|
79
|
+
? context.selection.focus.path[2]._key
|
|
80
|
+
: undefined
|
|
81
|
+
: undefined
|
|
82
|
+
|
|
83
|
+
const node = key
|
|
84
|
+
? focusBlock.node.children.find((span) => span._key === key)
|
|
85
|
+
: undefined
|
|
86
|
+
|
|
87
|
+
return node && key
|
|
88
|
+
? {node, path: [...focusBlock.path, 'children', {_key: key}]}
|
|
89
|
+
: undefined
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function getFocusSpan(
|
|
93
|
+
context: BehaviorContext,
|
|
94
|
+
):
|
|
95
|
+
| {node: PortableTextSpan; path: [KeyedSegment, 'children', KeyedSegment]}
|
|
96
|
+
| undefined {
|
|
97
|
+
const focusChild = getFocusChild(context)
|
|
98
|
+
|
|
99
|
+
return focusChild && isPortableTextSpan(focusChild.node)
|
|
100
|
+
? {node: focusChild.node, path: focusChild.path}
|
|
101
|
+
: undefined
|
|
102
|
+
}
|
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
RenderDecoratorFunction,
|
|
26
26
|
} from '../../types/editor'
|
|
27
27
|
import {debugWithName} from '../../utils/debug'
|
|
28
|
+
import type {EditorActor} from '../editor-machine'
|
|
28
29
|
import {usePortableTextEditor} from '../hooks/usePortableTextEditor'
|
|
29
30
|
import {DefaultAnnotation} from '../nodes/DefaultAnnotation'
|
|
30
31
|
import {PortableTextEditor} from '../PortableTextEditor'
|
|
@@ -37,6 +38,7 @@ const EMPTY_MARKS: string[] = []
|
|
|
37
38
|
* @internal
|
|
38
39
|
*/
|
|
39
40
|
export interface LeafProps extends RenderLeafProps {
|
|
41
|
+
editorActor: EditorActor
|
|
40
42
|
children: ReactElement
|
|
41
43
|
schemaTypes: PortableTextMemberSchemaTypes
|
|
42
44
|
renderAnnotation?: RenderAnnotationFunction
|
|
@@ -51,6 +53,7 @@ export interface LeafProps extends RenderLeafProps {
|
|
|
51
53
|
*/
|
|
52
54
|
export const Leaf = (props: LeafProps) => {
|
|
53
55
|
const {
|
|
56
|
+
editorActor,
|
|
54
57
|
attributes,
|
|
55
58
|
children,
|
|
56
59
|
leaf,
|
|
@@ -142,12 +145,12 @@ export const Leaf = (props: LeafProps) => {
|
|
|
142
145
|
return undefined
|
|
143
146
|
}
|
|
144
147
|
|
|
145
|
-
const onBlur =
|
|
148
|
+
const onBlur = editorActor.on('blur', () => {
|
|
146
149
|
setFocused(false)
|
|
147
150
|
setSelected(false)
|
|
148
151
|
})
|
|
149
152
|
|
|
150
|
-
const onFocus =
|
|
153
|
+
const onFocus = editorActor.on('focus', () => {
|
|
151
154
|
const sel = PortableTextEditor.getSelection(portableTextEditor)
|
|
152
155
|
if (
|
|
153
156
|
sel &&
|
|
@@ -159,21 +162,18 @@ export const Leaf = (props: LeafProps) => {
|
|
|
159
162
|
setSelectedFromRange()
|
|
160
163
|
})
|
|
161
164
|
|
|
162
|
-
const onSelection =
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
setSelectedFromRange()
|
|
175
|
-
},
|
|
176
|
-
)
|
|
165
|
+
const onSelection = editorActor.on('selection', (event) => {
|
|
166
|
+
if (
|
|
167
|
+
event.selection &&
|
|
168
|
+
isEqual(event.selection.focus.path, path) &&
|
|
169
|
+
PortableTextEditor.isCollapsedSelection(portableTextEditor)
|
|
170
|
+
) {
|
|
171
|
+
setFocused(true)
|
|
172
|
+
} else {
|
|
173
|
+
setFocused(false)
|
|
174
|
+
}
|
|
175
|
+
setSelectedFromRange()
|
|
176
|
+
})
|
|
177
177
|
|
|
178
178
|
return () => {
|
|
179
179
|
onBlur.unsubscribe()
|
|
@@ -4,6 +4,7 @@ import {Slate, withReact} from 'slate-react'
|
|
|
4
4
|
import type {PatchObservable} from '../../types/editor'
|
|
5
5
|
import {debugWithName} from '../../utils/debug'
|
|
6
6
|
import {KEY_TO_SLATE_ELEMENT, KEY_TO_VALUE_ELEMENT} from '../../utils/weakMaps'
|
|
7
|
+
import type {EditorActor} from '../editor-machine'
|
|
7
8
|
import {withPlugins} from '../plugins'
|
|
8
9
|
import type {PortableTextEditor} from '../PortableTextEditor'
|
|
9
10
|
|
|
@@ -13,6 +14,7 @@ const debug = debugWithName('component:PortableTextEditor:SlateContainer')
|
|
|
13
14
|
* @internal
|
|
14
15
|
*/
|
|
15
16
|
export interface SlateContainerProps extends PropsWithChildren {
|
|
17
|
+
editorActor: EditorActor
|
|
16
18
|
maxBlocks: number | undefined
|
|
17
19
|
patches$?: PatchObservable
|
|
18
20
|
portableTextEditor: PortableTextEditor
|
|
@@ -24,12 +26,13 @@ export interface SlateContainerProps extends PropsWithChildren {
|
|
|
24
26
|
* @internal
|
|
25
27
|
*/
|
|
26
28
|
export function SlateContainer(props: SlateContainerProps) {
|
|
27
|
-
const {patches$, portableTextEditor, readOnly, maxBlocks} = props
|
|
29
|
+
const {editorActor, patches$, portableTextEditor, readOnly, maxBlocks} = props
|
|
28
30
|
|
|
29
31
|
// Create the slate instance, using `useState` ensures setup is only run once, initially
|
|
30
32
|
const [[slateEditor, subscribe]] = useState(() => {
|
|
31
33
|
debug('Creating new Slate editor instance')
|
|
32
34
|
const {editor, subscribe: _sub} = withPlugins(withReact(createEditor()), {
|
|
35
|
+
editorActor,
|
|
33
36
|
maxBlocks,
|
|
34
37
|
patches$,
|
|
35
38
|
portableTextEditor,
|
|
@@ -51,6 +54,7 @@ export function SlateContainer(props: SlateContainerProps) {
|
|
|
51
54
|
useEffect(() => {
|
|
52
55
|
debug('Re-initializing plugin chain')
|
|
53
56
|
withPlugins(slateEditor, {
|
|
57
|
+
editorActor,
|
|
54
58
|
maxBlocks,
|
|
55
59
|
patches$,
|
|
56
60
|
portableTextEditor,
|
|
@@ -18,7 +18,7 @@ import type {
|
|
|
18
18
|
import {toPortableTextRange} from '../utils/ranges'
|
|
19
19
|
import {fromSlateValue} from '../utils/values'
|
|
20
20
|
import {KEY_TO_VALUE_ELEMENT} from '../utils/weakMaps'
|
|
21
|
-
import {
|
|
21
|
+
import {behaviorActionImplementations} from './behavior/behavior.actions'
|
|
22
22
|
import type {
|
|
23
23
|
Behavior,
|
|
24
24
|
BehaviorAction,
|
|
@@ -124,14 +124,6 @@ export const editorMachine = setup({
|
|
|
124
124
|
},
|
|
125
125
|
},
|
|
126
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
127
|
'assign schema': assign({
|
|
136
128
|
schema: ({event}) => {
|
|
137
129
|
assertEvent(event, 'update schema')
|
|
@@ -161,7 +153,7 @@ export const editorMachine = setup({
|
|
|
161
153
|
pendingEvents: [],
|
|
162
154
|
}),
|
|
163
155
|
'handle behavior event': enqueueActions(({context, event, enqueue}) => {
|
|
164
|
-
assertEvent(event, ['key down'])
|
|
156
|
+
assertEvent(event, ['key down', 'before insert text'])
|
|
165
157
|
|
|
166
158
|
const eventBehaviors = context.behaviors.filter(
|
|
167
159
|
(behavior) => behavior.on === event.type,
|
|
@@ -255,11 +247,20 @@ export const editorMachine = setup({
|
|
|
255
247
|
'key down': {
|
|
256
248
|
actions: ['handle behavior event'],
|
|
257
249
|
},
|
|
250
|
+
'before insert text': {
|
|
251
|
+
actions: ['handle behavior event'],
|
|
252
|
+
},
|
|
253
|
+
'apply block style': {
|
|
254
|
+
actions: [behaviorActionImplementations['apply block style']],
|
|
255
|
+
},
|
|
256
|
+
'delete text': {
|
|
257
|
+
actions: [behaviorActionImplementations['delete text']],
|
|
258
|
+
},
|
|
258
259
|
'insert text': {
|
|
259
|
-
actions: ['
|
|
260
|
+
actions: [behaviorActionImplementations['insert text']],
|
|
260
261
|
},
|
|
261
262
|
'insert text block': {
|
|
262
|
-
actions: ['
|
|
263
|
+
actions: [behaviorActionImplementations['insert text block']],
|
|
263
264
|
},
|
|
264
265
|
},
|
|
265
266
|
initial: 'pristine',
|
|
@@ -52,7 +52,7 @@ export function useSyncValue(
|
|
|
52
52
|
userCallbackFn?: () => void,
|
|
53
53
|
) => void {
|
|
54
54
|
const {editorActor, portableTextEditor, readOnly} = props
|
|
55
|
-
const
|
|
55
|
+
const schemaTypes = editorActor.getSnapshot().context.schema
|
|
56
56
|
const previousValue = useRef<PortableTextBlock[] | undefined>()
|
|
57
57
|
const slateEditor = useSlate()
|
|
58
58
|
const updateValueFunctionRef =
|
|
@@ -3,10 +3,7 @@ import {isHotkey} from 'is-hotkey-esm'
|
|
|
3
3
|
import type {KeyboardEvent} from 'react'
|
|
4
4
|
import {Editor, Node, Path, Range, Transforms} from 'slate'
|
|
5
5
|
import type {ReactEditor} from 'slate-react'
|
|
6
|
-
import type {
|
|
7
|
-
PortableTextMemberSchemaTypes,
|
|
8
|
-
PortableTextSlateEditor,
|
|
9
|
-
} from '../../types/editor'
|
|
6
|
+
import type {PortableTextSlateEditor} from '../../types/editor'
|
|
10
7
|
import type {HotkeyOptions} from '../../types/options'
|
|
11
8
|
import type {SlateTextBlock, VoidElement} from '../../types/slate'
|
|
12
9
|
import {debugWithName} from '../../utils/debug'
|
|
@@ -29,7 +26,6 @@ const DEFAULT_HOTKEYS: HotkeyOptions = {
|
|
|
29
26
|
*
|
|
30
27
|
*/
|
|
31
28
|
export function createWithHotkeys(
|
|
32
|
-
types: PortableTextMemberSchemaTypes,
|
|
33
29
|
portableTextEditor: PortableTextEditor,
|
|
34
30
|
hotkeysFromOptions?: HotkeyOptions,
|
|
35
31
|
): (editor: PortableTextSlateEditor & ReactEditor) => any {
|
|
@@ -167,25 +163,6 @@ export function createWithHotkeys(
|
|
|
167
163
|
}
|
|
168
164
|
return
|
|
169
165
|
}
|
|
170
|
-
|
|
171
|
-
// Enter from another style than the first (default one)
|
|
172
|
-
if (
|
|
173
|
-
editor.isTextBlock(focusBlock) &&
|
|
174
|
-
focusBlock.style &&
|
|
175
|
-
focusBlock.style !== types.styles[0].value
|
|
176
|
-
) {
|
|
177
|
-
const [, end] = Range.edges(editor.selection)
|
|
178
|
-
const endAtEndOfNode = Editor.isEnd(editor, end, end.path)
|
|
179
|
-
if (endAtEndOfNode) {
|
|
180
|
-
Editor.insertNode(
|
|
181
|
-
editor,
|
|
182
|
-
editor.pteCreateTextBlock({decorators: []}),
|
|
183
|
-
)
|
|
184
|
-
event.preventDefault()
|
|
185
|
-
editor.onChange()
|
|
186
|
-
return
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
166
|
}
|
|
190
167
|
}
|
|
191
168
|
return editor
|