@portabletext/editor 1.6.0 → 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 +4337 -4089
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +4325 -4077
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +4337 -4089
- package/lib/index.mjs.map +1 -1
- package/package.json +10 -11
- package/src/editor/Editable.tsx +26 -28
- 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/editor/withSyncRangeDecorations.ts +20 -0
- 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,9 +67,10 @@ 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'
|
|
73
|
+
import {withSyncRangeDecorations} from './withSyncRangeDecorations'
|
|
72
74
|
|
|
73
75
|
const debug = debugWithName('component:Editable')
|
|
74
76
|
|
|
@@ -139,7 +141,6 @@ export const PortableTextEditable = forwardRef<
|
|
|
139
141
|
} = props
|
|
140
142
|
|
|
141
143
|
const portableTextEditor = usePortableTextEditor()
|
|
142
|
-
const readOnly = usePortableTextEditorReadOnlyStatus()
|
|
143
144
|
const ref = useRef<HTMLDivElement | null>(null)
|
|
144
145
|
const [editableElement, setEditableElement] = useState<HTMLDivElement | null>(
|
|
145
146
|
null,
|
|
@@ -158,32 +159,39 @@ export const PortableTextEditable = forwardRef<
|
|
|
158
159
|
const rangeDecorationsRef = useRef(rangeDecorations)
|
|
159
160
|
|
|
160
161
|
const editorActor = useContext(EditorActorContext)
|
|
162
|
+
const readOnly = useSelector(editorActor, (s) => s.context.readOnly)
|
|
161
163
|
const {schemaTypes} = portableTextEditor
|
|
162
164
|
const slateEditor = useSlate()
|
|
163
165
|
|
|
164
166
|
const blockTypeName = schemaTypes.block.name
|
|
165
167
|
|
|
166
|
-
// React/UI-specific plugins
|
|
167
|
-
const withInsertData = useMemo(
|
|
168
|
-
() => createWithInsertData(editorActor, schemaTypes),
|
|
169
|
-
[editorActor, schemaTypes],
|
|
170
|
-
)
|
|
171
|
-
const withHotKeys = useMemo(
|
|
172
|
-
() => createWithHotkeys(editorActor, portableTextEditor, hotkeys),
|
|
173
|
-
[editorActor, hotkeys, portableTextEditor],
|
|
174
|
-
)
|
|
175
|
-
|
|
176
168
|
// Output a minimal React editor inside Editable when in readOnly mode.
|
|
177
169
|
// NOTE: make sure all the plugins used here can be safely run over again at any point.
|
|
178
170
|
// There will be a problem if they redefine editor methods and then calling the original method within themselves.
|
|
179
171
|
useMemo(() => {
|
|
172
|
+
// React/UI-specific plugins
|
|
173
|
+
const withInsertData = createWithInsertData(editorActor, schemaTypes)
|
|
174
|
+
|
|
180
175
|
if (readOnly) {
|
|
181
176
|
debug('Editable is in read only mode')
|
|
182
177
|
return withInsertData(slateEditor)
|
|
183
178
|
}
|
|
179
|
+
const withHotKeys = createWithHotkeys(
|
|
180
|
+
editorActor,
|
|
181
|
+
portableTextEditor,
|
|
182
|
+
hotkeys,
|
|
183
|
+
)
|
|
184
|
+
|
|
184
185
|
debug('Editable is in edit mode')
|
|
185
186
|
return withInsertData(withHotKeys(slateEditor))
|
|
186
|
-
}, [
|
|
187
|
+
}, [
|
|
188
|
+
editorActor,
|
|
189
|
+
hotkeys,
|
|
190
|
+
portableTextEditor,
|
|
191
|
+
readOnly,
|
|
192
|
+
schemaTypes,
|
|
193
|
+
slateEditor,
|
|
194
|
+
])
|
|
187
195
|
|
|
188
196
|
const renderElement = useCallback(
|
|
189
197
|
(eProps: RenderElementProps) => (
|
|
@@ -381,9 +389,6 @@ export const PortableTextEditable = forwardRef<
|
|
|
381
389
|
}
|
|
382
390
|
}, [hasInvalidValue, propsSelection, restoreSelectionFromProps])
|
|
383
391
|
|
|
384
|
-
// Store reference to original apply function (see below for usage in useEffect)
|
|
385
|
-
const originalApply = useMemo(() => slateEditor.apply, [slateEditor])
|
|
386
|
-
|
|
387
392
|
const [syncedRangeDecorations, setSyncedRangeDecorations] = useState(false)
|
|
388
393
|
useEffect(() => {
|
|
389
394
|
if (!syncedRangeDecorations) {
|
|
@@ -402,16 +407,9 @@ export const PortableTextEditable = forwardRef<
|
|
|
402
407
|
|
|
403
408
|
// Sync range decorations after an operation is applied
|
|
404
409
|
useEffect(() => {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
syncRangeDecorations(op)
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
return () => {
|
|
412
|
-
slateEditor.apply = originalApply
|
|
413
|
-
}
|
|
414
|
-
}, [originalApply, slateEditor, syncRangeDecorations])
|
|
410
|
+
const teardown = withSyncRangeDecorations(slateEditor, syncRangeDecorations)
|
|
411
|
+
return () => teardown()
|
|
412
|
+
}, [slateEditor, syncRangeDecorations])
|
|
415
413
|
|
|
416
414
|
// Handle from props onCopy function
|
|
417
415
|
const handleCopy = useCallback(
|
|
@@ -498,7 +496,7 @@ export const PortableTextEditable = forwardRef<
|
|
|
498
496
|
Transforms.select(slateEditor, Editor.start(slateEditor, []))
|
|
499
497
|
slateEditor.onChange()
|
|
500
498
|
}
|
|
501
|
-
editorActor.send({type: '
|
|
499
|
+
editorActor.send({type: 'focused', event})
|
|
502
500
|
const newSelection = PortableTextEditor.getSelection(portableTextEditor)
|
|
503
501
|
// If the selection is the same, emit it explicitly here as there is no actual onChange event triggered.
|
|
504
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
|
}
|