@portabletext/editor 1.23.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 +23 -9
- 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 +23 -9
- 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 +535 -333
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +158 -1
- package/lib/index.d.ts +158 -1
- package/lib/index.js +539 -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 +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 +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 +2 -0
- package/src/editor/editor-machine.ts +18 -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 +87 -0
- package/src/utils/util.slice-blocks.ts +27 -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'
|
|
@@ -118,6 +119,7 @@ export function useCreateEditor(config: EditorConfig): Editor {
|
|
|
118
119
|
function editorConfigToMachineInput(config: EditorConfig) {
|
|
119
120
|
return {
|
|
120
121
|
behaviors: config.behaviors,
|
|
122
|
+
converters: coreConverters,
|
|
121
123
|
keyGenerator: config.keyGenerator ?? defaultKeyGenerator,
|
|
122
124
|
maxBlocks: config.maxBlocks,
|
|
123
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,
|
|
@@ -204,6 +205,7 @@ export const editorMachine = setup({
|
|
|
204
205
|
types: {
|
|
205
206
|
context: {} as {
|
|
206
207
|
behaviors: Set<Behavior>
|
|
208
|
+
converters: Set<Converter>
|
|
207
209
|
keyGenerator: () => string
|
|
208
210
|
pendingEvents: Array<PatchEvent | MutationEvent>
|
|
209
211
|
schema: EditorSchema
|
|
@@ -216,6 +218,7 @@ export const editorMachine = setup({
|
|
|
216
218
|
emitted: {} as InternalEditorEmittedEvent,
|
|
217
219
|
input: {} as {
|
|
218
220
|
behaviors?: Array<Behavior>
|
|
221
|
+
converters?: Array<Converter>
|
|
219
222
|
keyGenerator: () => string
|
|
220
223
|
maxBlocks?: number
|
|
221
224
|
readOnly?: boolean
|
|
@@ -283,9 +286,11 @@ export const editorMachine = setup({
|
|
|
283
286
|
const defaultAction =
|
|
284
287
|
event.type === 'custom behavior event' ||
|
|
285
288
|
event.behaviorEvent.type === 'copy' ||
|
|
289
|
+
event.behaviorEvent.type === 'deserialize' ||
|
|
286
290
|
event.behaviorEvent.type === 'key.down' ||
|
|
287
291
|
event.behaviorEvent.type === 'key.up' ||
|
|
288
|
-
event.behaviorEvent.type === 'paste'
|
|
292
|
+
event.behaviorEvent.type === 'paste' ||
|
|
293
|
+
event.behaviorEvent.type === 'serialize'
|
|
289
294
|
? undefined
|
|
290
295
|
: ({
|
|
291
296
|
...event.behaviorEvent,
|
|
@@ -343,6 +348,7 @@ export const editorMachine = setup({
|
|
|
343
348
|
}
|
|
344
349
|
|
|
345
350
|
const editorSnapshot = createEditorSnapshot({
|
|
351
|
+
converters: [...context.converters],
|
|
346
352
|
editor: event.editor,
|
|
347
353
|
keyGenerator: context.keyGenerator,
|
|
348
354
|
schema: context.schema,
|
|
@@ -470,6 +476,7 @@ export const editorMachine = setup({
|
|
|
470
476
|
id: 'editor',
|
|
471
477
|
context: ({input}) => ({
|
|
472
478
|
behaviors: new Set(input.behaviors ?? coreBehaviors),
|
|
479
|
+
converters: new Set(input.converters ?? []),
|
|
473
480
|
keyGenerator: input.keyGenerator,
|
|
474
481
|
pendingEvents: [],
|
|
475
482
|
schema: input.schema,
|
|
@@ -529,6 +536,16 @@ export const editorMachine = setup({
|
|
|
529
536
|
},
|
|
530
537
|
'read only': {
|
|
531
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
|
+
},
|
|
532
549
|
'update readOnly': {
|
|
533
550
|
guard: ({event}) => !event.readOnly,
|
|
534
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}`
|