@portabletext/editor 1.1.5 → 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.
@@ -4,8 +4,8 @@
4
4
  *
5
5
  */
6
6
 
7
- import {isPortableTextBlock, isPortableTextSpan} from '@portabletext/toolkit'
8
- import type {PortableTextObject, PortableTextSpan} from '@sanity/types'
7
+ import {isPortableTextBlock} from '@portabletext/toolkit'
8
+ import type {PortableTextObject} from '@sanity/types'
9
9
  import {isEqual, uniq} from 'lodash'
10
10
  import {Editor, Element, Node, Path, Range, Text, Transforms} from 'slate'
11
11
  import type {
@@ -14,6 +14,7 @@ import type {
14
14
  } from '../../types/editor'
15
15
  import {debugWithName} from '../../utils/debug'
16
16
  import {toPortableTextRange} from '../../utils/ranges'
17
+ import {getNextSpan, getPreviousSpan} from '../../utils/sibling-utils'
17
18
  import {isChangingRemotely} from '../../utils/withChanges'
18
19
  import {isRedoing, isUndoing} from '../../utils/withUndoRedo'
19
20
  import type {EditorActor} from '../editor-machine'
@@ -313,32 +314,13 @@ export function createWithPortableTextMarkModel(
313
314
  const atTheBeginningOfSpan = selection.anchor.offset === 0
314
315
  const atTheEndOfSpan = selection.anchor.offset === span.text.length
315
316
 
316
- let previousSpan: PortableTextSpan | undefined
317
- let nextSpan: PortableTextSpan | undefined
318
-
319
- for (const [child, childPath] of Node.children(editor, blockPath, {
320
- reverse: true,
321
- })) {
322
- if (!editor.isTextSpan(child)) {
323
- continue
324
- }
325
-
326
- if (Path.isBefore(childPath, spanPath)) {
327
- previousSpan = child
328
- break
329
- }
330
- }
331
-
332
- for (const [child, childPath] of Node.children(editor, blockPath)) {
333
- if (!editor.isTextSpan(child)) {
334
- continue
335
- }
336
-
337
- if (Path.isAfter(childPath, spanPath)) {
338
- nextSpan = child
339
- break
340
- }
341
- }
317
+ const previousSpan = getPreviousSpan({editor, blockPath, spanPath})
318
+ const nextSpan = getNextSpan({editor, blockPath, spanPath})
319
+ const nextSpanAnnotations =
320
+ nextSpan?.marks?.filter((mark) => !decorators.includes(mark)) ?? []
321
+ const spanAnnotations = marks.filter(
322
+ (mark) => !decorators.includes(mark),
323
+ )
342
324
 
343
325
  const previousSpanHasSameAnnotation = previousSpan
344
326
  ? previousSpan.marks?.some(
@@ -348,14 +330,9 @@ export function createWithPortableTextMarkModel(
348
330
  const previousSpanHasSameMarks = previousSpan
349
331
  ? previousSpan.marks?.every((mark) => marks.includes(mark))
350
332
  : false
351
- const nextSpanHasSameAnnotation = nextSpan
352
- ? nextSpan.marks?.some(
353
- (mark) => !decorators.includes(mark) && marks.includes(mark),
354
- )
355
- : false
356
- const nextSpanHasSameMarks = nextSpan
357
- ? nextSpan.marks?.every((mark) => marks.includes(mark))
358
- : false
333
+ const nextSpanSharesSomeAnnotations = spanAnnotations.some((mark) =>
334
+ nextSpanAnnotations?.includes(mark),
335
+ )
359
336
 
360
337
  if (spanHasAnnotations && !spanIsEmpty) {
361
338
  if (atTheBeginningOfSpan) {
@@ -380,105 +357,100 @@ export function createWithPortableTextMarkModel(
380
357
  }
381
358
 
382
359
  if (atTheEndOfSpan) {
383
- if (nextSpanHasSameMarks) {
360
+ if (
361
+ (nextSpan &&
362
+ nextSpanSharesSomeAnnotations &&
363
+ nextSpanAnnotations.length < spanAnnotations.length) ||
364
+ !nextSpanSharesSomeAnnotations
365
+ ) {
384
366
  Transforms.insertNodes(editor, {
385
367
  _type: 'span',
386
368
  _key: editorActor.getSnapshot().context.keyGenerator(),
387
369
  text: op.text,
388
370
  marks: nextSpan?.marks ?? [],
389
371
  })
390
- } else if (nextSpanHasSameAnnotation) {
391
- apply(op)
392
- } else {
372
+ return
373
+ }
374
+
375
+ if (!nextSpan) {
393
376
  Transforms.insertNodes(editor, {
394
377
  _type: 'span',
395
378
  _key: editorActor.getSnapshot().context.keyGenerator(),
396
379
  text: op.text,
397
380
  marks: [],
398
381
  })
382
+ return
399
383
  }
400
- return
401
384
  }
402
385
  }
403
386
  }
404
387
  }
405
388
 
406
389
  if (op.type === 'remove_text') {
407
- const nodeEntry = Array.from(
408
- Editor.nodes(editor, {
409
- mode: 'lowest',
410
- at: {path: op.path, offset: op.offset},
411
- match: (n) => n._type === types.span.name,
412
- voids: false,
413
- }),
414
- )[0]
415
- const node = nodeEntry[0]
416
- const blockEntry = Editor.node(editor, Path.parent(op.path))
417
- const block = blockEntry[0]
418
-
419
- if (
420
- node &&
421
- isPortableTextSpan(node) &&
422
- block &&
423
- isPortableTextBlock(block)
424
- ) {
425
- const markDefs = block.markDefs ?? []
426
- const nodeHasAnnotations = (node.marks ?? []).some((mark) =>
427
- markDefs.find((markDef) => markDef._key === mark),
428
- )
429
- const deletingPartOfTheNode = op.offset !== 0
430
- const deletingFromTheEnd =
431
- op.offset + op.text.length === node.text.length
390
+ const {selection} = editor
432
391
 
433
- if (
434
- nodeHasAnnotations &&
435
- deletingPartOfTheNode &&
436
- deletingFromTheEnd
437
- ) {
438
- Editor.withoutNormalizing(editor, () => {
439
- Transforms.splitNodes(editor, {
440
- match: Text.isText,
392
+ if (selection && Range.isExpanded(selection)) {
393
+ const [block, blockPath] = Editor.node(editor, selection, {
394
+ depth: 1,
395
+ })
396
+ const [span, spanPath] =
397
+ Array.from(
398
+ Editor.nodes(editor, {
399
+ mode: 'lowest',
441
400
  at: {path: op.path, offset: op.offset},
442
- })
443
- Transforms.removeNodes(editor, {at: Path.next(op.path)})
444
- })
445
-
446
- editor.onChange()
447
- return
448
- }
449
-
450
- const deletingAllText = op.offset === 0 && deletingFromTheEnd
451
-
452
- if (nodeHasAnnotations && deletingAllText) {
453
- const marksWithoutAnnotationMarks: string[] = (
454
- {
455
- ...(Editor.marks(editor) || {}),
456
- }.marks || []
457
- ).filter((mark) => decorators.includes(mark))
401
+ match: (n) => editor.isTextSpan(n),
402
+ voids: false,
403
+ }),
404
+ )[0] ?? ([undefined, undefined] as const)
458
405
 
459
- Editor.withoutNormalizing(editor, () => {
460
- apply(op)
461
- Transforms.setNodes(
462
- editor,
463
- {marks: marksWithoutAnnotationMarks},
464
- {at: op.path},
465
- )
466
- })
406
+ if (span && block && isPortableTextBlock(block)) {
407
+ const markDefs = block.markDefs ?? []
408
+ const marks = span.marks ?? []
409
+ const spanHasAnnotations = marks.some((mark) =>
410
+ markDefs.find((markDef) => markDef._key === mark),
411
+ )
412
+ const deletingFromTheEnd =
413
+ op.offset + op.text.length === span.text.length
414
+ const deletingAllText = op.offset === 0 && deletingFromTheEnd
467
415
 
468
- editor.onChange()
469
- return
470
- }
416
+ const previousSpan = getPreviousSpan({editor, blockPath, spanPath})
417
+ const nextSpan = getNextSpan({editor, blockPath, spanPath})
471
418
 
472
- const nodeHasMarks = node.marks !== undefined && node.marks.length > 0
419
+ const previousSpanHasSameAnnotation = previousSpan
420
+ ? previousSpan.marks?.some(
421
+ (mark) => !decorators.includes(mark) && marks.includes(mark),
422
+ )
423
+ : false
424
+ const nextSpanHasSameAnnotation = nextSpan
425
+ ? nextSpan.marks?.some(
426
+ (mark) => !decorators.includes(mark) && marks.includes(mark),
427
+ )
428
+ : false
429
+
430
+ if (
431
+ spanHasAnnotations &&
432
+ deletingAllText &&
433
+ !previousSpanHasSameAnnotation &&
434
+ !nextSpanHasSameAnnotation
435
+ ) {
436
+ const marksWithoutAnnotationMarks: string[] = (
437
+ {
438
+ ...(Editor.marks(editor) || {}),
439
+ }.marks || []
440
+ ).filter((mark) => decorators.includes(mark))
473
441
 
474
- if (nodeHasMarks && deletingAllText) {
475
- Editor.withoutNormalizing(editor, () => {
476
- apply(op)
477
- Transforms.setNodes(editor, {marks: []}, {at: op.path})
478
- })
442
+ Editor.withoutNormalizing(editor, () => {
443
+ apply(op)
444
+ Transforms.setNodes(
445
+ editor,
446
+ {marks: marksWithoutAnnotationMarks},
447
+ {at: op.path},
448
+ )
449
+ })
479
450
 
480
- editor.onChange()
481
- return
451
+ editor.onChange()
452
+ return
453
+ }
482
454
  }
483
455
  }
484
456
  }
package/src/index.ts CHANGED
@@ -1,4 +1,13 @@
1
1
  export type {Patch} from '@portabletext/patches'
2
+ export type {
3
+ Behavior,
4
+ BehaviorActionIntend,
5
+ BehaviorContext,
6
+ BehaviorEvent,
7
+ BehaviorGuard,
8
+ RaiseBehaviorActionIntend,
9
+ PickFromUnion,
10
+ } from './editor/behavior/behavior.types'
2
11
  export {PortableTextEditable} from './editor/Editable'
3
12
  export type {PortableTextEditableProps} from './editor/Editable'
4
13
  export {
@@ -8,8 +17,8 @@ export {
8
17
  type PatchEvent,
9
18
  } from './editor/editor-machine'
10
19
  export {usePortableTextEditor} from './editor/hooks/usePortableTextEditor'
11
- export {defaultKeyGenerator as keyGenerator} from './editor/key-generator'
12
20
  export {usePortableTextEditorSelection} from './editor/hooks/usePortableTextEditorSelection'
21
+ export {defaultKeyGenerator as keyGenerator} from './editor/key-generator'
13
22
  export {PortableTextEditor} from './editor/PortableTextEditor'
14
23
  export type {PortableTextEditorProps} from './editor/PortableTextEditor'
15
24
  export * from './types/editor'
@@ -0,0 +1,55 @@
1
+ import type {PortableTextSpan} from '@sanity/types'
2
+ import {Node, Path} from 'slate'
3
+ import type {PortableTextSlateEditor} from '../types/editor'
4
+
5
+ export function getPreviousSpan({
6
+ editor,
7
+ blockPath,
8
+ spanPath,
9
+ }: {
10
+ editor: PortableTextSlateEditor
11
+ blockPath: Path
12
+ spanPath: Path
13
+ }): PortableTextSpan | undefined {
14
+ let previousSpan: PortableTextSpan | undefined
15
+
16
+ for (const [child, childPath] of Node.children(editor, blockPath, {
17
+ reverse: true,
18
+ })) {
19
+ if (!editor.isTextSpan(child)) {
20
+ continue
21
+ }
22
+
23
+ if (Path.isBefore(childPath, spanPath)) {
24
+ previousSpan = child
25
+ break
26
+ }
27
+ }
28
+
29
+ return previousSpan
30
+ }
31
+
32
+ export function getNextSpan({
33
+ editor,
34
+ blockPath,
35
+ spanPath,
36
+ }: {
37
+ editor: PortableTextSlateEditor
38
+ blockPath: Path
39
+ spanPath: Path
40
+ }): PortableTextSpan | undefined {
41
+ let nextSpan: PortableTextSpan | undefined
42
+
43
+ for (const [child, childPath] of Node.children(editor, blockPath)) {
44
+ if (!editor.isTextSpan(child)) {
45
+ continue
46
+ }
47
+
48
+ if (Path.isAfter(childPath, spanPath)) {
49
+ nextSpan = child
50
+ break
51
+ }
52
+ }
53
+
54
+ return nextSpan
55
+ }