@portabletext/editor 1.6.1 → 1.7.0
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 +3317 -3488
- package/lib/index.d.ts +3317 -3488
- package/lib/index.esm.js +4316 -4081
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +4307 -4072
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +4316 -4081
- package/lib/index.mjs.map +1 -1
- package/package.json +10 -11
- package/src/editor/Editable.tsx +5 -4
- package/src/editor/PortableTextEditor.tsx +90 -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 +67 -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 +46 -14
- package/src/index.ts +9 -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.0",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -45,10 +45,11 @@
|
|
|
45
45
|
"@portabletext/patches": "1.1.0",
|
|
46
46
|
"@xstate/react": "^4.1.3",
|
|
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",
|
|
@@ -59,10 +60,9 @@
|
|
|
59
60
|
"@portabletext/toolkit": "^2.0.16",
|
|
60
61
|
"@sanity/block-tools": "^3.63.0",
|
|
61
62
|
"@sanity/diff-match-patch": "^3.1.1",
|
|
62
|
-
"@sanity/pkg-utils": "^6.11.
|
|
63
|
+
"@sanity/pkg-utils": "^6.11.10",
|
|
63
64
|
"@sanity/schema": "^3.63.0",
|
|
64
65
|
"@sanity/types": "^3.63.0",
|
|
65
|
-
"@sanity/util": "^3.63.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,7 +84,7 @@
|
|
|
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"
|
|
@@ -93,7 +93,6 @@
|
|
|
93
93
|
"@sanity/block-tools": "^3.63.0",
|
|
94
94
|
"@sanity/schema": "^3.63.0",
|
|
95
95
|
"@sanity/types": "^3.63.0",
|
|
96
|
-
"@sanity/util": "^3.63.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,14 @@ 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
|
|
133
139
|
this.editorActor.start()
|
|
134
140
|
this.schemaTypes = this.editorActor.getSnapshot().context.schema
|
|
135
141
|
} else {
|
|
@@ -149,16 +155,37 @@ export class PortableTextEditor extends Component<
|
|
|
149
155
|
: compileType(props.schemaType),
|
|
150
156
|
)
|
|
151
157
|
|
|
152
|
-
this.editorActor =
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
},
|
|
159
|
-
})
|
|
158
|
+
this.editorActor = createActor(editorMachine, {
|
|
159
|
+
input: {
|
|
160
|
+
keyGenerator: props.keyGenerator || defaultKeyGenerator,
|
|
161
|
+
schema: this.schemaTypes,
|
|
162
|
+
},
|
|
163
|
+
})
|
|
160
164
|
this.editorActor.start()
|
|
165
|
+
|
|
166
|
+
if (props.readOnly) {
|
|
167
|
+
this.editorActor.send({
|
|
168
|
+
type: 'toggle readOnly',
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (props.maxBlocks) {
|
|
173
|
+
this.editorActor.send({
|
|
174
|
+
type: 'update maxBlocks',
|
|
175
|
+
maxBlocks:
|
|
176
|
+
props.maxBlocks === undefined
|
|
177
|
+
? undefined
|
|
178
|
+
: Number.parseInt(props.maxBlocks.toString(), 10),
|
|
179
|
+
})
|
|
180
|
+
}
|
|
161
181
|
}
|
|
182
|
+
this.slateEditor = createSlateEditor({
|
|
183
|
+
editorActor: this.editorActor,
|
|
184
|
+
})
|
|
185
|
+
this.editable = createEditableAPI(
|
|
186
|
+
this.slateEditor.instance,
|
|
187
|
+
this.editorActor,
|
|
188
|
+
)
|
|
162
189
|
}
|
|
163
190
|
|
|
164
191
|
componentDidUpdate(prevProps: PortableTextEditorProps) {
|
|
@@ -180,11 +207,33 @@ export class PortableTextEditor extends Component<
|
|
|
180
207
|
})
|
|
181
208
|
}
|
|
182
209
|
|
|
210
|
+
if (!this.props.editor && !prevProps.editor) {
|
|
211
|
+
if (this.props.readOnly !== prevProps.readOnly) {
|
|
212
|
+
this.editorActor.send({
|
|
213
|
+
type: 'toggle readOnly',
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (this.props.maxBlocks !== prevProps.maxBlocks) {
|
|
218
|
+
this.editorActor.send({
|
|
219
|
+
type: 'update maxBlocks',
|
|
220
|
+
maxBlocks:
|
|
221
|
+
this.props.maxBlocks === undefined
|
|
222
|
+
? undefined
|
|
223
|
+
: Number.parseInt(this.props.maxBlocks.toString(), 10),
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
183
228
|
if (this.props.editorRef !== prevProps.editorRef && this.props.editorRef) {
|
|
184
229
|
this.props.editorRef.current = this
|
|
185
230
|
}
|
|
186
231
|
}
|
|
187
232
|
|
|
233
|
+
componentWillUnmount(): void {
|
|
234
|
+
this.slateEditor.destroy()
|
|
235
|
+
}
|
|
236
|
+
|
|
188
237
|
public setEditable = (editable: EditableAPI) => {
|
|
189
238
|
this.editable = {...this.editable, ...editable}
|
|
190
239
|
}
|
|
@@ -198,13 +247,6 @@ export class PortableTextEditor extends Component<
|
|
|
198
247
|
}
|
|
199
248
|
|
|
200
249
|
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
250
|
const legacyPatches = !this.props.editor
|
|
209
251
|
? (this.props.incomingPatches$ ?? this.props.patches$)
|
|
210
252
|
: undefined
|
|
@@ -218,37 +260,33 @@ export class PortableTextEditor extends Component<
|
|
|
218
260
|
/>
|
|
219
261
|
) : null}
|
|
220
262
|
<EditorActorContext.Provider value={this.editorActor}>
|
|
221
|
-
<
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
portableTextEditor={this}
|
|
225
|
-
readOnly={readOnly}
|
|
263
|
+
<Slate
|
|
264
|
+
editor={this.slateEditor.instance}
|
|
265
|
+
initialValue={this.slateEditor.initialValue}
|
|
226
266
|
>
|
|
227
267
|
<PortableTextEditorContext.Provider value={this}>
|
|
228
|
-
<
|
|
229
|
-
|
|
268
|
+
<PortableTextEditorSelectionProvider
|
|
269
|
+
editorActor={this.editorActor}
|
|
270
|
+
>
|
|
271
|
+
<Synchronizer
|
|
230
272
|
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>
|
|
273
|
+
getValue={this.getValue}
|
|
274
|
+
onChange={(change) => {
|
|
275
|
+
if (!this.props.editor) {
|
|
276
|
+
this.props.onChange(change)
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* For backwards compatibility, we relay all changes to the
|
|
280
|
+
* `change$` Subject as well.
|
|
281
|
+
*/
|
|
282
|
+
this.change$.next(change)
|
|
283
|
+
}}
|
|
284
|
+
value={this.props.value}
|
|
285
|
+
/>
|
|
286
|
+
{this.props.children}
|
|
287
|
+
</PortableTextEditorSelectionProvider>
|
|
250
288
|
</PortableTextEditorContext.Provider>
|
|
251
|
-
</
|
|
289
|
+
</Slate>
|
|
252
290
|
</EditorActorContext.Provider>
|
|
253
291
|
</>
|
|
254
292
|
)
|
|
@@ -272,22 +310,8 @@ export class PortableTextEditor extends Component<
|
|
|
272
310
|
editor: PortableTextEditor,
|
|
273
311
|
type: TSchemaType,
|
|
274
312
|
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)
|
|
313
|
+
): AddedAnnotationPaths | undefined =>
|
|
314
|
+
editor.editable?.addAnnotation(type, value)
|
|
291
315
|
static blur = (editor: PortableTextEditor): void => {
|
|
292
316
|
debug('Host blurred')
|
|
293
317
|
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,67 @@
|
|
|
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
|
+
export type SlateEditor = {
|
|
16
|
+
instance: PortableTextSlateEditor
|
|
17
|
+
initialValue: Array<Descendant>
|
|
18
|
+
destroy: () => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const slateEditors = new WeakMap<EditorActor, SlateEditor>()
|
|
22
|
+
|
|
23
|
+
export function createSlateEditor(config: SlateEditorConfig): SlateEditor {
|
|
24
|
+
const existingSlateEditor = slateEditors.get(config.editorActor)
|
|
25
|
+
|
|
26
|
+
if (existingSlateEditor) {
|
|
27
|
+
debug('Reusing existing Slate editor instance', config.editorActor.id)
|
|
28
|
+
return existingSlateEditor
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
debug('Creating new Slate editor instance', config.editorActor.id)
|
|
32
|
+
|
|
33
|
+
let unsubscriptions: Array<() => void> = []
|
|
34
|
+
let subscriptions: Array<() => () => void> = []
|
|
35
|
+
|
|
36
|
+
const instance = withPlugins(withReact(createEditor()), {
|
|
37
|
+
editorActor: config.editorActor,
|
|
38
|
+
subscriptions,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
KEY_TO_VALUE_ELEMENT.set(instance, {})
|
|
42
|
+
KEY_TO_SLATE_ELEMENT.set(instance, {})
|
|
43
|
+
|
|
44
|
+
for (const subscription of subscriptions) {
|
|
45
|
+
unsubscriptions.push(subscription())
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const initialValue = [instance.pteCreateTextBlock({decorators: []})]
|
|
49
|
+
|
|
50
|
+
const slateEditor: SlateEditor = {
|
|
51
|
+
instance,
|
|
52
|
+
initialValue,
|
|
53
|
+
destroy: () => {
|
|
54
|
+
debug('Destroying Slate editor')
|
|
55
|
+
instance.destroy()
|
|
56
|
+
for (const unsubscribe of unsubscriptions) {
|
|
57
|
+
unsubscribe()
|
|
58
|
+
}
|
|
59
|
+
subscriptions = []
|
|
60
|
+
unsubscriptions = []
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
slateEditors.set(config.editorActor, slateEditor)
|
|
65
|
+
|
|
66
|
+
return slateEditor
|
|
67
|
+
}
|