@portabletext/editor 1.23.0 → 1.25.0
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/_chunks-cjs/behavior.core.cjs +249 -62
- package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
- package/lib/_chunks-cjs/{selector.is-selection-collapsed.cjs → selector.is-active-style.cjs} +158 -3
- package/lib/_chunks-cjs/selector.is-active-style.cjs.map +1 -0
- package/lib/_chunks-cjs/util.slice-blocks.cjs +23 -9
- package/lib/_chunks-cjs/util.slice-blocks.cjs.map +1 -1
- package/lib/_chunks-es/behavior.core.js +225 -38
- package/lib/_chunks-es/behavior.core.js.map +1 -1
- package/lib/_chunks-es/{selector.is-selection-collapsed.js → selector.is-active-style.js} +159 -4
- package/lib/_chunks-es/selector.is-active-style.js.map +1 -0
- package/lib/_chunks-es/util.slice-blocks.js +23 -9
- package/lib/_chunks-es/util.slice-blocks.js.map +1 -1
- package/lib/behaviors/index.cjs +27 -27
- package/lib/behaviors/index.cjs.map +1 -1
- package/lib/behaviors/index.d.cts +2830 -139
- package/lib/behaviors/index.d.ts +2830 -139
- package/lib/behaviors/index.js +1 -1
- package/lib/index.cjs +695 -526
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +8950 -246
- package/lib/index.d.ts +8950 -246
- package/lib/index.js +696 -525
- package/lib/index.js.map +1 -1
- package/lib/selectors/index.cjs +24 -171
- package/lib/selectors/index.cjs.map +1 -1
- package/lib/selectors/index.d.cts +73 -0
- package/lib/selectors/index.d.ts +73 -0
- package/lib/selectors/index.js +3 -151
- package/lib/selectors/index.js.map +1 -1
- package/package.json +11 -10
- package/src/behavior-actions/behavior.action.data-transfer-set.ts +7 -0
- package/src/behavior-actions/behavior.action.insert-blocks.ts +61 -0
- package/src/behavior-actions/behavior.actions.ts +159 -83
- package/src/behaviors/behavior.core.annotations.ts +29 -0
- package/src/behaviors/behavior.core.block-objects.ts +13 -13
- package/src/behaviors/behavior.core.decorators.ts +19 -0
- package/src/behaviors/behavior.core.deserialize.ts +46 -0
- package/src/behaviors/behavior.core.lists.ts +57 -23
- package/src/behaviors/behavior.core.serialize.ts +44 -0
- package/src/behaviors/behavior.core.style.ts +19 -0
- package/src/behaviors/behavior.core.ts +19 -0
- package/src/behaviors/behavior.types.ts +126 -89
- package/src/converters/converter.json.ts +53 -0
- package/src/converters/converter.portable-text.deserialize.test.ts +686 -0
- package/src/converters/converter.portable-text.ts +59 -0
- package/src/converters/converter.text-html.deserialize.test.ts +349 -0
- package/src/converters/converter.text-html.serialize.test.ts +233 -0
- package/src/converters/converter.text-html.ts +61 -0
- package/src/converters/converter.text-plain.test.ts +241 -0
- package/src/converters/converter.text-plain.ts +91 -0
- package/src/converters/converter.ts +65 -0
- package/src/converters/converters.ts +11 -0
- package/src/editor/Editable.tsx +3 -13
- package/src/editor/create-editor.ts +48 -6
- package/src/editor/editor-machine.ts +56 -2
- package/src/editor/editor-selector.ts +1 -0
- package/src/editor/editor-snapshot.ts +5 -0
- package/src/editor/plugins/create-with-event-listeners.ts +82 -106
- package/src/internal-utils/asserters.ts +9 -0
- package/src/internal-utils/mime-type.ts +1 -0
- package/src/internal-utils/parse-blocks.ts +136 -0
- package/src/internal-utils/test-key-generator.ts +9 -0
- package/src/selectors/selector.get-selected-spans.test.ts +1 -0
- package/src/selectors/selector.get-selection-text.test.ts +1 -0
- package/src/selectors/selector.is-active-decorator.test.ts +1 -0
- package/src/utils/util.slice-blocks.test.ts +87 -0
- package/src/utils/util.slice-blocks.ts +27 -10
- package/lib/_chunks-cjs/selector.is-selection-collapsed.cjs.map +0 -1
- package/lib/_chunks-es/selector.is-selection-collapsed.js.map +0 -1
- package/src/editor/plugins/__tests__/createWithInsertData.test.tsx +0 -181
- package/src/editor/plugins/createWithInsertData.ts +0 -425
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import type {PortableTextBlock, PortableTextTextBlock} from '@sanity/types'
|
|
2
|
+
import {expect, test} from 'vitest'
|
|
3
|
+
import {
|
|
4
|
+
compileSchemaDefinition,
|
|
5
|
+
defineSchema,
|
|
6
|
+
type SchemaDefinition,
|
|
7
|
+
} from '../editor/define-schema'
|
|
8
|
+
import type {EditorContext} from '../editor/editor-snapshot'
|
|
9
|
+
import type {EditorSelection} from '../utils'
|
|
10
|
+
import {converterTextPlain} from './converter.text-plain'
|
|
11
|
+
import {coreConverters} from './converters'
|
|
12
|
+
|
|
13
|
+
const b1: PortableTextTextBlock = {
|
|
14
|
+
_type: 'block',
|
|
15
|
+
_key: 'b1',
|
|
16
|
+
children: [
|
|
17
|
+
{
|
|
18
|
+
_type: 'span',
|
|
19
|
+
_key: 'b1c1',
|
|
20
|
+
text: 'foo',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
_type: 'span',
|
|
24
|
+
_key: 'b1c2',
|
|
25
|
+
text: 'bar',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
}
|
|
29
|
+
const b2: PortableTextBlock = {
|
|
30
|
+
_type: 'image',
|
|
31
|
+
_key: 'b2',
|
|
32
|
+
src: 'https://example.com/image.jpg',
|
|
33
|
+
alt: 'Example',
|
|
34
|
+
}
|
|
35
|
+
const b3: PortableTextTextBlock = {
|
|
36
|
+
_type: 'block',
|
|
37
|
+
_key: 'b3',
|
|
38
|
+
children: [
|
|
39
|
+
{
|
|
40
|
+
_type: 'span',
|
|
41
|
+
_key: 'b3c1',
|
|
42
|
+
text: 'baz',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
}
|
|
46
|
+
const b4: PortableTextTextBlock = {
|
|
47
|
+
_type: 'block',
|
|
48
|
+
_key: 'b4',
|
|
49
|
+
children: [
|
|
50
|
+
{
|
|
51
|
+
_type: 'span',
|
|
52
|
+
_key: 'b4c1',
|
|
53
|
+
text: 'fizz',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
_type: 'stock-ticker',
|
|
57
|
+
_key: 'b4c2',
|
|
58
|
+
symbol: 'AAPL',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
_type: 'span',
|
|
62
|
+
_key: 'b4c3',
|
|
63
|
+
text: 'buzz',
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function createContext({
|
|
69
|
+
schema,
|
|
70
|
+
selection,
|
|
71
|
+
}: {
|
|
72
|
+
schema: SchemaDefinition
|
|
73
|
+
selection: EditorSelection
|
|
74
|
+
}): EditorContext {
|
|
75
|
+
return {
|
|
76
|
+
converters: coreConverters,
|
|
77
|
+
activeDecorators: [],
|
|
78
|
+
keyGenerator: () => '',
|
|
79
|
+
schema: compileSchemaDefinition(schema),
|
|
80
|
+
selection,
|
|
81
|
+
value: [b1, b2, b3, b4],
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
test(converterTextPlain.serialize.name, () => {
|
|
86
|
+
expect(
|
|
87
|
+
converterTextPlain.serialize({
|
|
88
|
+
context: createContext({
|
|
89
|
+
schema: defineSchema({}),
|
|
90
|
+
selection: {
|
|
91
|
+
anchor: {
|
|
92
|
+
path: [{_key: b3._key}, 'children', {_key: b3.children[0]._key}],
|
|
93
|
+
offset: 0,
|
|
94
|
+
},
|
|
95
|
+
focus: {
|
|
96
|
+
path: [{_key: b4._key}, 'children', {_key: b4.children[0]._key}],
|
|
97
|
+
offset: 4,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
event: {
|
|
102
|
+
type: 'serialize',
|
|
103
|
+
originEvent: 'unknown',
|
|
104
|
+
},
|
|
105
|
+
}),
|
|
106
|
+
).toMatchObject({
|
|
107
|
+
data: 'baz\n\nfizz',
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
expect(
|
|
111
|
+
converterTextPlain.serialize({
|
|
112
|
+
context: createContext({
|
|
113
|
+
schema: defineSchema({}),
|
|
114
|
+
selection: {
|
|
115
|
+
anchor: {
|
|
116
|
+
path: [{_key: b1._key}, 'children', {_key: b1.children[0]._key}],
|
|
117
|
+
offset: 0,
|
|
118
|
+
},
|
|
119
|
+
focus: {
|
|
120
|
+
path: [{_key: b3._key}, 'children', {_key: b3.children[0]._key}],
|
|
121
|
+
offset: 3,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
}),
|
|
125
|
+
event: {
|
|
126
|
+
type: 'serialize',
|
|
127
|
+
originEvent: 'unknown',
|
|
128
|
+
},
|
|
129
|
+
}),
|
|
130
|
+
).toMatchObject({
|
|
131
|
+
data: 'foobar\n\n[Object]\n\nbaz',
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
expect(
|
|
135
|
+
converterTextPlain.serialize({
|
|
136
|
+
context: createContext({
|
|
137
|
+
schema: defineSchema({}),
|
|
138
|
+
selection: {
|
|
139
|
+
anchor: {
|
|
140
|
+
path: [{_key: b2._key}],
|
|
141
|
+
offset: 0,
|
|
142
|
+
},
|
|
143
|
+
focus: {
|
|
144
|
+
path: [{_key: b2._key}],
|
|
145
|
+
offset: 0,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
}),
|
|
149
|
+
event: {
|
|
150
|
+
type: 'serialize',
|
|
151
|
+
originEvent: 'unknown',
|
|
152
|
+
},
|
|
153
|
+
}),
|
|
154
|
+
).toMatchObject({
|
|
155
|
+
data: '[Object]',
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
expect(
|
|
159
|
+
converterTextPlain.serialize({
|
|
160
|
+
context: createContext({
|
|
161
|
+
schema: defineSchema({
|
|
162
|
+
blockObjects: [
|
|
163
|
+
{
|
|
164
|
+
name: 'image',
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
}),
|
|
168
|
+
selection: {
|
|
169
|
+
anchor: {
|
|
170
|
+
path: [{_key: b1._key}, 'children', {_key: b1.children[0]._key}],
|
|
171
|
+
offset: 0,
|
|
172
|
+
},
|
|
173
|
+
focus: {
|
|
174
|
+
path: [{_key: b3._key}, 'children', {_key: b3.children[0]._key}],
|
|
175
|
+
offset: 3,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
}),
|
|
179
|
+
event: {
|
|
180
|
+
type: 'serialize',
|
|
181
|
+
originEvent: 'unknown',
|
|
182
|
+
},
|
|
183
|
+
}),
|
|
184
|
+
).toMatchObject({
|
|
185
|
+
data: 'foobar\n\n[Image]\n\nbaz',
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
expect(
|
|
189
|
+
converterTextPlain.serialize({
|
|
190
|
+
context: createContext({
|
|
191
|
+
schema: defineSchema({}),
|
|
192
|
+
selection: {
|
|
193
|
+
anchor: {
|
|
194
|
+
path: [{_key: b4._key}, 'children', {_key: b4.children[0]._key}],
|
|
195
|
+
offset: 0,
|
|
196
|
+
},
|
|
197
|
+
focus: {
|
|
198
|
+
path: [{_key: b4._key}, 'children', {_key: b4.children[2]._key}],
|
|
199
|
+
offset: 4,
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
}),
|
|
203
|
+
event: {
|
|
204
|
+
type: 'serialize',
|
|
205
|
+
originEvent: 'unknown',
|
|
206
|
+
},
|
|
207
|
+
}),
|
|
208
|
+
).toMatchObject({
|
|
209
|
+
data: 'fizz[Object]buzz',
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
expect(
|
|
213
|
+
converterTextPlain.serialize({
|
|
214
|
+
context: createContext({
|
|
215
|
+
schema: defineSchema({
|
|
216
|
+
inlineObjects: [
|
|
217
|
+
{
|
|
218
|
+
name: 'stock-ticker',
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
}),
|
|
222
|
+
selection: {
|
|
223
|
+
anchor: {
|
|
224
|
+
path: [{_key: b4._key}, 'children', {_key: b4.children[0]._key}],
|
|
225
|
+
offset: 0,
|
|
226
|
+
},
|
|
227
|
+
focus: {
|
|
228
|
+
path: [{_key: b4._key}, 'children', {_key: b4.children[2]._key}],
|
|
229
|
+
offset: 4,
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
}),
|
|
233
|
+
event: {
|
|
234
|
+
type: 'serialize',
|
|
235
|
+
originEvent: 'unknown',
|
|
236
|
+
},
|
|
237
|
+
}),
|
|
238
|
+
).toMatchObject({
|
|
239
|
+
data: 'fizz[Stock Ticker]buzz',
|
|
240
|
+
})
|
|
241
|
+
})
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {htmlToBlocks} from '@portabletext/block-tools'
|
|
2
|
+
import {isPortableTextTextBlock, type PortableTextBlock} from '@sanity/types'
|
|
3
|
+
import {sliceBlocks} from '../utils'
|
|
4
|
+
import type {Converter} from './converter'
|
|
5
|
+
|
|
6
|
+
export const converterTextPlain: Converter<'text/plain'> = {
|
|
7
|
+
mimeType: 'text/plain',
|
|
8
|
+
serialize: ({context, event}) => {
|
|
9
|
+
if (!context.selection) {
|
|
10
|
+
return {
|
|
11
|
+
type: 'serialization.failure',
|
|
12
|
+
mimeType: 'text/plain',
|
|
13
|
+
originEvent: event.originEvent,
|
|
14
|
+
reason: 'No selection',
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const blocks = sliceBlocks({
|
|
19
|
+
blocks: context.value,
|
|
20
|
+
selection: context.selection,
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const data = blocks
|
|
24
|
+
.map((block) => {
|
|
25
|
+
if (isPortableTextTextBlock(block)) {
|
|
26
|
+
return block.children
|
|
27
|
+
.map((child) => {
|
|
28
|
+
if (child._type === context.schema.span.name) {
|
|
29
|
+
return child.text
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return `[${
|
|
33
|
+
context.schema.inlineObjects.find(
|
|
34
|
+
(inlineObjectType) => inlineObjectType.name === child._type,
|
|
35
|
+
)?.title ?? 'Object'
|
|
36
|
+
}]`
|
|
37
|
+
})
|
|
38
|
+
.join('')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return `[${
|
|
42
|
+
context.schema.blockObjects.find(
|
|
43
|
+
(blockObjectType) => blockObjectType.name === block._type,
|
|
44
|
+
)?.title ?? 'Object'
|
|
45
|
+
}]`
|
|
46
|
+
})
|
|
47
|
+
.join('\n\n')
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
type: 'serialization.success',
|
|
51
|
+
data,
|
|
52
|
+
mimeType: 'text/plain',
|
|
53
|
+
originEvent: event.originEvent,
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
deserialize: ({context, event}) => {
|
|
57
|
+
const html = escapeHtml(event.data)
|
|
58
|
+
.split(/\n{2,}/)
|
|
59
|
+
.map((line) =>
|
|
60
|
+
line ? `<p>${line.replace(/(?:\r\n|\r|\n)/g, '<br/>')}</p>` : '<p></p>',
|
|
61
|
+
)
|
|
62
|
+
.join('')
|
|
63
|
+
|
|
64
|
+
const textToHtml = `<html><body>${html}</body></html>`
|
|
65
|
+
|
|
66
|
+
const blocks = htmlToBlocks(textToHtml, context.schema.portableText, {
|
|
67
|
+
keyGenerator: context.keyGenerator,
|
|
68
|
+
}) as Array<PortableTextBlock>
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
type: 'deserialization.success',
|
|
72
|
+
data: blocks,
|
|
73
|
+
mimeType: 'text/plain',
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const entityMap: Record<string, string> = {
|
|
79
|
+
'&': '&',
|
|
80
|
+
'<': '<',
|
|
81
|
+
'>': '>',
|
|
82
|
+
'"': '"',
|
|
83
|
+
"'": ''',
|
|
84
|
+
'/': '/',
|
|
85
|
+
'`': '`',
|
|
86
|
+
'=': '=',
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function escapeHtml(str: string) {
|
|
90
|
+
return String(str).replace(/[&<>"'`=/]/g, (s: string) => entityMap[s])
|
|
91
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type {PortableTextBlock} from '@sanity/types'
|
|
2
|
+
import type {EditorContext} from '../editor/editor-snapshot'
|
|
3
|
+
import type {MIMEType} from '../internal-utils/mime-type'
|
|
4
|
+
import type {PickFromUnion} from '../type-utils'
|
|
5
|
+
|
|
6
|
+
export type Converter<TMIMEType extends MIMEType = MIMEType> = {
|
|
7
|
+
mimeType: TMIMEType
|
|
8
|
+
serialize: Serializer<TMIMEType>
|
|
9
|
+
deserialize: Deserializer<TMIMEType>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ConverterEvent<TMIMEType extends MIMEType = MIMEType> =
|
|
13
|
+
| {
|
|
14
|
+
type: 'serialize'
|
|
15
|
+
originEvent: 'copy' | 'cut' | 'unknown'
|
|
16
|
+
}
|
|
17
|
+
| {
|
|
18
|
+
type: 'serialization.failure'
|
|
19
|
+
mimeType: TMIMEType
|
|
20
|
+
reason: string
|
|
21
|
+
}
|
|
22
|
+
| {
|
|
23
|
+
type: 'serialization.success'
|
|
24
|
+
data: string
|
|
25
|
+
mimeType: TMIMEType
|
|
26
|
+
originEvent: 'copy' | 'cut' | 'unknown'
|
|
27
|
+
}
|
|
28
|
+
| {
|
|
29
|
+
type: 'deserialize'
|
|
30
|
+
data: string
|
|
31
|
+
}
|
|
32
|
+
| {
|
|
33
|
+
type: 'deserialization.failure'
|
|
34
|
+
mimeType: TMIMEType
|
|
35
|
+
reason: string
|
|
36
|
+
}
|
|
37
|
+
| {
|
|
38
|
+
type: 'deserialization.success'
|
|
39
|
+
data: Array<PortableTextBlock>
|
|
40
|
+
mimeType: TMIMEType
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type Serializer<TMIMEType extends MIMEType> = ({
|
|
44
|
+
context,
|
|
45
|
+
event,
|
|
46
|
+
}: {
|
|
47
|
+
context: EditorContext
|
|
48
|
+
event: PickFromUnion<ConverterEvent<TMIMEType>, 'type', 'serialize'>
|
|
49
|
+
}) => PickFromUnion<
|
|
50
|
+
ConverterEvent<TMIMEType>,
|
|
51
|
+
'type',
|
|
52
|
+
'serialization.success' | 'serialization.failure'
|
|
53
|
+
>
|
|
54
|
+
|
|
55
|
+
export type Deserializer<TMIMEType extends MIMEType> = ({
|
|
56
|
+
context,
|
|
57
|
+
event,
|
|
58
|
+
}: {
|
|
59
|
+
context: EditorContext
|
|
60
|
+
event: PickFromUnion<ConverterEvent<TMIMEType>, 'type', 'deserialize'>
|
|
61
|
+
}) => PickFromUnion<
|
|
62
|
+
ConverterEvent<TMIMEType>,
|
|
63
|
+
'type',
|
|
64
|
+
'deserialization.success' | 'deserialization.failure'
|
|
65
|
+
>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {converterJson} from './converter.json'
|
|
2
|
+
import {converterPortableText} from './converter.portable-text'
|
|
3
|
+
import {converterTextHtml} from './converter.text-html'
|
|
4
|
+
import {converterTextPlain} from './converter.text-plain'
|
|
5
|
+
|
|
6
|
+
export const coreConverters = [
|
|
7
|
+
converterJson,
|
|
8
|
+
converterPortableText,
|
|
9
|
+
converterTextHtml,
|
|
10
|
+
converterTextPlain,
|
|
11
|
+
]
|
package/src/editor/Editable.tsx
CHANGED
|
@@ -68,7 +68,6 @@ import {Leaf} from './components/Leaf'
|
|
|
68
68
|
import {EditorActorContext} from './editor-actor-context'
|
|
69
69
|
import {usePortableTextEditor} from './hooks/usePortableTextEditor'
|
|
70
70
|
import {createWithHotkeys} from './plugins/createWithHotKeys'
|
|
71
|
-
import {createWithInsertData} from './plugins/createWithInsertData'
|
|
72
71
|
import {PortableTextEditor} from './PortableTextEditor'
|
|
73
72
|
import {withSyncRangeDecorations} from './withSyncRangeDecorations'
|
|
74
73
|
|
|
@@ -189,11 +188,9 @@ export const PortableTextEditable = forwardRef<
|
|
|
189
188
|
// There will be a problem if they redefine editor methods and then calling the original method within themselves.
|
|
190
189
|
useMemo(() => {
|
|
191
190
|
// React/UI-specific plugins
|
|
192
|
-
const withInsertData = createWithInsertData(editorActor, schemaTypes)
|
|
193
|
-
|
|
194
191
|
if (readOnly) {
|
|
195
192
|
debug('Editable is in read only mode')
|
|
196
|
-
return
|
|
193
|
+
return slateEditor
|
|
197
194
|
}
|
|
198
195
|
const withHotKeys = createWithHotkeys(
|
|
199
196
|
editorActor,
|
|
@@ -202,15 +199,8 @@ export const PortableTextEditable = forwardRef<
|
|
|
202
199
|
)
|
|
203
200
|
|
|
204
201
|
debug('Editable is in edit mode')
|
|
205
|
-
return
|
|
206
|
-
}, [
|
|
207
|
-
editorActor,
|
|
208
|
-
hotkeys,
|
|
209
|
-
portableTextEditor,
|
|
210
|
-
readOnly,
|
|
211
|
-
schemaTypes,
|
|
212
|
-
slateEditor,
|
|
213
|
-
])
|
|
202
|
+
return withHotKeys(slateEditor)
|
|
203
|
+
}, [editorActor, hotkeys, portableTextEditor, readOnly, slateEditor])
|
|
214
204
|
|
|
215
205
|
const renderElement = useCallback(
|
|
216
206
|
(eProps: RenderElementProps) => (
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type Snapshot,
|
|
13
13
|
} from 'xstate'
|
|
14
14
|
import type {Behavior, CustomBehaviorEvent} from '../behaviors/behavior.types'
|
|
15
|
+
import {coreConverters} from '../converters/converters'
|
|
15
16
|
import {compileType} from '../internal-utils/schema'
|
|
16
17
|
import type {PickFromUnion} from '../type-utils'
|
|
17
18
|
import type {EditableAPI} from '../types/editor'
|
|
@@ -64,14 +65,37 @@ export type EditorEvent =
|
|
|
64
65
|
'type',
|
|
65
66
|
| 'annotation.add'
|
|
66
67
|
| 'annotation.remove'
|
|
68
|
+
| 'annotation.toggle'
|
|
67
69
|
| 'blur'
|
|
70
|
+
| 'data transfer.set'
|
|
71
|
+
| 'decorator.add'
|
|
72
|
+
| 'decorator.remove'
|
|
68
73
|
| 'decorator.toggle'
|
|
74
|
+
| 'delete.block'
|
|
75
|
+
| 'delete.text'
|
|
76
|
+
| 'deserialization.failure'
|
|
77
|
+
| 'deserialization.success'
|
|
69
78
|
| 'focus'
|
|
70
79
|
| 'insert.block object'
|
|
71
80
|
| 'insert.inline object'
|
|
81
|
+
| 'insert.span'
|
|
82
|
+
| 'insert.text block'
|
|
83
|
+
| 'list item.add'
|
|
84
|
+
| 'list item.remove'
|
|
72
85
|
| 'list item.toggle'
|
|
86
|
+
| 'move.block'
|
|
87
|
+
| 'move.block down'
|
|
88
|
+
| 'move.block up'
|
|
73
89
|
| 'select'
|
|
90
|
+
| 'select.next block'
|
|
91
|
+
| 'select.previous block'
|
|
92
|
+
| 'serialization.failure'
|
|
93
|
+
| 'serialization.success'
|
|
94
|
+
| 'style.add'
|
|
95
|
+
| 'style.remove'
|
|
74
96
|
| 'style.toggle'
|
|
97
|
+
| 'text block.set'
|
|
98
|
+
| 'text block.unset'
|
|
75
99
|
| 'patches'
|
|
76
100
|
| 'update behaviors'
|
|
77
101
|
| 'update key generator'
|
|
@@ -118,6 +142,7 @@ export function useCreateEditor(config: EditorConfig): Editor {
|
|
|
118
142
|
function editorConfigToMachineInput(config: EditorConfig) {
|
|
119
143
|
return {
|
|
120
144
|
behaviors: config.behaviors,
|
|
145
|
+
converters: coreConverters,
|
|
121
146
|
keyGenerator: config.keyGenerator ?? defaultKeyGenerator,
|
|
122
147
|
maxBlocks: config.maxBlocks,
|
|
123
148
|
readOnly: config.readOnly,
|
|
@@ -158,12 +183,29 @@ function createEditorFromActor(editorActor: EditorActor): Editor {
|
|
|
158
183
|
send: (event) => {
|
|
159
184
|
editorActor.send(event)
|
|
160
185
|
},
|
|
161
|
-
on: (event, listener) =>
|
|
162
|
-
editorActor.on(
|
|
163
|
-
event
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
186
|
+
on: (event, listener) => {
|
|
187
|
+
const subscription = editorActor.on(event, (event) => {
|
|
188
|
+
switch (event.type) {
|
|
189
|
+
case 'blurred':
|
|
190
|
+
case 'done loading':
|
|
191
|
+
case 'editable':
|
|
192
|
+
case 'error':
|
|
193
|
+
case 'focused':
|
|
194
|
+
case 'invalid value':
|
|
195
|
+
case 'loading':
|
|
196
|
+
case 'mutation':
|
|
197
|
+
case 'patch':
|
|
198
|
+
case 'read only':
|
|
199
|
+
case 'ready':
|
|
200
|
+
case 'selection':
|
|
201
|
+
case 'value changed':
|
|
202
|
+
listener(event)
|
|
203
|
+
break
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
return subscription
|
|
208
|
+
},
|
|
167
209
|
_internal: {
|
|
168
210
|
editable,
|
|
169
211
|
editorActor,
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type NativeBehaviorEvent,
|
|
21
21
|
type SyntheticBehaviorEvent,
|
|
22
22
|
} from '../behaviors/behavior.types'
|
|
23
|
+
import type {Converter} from '../converters/converter'
|
|
23
24
|
import type {OmitFromUnion, PickFromUnion} from '../type-utils'
|
|
24
25
|
import type {
|
|
25
26
|
EditorSelection,
|
|
@@ -184,13 +185,38 @@ export type InternalEditorEmittedEvent =
|
|
|
184
185
|
'type',
|
|
185
186
|
| 'annotation.add'
|
|
186
187
|
| 'annotation.remove'
|
|
188
|
+
| 'annotation.toggle'
|
|
187
189
|
| 'blur'
|
|
190
|
+
| 'data transfer.set'
|
|
191
|
+
| 'decorator.add'
|
|
192
|
+
| 'decorator.remove'
|
|
188
193
|
| 'decorator.toggle'
|
|
194
|
+
| 'delete.backward'
|
|
195
|
+
| 'delete.block'
|
|
196
|
+
| 'delete.forward'
|
|
197
|
+
| 'delete.text'
|
|
198
|
+
| 'deserialization.failure'
|
|
199
|
+
| 'deserialization.success'
|
|
200
|
+
| 'focus'
|
|
189
201
|
| 'insert.block object'
|
|
190
202
|
| 'insert.inline object'
|
|
203
|
+
| 'insert.span'
|
|
204
|
+
| 'insert.text block'
|
|
205
|
+
| 'list item.add'
|
|
206
|
+
| 'list item.remove'
|
|
191
207
|
| 'list item.toggle'
|
|
192
|
-
| '
|
|
208
|
+
| 'move.block'
|
|
209
|
+
| 'move.block down'
|
|
210
|
+
| 'move.block up'
|
|
211
|
+
| 'select.next block'
|
|
212
|
+
| 'select.previous block'
|
|
213
|
+
| 'serialization.failure'
|
|
214
|
+
| 'serialization.success'
|
|
215
|
+
| 'style.add'
|
|
216
|
+
| 'style.remove'
|
|
193
217
|
| 'style.toggle'
|
|
218
|
+
| 'text block.set'
|
|
219
|
+
| 'text block.unset'
|
|
194
220
|
>
|
|
195
221
|
| {
|
|
196
222
|
type: 'custom.*'
|
|
@@ -204,6 +230,7 @@ export const editorMachine = setup({
|
|
|
204
230
|
types: {
|
|
205
231
|
context: {} as {
|
|
206
232
|
behaviors: Set<Behavior>
|
|
233
|
+
converters: Set<Converter>
|
|
207
234
|
keyGenerator: () => string
|
|
208
235
|
pendingEvents: Array<PatchEvent | MutationEvent>
|
|
209
236
|
schema: EditorSchema
|
|
@@ -216,6 +243,7 @@ export const editorMachine = setup({
|
|
|
216
243
|
emitted: {} as InternalEditorEmittedEvent,
|
|
217
244
|
input: {} as {
|
|
218
245
|
behaviors?: Array<Behavior>
|
|
246
|
+
converters?: Array<Converter>
|
|
219
247
|
keyGenerator: () => string
|
|
220
248
|
maxBlocks?: number
|
|
221
249
|
readOnly?: boolean
|
|
@@ -283,9 +311,11 @@ export const editorMachine = setup({
|
|
|
283
311
|
const defaultAction =
|
|
284
312
|
event.type === 'custom behavior event' ||
|
|
285
313
|
event.behaviorEvent.type === 'copy' ||
|
|
314
|
+
event.behaviorEvent.type === 'deserialize' ||
|
|
286
315
|
event.behaviorEvent.type === 'key.down' ||
|
|
287
316
|
event.behaviorEvent.type === 'key.up' ||
|
|
288
|
-
event.behaviorEvent.type === 'paste'
|
|
317
|
+
event.behaviorEvent.type === 'paste' ||
|
|
318
|
+
event.behaviorEvent.type === 'serialize'
|
|
289
319
|
? undefined
|
|
290
320
|
: ({
|
|
291
321
|
...event.behaviorEvent,
|
|
@@ -343,6 +373,7 @@ export const editorMachine = setup({
|
|
|
343
373
|
}
|
|
344
374
|
|
|
345
375
|
const editorSnapshot = createEditorSnapshot({
|
|
376
|
+
converters: [...context.converters],
|
|
346
377
|
editor: event.editor,
|
|
347
378
|
keyGenerator: context.keyGenerator,
|
|
348
379
|
schema: context.schema,
|
|
@@ -470,6 +501,7 @@ export const editorMachine = setup({
|
|
|
470
501
|
id: 'editor',
|
|
471
502
|
context: ({input}) => ({
|
|
472
503
|
behaviors: new Set(input.behaviors ?? coreBehaviors),
|
|
504
|
+
converters: new Set(input.converters ?? []),
|
|
473
505
|
keyGenerator: input.keyGenerator,
|
|
474
506
|
pendingEvents: [],
|
|
475
507
|
schema: input.schema,
|
|
@@ -529,6 +561,16 @@ export const editorMachine = setup({
|
|
|
529
561
|
},
|
|
530
562
|
'read only': {
|
|
531
563
|
on: {
|
|
564
|
+
'behavior event': {
|
|
565
|
+
actions: 'handle behavior event',
|
|
566
|
+
guard: ({event}) =>
|
|
567
|
+
event.behaviorEvent.type === 'copy' ||
|
|
568
|
+
event.behaviorEvent.type === 'data transfer.set' ||
|
|
569
|
+
event.behaviorEvent.type === 'serialize' ||
|
|
570
|
+
event.behaviorEvent.type === 'serialization.failure' ||
|
|
571
|
+
event.behaviorEvent.type === 'serialization.success' ||
|
|
572
|
+
event.behaviorEvent.type === 'select',
|
|
573
|
+
},
|
|
532
574
|
'update readOnly': {
|
|
533
575
|
guard: ({event}) => !event.readOnly,
|
|
534
576
|
target: '#editor.edit mode.editable',
|
|
@@ -563,6 +605,9 @@ export const editorMachine = setup({
|
|
|
563
605
|
'decorator.*': {
|
|
564
606
|
actions: emit(({event}) => event),
|
|
565
607
|
},
|
|
608
|
+
'delete.*': {
|
|
609
|
+
actions: emit(({event}) => event),
|
|
610
|
+
},
|
|
566
611
|
'focus': {
|
|
567
612
|
actions: emit(({event}) => event),
|
|
568
613
|
},
|
|
@@ -572,12 +617,21 @@ export const editorMachine = setup({
|
|
|
572
617
|
'list item.*': {
|
|
573
618
|
actions: emit(({event}) => event),
|
|
574
619
|
},
|
|
620
|
+
'move.*': {
|
|
621
|
+
actions: emit(({event}) => event),
|
|
622
|
+
},
|
|
575
623
|
'select': {
|
|
576
624
|
actions: emit(({event}) => event),
|
|
577
625
|
},
|
|
626
|
+
'select.*': {
|
|
627
|
+
actions: emit(({event}) => event),
|
|
628
|
+
},
|
|
578
629
|
'style.*': {
|
|
579
630
|
actions: emit(({event}) => event),
|
|
580
631
|
},
|
|
632
|
+
'text block.*': {
|
|
633
|
+
actions: emit(({event}) => event),
|
|
634
|
+
},
|
|
581
635
|
},
|
|
582
636
|
},
|
|
583
637
|
},
|