@portabletext/editor 3.2.6 → 3.3.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
"xstate": "^5.24.0",
|
|
75
75
|
"@portabletext/block-tools": "^4.1.7",
|
|
76
76
|
"@portabletext/keyboard-shortcuts": "^2.1.0",
|
|
77
|
+
"@portabletext/markdown": "^1.0.3",
|
|
77
78
|
"@portabletext/patches": "^2.0.0",
|
|
78
79
|
"@portabletext/schema": "^2.0.0"
|
|
79
80
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {isTextBlock} from '@portabletext/schema'
|
|
2
|
+
import type {MIMEType} from '../internal-utils/mime-type'
|
|
2
3
|
import {getActiveAnnotations} from '../selectors/selector.get-active-annotations'
|
|
3
4
|
import {getActiveDecorators} from '../selectors/selector.get-active-decorators'
|
|
4
5
|
import {getFocusTextBlock} from '../selectors/selector.get-focus-text-block'
|
|
@@ -6,60 +7,67 @@ import {getTextBlockText} from '../utils/util.get-text-block-text'
|
|
|
6
7
|
import {raise} from './behavior.types.action'
|
|
7
8
|
import {defineBehavior} from './behavior.types.behavior'
|
|
8
9
|
|
|
10
|
+
const mimeTypePriority: Array<MIMEType> = [
|
|
11
|
+
'application/x-portable-text',
|
|
12
|
+
'application/json',
|
|
13
|
+
'text/markdown',
|
|
14
|
+
'text/html',
|
|
15
|
+
'text/plain',
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Finds the first available data from the dataTransfer based on priority.
|
|
20
|
+
* Optionally starts from a specific mime type in the priority list.
|
|
21
|
+
*/
|
|
22
|
+
function getFirstAvailableData({
|
|
23
|
+
dataTransfer,
|
|
24
|
+
startAfter,
|
|
25
|
+
}: {
|
|
26
|
+
dataTransfer: DataTransfer
|
|
27
|
+
startAfter?: MIMEType
|
|
28
|
+
}): {mimeType: MIMEType; data: string} | undefined {
|
|
29
|
+
const startIndex = startAfter ? mimeTypePriority.indexOf(startAfter) + 1 : 0
|
|
30
|
+
|
|
31
|
+
for (let index = startIndex; index < mimeTypePriority.length; index++) {
|
|
32
|
+
const mimeType = mimeTypePriority.at(index)
|
|
33
|
+
|
|
34
|
+
if (!mimeType) {
|
|
35
|
+
continue
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const data = dataTransfer.getData(mimeType)
|
|
39
|
+
|
|
40
|
+
if (!data) {
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
mimeType,
|
|
46
|
+
data,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return undefined
|
|
51
|
+
}
|
|
52
|
+
|
|
9
53
|
export const abstractDeserializeBehaviors = [
|
|
10
54
|
defineBehavior({
|
|
11
55
|
on: 'deserialize',
|
|
12
56
|
guard: ({event}) => {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
if (portableText) {
|
|
18
|
-
return {
|
|
19
|
-
type: 'deserialize.data',
|
|
20
|
-
mimeType: 'application/x-portable-text',
|
|
21
|
-
data: portableText,
|
|
22
|
-
originEvent: event.originEvent,
|
|
23
|
-
} as const
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const json =
|
|
27
|
-
event.originEvent.originEvent.dataTransfer.getData('application/json')
|
|
28
|
-
|
|
29
|
-
if (json) {
|
|
30
|
-
return {
|
|
31
|
-
type: 'deserialize.data',
|
|
32
|
-
mimeType: 'application/json',
|
|
33
|
-
data: json,
|
|
34
|
-
originEvent: event.originEvent,
|
|
35
|
-
} as const
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const html =
|
|
39
|
-
event.originEvent.originEvent.dataTransfer.getData('text/html')
|
|
57
|
+
const availableData = getFirstAvailableData({
|
|
58
|
+
dataTransfer: event.originEvent.originEvent.dataTransfer,
|
|
59
|
+
})
|
|
40
60
|
|
|
41
|
-
if (
|
|
42
|
-
return
|
|
43
|
-
type: 'deserialize.data',
|
|
44
|
-
mimeType: 'text/html',
|
|
45
|
-
data: html,
|
|
46
|
-
originEvent: event.originEvent,
|
|
47
|
-
} as const
|
|
61
|
+
if (!availableData) {
|
|
62
|
+
return false
|
|
48
63
|
}
|
|
49
64
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
type: 'deserialize.data',
|
|
56
|
-
mimeType: 'text/plain',
|
|
57
|
-
data: text,
|
|
58
|
-
originEvent: event.originEvent,
|
|
59
|
-
} as const
|
|
65
|
+
return {
|
|
66
|
+
type: 'deserialize.data' as const,
|
|
67
|
+
mimeType: availableData.mimeType,
|
|
68
|
+
data: availableData.data,
|
|
69
|
+
originEvent: event.originEvent,
|
|
60
70
|
}
|
|
61
|
-
|
|
62
|
-
return false
|
|
63
71
|
},
|
|
64
72
|
actions: [(_, deserializeEvent) => [raise(deserializeEvent)]],
|
|
65
73
|
}),
|
|
@@ -184,49 +192,25 @@ export const abstractDeserializeBehaviors = [
|
|
|
184
192
|
defineBehavior({
|
|
185
193
|
on: 'deserialization.failure',
|
|
186
194
|
guard: ({event}) => {
|
|
187
|
-
if (event.mimeType === '
|
|
188
|
-
|
|
189
|
-
event.originEvent.originEvent.dataTransfer.getData('application/json')
|
|
190
|
-
|
|
191
|
-
if (json) {
|
|
192
|
-
return {
|
|
193
|
-
type: 'deserialize.data',
|
|
194
|
-
mimeType: 'application/json',
|
|
195
|
-
data: json,
|
|
196
|
-
originEvent: event.originEvent,
|
|
197
|
-
} as const
|
|
198
|
-
}
|
|
195
|
+
if (event.mimeType === '*/*') {
|
|
196
|
+
return false
|
|
199
197
|
}
|
|
200
198
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (html) {
|
|
206
|
-
return {
|
|
207
|
-
type: 'deserialize.data',
|
|
208
|
-
mimeType: 'text/html',
|
|
209
|
-
data: html,
|
|
210
|
-
originEvent: event.originEvent,
|
|
211
|
-
} as const
|
|
212
|
-
}
|
|
213
|
-
}
|
|
199
|
+
const availableData = getFirstAvailableData({
|
|
200
|
+
dataTransfer: event.originEvent.originEvent.dataTransfer,
|
|
201
|
+
startAfter: event.mimeType,
|
|
202
|
+
})
|
|
214
203
|
|
|
215
|
-
if (
|
|
216
|
-
|
|
217
|
-
event.originEvent.originEvent.dataTransfer.getData('text/plain')
|
|
218
|
-
|
|
219
|
-
if (text) {
|
|
220
|
-
return {
|
|
221
|
-
type: 'deserialize.data',
|
|
222
|
-
mimeType: 'text/plain',
|
|
223
|
-
data: text,
|
|
224
|
-
originEvent: event.originEvent,
|
|
225
|
-
} as const
|
|
226
|
-
}
|
|
204
|
+
if (!availableData) {
|
|
205
|
+
return false
|
|
227
206
|
}
|
|
228
207
|
|
|
229
|
-
return
|
|
208
|
+
return {
|
|
209
|
+
type: 'deserialize.data' as const,
|
|
210
|
+
mimeType: availableData.mimeType,
|
|
211
|
+
data: availableData.data,
|
|
212
|
+
originEvent: event.originEvent,
|
|
213
|
+
}
|
|
230
214
|
},
|
|
231
215
|
actions: [(_, deserializeDataEvent) => [raise(deserializeDataEvent)]],
|
|
232
216
|
}),
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
markdownToPortableText,
|
|
3
|
+
portableTextToMarkdown,
|
|
4
|
+
} from '@portabletext/markdown'
|
|
5
|
+
import {getSelectedValue} from '../selectors/selector.get-selected-value'
|
|
6
|
+
import {parseBlock} from '../utils/parse-blocks'
|
|
7
|
+
import {defineConverter} from './converter.types'
|
|
8
|
+
|
|
9
|
+
export const converterTextMarkdown = defineConverter({
|
|
10
|
+
mimeType: 'text/markdown',
|
|
11
|
+
serialize: ({snapshot, event}) => {
|
|
12
|
+
const selection = snapshot.context.selection
|
|
13
|
+
|
|
14
|
+
if (!selection) {
|
|
15
|
+
return {
|
|
16
|
+
type: 'serialization.failure',
|
|
17
|
+
mimeType: 'text/markdown',
|
|
18
|
+
reason: 'No selection',
|
|
19
|
+
originEvent: event.originEvent,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const blocks = getSelectedValue(snapshot)
|
|
24
|
+
|
|
25
|
+
const markdown = portableTextToMarkdown(blocks)
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
type: 'serialization.success',
|
|
29
|
+
data: markdown,
|
|
30
|
+
mimeType: 'text/markdown',
|
|
31
|
+
originEvent: event.originEvent,
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
deserialize: ({snapshot, event}) => {
|
|
35
|
+
const blocks = markdownToPortableText(event.data, {
|
|
36
|
+
keyGenerator: snapshot.context.keyGenerator,
|
|
37
|
+
schema: snapshot.context.schema,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const parsedBlocks = blocks.flatMap((block) => {
|
|
41
|
+
const parsedBlock = parseBlock({
|
|
42
|
+
context: snapshot.context,
|
|
43
|
+
block,
|
|
44
|
+
options: {
|
|
45
|
+
normalize: false,
|
|
46
|
+
removeUnusedMarkDefs: true,
|
|
47
|
+
validateFields: false,
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
return parsedBlock ? [parsedBlock] : []
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
if (parsedBlocks.length === 0) {
|
|
54
|
+
return {
|
|
55
|
+
type: 'deserialization.failure',
|
|
56
|
+
mimeType: 'text/markdown',
|
|
57
|
+
reason: 'No blocks deserialized',
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
type: 'deserialization.success',
|
|
63
|
+
data: parsedBlocks,
|
|
64
|
+
mimeType: 'text/markdown',
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
})
|
|
@@ -2,6 +2,7 @@ import type {PortableTextMemberSchemaTypes} from '../types/editor'
|
|
|
2
2
|
import {converterJson} from './converter.json'
|
|
3
3
|
import {converterPortableText} from './converter.portable-text'
|
|
4
4
|
import {createConverterTextHtml} from './converter.text-html'
|
|
5
|
+
import {converterTextMarkdown} from './converter.text-markdown'
|
|
5
6
|
import {createConverterTextPlain} from './converter.text-plain'
|
|
6
7
|
|
|
7
8
|
export function createCoreConverters(
|
|
@@ -10,6 +11,7 @@ export function createCoreConverters(
|
|
|
10
11
|
return [
|
|
11
12
|
converterJson,
|
|
12
13
|
converterPortableText,
|
|
14
|
+
converterTextMarkdown,
|
|
13
15
|
createConverterTextHtml(legacySchema),
|
|
14
16
|
createConverterTextPlain(legacySchema),
|
|
15
17
|
]
|