@portabletext/editor 1.1.4 → 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 +631 -30
- package/lib/index.d.ts +631 -30
- package/lib/index.esm.js +303 -200
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +295 -192
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +303 -200
- package/lib/index.mjs.map +1 -1
- package/package.json +30 -25
- package/src/editor/Editable.tsx +11 -11
- package/src/editor/PortableTextEditor.tsx +37 -32
- 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/components/SlateContainer.tsx +2 -13
- package/src/editor/components/Synchronizer.tsx +0 -3
- package/src/editor/editor-machine.ts +120 -3
- package/src/editor/hooks/useSyncValue.ts +3 -5
- package/src/editor/key-generator.ts +6 -0
- package/src/editor/plugins/createWithEditableAPI.ts +8 -5
- package/src/editor/plugins/createWithHotKeys.ts +1 -32
- package/src/editor/plugins/createWithInsertBreak.ts +6 -2
- package/src/editor/plugins/createWithInsertData.ts +7 -4
- package/src/editor/plugins/createWithObjectKeys.ts +12 -5
- package/src/editor/plugins/createWithPatches.ts +0 -1
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +85 -114
- package/src/editor/plugins/createWithSchemaTypes.ts +3 -4
- package/src/editor/plugins/createWithUtils.ts +5 -4
- package/src/editor/plugins/index.ts +5 -13
- package/src/index.ts +11 -2
- package/src/types/options.ts +0 -1
- package/src/utils/__tests__/operationToPatches.test.ts +0 -2
- package/src/utils/__tests__/patchToOperations.test.ts +1 -2
- package/src/utils/sibling-utils.ts +55 -0
- package/src/editor/hooks/usePortableTextEditorKeyGenerator.ts +0 -27
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",
|
|
@@ -46,60 +46,65 @@
|
|
|
46
46
|
"debug": "^4.3.4",
|
|
47
47
|
"is-hotkey-esm": "^1.0.0",
|
|
48
48
|
"lodash": "^4.17.21",
|
|
49
|
-
"slate": "0.
|
|
50
|
-
"slate-react": "0.110.
|
|
49
|
+
"slate": "0.110.2",
|
|
50
|
+
"slate-react": "0.110.2",
|
|
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
|
-
"@playwright/test": "1.
|
|
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
|
-
"@testing-library/jest-dom": "^6.
|
|
68
|
+
"@testing-library/jest-dom": "^6.6.2",
|
|
69
69
|
"@testing-library/react": "^16.0.1",
|
|
70
|
+
"@testing-library/user-event": "^14.5.2",
|
|
70
71
|
"@types/debug": "^4.1.5",
|
|
71
72
|
"@types/express": "^4.17.21",
|
|
72
73
|
"@types/express-ws": "^3.0.5",
|
|
73
|
-
"@types/lodash": "^4.17.
|
|
74
|
+
"@types/lodash": "^4.17.12",
|
|
74
75
|
"@types/node": "^18.19.8",
|
|
75
76
|
"@types/node-ipc": "^9.2.3",
|
|
76
|
-
"@types/react": "^18.3.
|
|
77
|
-
"@types/react-dom": "^18.3.
|
|
77
|
+
"@types/react": "^18.3.12",
|
|
78
|
+
"@types/react-dom": "^18.3.1",
|
|
78
79
|
"@types/ws": "~8.5.12",
|
|
79
|
-
"@vitejs/plugin-react": "^4.3.
|
|
80
|
+
"@vitejs/plugin-react": "^4.3.3",
|
|
81
|
+
"@vitest/browser": "^2.1.3",
|
|
82
|
+
"@xstate/react": "^4.1.3",
|
|
80
83
|
"dotenv": "^16.4.5",
|
|
81
|
-
"express": "^4.
|
|
84
|
+
"express": "^4.21.1",
|
|
82
85
|
"express-ws": "^5.0.2",
|
|
83
86
|
"jest": "^29.7.0",
|
|
84
87
|
"jest-dev-server": "^10.1.1",
|
|
85
88
|
"jest-environment-node": "^29.7.0",
|
|
86
89
|
"jsdom": "^25.0.1",
|
|
87
90
|
"node-ipc": "npm:@node-ipc/compat@9.2.5",
|
|
91
|
+
"playwright": "^1.48.1",
|
|
88
92
|
"react": "^18.3.1",
|
|
89
93
|
"react-dom": "^18.3.1",
|
|
90
94
|
"rxjs": "^7.8.1",
|
|
91
95
|
"styled-components": "^6.1.13",
|
|
92
96
|
"ts-node": "^10.9.2",
|
|
93
|
-
"typescript": "5.6.
|
|
94
|
-
"vite": "^5.4.
|
|
95
|
-
"vitest": "^2.1.
|
|
97
|
+
"typescript": "5.6.3",
|
|
98
|
+
"vite": "^5.4.10",
|
|
99
|
+
"vitest": "^2.1.3",
|
|
100
|
+
"vitest-browser-react": "^0.0.3",
|
|
96
101
|
"@sanity/gherkin-driver": "^0.0.1"
|
|
97
102
|
},
|
|
98
103
|
"peerDependencies": {
|
|
99
|
-
"@sanity/block-tools": "^3.
|
|
100
|
-
"@sanity/schema": "^3.
|
|
101
|
-
"@sanity/types": "^3.
|
|
102
|
-
"@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",
|
|
103
108
|
"react": "^16.9 || ^17 || ^18",
|
|
104
109
|
"rxjs": "^7.8.1",
|
|
105
110
|
"styled-components": "^6.1.13"
|
package/src/editor/Editable.tsx
CHANGED
|
@@ -11,8 +11,6 @@ import {
|
|
|
11
11
|
type ClipboardEvent,
|
|
12
12
|
type CSSProperties,
|
|
13
13
|
type FocusEventHandler,
|
|
14
|
-
type ForwardedRef,
|
|
15
|
-
type HTMLProps,
|
|
16
14
|
type KeyboardEvent,
|
|
17
15
|
type MutableRefObject,
|
|
18
16
|
type TextareaHTMLAttributes,
|
|
@@ -66,7 +64,6 @@ import {
|
|
|
66
64
|
import {Element} from './components/Element'
|
|
67
65
|
import {Leaf} from './components/Leaf'
|
|
68
66
|
import {usePortableTextEditor} from './hooks/usePortableTextEditor'
|
|
69
|
-
import {usePortableTextEditorKeyGenerator} from './hooks/usePortableTextEditorKeyGenerator'
|
|
70
67
|
import {usePortableTextEditorReadOnlyStatus} from './hooks/usePortableTextReadOnly'
|
|
71
68
|
import {createWithHotkeys, createWithInsertData} from './plugins'
|
|
72
69
|
import {PortableTextEditor} from './PortableTextEditor'
|
|
@@ -113,11 +110,10 @@ export type PortableTextEditableProps = Omit<
|
|
|
113
110
|
/**
|
|
114
111
|
* @public
|
|
115
112
|
*/
|
|
116
|
-
export const PortableTextEditable = forwardRef
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
) {
|
|
113
|
+
export const PortableTextEditable = forwardRef<
|
|
114
|
+
Omit<HTMLDivElement, 'as' | 'onPaste' | 'onBeforeInput'>,
|
|
115
|
+
PortableTextEditableProps
|
|
116
|
+
>(function PortableTextEditable(props, forwardedRef) {
|
|
121
117
|
const {
|
|
122
118
|
hotkeys,
|
|
123
119
|
onBlur,
|
|
@@ -142,7 +138,6 @@ export const PortableTextEditable = forwardRef(function PortableTextEditable(
|
|
|
142
138
|
|
|
143
139
|
const portableTextEditor = usePortableTextEditor()
|
|
144
140
|
const readOnly = usePortableTextEditorReadOnlyStatus()
|
|
145
|
-
const keyGenerator = usePortableTextEditorKeyGenerator()
|
|
146
141
|
const ref = useRef<HTMLDivElement | null>(null)
|
|
147
142
|
const [editableElement, setEditableElement] = useState<HTMLDivElement | null>(
|
|
148
143
|
null,
|
|
@@ -167,8 +162,8 @@ export const PortableTextEditable = forwardRef(function PortableTextEditable(
|
|
|
167
162
|
|
|
168
163
|
// React/UI-specific plugins
|
|
169
164
|
const withInsertData = useMemo(
|
|
170
|
-
() => createWithInsertData(editorActor, schemaTypes
|
|
171
|
-
[editorActor,
|
|
165
|
+
() => createWithInsertData(editorActor, schemaTypes),
|
|
166
|
+
[editorActor, schemaTypes],
|
|
172
167
|
)
|
|
173
168
|
const withHotKeys = useMemo(
|
|
174
169
|
() => createWithHotkeys(schemaTypes, portableTextEditor, hotkeys),
|
|
@@ -639,6 +634,11 @@ export const PortableTextEditable = forwardRef(function PortableTextEditable(
|
|
|
639
634
|
props.onKeyDown(event)
|
|
640
635
|
}
|
|
641
636
|
if (!event.isDefaultPrevented()) {
|
|
637
|
+
editorActor.send({
|
|
638
|
+
type: 'key down',
|
|
639
|
+
nativeEvent: event.nativeEvent,
|
|
640
|
+
editor: slateEditor,
|
|
641
|
+
})
|
|
642
642
|
slateEditor.pteWithHotKeys(event)
|
|
643
643
|
}
|
|
644
644
|
},
|
|
@@ -24,16 +24,14 @@ 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'
|
|
30
31
|
import {PortableTextEditorContext} from './hooks/usePortableTextEditor'
|
|
31
|
-
import {
|
|
32
|
-
defaultKeyGenerator,
|
|
33
|
-
PortableTextEditorKeyGeneratorContext,
|
|
34
|
-
} from './hooks/usePortableTextEditorKeyGenerator'
|
|
35
32
|
import {PortableTextEditorSelectionProvider} from './hooks/usePortableTextEditorSelection'
|
|
36
33
|
import {PortableTextEditorReadOnlyContext} from './hooks/usePortableTextReadOnly'
|
|
34
|
+
import {defaultKeyGenerator} from './key-generator'
|
|
37
35
|
|
|
38
36
|
const debug = debugWithName('component:PortableTextEditor')
|
|
39
37
|
|
|
@@ -125,14 +123,20 @@ export class PortableTextEditor extends Component<PortableTextEditorProps> {
|
|
|
125
123
|
)
|
|
126
124
|
}
|
|
127
125
|
|
|
128
|
-
this.editorActor = createActor(editorMachine)
|
|
129
|
-
this.editorActor.start()
|
|
130
|
-
|
|
131
126
|
this.schemaTypes = getPortableTextMemberSchemaTypes(
|
|
132
127
|
props.schemaType.hasOwnProperty('jsonType')
|
|
133
128
|
? props.schemaType
|
|
134
129
|
: compileType(props.schemaType),
|
|
135
130
|
)
|
|
131
|
+
|
|
132
|
+
this.editorActor = createActor(editorMachine, {
|
|
133
|
+
input: {
|
|
134
|
+
behaviors: coreBehaviors,
|
|
135
|
+
keyGenerator: props.keyGenerator || defaultKeyGenerator,
|
|
136
|
+
schema: this.schemaTypes,
|
|
137
|
+
},
|
|
138
|
+
})
|
|
139
|
+
this.editorActor.start()
|
|
136
140
|
}
|
|
137
141
|
|
|
138
142
|
componentDidUpdate(prevProps: PortableTextEditorProps) {
|
|
@@ -143,7 +147,13 @@ export class PortableTextEditor extends Component<PortableTextEditorProps> {
|
|
|
143
147
|
? this.props.schemaType
|
|
144
148
|
: compileType(this.props.schemaType),
|
|
145
149
|
)
|
|
150
|
+
|
|
151
|
+
this.editorActor.send({
|
|
152
|
+
type: 'update schema',
|
|
153
|
+
schema: this.schemaTypes,
|
|
154
|
+
})
|
|
146
155
|
}
|
|
156
|
+
|
|
147
157
|
if (this.props.editorRef !== prevProps.editorRef && this.props.editorRef) {
|
|
148
158
|
this.props.editorRef.current = this
|
|
149
159
|
}
|
|
@@ -171,39 +181,34 @@ export class PortableTextEditor extends Component<PortableTextEditorProps> {
|
|
|
171
181
|
: Number.parseInt(this.props.maxBlocks.toString(), 10) || undefined
|
|
172
182
|
|
|
173
183
|
const readOnly = Boolean(this.props.readOnly)
|
|
174
|
-
|
|
184
|
+
|
|
175
185
|
return (
|
|
176
186
|
<SlateContainer
|
|
177
|
-
keyGenerator={keyGenerator}
|
|
178
187
|
maxBlocks={maxBlocks}
|
|
179
188
|
patches$={_patches$}
|
|
180
189
|
portableTextEditor={this}
|
|
181
190
|
readOnly={readOnly}
|
|
182
191
|
>
|
|
183
|
-
<
|
|
184
|
-
<
|
|
185
|
-
<
|
|
186
|
-
<
|
|
192
|
+
<PortableTextEditorContext.Provider value={this}>
|
|
193
|
+
<PortableTextEditorReadOnlyContext.Provider value={readOnly}>
|
|
194
|
+
<PortableTextEditorSelectionProvider editorActor={this.editorActor}>
|
|
195
|
+
<Synchronizer
|
|
187
196
|
editorActor={this.editorActor}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
</PortableTextEditorSelectionProvider>
|
|
204
|
-
</PortableTextEditorReadOnlyContext.Provider>
|
|
205
|
-
</PortableTextEditorContext.Provider>
|
|
206
|
-
</PortableTextEditorKeyGeneratorContext.Provider>
|
|
197
|
+
getValue={this.getValue}
|
|
198
|
+
onChange={(change) => {
|
|
199
|
+
this.props.onChange(change)
|
|
200
|
+
/**
|
|
201
|
+
* For backwards compatibility, we relay all changes to the
|
|
202
|
+
* `change$` Subject as well.
|
|
203
|
+
*/
|
|
204
|
+
this.change$.next(change)
|
|
205
|
+
}}
|
|
206
|
+
value={value}
|
|
207
|
+
/>
|
|
208
|
+
{children}
|
|
209
|
+
</PortableTextEditorSelectionProvider>
|
|
210
|
+
</PortableTextEditorReadOnlyContext.Provider>
|
|
211
|
+
</PortableTextEditorContext.Provider>
|
|
207
212
|
</SlateContainer>
|
|
208
213
|
)
|
|
209
214
|
}
|
|
@@ -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 '../../../
|
|
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
|
+
}
|
|
@@ -13,7 +13,6 @@ const debug = debugWithName('component:PortableTextEditor:SlateContainer')
|
|
|
13
13
|
* @internal
|
|
14
14
|
*/
|
|
15
15
|
export interface SlateContainerProps extends PropsWithChildren {
|
|
16
|
-
keyGenerator: () => string
|
|
17
16
|
maxBlocks: number | undefined
|
|
18
17
|
patches$?: PatchObservable
|
|
19
18
|
portableTextEditor: PortableTextEditor
|
|
@@ -25,14 +24,12 @@ export interface SlateContainerProps extends PropsWithChildren {
|
|
|
25
24
|
* @internal
|
|
26
25
|
*/
|
|
27
26
|
export function SlateContainer(props: SlateContainerProps) {
|
|
28
|
-
const {patches$, portableTextEditor, readOnly, maxBlocks
|
|
29
|
-
props
|
|
27
|
+
const {patches$, portableTextEditor, readOnly, maxBlocks} = props
|
|
30
28
|
|
|
31
29
|
// Create the slate instance, using `useState` ensures setup is only run once, initially
|
|
32
30
|
const [[slateEditor, subscribe]] = useState(() => {
|
|
33
31
|
debug('Creating new Slate editor instance')
|
|
34
32
|
const {editor, subscribe: _sub} = withPlugins(withReact(createEditor()), {
|
|
35
|
-
keyGenerator,
|
|
36
33
|
maxBlocks,
|
|
37
34
|
patches$,
|
|
38
35
|
portableTextEditor,
|
|
@@ -54,20 +51,12 @@ export function SlateContainer(props: SlateContainerProps) {
|
|
|
54
51
|
useEffect(() => {
|
|
55
52
|
debug('Re-initializing plugin chain')
|
|
56
53
|
withPlugins(slateEditor, {
|
|
57
|
-
keyGenerator,
|
|
58
54
|
maxBlocks,
|
|
59
55
|
patches$,
|
|
60
56
|
portableTextEditor,
|
|
61
57
|
readOnly,
|
|
62
58
|
})
|
|
63
|
-
}, [
|
|
64
|
-
keyGenerator,
|
|
65
|
-
portableTextEditor,
|
|
66
|
-
maxBlocks,
|
|
67
|
-
readOnly,
|
|
68
|
-
patches$,
|
|
69
|
-
slateEditor,
|
|
70
|
-
])
|
|
59
|
+
}, [portableTextEditor, maxBlocks, readOnly, patches$, slateEditor])
|
|
71
60
|
|
|
72
61
|
const initialValue = useMemo(() => {
|
|
73
62
|
return [slateEditor.pteCreateTextBlock({decorators: []})]
|
|
@@ -9,7 +9,6 @@ import {debugWithName} from '../../utils/debug'
|
|
|
9
9
|
import {IS_PROCESSING_LOCAL_CHANGES} from '../../utils/weakMaps'
|
|
10
10
|
import type {EditorActor} from '../editor-machine'
|
|
11
11
|
import {usePortableTextEditor} from '../hooks/usePortableTextEditor'
|
|
12
|
-
import {usePortableTextEditorKeyGenerator} from '../hooks/usePortableTextEditorKeyGenerator'
|
|
13
12
|
import {usePortableTextEditorReadOnlyStatus} from '../hooks/usePortableTextReadOnly'
|
|
14
13
|
import {useSyncValue} from '../hooks/useSyncValue'
|
|
15
14
|
|
|
@@ -36,14 +35,12 @@ export interface SynchronizerProps {
|
|
|
36
35
|
*/
|
|
37
36
|
export function Synchronizer(props: SynchronizerProps) {
|
|
38
37
|
const portableTextEditor = usePortableTextEditor()
|
|
39
|
-
const keyGenerator = usePortableTextEditorKeyGenerator()
|
|
40
38
|
const readOnly = usePortableTextEditorReadOnlyStatus()
|
|
41
39
|
const {editorActor, getValue, onChange, value} = props
|
|
42
40
|
const pendingPatches = useRef<Patch[]>([])
|
|
43
41
|
|
|
44
42
|
const syncValue = useSyncValue({
|
|
45
43
|
editorActor,
|
|
46
|
-
keyGenerator,
|
|
47
44
|
portableTextEditor,
|
|
48
45
|
readOnly,
|
|
49
46
|
})
|