@portabletext/editor 1.6.1 → 1.7.1
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 +5 -0
- package/lib/index.d.mts +3327 -3489
- package/lib/index.d.ts +3327 -3489
- package/lib/index.esm.js +4319 -4079
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +4311 -4071
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +4319 -4079
- package/lib/index.mjs.map +1 -1
- package/package.json +18 -19
- package/src/editor/Editable.tsx +5 -4
- package/src/editor/PortableTextEditor.tsx +88 -66
- package/src/editor/behavior/behavior.action.insert-break.ts +12 -2
- package/src/editor/behavior/behavior.actions.ts +51 -11
- package/src/editor/behavior/behavior.types.ts +23 -0
- package/src/editor/components/Synchronizer.tsx +11 -4
- package/src/editor/create-slate-editor.tsx +72 -0
- package/src/editor/editor-machine.ts +58 -8
- package/src/editor/key-generator.ts +30 -1
- package/src/editor/plugins/create-with-event-listeners.ts +62 -1
- package/src/editor/plugins/createWithEditableAPI.ts +800 -728
- package/src/editor/plugins/createWithMaxBlocks.ts +7 -2
- package/src/editor/plugins/createWithPatches.ts +4 -4
- package/src/editor/plugins/createWithPlaceholderBlock.ts +8 -3
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +3 -4
- package/src/editor/plugins/createWithUndoRedo.ts +6 -7
- package/src/editor/plugins/createWithUtils.ts +2 -8
- package/src/editor/plugins/{index.ts → with-plugins.ts} +22 -79
- package/src/editor/use-editor.ts +50 -14
- package/src/index.ts +10 -1
- package/src/types/editor.ts +0 -1
- package/src/types/options.ts +1 -3
- package/src/utils/__tests__/operationToPatches.test.ts +7 -14
- package/src/utils/__tests__/patchToOperations.test.ts +4 -7
- package/src/editor/components/SlateContainer.tsx +0 -79
- package/src/editor/hooks/usePortableTextReadOnly.ts +0 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -43,26 +43,26 @@
|
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@portabletext/patches": "1.1.0",
|
|
46
|
-
"@xstate/react": "^
|
|
46
|
+
"@xstate/react": "^5.0.0",
|
|
47
47
|
"debug": "^4.3.4",
|
|
48
|
+
"get-random-values-esm": "^1.0.2",
|
|
48
49
|
"is-hotkey-esm": "^1.0.0",
|
|
49
50
|
"lodash": "^4.17.21",
|
|
50
51
|
"lodash.startcase": "^4.4.0",
|
|
51
|
-
"react-compiler-runtime": "19.0.0-beta-
|
|
52
|
+
"react-compiler-runtime": "19.0.0-beta-a7bf2bd-20241110",
|
|
52
53
|
"slate": "0.110.2",
|
|
53
54
|
"slate-dom": "^0.111.0",
|
|
54
55
|
"slate-react": "0.111.0",
|
|
55
56
|
"use-effect-event": "^1.0.2",
|
|
56
|
-
"xstate": "^5.
|
|
57
|
+
"xstate": "^5.19.0"
|
|
57
58
|
},
|
|
58
59
|
"devDependencies": {
|
|
59
60
|
"@portabletext/toolkit": "^2.0.16",
|
|
60
|
-
"@sanity/block-tools": "^3.
|
|
61
|
+
"@sanity/block-tools": "^3.64.0",
|
|
61
62
|
"@sanity/diff-match-patch": "^3.1.1",
|
|
62
|
-
"@sanity/pkg-utils": "^6.11.
|
|
63
|
-
"@sanity/schema": "^3.
|
|
64
|
-
"@sanity/types": "^3.
|
|
65
|
-
"@sanity/util": "^3.63.0",
|
|
63
|
+
"@sanity/pkg-utils": "^6.11.10",
|
|
64
|
+
"@sanity/schema": "^3.64.0",
|
|
65
|
+
"@sanity/types": "^3.64.0",
|
|
66
66
|
"@testing-library/jest-dom": "^6.6.3",
|
|
67
67
|
"@testing-library/react": "^16.0.1",
|
|
68
68
|
"@types/debug": "^4.1.5",
|
|
@@ -70,13 +70,13 @@
|
|
|
70
70
|
"@types/lodash.startcase": "^4.4.9",
|
|
71
71
|
"@types/react": "^18.3.12",
|
|
72
72
|
"@types/react-dom": "^18.3.1",
|
|
73
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
74
|
-
"@typescript-eslint/parser": "^8.
|
|
73
|
+
"@typescript-eslint/eslint-plugin": "^8.14.0",
|
|
74
|
+
"@typescript-eslint/parser": "^8.14.0",
|
|
75
75
|
"@vitejs/plugin-react": "^4.3.3",
|
|
76
76
|
"@vitest/browser": "^2.1.4",
|
|
77
|
-
"babel-plugin-react-compiler": "19.0.0-beta-
|
|
77
|
+
"babel-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
|
|
78
78
|
"eslint": "8.57.1",
|
|
79
|
-
"eslint-plugin-react-compiler": "19.0.0-beta-
|
|
79
|
+
"eslint-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
|
|
80
80
|
"eslint-plugin-react-hooks": "^5.0.0",
|
|
81
81
|
"jsdom": "^25.0.1",
|
|
82
82
|
"react": "^18.3.1",
|
|
@@ -84,16 +84,15 @@
|
|
|
84
84
|
"rxjs": "^7.8.1",
|
|
85
85
|
"styled-components": "^6.1.13",
|
|
86
86
|
"typescript": "5.6.3",
|
|
87
|
-
"vite": "^5.4.
|
|
87
|
+
"vite": "^5.4.11",
|
|
88
88
|
"vitest": "^2.1.4",
|
|
89
89
|
"vitest-browser-react": "^0.0.3",
|
|
90
90
|
"@sanity/gherkin-driver": "^0.0.1"
|
|
91
91
|
},
|
|
92
92
|
"peerDependencies": {
|
|
93
|
-
"@sanity/block-tools": "^3.
|
|
94
|
-
"@sanity/schema": "^3.
|
|
95
|
-
"@sanity/types": "^3.
|
|
96
|
-
"@sanity/util": "^3.63.0",
|
|
93
|
+
"@sanity/block-tools": "^3.64.0",
|
|
94
|
+
"@sanity/schema": "^3.64.0",
|
|
95
|
+
"@sanity/types": "^3.64.0",
|
|
97
96
|
"react": "^16.9 || ^17 || ^18",
|
|
98
97
|
"rxjs": "^7.8.1",
|
|
99
98
|
"styled-components": "^6.1.13"
|
|
@@ -108,7 +107,7 @@
|
|
|
108
107
|
"build": "pkg-utils build --strict --check --clean",
|
|
109
108
|
"check:lint": "biome lint .",
|
|
110
109
|
"check:types": "tsc",
|
|
111
|
-
"check:react-compiler": "eslint --cache --no-inline-config --no-eslintrc --ext .cjs,.mjs,.js,.jsx,.ts,.tsx --parser @typescript-eslint/parser --plugin react-compiler --plugin react-hooks --rule 'react-compiler/react-compiler: [warn]' --rule 'react-hooks/rules-of-hooks: [error]' --rule 'react-hooks/exhaustive-deps: [error]' src",
|
|
110
|
+
"check:react-compiler": "eslint --cache --no-inline-config --no-eslintrc --ignore-pattern '**/__tests__/**' --ext .cjs,.mjs,.js,.jsx,.ts,.tsx --parser @typescript-eslint/parser --plugin react-compiler --plugin react-hooks --rule 'react-compiler/react-compiler: [warn,{__unstable_donotuse_reportAllBailouts: true}]' --rule 'react-hooks/rules-of-hooks: [error]' --rule 'react-hooks/exhaustive-deps: [error]' src",
|
|
112
111
|
"clean": "del .turbo && del lib && del node_modules",
|
|
113
112
|
"dev": "pkg-utils watch",
|
|
114
113
|
"lint:fix": "biome lint --write .",
|
package/src/editor/Editable.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {PortableTextBlock} from '@sanity/types'
|
|
2
|
+
import {useSelector} from '@xstate/react'
|
|
2
3
|
import {isEqual, noop} from 'lodash'
|
|
3
4
|
import {
|
|
4
5
|
forwardRef,
|
|
@@ -66,8 +67,8 @@ import {Element} from './components/Element'
|
|
|
66
67
|
import {Leaf} from './components/Leaf'
|
|
67
68
|
import {EditorActorContext} from './editor-actor-context'
|
|
68
69
|
import {usePortableTextEditor} from './hooks/usePortableTextEditor'
|
|
69
|
-
import {
|
|
70
|
-
import {
|
|
70
|
+
import {createWithHotkeys} from './plugins/createWithHotKeys'
|
|
71
|
+
import {createWithInsertData} from './plugins/createWithInsertData'
|
|
71
72
|
import {PortableTextEditor} from './PortableTextEditor'
|
|
72
73
|
import {withSyncRangeDecorations} from './withSyncRangeDecorations'
|
|
73
74
|
|
|
@@ -140,7 +141,6 @@ export const PortableTextEditable = forwardRef<
|
|
|
140
141
|
} = props
|
|
141
142
|
|
|
142
143
|
const portableTextEditor = usePortableTextEditor()
|
|
143
|
-
const readOnly = usePortableTextEditorReadOnlyStatus()
|
|
144
144
|
const ref = useRef<HTMLDivElement | null>(null)
|
|
145
145
|
const [editableElement, setEditableElement] = useState<HTMLDivElement | null>(
|
|
146
146
|
null,
|
|
@@ -159,6 +159,7 @@ export const PortableTextEditable = forwardRef<
|
|
|
159
159
|
const rangeDecorationsRef = useRef(rangeDecorations)
|
|
160
160
|
|
|
161
161
|
const editorActor = useContext(EditorActorContext)
|
|
162
|
+
const readOnly = useSelector(editorActor, (s) => s.context.readOnly)
|
|
162
163
|
const {schemaTypes} = portableTextEditor
|
|
163
164
|
const slateEditor = useSlate()
|
|
164
165
|
|
|
@@ -495,7 +496,7 @@ export const PortableTextEditable = forwardRef<
|
|
|
495
496
|
Transforms.select(slateEditor, Editor.start(slateEditor, []))
|
|
496
497
|
slateEditor.onChange()
|
|
497
498
|
}
|
|
498
|
-
editorActor.send({type: '
|
|
499
|
+
editorActor.send({type: 'focused', event})
|
|
499
500
|
const newSelection = PortableTextEditor.getSelection(portableTextEditor)
|
|
500
501
|
// If the selection is the same, emit it explicitly here as there is no actual onChange event triggered.
|
|
501
502
|
if (selection === newSelection) {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type PropsWithChildren,
|
|
14
14
|
} from 'react'
|
|
15
15
|
import {Subject} from 'rxjs'
|
|
16
|
+
import {Slate} from 'slate-react'
|
|
16
17
|
import {createActor} from 'xstate'
|
|
17
18
|
import type {
|
|
18
19
|
EditableAPI,
|
|
@@ -26,14 +27,17 @@ import type {
|
|
|
26
27
|
import {debugWithName} from '../utils/debug'
|
|
27
28
|
import {getPortableTextMemberSchemaTypes} from '../utils/getPortableTextMemberSchemaTypes'
|
|
28
29
|
import {compileType} from '../utils/schema'
|
|
29
|
-
import {SlateContainer} from './components/SlateContainer'
|
|
30
30
|
import {Synchronizer} from './components/Synchronizer'
|
|
31
|
+
import {createSlateEditor, type SlateEditor} from './create-slate-editor'
|
|
31
32
|
import {EditorActorContext} from './editor-actor-context'
|
|
32
33
|
import {editorMachine, type EditorActor} from './editor-machine'
|
|
33
34
|
import {PortableTextEditorContext} from './hooks/usePortableTextEditor'
|
|
34
35
|
import {PortableTextEditorSelectionProvider} from './hooks/usePortableTextEditorSelection'
|
|
35
|
-
import {PortableTextEditorReadOnlyContext} from './hooks/usePortableTextReadOnly'
|
|
36
36
|
import {defaultKeyGenerator} from './key-generator'
|
|
37
|
+
import {
|
|
38
|
+
createEditableAPI,
|
|
39
|
+
type AddedAnnotationPaths,
|
|
40
|
+
} from './plugins/createWithEditableAPI'
|
|
37
41
|
import type {Editor} from './use-editor'
|
|
38
42
|
|
|
39
43
|
const debug = debugWithName('component:PortableTextEditor')
|
|
@@ -85,12 +89,12 @@ export type PortableTextEditorProps<
|
|
|
85
89
|
* Backward compatibility (renamed to patches$).
|
|
86
90
|
*/
|
|
87
91
|
incomingPatches$?: PatchObservable
|
|
88
|
-
}) & {
|
|
89
|
-
/**
|
|
90
|
-
* Whether or not the editor should be in read-only mode
|
|
91
|
-
*/
|
|
92
|
-
readOnly?: boolean
|
|
93
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Whether or not the editor should be in read-only mode
|
|
95
|
+
*/
|
|
96
|
+
readOnly?: boolean
|
|
97
|
+
}) & {
|
|
94
98
|
/**
|
|
95
99
|
* The current value of the portable text field
|
|
96
100
|
*/
|
|
@@ -124,12 +128,15 @@ export class PortableTextEditor extends Component<
|
|
|
124
128
|
*/
|
|
125
129
|
private editable?: EditableAPI
|
|
126
130
|
private editorActor: EditorActor
|
|
131
|
+
private slateEditor: SlateEditor
|
|
127
132
|
|
|
128
133
|
constructor(props: PortableTextEditorProps) {
|
|
129
134
|
super(props)
|
|
130
135
|
|
|
131
136
|
if (props.editor) {
|
|
132
|
-
|
|
137
|
+
const editor = props.editor as Editor
|
|
138
|
+
this.editorActor = editor._internal.editorActor
|
|
139
|
+
this.slateEditor = editor._internal.slateEditor
|
|
133
140
|
this.editorActor.start()
|
|
134
141
|
this.schemaTypes = this.editorActor.getSnapshot().context.schema
|
|
135
142
|
} else {
|
|
@@ -149,16 +156,38 @@ export class PortableTextEditor extends Component<
|
|
|
149
156
|
: compileType(props.schemaType),
|
|
150
157
|
)
|
|
151
158
|
|
|
152
|
-
this.editorActor =
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
},
|
|
159
|
-
})
|
|
159
|
+
this.editorActor = createActor(editorMachine, {
|
|
160
|
+
input: {
|
|
161
|
+
keyGenerator: props.keyGenerator || defaultKeyGenerator,
|
|
162
|
+
schema: this.schemaTypes,
|
|
163
|
+
},
|
|
164
|
+
})
|
|
160
165
|
this.editorActor.start()
|
|
166
|
+
|
|
167
|
+
this.slateEditor = createSlateEditor({
|
|
168
|
+
editorActor: this.editorActor,
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
if (props.readOnly) {
|
|
172
|
+
this.editorActor.send({
|
|
173
|
+
type: 'toggle readOnly',
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (props.maxBlocks) {
|
|
178
|
+
this.editorActor.send({
|
|
179
|
+
type: 'update maxBlocks',
|
|
180
|
+
maxBlocks:
|
|
181
|
+
props.maxBlocks === undefined
|
|
182
|
+
? undefined
|
|
183
|
+
: Number.parseInt(props.maxBlocks.toString(), 10),
|
|
184
|
+
})
|
|
185
|
+
}
|
|
161
186
|
}
|
|
187
|
+
this.editable = createEditableAPI(
|
|
188
|
+
this.slateEditor.instance,
|
|
189
|
+
this.editorActor,
|
|
190
|
+
)
|
|
162
191
|
}
|
|
163
192
|
|
|
164
193
|
componentDidUpdate(prevProps: PortableTextEditorProps) {
|
|
@@ -180,6 +209,24 @@ export class PortableTextEditor extends Component<
|
|
|
180
209
|
})
|
|
181
210
|
}
|
|
182
211
|
|
|
212
|
+
if (!this.props.editor && !prevProps.editor) {
|
|
213
|
+
if (this.props.readOnly !== prevProps.readOnly) {
|
|
214
|
+
this.editorActor.send({
|
|
215
|
+
type: 'toggle readOnly',
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (this.props.maxBlocks !== prevProps.maxBlocks) {
|
|
220
|
+
this.editorActor.send({
|
|
221
|
+
type: 'update maxBlocks',
|
|
222
|
+
maxBlocks:
|
|
223
|
+
this.props.maxBlocks === undefined
|
|
224
|
+
? undefined
|
|
225
|
+
: Number.parseInt(this.props.maxBlocks.toString(), 10),
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
183
230
|
if (this.props.editorRef !== prevProps.editorRef && this.props.editorRef) {
|
|
184
231
|
this.props.editorRef.current = this
|
|
185
232
|
}
|
|
@@ -198,13 +245,6 @@ export class PortableTextEditor extends Component<
|
|
|
198
245
|
}
|
|
199
246
|
|
|
200
247
|
render() {
|
|
201
|
-
const maxBlocks = !this.props.editor
|
|
202
|
-
? typeof this.props.maxBlocks === 'undefined'
|
|
203
|
-
? undefined
|
|
204
|
-
: Number.parseInt(this.props.maxBlocks.toString(), 10) || undefined
|
|
205
|
-
: undefined
|
|
206
|
-
|
|
207
|
-
const readOnly = Boolean(this.props.readOnly)
|
|
208
248
|
const legacyPatches = !this.props.editor
|
|
209
249
|
? (this.props.incomingPatches$ ?? this.props.patches$)
|
|
210
250
|
: undefined
|
|
@@ -218,37 +258,33 @@ export class PortableTextEditor extends Component<
|
|
|
218
258
|
/>
|
|
219
259
|
) : null}
|
|
220
260
|
<EditorActorContext.Provider value={this.editorActor}>
|
|
221
|
-
<
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
portableTextEditor={this}
|
|
225
|
-
readOnly={readOnly}
|
|
261
|
+
<Slate
|
|
262
|
+
editor={this.slateEditor.instance}
|
|
263
|
+
initialValue={this.slateEditor.initialValue}
|
|
226
264
|
>
|
|
227
265
|
<PortableTextEditorContext.Provider value={this}>
|
|
228
|
-
<
|
|
229
|
-
|
|
266
|
+
<PortableTextEditorSelectionProvider
|
|
267
|
+
editorActor={this.editorActor}
|
|
268
|
+
>
|
|
269
|
+
<Synchronizer
|
|
230
270
|
editorActor={this.editorActor}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
/>
|
|
247
|
-
{this.props.children}
|
|
248
|
-
</PortableTextEditorSelectionProvider>
|
|
249
|
-
</PortableTextEditorReadOnlyContext.Provider>
|
|
271
|
+
getValue={this.getValue}
|
|
272
|
+
onChange={(change) => {
|
|
273
|
+
if (!this.props.editor) {
|
|
274
|
+
this.props.onChange(change)
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* For backwards compatibility, we relay all changes to the
|
|
278
|
+
* `change$` Subject as well.
|
|
279
|
+
*/
|
|
280
|
+
this.change$.next(change)
|
|
281
|
+
}}
|
|
282
|
+
value={this.props.value}
|
|
283
|
+
/>
|
|
284
|
+
{this.props.children}
|
|
285
|
+
</PortableTextEditorSelectionProvider>
|
|
250
286
|
</PortableTextEditorContext.Provider>
|
|
251
|
-
</
|
|
287
|
+
</Slate>
|
|
252
288
|
</EditorActorContext.Provider>
|
|
253
289
|
</>
|
|
254
290
|
)
|
|
@@ -272,22 +308,8 @@ export class PortableTextEditor extends Component<
|
|
|
272
308
|
editor: PortableTextEditor,
|
|
273
309
|
type: TSchemaType,
|
|
274
310
|
value?: {[prop: string]: unknown},
|
|
275
|
-
):
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* @deprecated An annotation may be applied to multiple blocks, resulting
|
|
279
|
-
* in multiple `markDef`'s being created. Use `markDefPaths` instead.
|
|
280
|
-
*/
|
|
281
|
-
markDefPath: Path
|
|
282
|
-
markDefPaths: Array<Path>
|
|
283
|
-
/**
|
|
284
|
-
* @deprecated Does not return anything meaningful since an annotation
|
|
285
|
-
* can span multiple blocks and spans. If references the span closest
|
|
286
|
-
* to the focus point of the selection.
|
|
287
|
-
*/
|
|
288
|
-
spanPath: Path
|
|
289
|
-
}
|
|
290
|
-
| undefined => editor.editable?.addAnnotation(type, value)
|
|
311
|
+
): AddedAnnotationPaths | undefined =>
|
|
312
|
+
editor.editable?.addAnnotation(type, value)
|
|
291
313
|
static blur = (editor: PortableTextEditor): void => {
|
|
292
314
|
debug('Host blurred')
|
|
293
315
|
editor.editable?.blur()
|
|
@@ -2,10 +2,9 @@ import {isEqual} from 'lodash'
|
|
|
2
2
|
import {Editor, Node, Path, Range, Transforms} from 'slate'
|
|
3
3
|
import type {SlateTextBlock, VoidElement} from '../../types/slate'
|
|
4
4
|
import type {BehaviourActionImplementation} from './behavior.actions'
|
|
5
|
-
import type {BehaviorAction, PickFromUnion} from './behavior.types'
|
|
6
5
|
|
|
7
6
|
export const insertBreakActionImplementation: BehaviourActionImplementation<
|
|
8
|
-
|
|
7
|
+
'insert break'
|
|
9
8
|
> = ({context, action}) => {
|
|
10
9
|
const keyGenerator = context.keyGenerator
|
|
11
10
|
const schema = context.schema
|
|
@@ -204,3 +203,14 @@ export const insertBreakActionImplementation: BehaviourActionImplementation<
|
|
|
204
203
|
}
|
|
205
204
|
}
|
|
206
205
|
}
|
|
206
|
+
|
|
207
|
+
export const insertSoftBreakActionImplementation: BehaviourActionImplementation<
|
|
208
|
+
'insert soft break'
|
|
209
|
+
> = ({context, action}) => {
|
|
210
|
+
// This mimics Slate's internal which also just does a regular insert break
|
|
211
|
+
// when soft-breaking
|
|
212
|
+
insertBreakActionImplementation({
|
|
213
|
+
context,
|
|
214
|
+
action: {...action, type: 'insert break'},
|
|
215
|
+
})
|
|
216
|
+
}
|
|
@@ -5,14 +5,23 @@ import {
|
|
|
5
5
|
insertText,
|
|
6
6
|
Transforms,
|
|
7
7
|
} from 'slate'
|
|
8
|
+
import {ReactEditor} from 'slate-react'
|
|
8
9
|
import type {PortableTextMemberSchemaTypes} from '../../types/editor'
|
|
9
10
|
import {toSlateRange} from '../../utils/ranges'
|
|
11
|
+
import {
|
|
12
|
+
addAnnotationActionImplementation,
|
|
13
|
+
removeAnnotationActionImplementation,
|
|
14
|
+
toggleAnnotationActionImplementation,
|
|
15
|
+
} from '../plugins/createWithEditableAPI'
|
|
10
16
|
import {
|
|
11
17
|
addDecoratorActionImplementation,
|
|
12
18
|
removeDecoratorActionImplementation,
|
|
13
19
|
toggleDecoratorActionImplementation,
|
|
14
20
|
} from '../plugins/createWithPortableTextMarkModel'
|
|
15
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
insertBreakActionImplementation,
|
|
23
|
+
insertSoftBreakActionImplementation,
|
|
24
|
+
} from './behavior.action.insert-break'
|
|
16
25
|
import type {
|
|
17
26
|
BehaviorAction,
|
|
18
27
|
BehaviorEvent,
|
|
@@ -25,25 +34,30 @@ export type BehaviorActionContext = {
|
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
export type BehaviourActionImplementation<
|
|
28
|
-
|
|
37
|
+
TBehaviorActionType extends BehaviorAction['type'],
|
|
38
|
+
TReturnType = void,
|
|
29
39
|
> = ({
|
|
30
40
|
context,
|
|
31
41
|
action,
|
|
32
42
|
}: {
|
|
33
43
|
context: BehaviorActionContext
|
|
34
|
-
action:
|
|
35
|
-
}) =>
|
|
44
|
+
action: PickFromUnion<BehaviorAction, 'type', TBehaviorActionType>
|
|
45
|
+
}) => TReturnType
|
|
36
46
|
|
|
37
47
|
type BehaviourActionImplementations = {
|
|
38
|
-
[TBehaviorActionType in BehaviorAction['type']]: BehaviourActionImplementation<
|
|
39
|
-
PickFromUnion<BehaviorAction, 'type', TBehaviorActionType>
|
|
40
|
-
>
|
|
48
|
+
[TBehaviorActionType in BehaviorAction['type']]: BehaviourActionImplementation<TBehaviorActionType>
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
const behaviorActionImplementations: BehaviourActionImplementations = {
|
|
52
|
+
'annotation.add': addAnnotationActionImplementation,
|
|
53
|
+
'annotation.remove': removeAnnotationActionImplementation,
|
|
54
|
+
'annotation.toggle': toggleAnnotationActionImplementation,
|
|
44
55
|
'decorator.add': addDecoratorActionImplementation,
|
|
45
56
|
'decorator.remove': removeDecoratorActionImplementation,
|
|
46
57
|
'decorator.toggle': toggleDecoratorActionImplementation,
|
|
58
|
+
'focus': ({action}) => {
|
|
59
|
+
ReactEditor.focus(action.editor)
|
|
60
|
+
},
|
|
47
61
|
'set block': ({action}) => {
|
|
48
62
|
for (const path of action.paths) {
|
|
49
63
|
const at = toSlateRange(
|
|
@@ -99,9 +113,7 @@ const behaviorActionImplementations: BehaviourActionImplementations = {
|
|
|
99
113
|
}
|
|
100
114
|
},
|
|
101
115
|
'insert break': insertBreakActionImplementation,
|
|
102
|
-
|
|
103
|
-
// when on soft break
|
|
104
|
-
'insert soft break': insertBreakActionImplementation,
|
|
116
|
+
'insert soft break': insertSoftBreakActionImplementation,
|
|
105
117
|
'insert text': ({action}) => {
|
|
106
118
|
insertText(action.editor, action.text)
|
|
107
119
|
},
|
|
@@ -205,7 +217,7 @@ export function performAction({
|
|
|
205
217
|
}
|
|
206
218
|
}
|
|
207
219
|
|
|
208
|
-
|
|
220
|
+
function performDefaultAction({
|
|
209
221
|
context,
|
|
210
222
|
action,
|
|
211
223
|
}: {
|
|
@@ -213,6 +225,27 @@ export function performDefaultAction({
|
|
|
213
225
|
action: PickFromUnion<BehaviorAction, 'type', BehaviorEvent['type']>
|
|
214
226
|
}) {
|
|
215
227
|
switch (action.type) {
|
|
228
|
+
case 'annotation.add': {
|
|
229
|
+
behaviorActionImplementations['annotation.add']({
|
|
230
|
+
context,
|
|
231
|
+
action,
|
|
232
|
+
})
|
|
233
|
+
break
|
|
234
|
+
}
|
|
235
|
+
case 'annotation.remove': {
|
|
236
|
+
behaviorActionImplementations['annotation.remove']({
|
|
237
|
+
context,
|
|
238
|
+
action,
|
|
239
|
+
})
|
|
240
|
+
break
|
|
241
|
+
}
|
|
242
|
+
case 'annotation.toggle': {
|
|
243
|
+
behaviorActionImplementations['annotation.toggle']({
|
|
244
|
+
context,
|
|
245
|
+
action,
|
|
246
|
+
})
|
|
247
|
+
break
|
|
248
|
+
}
|
|
216
249
|
case 'decorator.add': {
|
|
217
250
|
behaviorActionImplementations['decorator.add']({
|
|
218
251
|
context,
|
|
@@ -248,6 +281,13 @@ export function performDefaultAction({
|
|
|
248
281
|
})
|
|
249
282
|
break
|
|
250
283
|
}
|
|
284
|
+
case 'focus': {
|
|
285
|
+
behaviorActionImplementations.focus({
|
|
286
|
+
context,
|
|
287
|
+
action,
|
|
288
|
+
})
|
|
289
|
+
break
|
|
290
|
+
}
|
|
251
291
|
case 'insert break': {
|
|
252
292
|
behaviorActionImplementations['insert break']({
|
|
253
293
|
context,
|
|
@@ -20,6 +20,26 @@ export type BehaviorContext = {
|
|
|
20
20
|
* @alpha
|
|
21
21
|
*/
|
|
22
22
|
export type BehaviorEvent =
|
|
23
|
+
| {
|
|
24
|
+
type: 'annotation.add'
|
|
25
|
+
annotation: {
|
|
26
|
+
name: string
|
|
27
|
+
value: {[prop: string]: unknown}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
| {
|
|
31
|
+
type: 'annotation.remove'
|
|
32
|
+
annotation: {
|
|
33
|
+
name: string
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
| {
|
|
37
|
+
type: 'annotation.toggle'
|
|
38
|
+
annotation: {
|
|
39
|
+
name: string
|
|
40
|
+
value: {[prop: string]: unknown}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
23
43
|
| {
|
|
24
44
|
type: 'decorator.add'
|
|
25
45
|
decorator: string
|
|
@@ -40,6 +60,9 @@ export type BehaviorEvent =
|
|
|
40
60
|
type: 'delete forward'
|
|
41
61
|
unit: TextUnit
|
|
42
62
|
}
|
|
63
|
+
| {
|
|
64
|
+
type: 'focus'
|
|
65
|
+
}
|
|
43
66
|
| {
|
|
44
67
|
type: 'insert soft break'
|
|
45
68
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {Patch} from '@portabletext/patches'
|
|
2
2
|
import type {PortableTextBlock} from '@sanity/types'
|
|
3
|
+
import {useSelector} from '@xstate/react'
|
|
3
4
|
import {throttle} from 'lodash'
|
|
4
5
|
import {useCallback, useEffect, useRef} from 'react'
|
|
5
6
|
import {Editor} from 'slate'
|
|
@@ -10,7 +11,6 @@ import {debugWithName} from '../../utils/debug'
|
|
|
10
11
|
import {IS_PROCESSING_LOCAL_CHANGES} from '../../utils/weakMaps'
|
|
11
12
|
import type {EditorActor} from '../editor-machine'
|
|
12
13
|
import {usePortableTextEditor} from '../hooks/usePortableTextEditor'
|
|
13
|
-
import {usePortableTextEditorReadOnlyStatus} from '../hooks/usePortableTextReadOnly'
|
|
14
14
|
import {useSyncValue} from '../hooks/useSyncValue'
|
|
15
15
|
|
|
16
16
|
const debug = debugWithName('component:PortableTextEditor:Synchronizer')
|
|
@@ -36,7 +36,7 @@ export interface SynchronizerProps {
|
|
|
36
36
|
*/
|
|
37
37
|
export function Synchronizer(props: SynchronizerProps) {
|
|
38
38
|
const portableTextEditor = usePortableTextEditor()
|
|
39
|
-
const readOnly =
|
|
39
|
+
const readOnly = useSelector(props.editorActor, (s) => s.context.readOnly)
|
|
40
40
|
const {editorActor, getValue, onChange, value} = props
|
|
41
41
|
const pendingPatches = useRef<Patch[]>([])
|
|
42
42
|
|
|
@@ -121,6 +121,10 @@ export function Synchronizer(props: SynchronizerProps) {
|
|
|
121
121
|
handleChange({type: 'loading', isLoading: false})
|
|
122
122
|
break
|
|
123
123
|
}
|
|
124
|
+
case 'focused': {
|
|
125
|
+
handleChange({type: 'focus', event: event.event})
|
|
126
|
+
break
|
|
127
|
+
}
|
|
124
128
|
case 'offline': {
|
|
125
129
|
handleChange({type: 'connection', value: 'offline'})
|
|
126
130
|
break
|
|
@@ -148,9 +152,12 @@ export function Synchronizer(props: SynchronizerProps) {
|
|
|
148
152
|
})
|
|
149
153
|
break
|
|
150
154
|
}
|
|
151
|
-
case '
|
|
155
|
+
case 'annotation.add':
|
|
156
|
+
case 'annotation.remove':
|
|
157
|
+
case 'annotation.toggle':
|
|
158
|
+
case 'focus':
|
|
159
|
+
case 'patches':
|
|
152
160
|
break
|
|
153
|
-
}
|
|
154
161
|
default:
|
|
155
162
|
handleChange(event)
|
|
156
163
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {createEditor, type Descendant} from 'slate'
|
|
2
|
+
import {withReact} from 'slate-react'
|
|
3
|
+
import type {PortableTextSlateEditor} from '../types/editor'
|
|
4
|
+
import {debugWithName} from '../utils/debug'
|
|
5
|
+
import {KEY_TO_SLATE_ELEMENT, KEY_TO_VALUE_ELEMENT} from '../utils/weakMaps'
|
|
6
|
+
import type {EditorActor} from './editor-machine'
|
|
7
|
+
import {withPlugins} from './plugins/with-plugins'
|
|
8
|
+
|
|
9
|
+
const debug = debugWithName('component:PortableTextEditor:SlateContainer')
|
|
10
|
+
|
|
11
|
+
type SlateEditorConfig = {
|
|
12
|
+
editorActor: EditorActor
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export type SlateEditor = {
|
|
19
|
+
instance: PortableTextSlateEditor
|
|
20
|
+
initialValue: Array<Descendant>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const slateEditors = new WeakMap<EditorActor, SlateEditor>()
|
|
24
|
+
|
|
25
|
+
export function createSlateEditor(config: SlateEditorConfig): SlateEditor {
|
|
26
|
+
const existingSlateEditor = slateEditors.get(config.editorActor)
|
|
27
|
+
|
|
28
|
+
if (existingSlateEditor) {
|
|
29
|
+
debug('Reusing existing Slate editor instance', config.editorActor.id)
|
|
30
|
+
return existingSlateEditor
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
debug('Creating new Slate editor instance', config.editorActor.id)
|
|
34
|
+
|
|
35
|
+
let unsubscriptions: Array<() => void> = []
|
|
36
|
+
let subscriptions: Array<() => () => void> = []
|
|
37
|
+
|
|
38
|
+
const instance = withPlugins(withReact(createEditor()), {
|
|
39
|
+
editorActor: config.editorActor,
|
|
40
|
+
subscriptions,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
KEY_TO_VALUE_ELEMENT.set(instance, {})
|
|
44
|
+
KEY_TO_SLATE_ELEMENT.set(instance, {})
|
|
45
|
+
|
|
46
|
+
for (const subscription of subscriptions) {
|
|
47
|
+
unsubscriptions.push(subscription())
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
config.editorActor.subscribe((snapshot) => {
|
|
51
|
+
if (snapshot.status !== 'active') {
|
|
52
|
+
debug('Destroying Slate editor')
|
|
53
|
+
instance.destroy()
|
|
54
|
+
for (const unsubscribe of unsubscriptions) {
|
|
55
|
+
unsubscribe()
|
|
56
|
+
}
|
|
57
|
+
subscriptions = []
|
|
58
|
+
unsubscriptions = []
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const initialValue = [instance.pteCreateTextBlock({decorators: []})]
|
|
63
|
+
|
|
64
|
+
const slateEditor: SlateEditor = {
|
|
65
|
+
instance,
|
|
66
|
+
initialValue,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
slateEditors.set(config.editorActor, slateEditor)
|
|
70
|
+
|
|
71
|
+
return slateEditor
|
|
72
|
+
}
|