@portabletext/editor 1.22.0 → 1.24.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 +65 -2
- package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
- package/lib/_chunks-cjs/util.slice-blocks.cjs +26 -12
- package/lib/_chunks-cjs/util.slice-blocks.cjs.map +1 -1
- package/lib/_chunks-es/behavior.core.js +65 -2
- package/lib/_chunks-es/behavior.core.js.map +1 -1
- package/lib/_chunks-es/util.slice-blocks.js +26 -12
- package/lib/_chunks-es/util.slice-blocks.js.map +1 -1
- package/lib/behaviors/index.d.cts +1111 -44
- package/lib/behaviors/index.d.ts +1111 -44
- package/lib/index.cjs +542 -333
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +446 -1
- package/lib/index.d.ts +446 -1
- package/lib/index.js +546 -335
- package/lib/index.js.map +1 -1
- package/lib/selectors/index.d.cts +73 -0
- package/lib/selectors/index.d.ts +73 -0
- package/package.json +23 -18
- 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 +75 -0
- package/src/behaviors/behavior.core.deserialize.ts +46 -0
- package/src/behaviors/behavior.core.serialize.ts +44 -0
- package/src/behaviors/behavior.core.ts +7 -0
- package/src/behaviors/behavior.types.ts +39 -2
- 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 +3 -0
- package/src/editor/editor-machine.ts +25 -1
- 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 +44 -0
- 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 +216 -35
- package/src/utils/util.slice-blocks.ts +37 -10
- 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'
|
|
@@ -74,6 +75,7 @@ export type EditorEvent =
|
|
|
74
75
|
| 'style.toggle'
|
|
75
76
|
| 'patches'
|
|
76
77
|
| 'update behaviors'
|
|
78
|
+
| 'update key generator'
|
|
77
79
|
| 'update readOnly'
|
|
78
80
|
| 'update value'
|
|
79
81
|
>
|
|
@@ -117,6 +119,7 @@ export function useCreateEditor(config: EditorConfig): Editor {
|
|
|
117
119
|
function editorConfigToMachineInput(config: EditorConfig) {
|
|
118
120
|
return {
|
|
119
121
|
behaviors: config.behaviors,
|
|
122
|
+
converters: coreConverters,
|
|
120
123
|
keyGenerator: config.keyGenerator ?? defaultKeyGenerator,
|
|
121
124
|
maxBlocks: config.maxBlocks,
|
|
122
125
|
readOnly: config.readOnly,
|
|
@@ -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,
|
|
@@ -105,6 +106,10 @@ export type InternalEditorEvent =
|
|
|
105
106
|
type: 'update behaviors'
|
|
106
107
|
behaviors: Array<Behavior>
|
|
107
108
|
}
|
|
109
|
+
| {
|
|
110
|
+
type: 'update key generator'
|
|
111
|
+
keyGenerator: () => string
|
|
112
|
+
}
|
|
108
113
|
| {
|
|
109
114
|
type: 'update value'
|
|
110
115
|
value: Array<PortableTextBlock> | undefined
|
|
@@ -200,6 +205,7 @@ export const editorMachine = setup({
|
|
|
200
205
|
types: {
|
|
201
206
|
context: {} as {
|
|
202
207
|
behaviors: Set<Behavior>
|
|
208
|
+
converters: Set<Converter>
|
|
203
209
|
keyGenerator: () => string
|
|
204
210
|
pendingEvents: Array<PatchEvent | MutationEvent>
|
|
205
211
|
schema: EditorSchema
|
|
@@ -212,6 +218,7 @@ export const editorMachine = setup({
|
|
|
212
218
|
emitted: {} as InternalEditorEmittedEvent,
|
|
213
219
|
input: {} as {
|
|
214
220
|
behaviors?: Array<Behavior>
|
|
221
|
+
converters?: Array<Converter>
|
|
215
222
|
keyGenerator: () => string
|
|
216
223
|
maxBlocks?: number
|
|
217
224
|
readOnly?: boolean
|
|
@@ -279,9 +286,11 @@ export const editorMachine = setup({
|
|
|
279
286
|
const defaultAction =
|
|
280
287
|
event.type === 'custom behavior event' ||
|
|
281
288
|
event.behaviorEvent.type === 'copy' ||
|
|
289
|
+
event.behaviorEvent.type === 'deserialize' ||
|
|
282
290
|
event.behaviorEvent.type === 'key.down' ||
|
|
283
291
|
event.behaviorEvent.type === 'key.up' ||
|
|
284
|
-
event.behaviorEvent.type === 'paste'
|
|
292
|
+
event.behaviorEvent.type === 'paste' ||
|
|
293
|
+
event.behaviorEvent.type === 'serialize'
|
|
285
294
|
? undefined
|
|
286
295
|
: ({
|
|
287
296
|
...event.behaviorEvent,
|
|
@@ -339,6 +348,7 @@ export const editorMachine = setup({
|
|
|
339
348
|
}
|
|
340
349
|
|
|
341
350
|
const editorSnapshot = createEditorSnapshot({
|
|
351
|
+
converters: [...context.converters],
|
|
342
352
|
editor: event.editor,
|
|
343
353
|
keyGenerator: context.keyGenerator,
|
|
344
354
|
schema: context.schema,
|
|
@@ -466,6 +476,7 @@ export const editorMachine = setup({
|
|
|
466
476
|
id: 'editor',
|
|
467
477
|
context: ({input}) => ({
|
|
468
478
|
behaviors: new Set(input.behaviors ?? coreBehaviors),
|
|
479
|
+
converters: new Set(input.converters ?? []),
|
|
469
480
|
keyGenerator: input.keyGenerator,
|
|
470
481
|
pendingEvents: [],
|
|
471
482
|
schema: input.schema,
|
|
@@ -493,6 +504,9 @@ export const editorMachine = setup({
|
|
|
493
504
|
'patches': {actions: emit(({event}) => event)},
|
|
494
505
|
'done loading': {actions: emit({type: 'done loading'})},
|
|
495
506
|
'update behaviors': {actions: 'assign behaviors'},
|
|
507
|
+
'update key generator': {
|
|
508
|
+
actions: assign({keyGenerator: ({event}) => event.keyGenerator}),
|
|
509
|
+
},
|
|
496
510
|
'update schema': {actions: 'assign schema'},
|
|
497
511
|
'update value': {actions: assign({value: ({event}) => event.value})},
|
|
498
512
|
'update maxBlocks': {
|
|
@@ -522,6 +536,16 @@ export const editorMachine = setup({
|
|
|
522
536
|
},
|
|
523
537
|
'read only': {
|
|
524
538
|
on: {
|
|
539
|
+
'behavior event': {
|
|
540
|
+
actions: 'handle behavior event',
|
|
541
|
+
guard: ({event}) =>
|
|
542
|
+
event.behaviorEvent.type === 'copy' ||
|
|
543
|
+
event.behaviorEvent.type === 'data transfer.set' ||
|
|
544
|
+
event.behaviorEvent.type === 'serialize' ||
|
|
545
|
+
event.behaviorEvent.type === 'serialization.failure' ||
|
|
546
|
+
event.behaviorEvent.type === 'serialization.success' ||
|
|
547
|
+
event.behaviorEvent.type === 'select',
|
|
548
|
+
},
|
|
525
549
|
'update readOnly': {
|
|
526
550
|
guard: ({event}) => !event.readOnly,
|
|
527
551
|
target: '#editor.edit mode.editable',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {PortableTextBlock} from '@sanity/types'
|
|
2
|
+
import type {Converter} from '../converters/converter'
|
|
2
3
|
import {toPortableTextRange} from '../internal-utils/ranges'
|
|
3
4
|
import {fromSlateValue} from '../internal-utils/values'
|
|
4
5
|
import {KEY_TO_VALUE_ELEMENT} from '../internal-utils/weakMaps'
|
|
@@ -11,6 +12,7 @@ import {getActiveDecorators} from './get-active-decorators'
|
|
|
11
12
|
*/
|
|
12
13
|
export type EditorContext = {
|
|
13
14
|
activeDecorators: Array<string>
|
|
15
|
+
converters: Array<Converter>
|
|
14
16
|
keyGenerator: () => string
|
|
15
17
|
schema: EditorSchema
|
|
16
18
|
selection: EditorSelection
|
|
@@ -25,10 +27,12 @@ export type EditorSnapshot = {
|
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
export function createEditorSnapshot({
|
|
30
|
+
converters,
|
|
28
31
|
editor,
|
|
29
32
|
keyGenerator,
|
|
30
33
|
schema,
|
|
31
34
|
}: {
|
|
35
|
+
converters: Array<Converter>
|
|
32
36
|
editor: PortableTextSlateEditor
|
|
33
37
|
keyGenerator: () => string
|
|
34
38
|
schema: EditorSchema
|
|
@@ -45,6 +49,7 @@ export function createEditorSnapshot({
|
|
|
45
49
|
schema,
|
|
46
50
|
slateEditorInstance: editor,
|
|
47
51
|
}),
|
|
52
|
+
converters,
|
|
48
53
|
keyGenerator,
|
|
49
54
|
schema,
|
|
50
55
|
selection,
|
|
@@ -146,9 +146,11 @@ export function createWithEventListeners(
|
|
|
146
146
|
deleteBackward,
|
|
147
147
|
deleteForward,
|
|
148
148
|
insertBreak,
|
|
149
|
+
insertData,
|
|
149
150
|
insertSoftBreak,
|
|
150
151
|
insertText,
|
|
151
152
|
select,
|
|
153
|
+
setFragmentData,
|
|
152
154
|
} = editor
|
|
153
155
|
|
|
154
156
|
editor.deleteBackward = (unit) => {
|
|
@@ -201,6 +203,23 @@ export function createWithEventListeners(
|
|
|
201
203
|
return
|
|
202
204
|
}
|
|
203
205
|
|
|
206
|
+
editor.insertData = (dataTransfer) => {
|
|
207
|
+
if (isApplyingBehaviorActions(editor)) {
|
|
208
|
+
insertData(dataTransfer)
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
editorActor.send({
|
|
213
|
+
type: 'behavior event',
|
|
214
|
+
behaviorEvent: {
|
|
215
|
+
type: 'deserialize',
|
|
216
|
+
dataTransfer,
|
|
217
|
+
},
|
|
218
|
+
editor,
|
|
219
|
+
})
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
|
|
204
223
|
editor.insertSoftBreak = () => {
|
|
205
224
|
if (isApplyingBehaviorActions(editor)) {
|
|
206
225
|
insertSoftBreak()
|
|
@@ -268,6 +287,31 @@ export function createWithEventListeners(
|
|
|
268
287
|
return
|
|
269
288
|
}
|
|
270
289
|
|
|
290
|
+
editor.setFragmentData = (dataTransfer, originEvent) => {
|
|
291
|
+
if (originEvent === 'drag') {
|
|
292
|
+
setFragmentData(dataTransfer)
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (isApplyingBehaviorActions(editor)) {
|
|
297
|
+
setFragmentData(dataTransfer)
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
dataTransfer.clearData()
|
|
302
|
+
|
|
303
|
+
editorActor.send({
|
|
304
|
+
type: 'behavior event',
|
|
305
|
+
behaviorEvent: {
|
|
306
|
+
type: 'serialize',
|
|
307
|
+
dataTransfer,
|
|
308
|
+
originEvent: originEvent ?? 'unknown',
|
|
309
|
+
},
|
|
310
|
+
editor,
|
|
311
|
+
})
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
|
|
271
315
|
return editor
|
|
272
316
|
}
|
|
273
317
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type {TypedObject} from '@sanity/types'
|
|
2
|
+
|
|
3
|
+
export function isTypedObject(object: unknown): object is TypedObject {
|
|
4
|
+
return isRecord(object) && typeof object._type === 'string'
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
8
|
+
return !!value && (typeof value === 'object' || typeof value === 'function')
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type MIMEType = `${string}/${string}`
|