@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
|
@@ -10,7 +10,21 @@ import {
|
|
|
10
10
|
setup,
|
|
11
11
|
type ActorRefFrom,
|
|
12
12
|
} from 'xstate'
|
|
13
|
-
import type {
|
|
13
|
+
import type {
|
|
14
|
+
EditorSelection,
|
|
15
|
+
InvalidValueResolution,
|
|
16
|
+
PortableTextMemberSchemaTypes,
|
|
17
|
+
} from '../types/editor'
|
|
18
|
+
import {toPortableTextRange} from '../utils/ranges'
|
|
19
|
+
import {fromSlateValue} from '../utils/values'
|
|
20
|
+
import {KEY_TO_VALUE_ELEMENT} from '../utils/weakMaps'
|
|
21
|
+
import {inserText, inserTextBlock} from './behavior/behavior.actions'
|
|
22
|
+
import type {
|
|
23
|
+
Behavior,
|
|
24
|
+
BehaviorAction,
|
|
25
|
+
BehaviorContext,
|
|
26
|
+
BehaviorEvent,
|
|
27
|
+
} from './behavior/behavior.types'
|
|
14
28
|
|
|
15
29
|
/**
|
|
16
30
|
* @internal
|
|
@@ -51,6 +65,12 @@ export type MutationEvent = {
|
|
|
51
65
|
type EditorEvent =
|
|
52
66
|
| {type: 'normalizing'}
|
|
53
67
|
| {type: 'done normalizing'}
|
|
68
|
+
| BehaviorEvent
|
|
69
|
+
| BehaviorAction
|
|
70
|
+
| {
|
|
71
|
+
type: 'update schema'
|
|
72
|
+
schema: PortableTextMemberSchemaTypes
|
|
73
|
+
}
|
|
54
74
|
| EditorEmittedEvent
|
|
55
75
|
|
|
56
76
|
type EditorEmittedEvent =
|
|
@@ -90,12 +110,34 @@ type EditorEmittedEvent =
|
|
|
90
110
|
export const editorMachine = setup({
|
|
91
111
|
types: {
|
|
92
112
|
context: {} as {
|
|
113
|
+
behaviors: Array<Behavior>
|
|
114
|
+
keyGenerator: () => string
|
|
93
115
|
pendingEvents: Array<PatchEvent | MutationEvent>
|
|
116
|
+
schema: PortableTextMemberSchemaTypes
|
|
94
117
|
},
|
|
95
118
|
events: {} as EditorEvent,
|
|
96
119
|
emitted: {} as EditorEmittedEvent,
|
|
120
|
+
input: {} as {
|
|
121
|
+
behaviors: Array<Behavior>
|
|
122
|
+
keyGenerator: () => string
|
|
123
|
+
schema: PortableTextMemberSchemaTypes
|
|
124
|
+
},
|
|
97
125
|
},
|
|
98
126
|
actions: {
|
|
127
|
+
'apply:insert text': ({context, event}) => {
|
|
128
|
+
assertEvent(event, 'insert text')
|
|
129
|
+
inserText({context, event})
|
|
130
|
+
},
|
|
131
|
+
'apply:insert text block': ({context, event}) => {
|
|
132
|
+
assertEvent(event, 'insert text block')
|
|
133
|
+
inserTextBlock({context, event})
|
|
134
|
+
},
|
|
135
|
+
'assign schema': assign({
|
|
136
|
+
schema: ({event}) => {
|
|
137
|
+
assertEvent(event, 'update schema')
|
|
138
|
+
return event.schema
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
99
141
|
'emit patch event': emit(({event}) => {
|
|
100
142
|
assertEvent(event, 'patch')
|
|
101
143
|
return event
|
|
@@ -118,15 +160,80 @@ export const editorMachine = setup({
|
|
|
118
160
|
'clear pending events': assign({
|
|
119
161
|
pendingEvents: [],
|
|
120
162
|
}),
|
|
163
|
+
'handle behavior event': enqueueActions(({context, event, enqueue}) => {
|
|
164
|
+
assertEvent(event, ['key down'])
|
|
165
|
+
|
|
166
|
+
const eventBehaviors = context.behaviors.filter(
|
|
167
|
+
(behavior) => behavior.on === event.type,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if (eventBehaviors.length === 0) {
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const value = fromSlateValue(
|
|
175
|
+
event.editor.children,
|
|
176
|
+
context.schema.block.name,
|
|
177
|
+
KEY_TO_VALUE_ELEMENT.get(event.editor),
|
|
178
|
+
)
|
|
179
|
+
const selection = toPortableTextRange(
|
|
180
|
+
value,
|
|
181
|
+
event.editor.selection,
|
|
182
|
+
context.schema,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if (!selection) {
|
|
186
|
+
console.warn(
|
|
187
|
+
`Unable to handle event ${event.type} due to missing selection`,
|
|
188
|
+
)
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const behaviorContext = {
|
|
193
|
+
schema: context.schema,
|
|
194
|
+
value,
|
|
195
|
+
selection,
|
|
196
|
+
} satisfies BehaviorContext
|
|
197
|
+
|
|
198
|
+
for (const eventBehavior of eventBehaviors) {
|
|
199
|
+
const shouldRun =
|
|
200
|
+
eventBehavior.guard?.({
|
|
201
|
+
context: behaviorContext,
|
|
202
|
+
event,
|
|
203
|
+
}) ?? true
|
|
204
|
+
|
|
205
|
+
if (!shouldRun) {
|
|
206
|
+
continue
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const actions = eventBehavior.actions.map((action) =>
|
|
210
|
+
action({context: behaviorContext, event}, shouldRun),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
for (const action of actions) {
|
|
214
|
+
if (typeof action !== 'object') {
|
|
215
|
+
continue
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
enqueue.raise({
|
|
219
|
+
...action,
|
|
220
|
+
editor: event.editor,
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}),
|
|
121
225
|
},
|
|
122
226
|
actors: {
|
|
123
227
|
networkLogic,
|
|
124
228
|
},
|
|
125
229
|
}).createMachine({
|
|
126
230
|
id: 'editor',
|
|
127
|
-
context: {
|
|
231
|
+
context: ({input}) => ({
|
|
232
|
+
behaviors: input.behaviors,
|
|
233
|
+
keyGenerator: input.keyGenerator,
|
|
128
234
|
pendingEvents: [],
|
|
129
|
-
|
|
235
|
+
schema: input.schema,
|
|
236
|
+
}),
|
|
130
237
|
invoke: {
|
|
131
238
|
id: 'networkLogic',
|
|
132
239
|
src: 'networkLogic',
|
|
@@ -144,6 +251,16 @@ export const editorMachine = setup({
|
|
|
144
251
|
'offline': {actions: emit({type: 'offline'})},
|
|
145
252
|
'loading': {actions: emit({type: 'loading'})},
|
|
146
253
|
'done loading': {actions: emit({type: 'done loading'})},
|
|
254
|
+
'update schema': {actions: 'assign schema'},
|
|
255
|
+
'key down': {
|
|
256
|
+
actions: ['handle behavior event'],
|
|
257
|
+
},
|
|
258
|
+
'insert text': {
|
|
259
|
+
actions: ['apply:insert text'],
|
|
260
|
+
},
|
|
261
|
+
'insert text block': {
|
|
262
|
+
actions: ['apply:insert text block'],
|
|
263
|
+
},
|
|
147
264
|
},
|
|
148
265
|
initial: 'pristine',
|
|
149
266
|
states: {
|
|
@@ -24,7 +24,6 @@ const debug = debugWithName('hook:useSyncValue')
|
|
|
24
24
|
*/
|
|
25
25
|
export interface UseSyncValueProps {
|
|
26
26
|
editorActor: EditorActor
|
|
27
|
-
keyGenerator: () => string
|
|
28
27
|
portableTextEditor: PortableTextEditor
|
|
29
28
|
readOnly: boolean
|
|
30
29
|
}
|
|
@@ -52,7 +51,7 @@ export function useSyncValue(
|
|
|
52
51
|
value: PortableTextBlock[] | undefined,
|
|
53
52
|
userCallbackFn?: () => void,
|
|
54
53
|
) => void {
|
|
55
|
-
const {editorActor, portableTextEditor, readOnly
|
|
54
|
+
const {editorActor, portableTextEditor, readOnly} = props
|
|
56
55
|
const {schemaTypes} = portableTextEditor
|
|
57
56
|
const previousValue = useRef<PortableTextBlock[] | undefined>()
|
|
58
57
|
const slateEditor = useSlate()
|
|
@@ -162,7 +161,7 @@ export function useSyncValue(
|
|
|
162
161
|
const validation = validateValue(
|
|
163
162
|
validationValue,
|
|
164
163
|
schemaTypes,
|
|
165
|
-
keyGenerator,
|
|
164
|
+
editorActor.getSnapshot().context.keyGenerator,
|
|
166
165
|
)
|
|
167
166
|
// Resolve validations that can be resolved automatically, without involving the user (but only if the value was changed)
|
|
168
167
|
if (
|
|
@@ -222,7 +221,7 @@ export function useSyncValue(
|
|
|
222
221
|
const validation = validateValue(
|
|
223
222
|
validationValue,
|
|
224
223
|
schemaTypes,
|
|
225
|
-
keyGenerator,
|
|
224
|
+
editorActor.getSnapshot().context.keyGenerator,
|
|
226
225
|
)
|
|
227
226
|
if (debug.enabled)
|
|
228
227
|
debug(
|
|
@@ -288,7 +287,6 @@ export function useSyncValue(
|
|
|
288
287
|
return updateFunction
|
|
289
288
|
}, [
|
|
290
289
|
editorActor,
|
|
291
|
-
keyGenerator,
|
|
292
290
|
portableTextEditor,
|
|
293
291
|
readOnly,
|
|
294
292
|
schemaTypes,
|
|
@@ -38,14 +38,15 @@ import {
|
|
|
38
38
|
KEY_TO_VALUE_ELEMENT,
|
|
39
39
|
SLATE_TO_PORTABLE_TEXT_RANGE,
|
|
40
40
|
} from '../../utils/weakMaps'
|
|
41
|
+
import type {EditorActor} from '../editor-machine'
|
|
41
42
|
import type {PortableTextEditor} from '../PortableTextEditor'
|
|
42
43
|
|
|
43
44
|
const debug = debugWithName('API:editable')
|
|
44
45
|
|
|
45
46
|
export function createWithEditableAPI(
|
|
47
|
+
editorActor: EditorActor,
|
|
46
48
|
portableTextEditor: PortableTextEditor,
|
|
47
49
|
types: PortableTextMemberSchemaTypes,
|
|
48
|
-
keyGenerator: () => string,
|
|
49
50
|
) {
|
|
50
51
|
return function withEditableAPI(
|
|
51
52
|
editor: PortableTextSlateEditor,
|
|
@@ -151,11 +152,11 @@ export function createWithEditableAPI(
|
|
|
151
152
|
const block = toSlateValue(
|
|
152
153
|
[
|
|
153
154
|
{
|
|
154
|
-
_key: keyGenerator(),
|
|
155
|
+
_key: editorActor.getSnapshot().context.keyGenerator(),
|
|
155
156
|
_type: types.block.name,
|
|
156
157
|
children: [
|
|
157
158
|
{
|
|
158
|
-
_key: keyGenerator(),
|
|
159
|
+
_key: editorActor.getSnapshot().context.keyGenerator(),
|
|
159
160
|
_type: type.name,
|
|
160
161
|
...(value ? value : {}),
|
|
161
162
|
},
|
|
@@ -199,7 +200,7 @@ export function createWithEditableAPI(
|
|
|
199
200
|
const block = toSlateValue(
|
|
200
201
|
[
|
|
201
202
|
{
|
|
202
|
-
_key: keyGenerator(),
|
|
203
|
+
_key: editorActor.getSnapshot().context.keyGenerator(),
|
|
203
204
|
_type: type.name,
|
|
204
205
|
...(value ? value : {}),
|
|
205
206
|
},
|
|
@@ -469,7 +470,9 @@ export function createWithEditableAPI(
|
|
|
469
470
|
continue
|
|
470
471
|
}
|
|
471
472
|
|
|
472
|
-
const annotationKey =
|
|
473
|
+
const annotationKey = editorActor
|
|
474
|
+
.getSnapshot()
|
|
475
|
+
.context.keyGenerator()
|
|
473
476
|
const markDefs = block.markDefs ?? []
|
|
474
477
|
const existingMarkDef = markDefs.find(
|
|
475
478
|
(markDef) =>
|
|
@@ -96,6 +96,7 @@ export function createWithHotkeys(
|
|
|
96
96
|
at: nextPath,
|
|
97
97
|
},
|
|
98
98
|
)
|
|
99
|
+
Transforms.select(editor, {path: [...nextPath, 0], offset: 0})
|
|
99
100
|
editor.onChange()
|
|
100
101
|
return
|
|
101
102
|
}
|
|
@@ -185,38 +186,6 @@ export function createWithHotkeys(
|
|
|
185
186
|
return
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
|
-
// Block object enter key
|
|
189
|
-
if (focusBlock && Editor.isVoid(editor, focusBlock)) {
|
|
190
|
-
Editor.insertNode(editor, editor.pteCreateTextBlock({decorators: []}))
|
|
191
|
-
event.preventDefault()
|
|
192
|
-
editor.onChange()
|
|
193
|
-
return
|
|
194
|
-
}
|
|
195
|
-
// Default enter key behavior
|
|
196
|
-
event.preventDefault()
|
|
197
|
-
editor.insertBreak()
|
|
198
|
-
editor.onChange()
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Soft line breaks
|
|
202
|
-
if (isShiftEnter) {
|
|
203
|
-
event.preventDefault()
|
|
204
|
-
editor.insertText('\n')
|
|
205
|
-
return
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Undo/redo
|
|
209
|
-
if (isHotkey('mod+z', event.nativeEvent)) {
|
|
210
|
-
event.preventDefault()
|
|
211
|
-
editor.undo()
|
|
212
|
-
return
|
|
213
|
-
}
|
|
214
|
-
if (
|
|
215
|
-
isHotkey('mod+y', event.nativeEvent) ||
|
|
216
|
-
isHotkey('mod+shift+z', event.nativeEvent)
|
|
217
|
-
) {
|
|
218
|
-
event.preventDefault()
|
|
219
|
-
editor.redo()
|
|
220
189
|
}
|
|
221
190
|
}
|
|
222
191
|
return editor
|
|
@@ -5,10 +5,11 @@ import type {
|
|
|
5
5
|
PortableTextSlateEditor,
|
|
6
6
|
} from '../../types/editor'
|
|
7
7
|
import type {SlateTextBlock, VoidElement} from '../../types/slate'
|
|
8
|
+
import type {EditorActor} from '../editor-machine'
|
|
8
9
|
|
|
9
10
|
export function createWithInsertBreak(
|
|
11
|
+
editorActor: EditorActor,
|
|
10
12
|
types: PortableTextMemberSchemaTypes,
|
|
11
|
-
keyGenerator: () => string,
|
|
12
13
|
): (editor: PortableTextSlateEditor) => PortableTextSlateEditor {
|
|
13
14
|
return function withInsertBreak(
|
|
14
15
|
editor: PortableTextSlateEditor,
|
|
@@ -175,7 +176,10 @@ export function createWithInsertBreak(
|
|
|
175
176
|
) {
|
|
176
177
|
// This annotation is both present in the previous block
|
|
177
178
|
// and this block, so let's assign a new key to it
|
|
178
|
-
newMarkDefKeys.set(
|
|
179
|
+
newMarkDefKeys.set(
|
|
180
|
+
mark,
|
|
181
|
+
editorActor.getSnapshot().context.keyGenerator(),
|
|
182
|
+
)
|
|
179
183
|
}
|
|
180
184
|
}
|
|
181
185
|
|
|
@@ -25,7 +25,6 @@ const debug = debugWithName('plugin:withInsertData')
|
|
|
25
25
|
export function createWithInsertData(
|
|
26
26
|
editorActor: EditorActor,
|
|
27
27
|
schemaTypes: PortableTextMemberSchemaTypes,
|
|
28
|
-
keyGenerator: () => string,
|
|
29
28
|
) {
|
|
30
29
|
return function withInsertData(
|
|
31
30
|
editor: PortableTextSlateEditor,
|
|
@@ -149,12 +148,16 @@ export function createWithInsertData(
|
|
|
149
148
|
const slateValue = _regenerateKeys(
|
|
150
149
|
editor,
|
|
151
150
|
toSlateValue(parsed, {schemaTypes}),
|
|
152
|
-
keyGenerator,
|
|
151
|
+
editorActor.getSnapshot().context.keyGenerator,
|
|
153
152
|
spanTypeName,
|
|
154
153
|
schemaTypes,
|
|
155
154
|
)
|
|
156
155
|
// Validate the result
|
|
157
|
-
const validation = validateValue(
|
|
156
|
+
const validation = validateValue(
|
|
157
|
+
parsed,
|
|
158
|
+
schemaTypes,
|
|
159
|
+
editorActor.getSnapshot().context.keyGenerator,
|
|
160
|
+
)
|
|
158
161
|
// Bail out if it's not valid
|
|
159
162
|
if (!validation.valid && !validation.resolution?.autoResolve) {
|
|
160
163
|
const errorDescription = `${validation.resolution?.description}`
|
|
@@ -224,7 +227,7 @@ export function createWithInsertData(
|
|
|
224
227
|
const validation = validateValue(
|
|
225
228
|
portableText,
|
|
226
229
|
schemaTypes,
|
|
227
|
-
keyGenerator,
|
|
230
|
+
editorActor.getSnapshot().context.keyGenerator,
|
|
228
231
|
)
|
|
229
232
|
|
|
230
233
|
// Bail out if it's not valid
|
|
@@ -14,7 +14,6 @@ import type {EditorActor} from '../editor-machine'
|
|
|
14
14
|
export function createWithObjectKeys(
|
|
15
15
|
editorActor: EditorActor,
|
|
16
16
|
schemaTypes: PortableTextMemberSchemaTypes,
|
|
17
|
-
keyGenerator: () => string,
|
|
18
17
|
) {
|
|
19
18
|
return function withKeys(
|
|
20
19
|
editor: PortableTextSlateEditor,
|
|
@@ -48,7 +47,7 @@ export function createWithObjectKeys(
|
|
|
48
47
|
...operation,
|
|
49
48
|
properties: {
|
|
50
49
|
...operation.properties,
|
|
51
|
-
_key: keyGenerator(),
|
|
50
|
+
_key: editorActor.getSnapshot().context.keyGenerator(),
|
|
52
51
|
},
|
|
53
52
|
})
|
|
54
53
|
|
|
@@ -61,7 +60,7 @@ export function createWithObjectKeys(
|
|
|
61
60
|
...operation,
|
|
62
61
|
node: {
|
|
63
62
|
...operation.node,
|
|
64
|
-
_key: keyGenerator(),
|
|
63
|
+
_key: editorActor.getSnapshot().context.keyGenerator(),
|
|
65
64
|
},
|
|
66
65
|
})
|
|
67
66
|
|
|
@@ -78,7 +77,11 @@ export function createWithObjectKeys(
|
|
|
78
77
|
// Set key on block itself
|
|
79
78
|
if (!node._key) {
|
|
80
79
|
editorActor.send({type: 'normalizing'})
|
|
81
|
-
Transforms.setNodes(
|
|
80
|
+
Transforms.setNodes(
|
|
81
|
+
editor,
|
|
82
|
+
{_key: editorActor.getSnapshot().context.keyGenerator()},
|
|
83
|
+
{at: path},
|
|
84
|
+
)
|
|
82
85
|
editorActor.send({type: 'done normalizing'})
|
|
83
86
|
return
|
|
84
87
|
}
|
|
@@ -86,7 +89,11 @@ export function createWithObjectKeys(
|
|
|
86
89
|
for (const [child, childPath] of Node.children(editor, path)) {
|
|
87
90
|
if (!child._key) {
|
|
88
91
|
editorActor.send({type: 'normalizing'})
|
|
89
|
-
Transforms.setNodes(
|
|
92
|
+
Transforms.setNodes(
|
|
93
|
+
editor,
|
|
94
|
+
{_key: editorActor.getSnapshot().context.keyGenerator()},
|
|
95
|
+
{at: childPath},
|
|
96
|
+
)
|
|
90
97
|
editorActor.send({type: 'done normalizing'})
|
|
91
98
|
return
|
|
92
99
|
}
|