@portabletext/editor 1.52.4 → 1.52.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/lib/index.cjs +113 -101
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +115 -103
- package/lib/index.js.map +1 -1
- package/package.json +3 -3
- package/src/editor/Editable.tsx +1 -16
- package/src/editor/plugins/createWithEditableAPI.ts +13 -64
- package/src/editor/plugins/slate-plugin.update-selection.ts +51 -0
- package/src/editor/plugins/with-plugins.ts +8 -9
- package/src/editor/relay-machine.ts +49 -2
- package/src/internal-utils/mark-state.ts +28 -6
- package/src/internal-utils/slate-utils.ts +0 -21
- package/src/editor/plugins/createWithPortableTextSelections.ts +0 -60
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "1.52.
|
|
3
|
+
"version": "1.52.6",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -80,8 +80,8 @@
|
|
|
80
80
|
"slate-react": "0.114.2",
|
|
81
81
|
"use-effect-event": "^1.0.2",
|
|
82
82
|
"xstate": "^5.19.4",
|
|
83
|
-
"@portabletext/
|
|
84
|
-
"@portabletext/
|
|
83
|
+
"@portabletext/patches": "1.1.4",
|
|
84
|
+
"@portabletext/block-tools": "1.1.30"
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|
|
87
87
|
"@portabletext/toolkit": "^2.0.17",
|
package/src/editor/Editable.tsx
CHANGED
|
@@ -534,24 +534,9 @@ export const PortableTextEditable = forwardRef<
|
|
|
534
534
|
|
|
535
535
|
if (!event.isDefaultPrevented()) {
|
|
536
536
|
relayActor.send({type: 'focused', event})
|
|
537
|
-
|
|
538
|
-
const selection = slateEditor.selection
|
|
539
|
-
? slateRangeToSelection({
|
|
540
|
-
schema: editorActor.getSnapshot().context.schema,
|
|
541
|
-
editor: slateEditor,
|
|
542
|
-
range: slateEditor.selection,
|
|
543
|
-
})
|
|
544
|
-
: null
|
|
545
|
-
|
|
546
|
-
if (selection) {
|
|
547
|
-
editorActor.send({
|
|
548
|
-
type: 'update selection',
|
|
549
|
-
selection,
|
|
550
|
-
})
|
|
551
|
-
}
|
|
552
537
|
}
|
|
553
538
|
},
|
|
554
|
-
[
|
|
539
|
+
[onFocus, relayActor],
|
|
555
540
|
)
|
|
556
541
|
|
|
557
542
|
const handleClick = useCallback(
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
type PortableTextTextBlock,
|
|
1
|
+
import type {
|
|
2
|
+
Path,
|
|
3
|
+
PortableTextBlock,
|
|
4
|
+
PortableTextChild,
|
|
5
|
+
PortableTextObject,
|
|
6
|
+
PortableTextTextBlock,
|
|
8
7
|
} from '@sanity/types'
|
|
9
8
|
import {
|
|
10
9
|
Editor,
|
|
@@ -29,6 +28,7 @@ import {
|
|
|
29
28
|
SLATE_TO_PORTABLE_TEXT_RANGE,
|
|
30
29
|
} from '../../internal-utils/weakMaps'
|
|
31
30
|
import {addAnnotationOperationImplementation} from '../../operations/behavior.operation.annotation.add'
|
|
31
|
+
import {isActiveAnnotation} from '../../selectors'
|
|
32
32
|
import type {
|
|
33
33
|
EditableAPI,
|
|
34
34
|
EditableAPIDeleteOptions,
|
|
@@ -396,7 +396,12 @@ export function createEditableAPI(
|
|
|
396
396
|
isAnnotationActive: (
|
|
397
397
|
annotationType: PortableTextObject['_type'],
|
|
398
398
|
): boolean => {
|
|
399
|
-
|
|
399
|
+
const snapshot = getEditorSnapshot({
|
|
400
|
+
editorActorSnapshot: editorActor.getSnapshot(),
|
|
401
|
+
slateEditorInstance: editor,
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
return isActiveAnnotation(annotationType)(snapshot)
|
|
400
405
|
},
|
|
401
406
|
addAnnotation: (type, value) => {
|
|
402
407
|
let paths: ReturnType<EditableAPI['addAnnotation']> = undefined
|
|
@@ -546,59 +551,3 @@ export function createEditableAPI(
|
|
|
546
551
|
|
|
547
552
|
return editableApi
|
|
548
553
|
}
|
|
549
|
-
|
|
550
|
-
function isAnnotationActive({
|
|
551
|
-
editor,
|
|
552
|
-
annotation,
|
|
553
|
-
}: {
|
|
554
|
-
editor: PortableTextSlateEditor
|
|
555
|
-
annotation: {
|
|
556
|
-
name: string
|
|
557
|
-
}
|
|
558
|
-
}) {
|
|
559
|
-
if (!editor.selection || editor.selection.focus.path.length < 2) {
|
|
560
|
-
return false
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
try {
|
|
564
|
-
const spans = [
|
|
565
|
-
...Editor.nodes(editor, {
|
|
566
|
-
at: editor.selection,
|
|
567
|
-
match: (node) => Text.isText(node),
|
|
568
|
-
}),
|
|
569
|
-
]
|
|
570
|
-
|
|
571
|
-
if (spans.length === 0) {
|
|
572
|
-
return false
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
if (
|
|
576
|
-
spans.some(
|
|
577
|
-
([span]) =>
|
|
578
|
-
!isPortableTextSpan(span) || !span.marks || span.marks?.length === 0,
|
|
579
|
-
)
|
|
580
|
-
)
|
|
581
|
-
return false
|
|
582
|
-
|
|
583
|
-
const selectionMarkDefs = spans.reduce((accMarkDefs, [, path]) => {
|
|
584
|
-
const [block] = Editor.node(editor, path, {depth: 1})
|
|
585
|
-
if (editor.isTextBlock(block) && block.markDefs) {
|
|
586
|
-
return [...accMarkDefs, ...block.markDefs]
|
|
587
|
-
}
|
|
588
|
-
return accMarkDefs
|
|
589
|
-
}, [] as PortableTextObject[])
|
|
590
|
-
|
|
591
|
-
return spans.every(([span]) => {
|
|
592
|
-
if (!isPortableTextSpan(span)) return false
|
|
593
|
-
|
|
594
|
-
const spanMarkDefs = span.marks?.map(
|
|
595
|
-
(markKey) =>
|
|
596
|
-
selectionMarkDefs.find((def) => def?._key === markKey)?._type,
|
|
597
|
-
)
|
|
598
|
-
|
|
599
|
-
return spanMarkDefs?.includes(annotation.name)
|
|
600
|
-
})
|
|
601
|
-
} catch {
|
|
602
|
-
return false
|
|
603
|
-
}
|
|
604
|
-
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {slateRangeToSelection} from '../../internal-utils/slate-utils'
|
|
2
|
+
import {SLATE_TO_PORTABLE_TEXT_RANGE} from '../../internal-utils/weakMaps'
|
|
3
|
+
import type {PortableTextSlateEditor} from '../../types/editor'
|
|
4
|
+
import type {EditorActor} from '../editor-machine'
|
|
5
|
+
|
|
6
|
+
export function pluginUpdateSelection({
|
|
7
|
+
editor,
|
|
8
|
+
editorActor,
|
|
9
|
+
}: {
|
|
10
|
+
editor: PortableTextSlateEditor
|
|
11
|
+
editorActor: EditorActor
|
|
12
|
+
}) {
|
|
13
|
+
const updateSelection = () => {
|
|
14
|
+
if (editor.selection) {
|
|
15
|
+
const existingSelection = SLATE_TO_PORTABLE_TEXT_RANGE.get(
|
|
16
|
+
editor.selection,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
if (existingSelection) {
|
|
20
|
+
editorActor.send({
|
|
21
|
+
type: 'update selection',
|
|
22
|
+
selection: existingSelection,
|
|
23
|
+
})
|
|
24
|
+
} else {
|
|
25
|
+
const selection = slateRangeToSelection({
|
|
26
|
+
schema: editorActor.getSnapshot().context.schema,
|
|
27
|
+
editor,
|
|
28
|
+
range: editor.selection,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
SLATE_TO_PORTABLE_TEXT_RANGE.set(editor.selection, selection)
|
|
32
|
+
|
|
33
|
+
editorActor.send({type: 'update selection', selection})
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
editorActor.send({type: 'update selection', selection: null})
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const {onChange} = editor
|
|
41
|
+
|
|
42
|
+
editor.onChange = () => {
|
|
43
|
+
onChange()
|
|
44
|
+
|
|
45
|
+
if (!editorActor.getSnapshot().matches({setup: 'setting up'})) {
|
|
46
|
+
updateSelection()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return editor
|
|
51
|
+
}
|
|
@@ -8,11 +8,11 @@ import {createWithObjectKeys} from './createWithObjectKeys'
|
|
|
8
8
|
import {createWithPatches} from './createWithPatches'
|
|
9
9
|
import {createWithPlaceholderBlock} from './createWithPlaceholderBlock'
|
|
10
10
|
import {createWithPortableTextMarkModel} from './createWithPortableTextMarkModel'
|
|
11
|
-
import {createWithPortableTextSelections} from './createWithPortableTextSelections'
|
|
12
11
|
import {createWithSchemaTypes} from './createWithSchemaTypes'
|
|
13
12
|
import {createWithUndoRedo} from './createWithUndoRedo'
|
|
14
13
|
import {createWithUtils} from './createWithUtils'
|
|
15
14
|
import {pluginUpdateMarkState} from './slate-plugin.update-mark-state'
|
|
15
|
+
import {pluginUpdateSelection} from './slate-plugin.update-selection'
|
|
16
16
|
import {pluginUpdateValue} from './slate-plugin.update-value'
|
|
17
17
|
|
|
18
18
|
export interface OriginalEditorFunctions {
|
|
@@ -54,8 +54,6 @@ export const withPlugins = <T extends Editor>(
|
|
|
54
54
|
const withUtils = createWithUtils({
|
|
55
55
|
editorActor,
|
|
56
56
|
})
|
|
57
|
-
const withPortableTextSelections =
|
|
58
|
-
createWithPortableTextSelections(editorActor)
|
|
59
57
|
const withEventListeners = createWithEventListeners(editorActor)
|
|
60
58
|
|
|
61
59
|
// Ordering is important here, selection dealing last, data manipulation in the middle and core model stuff first.
|
|
@@ -68,13 +66,14 @@ export const withPlugins = <T extends Editor>(
|
|
|
68
66
|
withMaxBlocks(
|
|
69
67
|
withUndoRedo(
|
|
70
68
|
withPatches(
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
pluginUpdateValue(
|
|
70
|
+
editorActor.getSnapshot().context,
|
|
71
|
+
pluginUpdateMarkState(
|
|
73
72
|
editorActor.getSnapshot().context,
|
|
74
|
-
|
|
75
|
-
editorActor
|
|
76
|
-
e,
|
|
77
|
-
),
|
|
73
|
+
pluginUpdateSelection({
|
|
74
|
+
editorActor,
|
|
75
|
+
editor: e,
|
|
76
|
+
}),
|
|
78
77
|
),
|
|
79
78
|
),
|
|
80
79
|
),
|
|
@@ -1,7 +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 {emit, setup, type ActorRefFrom} from 'xstate'
|
|
4
|
+
import {assign, emit, setup, type ActorRefFrom} from 'xstate'
|
|
5
5
|
import type {EditorSelection, InvalidValueResolution} from '../types/editor'
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -96,14 +96,61 @@ export type RelayActor = ActorRefFrom<typeof relayMachine>
|
|
|
96
96
|
|
|
97
97
|
export const relayMachine = setup({
|
|
98
98
|
types: {
|
|
99
|
+
context: {} as {
|
|
100
|
+
prevSelection: EditorSelection
|
|
101
|
+
lastEventWasFocused: boolean
|
|
102
|
+
},
|
|
99
103
|
events: {} as InternalEditorEmittedEvent,
|
|
100
104
|
emitted: {} as InternalEditorEmittedEvent,
|
|
101
105
|
},
|
|
102
106
|
}).createMachine({
|
|
103
107
|
id: 'relay',
|
|
108
|
+
context: {
|
|
109
|
+
prevSelection: null,
|
|
110
|
+
lastEventWasFocused: false,
|
|
111
|
+
},
|
|
104
112
|
on: {
|
|
113
|
+
'focused': {
|
|
114
|
+
actions: [
|
|
115
|
+
assign({
|
|
116
|
+
lastEventWasFocused: true,
|
|
117
|
+
}),
|
|
118
|
+
emit(({event}) => event),
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
'selection': [
|
|
122
|
+
{
|
|
123
|
+
guard: ({context}) => context.lastEventWasFocused,
|
|
124
|
+
actions: [
|
|
125
|
+
assign({
|
|
126
|
+
prevSelection: ({event}) => event.selection,
|
|
127
|
+
}),
|
|
128
|
+
emit(({event}) => event),
|
|
129
|
+
assign({
|
|
130
|
+
lastEventWasFocused: false,
|
|
131
|
+
}),
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
guard: ({context, event}) => context.prevSelection !== event.selection,
|
|
136
|
+
actions: [
|
|
137
|
+
assign({
|
|
138
|
+
prevSelection: ({event}) => event.selection,
|
|
139
|
+
}),
|
|
140
|
+
emit(({event}) => event),
|
|
141
|
+
assign({
|
|
142
|
+
lastEventWasFocused: false,
|
|
143
|
+
}),
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
],
|
|
105
147
|
'*': {
|
|
106
|
-
actions:
|
|
148
|
+
actions: [
|
|
149
|
+
emit(({event}) => event),
|
|
150
|
+
assign({
|
|
151
|
+
lastEventWasFocused: false,
|
|
152
|
+
}),
|
|
153
|
+
],
|
|
107
154
|
},
|
|
108
155
|
},
|
|
109
156
|
})
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {Range} from 'slate'
|
|
2
2
|
import type {EditorSchema} from '../editor/editor-schema'
|
|
3
|
+
import {getSelectedSpans} from '../selectors'
|
|
3
4
|
import type {PortableTextSlateEditor} from '../types/editor'
|
|
4
5
|
import {getNextSpan, getPreviousSpan} from './sibling-utils'
|
|
5
|
-
import {getFocusBlock, getFocusSpan,
|
|
6
|
+
import {getFocusBlock, getFocusSpan, slateRangeToSelection} from './slate-utils'
|
|
6
7
|
|
|
7
8
|
export type MarkState = {
|
|
8
9
|
state: 'changed' | 'unchanged'
|
|
@@ -36,18 +37,39 @@ export function getMarkState({
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
if (Range.isExpanded(editor.selection)) {
|
|
39
|
-
const
|
|
40
|
+
const selection = editor.selection
|
|
41
|
+
? slateRangeToSelection({
|
|
42
|
+
schema,
|
|
43
|
+
editor,
|
|
44
|
+
range: editor.selection,
|
|
45
|
+
})
|
|
46
|
+
: null
|
|
47
|
+
|
|
48
|
+
const selectedSpans = getSelectedSpans({
|
|
49
|
+
context: {
|
|
50
|
+
value: editor.value,
|
|
51
|
+
selection,
|
|
52
|
+
schema,
|
|
53
|
+
converters: [],
|
|
54
|
+
keyGenerator: () => '',
|
|
55
|
+
readOnly: false,
|
|
56
|
+
},
|
|
57
|
+
beta: {
|
|
58
|
+
activeAnnotations: [],
|
|
59
|
+
activeDecorators: [],
|
|
60
|
+
},
|
|
61
|
+
})
|
|
40
62
|
|
|
41
63
|
let index = 0
|
|
42
64
|
let marks: Array<string> = []
|
|
43
65
|
|
|
44
|
-
for (const
|
|
66
|
+
for (const span of selectedSpans) {
|
|
45
67
|
if (index === 0) {
|
|
46
|
-
marks = span.marks ?? []
|
|
68
|
+
marks = span.node.marks ?? []
|
|
47
69
|
} else {
|
|
48
70
|
if (
|
|
49
|
-
span.marks?.length === 0 ||
|
|
50
|
-
(span.marks ?? [])?.some((mark) => !marks.includes(mark))
|
|
71
|
+
span.node.marks?.length === 0 ||
|
|
72
|
+
(span.node.marks ?? [])?.some((mark) => !marks.includes(mark))
|
|
51
73
|
) {
|
|
52
74
|
marks = []
|
|
53
75
|
}
|
|
@@ -91,27 +91,6 @@ export function getFocusSpan({
|
|
|
91
91
|
return [undefined, undefined]
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
export function getSelectedSpans({
|
|
95
|
-
editor,
|
|
96
|
-
}: {
|
|
97
|
-
editor: PortableTextSlateEditor
|
|
98
|
-
}): Array<[node: PortableTextSpan, path: Path]> {
|
|
99
|
-
if (!editor.selection) {
|
|
100
|
-
return []
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
return Array.from(
|
|
105
|
-
Editor.nodes(editor, {
|
|
106
|
-
at: editor.selection,
|
|
107
|
-
match: (node) => editor.isTextSpan(node),
|
|
108
|
-
}),
|
|
109
|
-
)
|
|
110
|
-
} catch {
|
|
111
|
-
return []
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
94
|
export function getSelectionStartBlock({
|
|
116
95
|
editor,
|
|
117
96
|
}: {
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import type {BaseRange} from 'slate'
|
|
2
|
-
import {debugWithName} from '../../internal-utils/debug'
|
|
3
|
-
import {slateRangeToSelection} from '../../internal-utils/slate-utils'
|
|
4
|
-
import {SLATE_TO_PORTABLE_TEXT_RANGE} from '../../internal-utils/weakMaps'
|
|
5
|
-
import type {EditorSelection, PortableTextSlateEditor} from '../../types/editor'
|
|
6
|
-
import type {EditorActor} from '../editor-machine'
|
|
7
|
-
|
|
8
|
-
const debug = debugWithName('plugin:withPortableTextSelections')
|
|
9
|
-
const debugVerbose = debug.enabled && false
|
|
10
|
-
|
|
11
|
-
// This plugin will make sure that we emit a PT selection whenever the editor has changed.
|
|
12
|
-
export function createWithPortableTextSelections(
|
|
13
|
-
editorActor: EditorActor,
|
|
14
|
-
): (editor: PortableTextSlateEditor) => PortableTextSlateEditor {
|
|
15
|
-
let prevSelection: BaseRange | null = null
|
|
16
|
-
return function withPortableTextSelections(
|
|
17
|
-
editor: PortableTextSlateEditor,
|
|
18
|
-
): PortableTextSlateEditor {
|
|
19
|
-
const emitPortableTextSelection = () => {
|
|
20
|
-
if (prevSelection !== editor.selection) {
|
|
21
|
-
let ptRange: EditorSelection | null = null
|
|
22
|
-
if (editor.selection) {
|
|
23
|
-
const existing = SLATE_TO_PORTABLE_TEXT_RANGE.get(editor.selection)
|
|
24
|
-
if (existing) {
|
|
25
|
-
ptRange = existing
|
|
26
|
-
} else {
|
|
27
|
-
ptRange = slateRangeToSelection({
|
|
28
|
-
schema: editorActor.getSnapshot().context.schema,
|
|
29
|
-
editor,
|
|
30
|
-
range: editor.selection,
|
|
31
|
-
})
|
|
32
|
-
SLATE_TO_PORTABLE_TEXT_RANGE.set(editor.selection, ptRange)
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
if (debugVerbose) {
|
|
36
|
-
debug(
|
|
37
|
-
`Emitting selection ${JSON.stringify(ptRange || null)} (${JSON.stringify(
|
|
38
|
-
editor.selection,
|
|
39
|
-
)})`,
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
if (ptRange) {
|
|
43
|
-
editorActor.send({type: 'update selection', selection: ptRange})
|
|
44
|
-
} else {
|
|
45
|
-
editorActor.send({type: 'update selection', selection: null})
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
prevSelection = editor.selection
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const {onChange} = editor
|
|
52
|
-
editor.onChange = () => {
|
|
53
|
-
onChange()
|
|
54
|
-
if (!editorActor.getSnapshot().matches({setup: 'setting up'})) {
|
|
55
|
-
emitPortableTextSelection()
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return editor
|
|
59
|
-
}
|
|
60
|
-
}
|