@portabletext/editor 1.2.0 → 1.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -60,10 +60,10 @@
60
60
  "@portabletext/toolkit": "^2.0.15",
61
61
  "@sanity/block-tools": "^3.62.3",
62
62
  "@sanity/diff-match-patch": "^3.1.1",
63
- "@sanity/pkg-utils": "^6.11.7",
63
+ "@sanity/pkg-utils": "^6.11.8",
64
64
  "@sanity/schema": "^3.62.3",
65
65
  "@sanity/types": "^3.62.3",
66
- "@sanity/ui": "^2.8.13",
66
+ "@sanity/ui": "^2.8.17",
67
67
  "@sanity/util": "^3.62.3",
68
68
  "@testing-library/dom": "^10.4.0",
69
69
  "@testing-library/jest-dom": "^6.6.2",
@@ -4,8 +4,8 @@
4
4
  *
5
5
  */
6
6
 
7
- import {isPortableTextBlock} from '@portabletext/toolkit'
8
- import type {PortableTextObject} from '@sanity/types'
7
+ import {isPortableTextBlock, isPortableTextSpan} from '@portabletext/toolkit'
8
+ import type {PortableTextObject, PortableTextSpan} 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 {
@@ -281,6 +281,139 @@ export function createWithPortableTextMarkModel(
281
281
  return
282
282
  }
283
283
 
284
+ if (op.type === 'set_selection') {
285
+ const marks = Editor.marks(editor)
286
+
287
+ if (
288
+ marks &&
289
+ op.properties &&
290
+ op.newProperties &&
291
+ op.properties.anchor &&
292
+ op.properties.focus &&
293
+ op.newProperties.anchor &&
294
+ op.newProperties.focus
295
+ ) {
296
+ const previousSelectionIsCollapsed = Range.isCollapsed({
297
+ anchor: op.properties.anchor,
298
+ focus: op.properties.focus,
299
+ })
300
+ const newSelectionIsCollapsed = Range.isCollapsed({
301
+ anchor: op.newProperties.anchor,
302
+ focus: op.newProperties.focus,
303
+ })
304
+
305
+ if (previousSelectionIsCollapsed && newSelectionIsCollapsed) {
306
+ const focusSpan: PortableTextSpan | undefined = Array.from(
307
+ Editor.nodes(editor, {
308
+ mode: 'lowest',
309
+ at: op.properties.focus,
310
+ match: (n) => editor.isTextSpan(n),
311
+ voids: false,
312
+ }),
313
+ )[0]?.[0]
314
+ const newFocusSpan: PortableTextSpan | undefined = Array.from(
315
+ Editor.nodes(editor, {
316
+ mode: 'lowest',
317
+ at: op.newProperties.focus,
318
+ match: (n) => editor.isTextSpan(n),
319
+ voids: false,
320
+ }),
321
+ )[0]?.[0]
322
+ const movedToNextSpan =
323
+ focusSpan &&
324
+ newFocusSpan &&
325
+ op.newProperties.focus.path[0] === op.properties.focus.path[0] &&
326
+ op.newProperties.focus.path[1] ===
327
+ op.properties.focus.path[1] + 1 &&
328
+ focusSpan.text.length === op.properties.focus.offset &&
329
+ op.newProperties.focus.offset === 0
330
+ const movedToPreviousSpan =
331
+ focusSpan &&
332
+ newFocusSpan &&
333
+ op.newProperties.focus.path[0] === op.properties.focus.path[0] &&
334
+ op.newProperties.focus.path[1] ===
335
+ op.properties.focus.path[1] - 1 &&
336
+ op.properties.focus.offset === 0 &&
337
+ newFocusSpan.text.length === op.newProperties.focus.offset
338
+
339
+ // If the editor has marks and we are not visually moving the
340
+ // selection then we just abort. Otherwise the marks would be
341
+ // cleared and we can't use them for the possible subsequent insert
342
+ // operation.
343
+ if (movedToNextSpan || movedToPreviousSpan) {
344
+ return
345
+ }
346
+ }
347
+ }
348
+ }
349
+
350
+ if (op.type === 'insert_node') {
351
+ const {selection} = editor
352
+
353
+ if (selection) {
354
+ const [_block, blockPath] = Editor.node(editor, selection, {depth: 1})
355
+ const previousSpan = getPreviousSpan({
356
+ editor,
357
+ blockPath,
358
+ spanPath: op.path,
359
+ })
360
+ const previousSpanAnnotations = previousSpan
361
+ ? previousSpan.marks?.filter((mark) => !decorators.includes(mark))
362
+ : []
363
+
364
+ const nextSpan = getNextSpan({
365
+ editor,
366
+ blockPath,
367
+ spanPath: [op.path[0], op.path[1] - 1],
368
+ })
369
+ const nextSpanAnnotations = nextSpan
370
+ ? nextSpan.marks?.filter((mark) => !decorators.includes(mark))
371
+ : []
372
+
373
+ const annotationsEnding =
374
+ previousSpanAnnotations?.filter(
375
+ (annotation) => !nextSpanAnnotations?.includes(annotation),
376
+ ) ?? []
377
+ const atTheEndOfAnnotation = annotationsEnding.length > 0
378
+
379
+ if (
380
+ atTheEndOfAnnotation &&
381
+ isPortableTextSpan(op.node) &&
382
+ op.node.marks?.some((mark) => annotationsEnding.includes(mark))
383
+ ) {
384
+ Transforms.insertNodes(editor, {
385
+ ...op.node,
386
+ marks:
387
+ op.node.marks?.filter(
388
+ (mark) => !annotationsEnding.includes(mark),
389
+ ) ?? [],
390
+ })
391
+ return
392
+ }
393
+
394
+ const annotationsStarting =
395
+ nextSpanAnnotations?.filter(
396
+ (annotation) => !previousSpanAnnotations?.includes(annotation),
397
+ ) ?? []
398
+ const atTheStartOfAnnotation = annotationsStarting.length > 0
399
+
400
+ if (
401
+ atTheStartOfAnnotation &&
402
+ isPortableTextSpan(op.node) &&
403
+ op.node.marks?.some((mark) => annotationsStarting.includes(mark))
404
+ ) {
405
+ Transforms.insertNodes(editor, {
406
+ ...op.node,
407
+ marks:
408
+ op.node.marks?.filter(
409
+ (mark) => !annotationsStarting.includes(mark),
410
+ ) ?? [],
411
+ })
412
+ return
413
+ }
414
+ }
415
+ }
416
+
284
417
  if (op.type === 'insert_text') {
285
418
  const {selection} = editor
286
419
  const collapsedSelection = selection
@@ -322,11 +455,20 @@ export function createWithPortableTextMarkModel(
322
455
  (mark) => !decorators.includes(mark),
323
456
  )
324
457
 
458
+ const previousSpanHasAnnotations = previousSpan
459
+ ? previousSpan.marks?.some((mark) => !decorators.includes(mark))
460
+ : false
461
+ const previousSpanHasSameAnnotations = previousSpan
462
+ ? previousSpan.marks
463
+ ?.filter((mark) => !decorators.includes(mark))
464
+ .every((mark) => marks.includes(mark))
465
+ : false
325
466
  const previousSpanHasSameAnnotation = previousSpan
326
467
  ? previousSpan.marks?.some(
327
468
  (mark) => !decorators.includes(mark) && marks.includes(mark),
328
469
  )
329
470
  : false
471
+
330
472
  const previousSpanHasSameMarks = previousSpan
331
473
  ? previousSpan.marks?.every((mark) => marks.includes(mark))
332
474
  : false
@@ -343,17 +485,27 @@ export function createWithPortableTextMarkModel(
343
485
  text: op.text,
344
486
  marks: previousSpan?.marks ?? [],
345
487
  })
488
+ return
489
+ } else if (previousSpanHasSameAnnotations) {
490
+ Transforms.insertNodes(editor, {
491
+ _type: 'span',
492
+ _key: editorActor.getSnapshot().context.keyGenerator(),
493
+ text: op.text,
494
+ marks: previousSpan?.marks ?? [],
495
+ })
496
+ return
346
497
  } else if (previousSpanHasSameAnnotation) {
347
498
  apply(op)
348
- } else {
499
+ return
500
+ } else if (!previousSpan) {
349
501
  Transforms.insertNodes(editor, {
350
502
  _type: 'span',
351
503
  _key: editorActor.getSnapshot().context.keyGenerator(),
352
504
  text: op.text,
353
505
  marks: [],
354
506
  })
507
+ return
355
508
  }
356
- return
357
509
  }
358
510
 
359
511
  if (atTheEndOfSpan) {
@@ -389,9 +541,11 @@ export function createWithPortableTextMarkModel(
389
541
  _type: 'span',
390
542
  _key: editorActor.getSnapshot().context.keyGenerator(),
391
543
  text: op.text,
392
- marks: (previousSpan.marks ?? []).filter((mark) =>
393
- decorators.includes(mark),
394
- ),
544
+ marks: previousSpanHasAnnotations
545
+ ? []
546
+ : (previousSpan.marks ?? []).filter((mark) =>
547
+ decorators.includes(mark),
548
+ ),
395
549
  })
396
550
  return
397
551
  }