@portabletext/editor 1.1.10 → 1.1.11

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.
@@ -1,6 +1,7 @@
1
1
  import type {Patch} from '@portabletext/patches'
2
2
  import type {PortableTextBlock} from '@sanity/types'
3
3
  import type {FocusEvent} from 'react'
4
+ import {Editor} from 'slate'
4
5
  import {
5
6
  assertEvent,
6
7
  assign,
@@ -19,10 +20,14 @@ import type {
19
20
  import {toPortableTextRange} from '../utils/ranges'
20
21
  import {fromSlateValue} from '../utils/values'
21
22
  import {KEY_TO_VALUE_ELEMENT} from '../utils/weakMaps'
22
- import {behaviorActionImplementations} from './behavior/behavior.actions'
23
+ import {
24
+ behaviorActionImplementations,
25
+ performDefaultAction,
26
+ } from './behavior/behavior.actions'
23
27
  import type {
24
28
  Behavior,
25
29
  BehaviorAction,
30
+ BehaviorActionIntend,
26
31
  BehaviorContext,
27
32
  BehaviorEvent,
28
33
  } from './behavior/behavior.types'
@@ -71,7 +76,11 @@ type EditorEvent =
71
76
  behaviorEvent: BehaviorEvent
72
77
  editor: PortableTextSlateEditor
73
78
  }
74
- | BehaviorAction
79
+ | {
80
+ type: 'behavior action intends'
81
+ editor: PortableTextSlateEditor
82
+ actionIntends: Array<BehaviorActionIntend>
83
+ }
75
84
  | {
76
85
  type: 'update schema'
77
86
  schema: PortableTextMemberSchemaTypes
@@ -160,12 +169,17 @@ export const editorMachine = setup({
160
169
  'handle behavior event': enqueueActions(({context, event, enqueue}) => {
161
170
  assertEvent(event, ['behavior event'])
162
171
 
172
+ const defaultAction = {
173
+ ...event.behaviorEvent,
174
+ editor: event.editor,
175
+ } satisfies BehaviorAction
176
+
163
177
  const eventBehaviors = context.behaviors.filter(
164
178
  (behavior) => behavior.on === event.behaviorEvent.type,
165
179
  )
166
180
 
167
181
  if (eventBehaviors.length === 0) {
168
- event.behaviorEvent.default()
182
+ performDefaultAction({context, action: defaultAction})
169
183
  return
170
184
  }
171
185
 
@@ -184,7 +198,7 @@ export const editorMachine = setup({
184
198
  console.warn(
185
199
  `Unable to handle event ${event.type} due to missing selection`,
186
200
  )
187
- event.behaviorEvent.default()
201
+ performDefaultAction({context, action: defaultAction})
188
202
  return
189
203
  }
190
204
 
@@ -207,29 +221,28 @@ export const editorMachine = setup({
207
221
  continue
208
222
  }
209
223
 
210
- const actions = eventBehavior.actions.map((action) =>
211
- action(
224
+ const actionIntendSets = eventBehavior.actions.map((actionSet) =>
225
+ actionSet(
212
226
  {context: behaviorContext, event: event.behaviorEvent},
213
227
  shouldRun,
214
228
  ),
215
229
  )
216
230
 
217
- for (const action of actions) {
218
- if (typeof action !== 'object') {
219
- continue
220
- }
221
-
222
- behaviorOverwritten = true
231
+ for (const actionIntends of actionIntendSets) {
232
+ behaviorOverwritten =
233
+ actionIntends.length > 0 &&
234
+ actionIntends.some((actionIntend) => actionIntend.type !== 'effect')
223
235
 
224
236
  enqueue.raise({
225
- ...action,
237
+ type: 'behavior action intends',
226
238
  editor: event.editor,
239
+ actionIntends,
227
240
  })
228
241
  }
229
242
  }
230
243
 
231
244
  if (!behaviorOverwritten) {
232
- event.behaviorEvent.default()
245
+ performDefaultAction({context, action: defaultAction})
233
246
  }
234
247
  }),
235
248
  },
@@ -263,26 +276,85 @@ export const editorMachine = setup({
263
276
  'done loading': {actions: emit({type: 'done loading'})},
264
277
  'update schema': {actions: 'assign schema'},
265
278
  'behavior event': {actions: 'handle behavior event'},
266
- 'apply block style': {
267
- actions: [behaviorActionImplementations['apply block style']],
268
- },
269
- 'delete backward': {
270
- actions: [behaviorActionImplementations['delete backward']],
271
- },
272
- 'delete text': {
273
- actions: [behaviorActionImplementations['delete text']],
274
- },
275
- 'insert break': {
276
- actions: [behaviorActionImplementations['insert break']],
277
- },
278
- 'insert soft break': {
279
- actions: [behaviorActionImplementations['insert soft break']],
280
- },
281
- 'insert text': {
282
- actions: [behaviorActionImplementations['insert text']],
283
- },
284
- 'insert text block': {
285
- actions: [behaviorActionImplementations['insert text block']],
279
+ 'behavior action intends': {
280
+ actions: [
281
+ ({context, event}) => {
282
+ Editor.withoutNormalizing(event.editor, () => {
283
+ for (const actionIntend of event.actionIntends) {
284
+ const action = {
285
+ ...actionIntend,
286
+ editor: event.editor,
287
+ }
288
+
289
+ switch (action.type) {
290
+ case 'delete backward': {
291
+ behaviorActionImplementations['delete backward']({
292
+ context,
293
+ action,
294
+ })
295
+ break
296
+ }
297
+ case 'delete text': {
298
+ behaviorActionImplementations['delete text']({
299
+ context,
300
+ action,
301
+ })
302
+ break
303
+ }
304
+ case 'insert break': {
305
+ behaviorActionImplementations['insert break']({
306
+ context,
307
+ action,
308
+ })
309
+ break
310
+ }
311
+ case 'insert soft break': {
312
+ behaviorActionImplementations['insert soft break']({
313
+ context,
314
+ action,
315
+ })
316
+ break
317
+ }
318
+ case 'insert text': {
319
+ behaviorActionImplementations['insert text']({
320
+ context,
321
+ action,
322
+ })
323
+ break
324
+ }
325
+ case 'insert text block': {
326
+ behaviorActionImplementations['insert text block']({
327
+ context,
328
+ action,
329
+ })
330
+ break
331
+ }
332
+ case 'set block': {
333
+ behaviorActionImplementations['set block']({
334
+ context,
335
+ action,
336
+ })
337
+ break
338
+ }
339
+ case 'unset block': {
340
+ behaviorActionImplementations['unset block']({
341
+ context,
342
+ action,
343
+ })
344
+ break
345
+ }
346
+ default: {
347
+ behaviorActionImplementations.effect({
348
+ context,
349
+ action,
350
+ })
351
+ }
352
+ }
353
+ }
354
+ })
355
+ event.editor.onChange()
356
+ },
357
+ ],
286
358
  },
287
359
  },
288
360
  initial: 'pristine',
@@ -3,15 +3,12 @@ import type {EditorActor} from '../editor-machine'
3
3
 
4
4
  export function createWithEventListeners(editorActor: EditorActor) {
5
5
  return function withEventListeners(editor: Editor) {
6
- const {deleteBackward, insertBreak, insertSoftBreak, insertText} = editor
7
-
8
6
  editor.deleteBackward = (unit) => {
9
7
  editorActor.send({
10
8
  type: 'behavior event',
11
9
  behaviorEvent: {
12
10
  type: 'delete backward',
13
11
  unit,
14
- default: () => deleteBackward(unit),
15
12
  },
16
13
  editor,
17
14
  })
@@ -23,7 +20,6 @@ export function createWithEventListeners(editorActor: EditorActor) {
23
20
  type: 'behavior event',
24
21
  behaviorEvent: {
25
22
  type: 'insert break',
26
- default: insertBreak,
27
23
  },
28
24
  editor,
29
25
  })
@@ -35,7 +31,6 @@ export function createWithEventListeners(editorActor: EditorActor) {
35
31
  type: 'behavior event',
36
32
  behaviorEvent: {
37
33
  type: 'insert soft break',
38
- default: insertSoftBreak,
39
34
  },
40
35
  editor,
41
36
  })
@@ -48,7 +43,7 @@ export function createWithEventListeners(editorActor: EditorActor) {
48
43
  behaviorEvent: {
49
44
  type: 'insert text',
50
45
  text,
51
- default: () => insertText(text, options),
46
+ options,
52
47
  },
53
48
  editor,
54
49
  })
@@ -5,7 +5,6 @@ import type {createEditorOptions} from '../../types/options'
5
5
  import {createOperationToPatches} from '../../utils/operationToPatches'
6
6
  import {createWithEventListeners} from './create-with-event-listeners'
7
7
  import {createWithEditableAPI} from './createWithEditableAPI'
8
- import {createWithInsertBreak} from './createWithInsertBreak'
9
8
  import {createWithMaxBlocks} from './createWithMaxBlocks'
10
9
  import {createWithObjectKeys} from './createWithObjectKeys'
11
10
  import {createWithPatches} from './createWithPatches'
@@ -99,8 +98,6 @@ export const withPlugins = <T extends Editor>(
99
98
 
100
99
  const withPlaceholderBlock = createWithPlaceholderBlock()
101
100
 
102
- const withInsertBreak = createWithInsertBreak(editorActor, schemaTypes)
103
-
104
101
  const withUtils = createWithUtils({
105
102
  editorActor,
106
103
  schemaTypes,
@@ -131,9 +128,7 @@ export const withPlugins = <T extends Editor>(
131
128
  withUtils(
132
129
  withPlaceholderBlock(
133
130
  withPortableTextLists(
134
- withPortableTextSelections(
135
- withEditableAPI(withInsertBreak(e)),
136
- ),
131
+ withPortableTextSelections(withEditableAPI(e)),
137
132
  ),
138
133
  ),
139
134
  ),
@@ -158,9 +153,7 @@ export const withPlugins = <T extends Editor>(
158
153
  withMaxBlocks(
159
154
  withUndoRedo(
160
155
  withPatches(
161
- withPortableTextSelections(
162
- withEditableAPI(withInsertBreak(e)),
163
- ),
156
+ withPortableTextSelections(withEditableAPI(e)),
164
157
  ),
165
158
  ),
166
159
  ),
package/src/index.ts CHANGED
@@ -5,8 +5,8 @@ export type {
5
5
  BehaviorContext,
6
6
  BehaviorEvent,
7
7
  BehaviorGuard,
8
- RaiseBehaviorActionIntend,
9
8
  PickFromUnion,
9
+ BehaviorActionIntendSet,
10
10
  } from './editor/behavior/behavior.types'
11
11
  export {PortableTextEditable} from './editor/Editable'
12
12
  export type {PortableTextEditableProps} from './editor/Editable'
@@ -1,224 +0,0 @@
1
- import {isEqual} from 'lodash'
2
- import {Editor, Node, Path, Range, Transforms} from 'slate'
3
- import type {
4
- PortableTextMemberSchemaTypes,
5
- PortableTextSlateEditor,
6
- } from '../../types/editor'
7
- import type {SlateTextBlock, VoidElement} from '../../types/slate'
8
- import type {EditorActor} from '../editor-machine'
9
-
10
- export function createWithInsertBreak(
11
- editorActor: EditorActor,
12
- types: PortableTextMemberSchemaTypes,
13
- ): (editor: PortableTextSlateEditor) => PortableTextSlateEditor {
14
- return function withInsertBreak(
15
- editor: PortableTextSlateEditor,
16
- ): PortableTextSlateEditor {
17
- const {insertBreak} = editor
18
-
19
- editor.insertBreak = () => {
20
- if (!editor.selection) {
21
- insertBreak()
22
- return
23
- }
24
-
25
- const [focusSpan] = Array.from(
26
- Editor.nodes(editor, {
27
- mode: 'lowest',
28
- at: editor.selection.focus,
29
- match: (n) => editor.isTextSpan(n),
30
- voids: false,
31
- }),
32
- )[0] ?? [undefined]
33
- const focusDecorators =
34
- focusSpan.marks?.filter((mark) =>
35
- types.decorators.some((decorator) => decorator.value === mark),
36
- ) ?? []
37
- const focusAnnotations =
38
- focusSpan.marks?.filter(
39
- (mark) =>
40
- !types.decorators.some((decorator) => decorator.value === mark),
41
- ) ?? []
42
-
43
- const focusBlockPath = editor.selection.focus.path.slice(0, 1)
44
- const focusBlock = Node.descendant(editor, focusBlockPath) as
45
- | SlateTextBlock
46
- | VoidElement
47
-
48
- if (editor.isTextBlock(focusBlock)) {
49
- const [start, end] = Range.edges(editor.selection)
50
- const atTheStartOfBlock = isEqual(end, {
51
- path: [...focusBlockPath, 0],
52
- offset: 0,
53
- })
54
-
55
- if (atTheStartOfBlock && Range.isCollapsed(editor.selection)) {
56
- Editor.insertNode(
57
- editor,
58
- editor.pteCreateTextBlock({
59
- decorators: focusAnnotations.length === 0 ? focusDecorators : [],
60
- listItem: focusBlock.listItem,
61
- level: focusBlock.level,
62
- }),
63
- )
64
-
65
- const [nextBlockPath] = Path.next(focusBlockPath)
66
-
67
- Transforms.select(editor, {
68
- anchor: {path: [nextBlockPath, 0], offset: 0},
69
- focus: {path: [nextBlockPath, 0], offset: 0},
70
- })
71
-
72
- return
73
- }
74
-
75
- const lastFocusBlockChild =
76
- focusBlock.children[focusBlock.children.length - 1]
77
- const atTheEndOfBlock = isEqual(start, {
78
- path: [...focusBlockPath, focusBlock.children.length - 1],
79
- offset: editor.isTextSpan(lastFocusBlockChild)
80
- ? lastFocusBlockChild.text.length
81
- : 0,
82
- })
83
-
84
- if (atTheEndOfBlock && Range.isCollapsed(editor.selection)) {
85
- Editor.insertNode(
86
- editor,
87
- editor.pteCreateTextBlock({
88
- decorators: [],
89
- listItem: focusBlock.listItem,
90
- level: focusBlock.level,
91
- }),
92
- )
93
-
94
- const [nextBlockPath] = Path.next(focusBlockPath)
95
-
96
- Transforms.setSelection(editor, {
97
- anchor: {path: [nextBlockPath, 0], offset: 0},
98
- focus: {path: [nextBlockPath, 0], offset: 0},
99
- })
100
-
101
- return
102
- }
103
-
104
- const isInTheMiddleOfNode = !atTheStartOfBlock && !atTheEndOfBlock
105
-
106
- if (isInTheMiddleOfNode) {
107
- Editor.withoutNormalizing(editor, () => {
108
- if (!editor.selection) {
109
- return
110
- }
111
-
112
- Transforms.splitNodes(editor, {
113
- at: editor.selection,
114
- })
115
-
116
- const [nextNode, nextNodePath] = Editor.node(
117
- editor,
118
- Path.next(focusBlockPath),
119
- {depth: 1},
120
- )
121
-
122
- Transforms.setSelection(editor, {
123
- anchor: {path: [...nextNodePath, 0], offset: 0},
124
- focus: {path: [...nextNodePath, 0], offset: 0},
125
- })
126
-
127
- /**
128
- * Assign new keys to markDefs that are now split across two blocks
129
- */
130
- if (
131
- editor.isTextBlock(nextNode) &&
132
- nextNode.markDefs &&
133
- nextNode.markDefs.length > 0
134
- ) {
135
- const newMarkDefKeys = new Map<string, string>()
136
-
137
- const prevNodeSpans = Array.from(
138
- Node.children(editor, focusBlockPath),
139
- )
140
- .map((entry) => entry[0])
141
- .filter((node) => editor.isTextSpan(node))
142
- const children = Node.children(editor, nextNodePath)
143
-
144
- for (const [child, childPath] of children) {
145
- if (!editor.isTextSpan(child)) {
146
- continue
147
- }
148
-
149
- const marks = child.marks ?? []
150
-
151
- // Go through the marks of the span and figure out if any of
152
- // them refer to annotations that are also present in the
153
- // previous block
154
- for (const mark of marks) {
155
- if (
156
- types.decorators.some(
157
- (decorator) => decorator.value === mark,
158
- )
159
- ) {
160
- continue
161
- }
162
-
163
- if (
164
- prevNodeSpans.some((prevNodeSpan) =>
165
- prevNodeSpan.marks?.includes(mark),
166
- ) &&
167
- !newMarkDefKeys.has(mark)
168
- ) {
169
- // This annotation is both present in the previous block
170
- // and this block, so let's assign a new key to it
171
- newMarkDefKeys.set(
172
- mark,
173
- editorActor.getSnapshot().context.keyGenerator(),
174
- )
175
- }
176
- }
177
-
178
- const newMarks = marks.map(
179
- (mark) => newMarkDefKeys.get(mark) ?? mark,
180
- )
181
-
182
- // No need to update the marks if they are the same
183
- if (!isEqual(marks, newMarks)) {
184
- Transforms.setNodes(
185
- editor,
186
- {marks: newMarks},
187
- {
188
- at: childPath,
189
- },
190
- )
191
- }
192
- }
193
-
194
- // Time to update all the markDefs that need a new key because
195
- // they've been split across blocks
196
- const newMarkDefs = nextNode.markDefs.map((markDef) => ({
197
- ...markDef,
198
- _key: newMarkDefKeys.get(markDef._key) ?? markDef._key,
199
- }))
200
-
201
- // No need to update the markDefs if they are the same
202
- if (!isEqual(nextNode.markDefs, newMarkDefs)) {
203
- Transforms.setNodes(
204
- editor,
205
- {markDefs: newMarkDefs},
206
- {
207
- at: nextNodePath,
208
- match: (node) => editor.isTextBlock(node),
209
- },
210
- )
211
- }
212
- }
213
- })
214
- editor.onChange()
215
- return
216
- }
217
- }
218
-
219
- insertBreak()
220
- }
221
-
222
- return editor
223
- }
224
- }