@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.
- package/README.md +4 -0
- package/lib/index.d.mts +403 -0
- package/lib/index.d.ts +403 -0
- package/lib/index.esm.js +220 -105
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +213 -98
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +220 -105
- package/lib/index.mjs.map +1 -1
- package/package.json +18 -18
- package/src/editor/Editable.tsx +5 -0
- package/src/editor/PortableTextEditor.tsx +15 -6
- 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/editor-machine.ts +113 -1
- package/src/editor/plugins/createWithHotKeys.ts +0 -32
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +81 -109
- package/src/index.ts +10 -1
- package/src/utils/sibling-utils.ts +55 -0
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {isPortableTextBlock
|
|
8
|
-
import type {PortableTextObject
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
|
352
|
-
|
|
353
|
-
|
|
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 (
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
|
469
|
-
|
|
470
|
-
}
|
|
416
|
+
const previousSpan = getPreviousSpan({editor, blockPath, spanPath})
|
|
417
|
+
const nextSpan = getNextSpan({editor, blockPath, spanPath})
|
|
471
418
|
|
|
472
|
-
|
|
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
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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
|
-
|
|
481
|
-
|
|
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
|
+
}
|