@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
|
@@ -15,11 +15,61 @@ import {
|
|
|
15
15
|
PortableTextTextBlock,
|
|
16
16
|
} from '@sanity/types'
|
|
17
17
|
|
|
18
|
+
declare type Converter<TMIMEType extends MIMEType = MIMEType> = {
|
|
19
|
+
mimeType: TMIMEType
|
|
20
|
+
serialize: Serializer<TMIMEType>
|
|
21
|
+
deserialize: Deserializer<TMIMEType>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
declare type ConverterEvent<TMIMEType extends MIMEType = MIMEType> =
|
|
25
|
+
| {
|
|
26
|
+
type: 'serialize'
|
|
27
|
+
originEvent: 'copy' | 'cut' | 'unknown'
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
type: 'serialization.failure'
|
|
31
|
+
mimeType: TMIMEType
|
|
32
|
+
reason: string
|
|
33
|
+
}
|
|
34
|
+
| {
|
|
35
|
+
type: 'serialization.success'
|
|
36
|
+
data: string
|
|
37
|
+
mimeType: TMIMEType
|
|
38
|
+
originEvent: 'copy' | 'cut' | 'unknown'
|
|
39
|
+
}
|
|
40
|
+
| {
|
|
41
|
+
type: 'deserialize'
|
|
42
|
+
data: string
|
|
43
|
+
}
|
|
44
|
+
| {
|
|
45
|
+
type: 'deserialization.failure'
|
|
46
|
+
mimeType: TMIMEType
|
|
47
|
+
reason: string
|
|
48
|
+
}
|
|
49
|
+
| {
|
|
50
|
+
type: 'deserialization.success'
|
|
51
|
+
data: Array<PortableTextBlock>
|
|
52
|
+
mimeType: TMIMEType
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
declare 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
|
+
>
|
|
66
|
+
|
|
18
67
|
/**
|
|
19
68
|
* @public
|
|
20
69
|
*/
|
|
21
70
|
export declare type EditorContext = {
|
|
22
71
|
activeDecorators: Array<string>
|
|
72
|
+
converters: Array<Converter>
|
|
23
73
|
keyGenerator: () => string
|
|
24
74
|
schema: EditorSchema
|
|
25
75
|
selection: EditorSelection
|
|
@@ -289,6 +339,17 @@ export declare const isSelectionCollapsed: EditorSelector<boolean>
|
|
|
289
339
|
*/
|
|
290
340
|
export declare const isSelectionExpanded: EditorSelector<boolean>
|
|
291
341
|
|
|
342
|
+
declare type MIMEType = `${string}/${string}`
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* @alpha
|
|
346
|
+
*/
|
|
347
|
+
declare type PickFromUnion<
|
|
348
|
+
TUnion,
|
|
349
|
+
TTagKey extends keyof TUnion,
|
|
350
|
+
TPickedTags extends TUnion[TTagKey],
|
|
351
|
+
> = TUnion extends Record<TTagKey, TPickedTags> ? TUnion : never
|
|
352
|
+
|
|
292
353
|
/** @internal */
|
|
293
354
|
export declare type PortableTextMemberSchemaTypes = {
|
|
294
355
|
annotations: (ObjectSchemaType & {
|
|
@@ -304,4 +365,16 @@ export declare type PortableTextMemberSchemaTypes = {
|
|
|
304
365
|
lists: BlockListDefinition[]
|
|
305
366
|
}
|
|
306
367
|
|
|
368
|
+
declare type Serializer<TMIMEType extends MIMEType> = ({
|
|
369
|
+
context,
|
|
370
|
+
event,
|
|
371
|
+
}: {
|
|
372
|
+
context: EditorContext
|
|
373
|
+
event: PickFromUnion<ConverterEvent<TMIMEType>, 'type', 'serialize'>
|
|
374
|
+
}) => PickFromUnion<
|
|
375
|
+
ConverterEvent<TMIMEType>,
|
|
376
|
+
'type',
|
|
377
|
+
'serialization.success' | 'serialization.failure'
|
|
378
|
+
>
|
|
379
|
+
|
|
307
380
|
export {}
|
package/lib/selectors/index.d.ts
CHANGED
|
@@ -15,11 +15,61 @@ import {
|
|
|
15
15
|
PortableTextTextBlock,
|
|
16
16
|
} from '@sanity/types'
|
|
17
17
|
|
|
18
|
+
declare type Converter<TMIMEType extends MIMEType = MIMEType> = {
|
|
19
|
+
mimeType: TMIMEType
|
|
20
|
+
serialize: Serializer<TMIMEType>
|
|
21
|
+
deserialize: Deserializer<TMIMEType>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
declare type ConverterEvent<TMIMEType extends MIMEType = MIMEType> =
|
|
25
|
+
| {
|
|
26
|
+
type: 'serialize'
|
|
27
|
+
originEvent: 'copy' | 'cut' | 'unknown'
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
type: 'serialization.failure'
|
|
31
|
+
mimeType: TMIMEType
|
|
32
|
+
reason: string
|
|
33
|
+
}
|
|
34
|
+
| {
|
|
35
|
+
type: 'serialization.success'
|
|
36
|
+
data: string
|
|
37
|
+
mimeType: TMIMEType
|
|
38
|
+
originEvent: 'copy' | 'cut' | 'unknown'
|
|
39
|
+
}
|
|
40
|
+
| {
|
|
41
|
+
type: 'deserialize'
|
|
42
|
+
data: string
|
|
43
|
+
}
|
|
44
|
+
| {
|
|
45
|
+
type: 'deserialization.failure'
|
|
46
|
+
mimeType: TMIMEType
|
|
47
|
+
reason: string
|
|
48
|
+
}
|
|
49
|
+
| {
|
|
50
|
+
type: 'deserialization.success'
|
|
51
|
+
data: Array<PortableTextBlock>
|
|
52
|
+
mimeType: TMIMEType
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
declare 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
|
+
>
|
|
66
|
+
|
|
18
67
|
/**
|
|
19
68
|
* @public
|
|
20
69
|
*/
|
|
21
70
|
export declare type EditorContext = {
|
|
22
71
|
activeDecorators: Array<string>
|
|
72
|
+
converters: Array<Converter>
|
|
23
73
|
keyGenerator: () => string
|
|
24
74
|
schema: EditorSchema
|
|
25
75
|
selection: EditorSelection
|
|
@@ -289,6 +339,17 @@ export declare const isSelectionCollapsed: EditorSelector<boolean>
|
|
|
289
339
|
*/
|
|
290
340
|
export declare const isSelectionExpanded: EditorSelector<boolean>
|
|
291
341
|
|
|
342
|
+
declare type MIMEType = `${string}/${string}`
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* @alpha
|
|
346
|
+
*/
|
|
347
|
+
declare type PickFromUnion<
|
|
348
|
+
TUnion,
|
|
349
|
+
TTagKey extends keyof TUnion,
|
|
350
|
+
TPickedTags extends TUnion[TTagKey],
|
|
351
|
+
> = TUnion extends Record<TTagKey, TPickedTags> ? TUnion : never
|
|
352
|
+
|
|
292
353
|
/** @internal */
|
|
293
354
|
export declare type PortableTextMemberSchemaTypes = {
|
|
294
355
|
annotations: (ObjectSchemaType & {
|
|
@@ -304,4 +365,16 @@ export declare type PortableTextMemberSchemaTypes = {
|
|
|
304
365
|
lists: BlockListDefinition[]
|
|
305
366
|
}
|
|
306
367
|
|
|
368
|
+
declare type Serializer<TMIMEType extends MIMEType> = ({
|
|
369
|
+
context,
|
|
370
|
+
event,
|
|
371
|
+
}: {
|
|
372
|
+
context: EditorContext
|
|
373
|
+
event: PickFromUnion<ConverterEvent<TMIMEType>, 'type', 'serialize'>
|
|
374
|
+
}) => PickFromUnion<
|
|
375
|
+
ConverterEvent<TMIMEType>,
|
|
376
|
+
'type',
|
|
377
|
+
'serialization.success' | 'serialization.failure'
|
|
378
|
+
>
|
|
379
|
+
|
|
307
380
|
export {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.0",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -61,26 +61,27 @@
|
|
|
61
61
|
"src"
|
|
62
62
|
],
|
|
63
63
|
"dependencies": {
|
|
64
|
+
"@portabletext/to-html": "^2.0.13",
|
|
64
65
|
"@xstate/react": "^5.0.2",
|
|
65
66
|
"debug": "^4.4.0",
|
|
66
67
|
"get-random-values-esm": "^1.0.2",
|
|
67
68
|
"lodash": "^4.17.21",
|
|
68
69
|
"lodash.startcase": "^4.4.0",
|
|
69
|
-
"react-compiler-runtime": "19.0.0-beta-
|
|
70
|
+
"react-compiler-runtime": "19.0.0-beta-decd7b8-20250118",
|
|
70
71
|
"slate": "0.112.0",
|
|
71
72
|
"slate-dom": "^0.111.0",
|
|
72
|
-
"slate-react": "0.112.
|
|
73
|
+
"slate-react": "0.112.1",
|
|
73
74
|
"use-effect-event": "^1.0.2",
|
|
74
75
|
"xstate": "^5.19.2",
|
|
75
|
-
"@portabletext/block-tools": "1.1.
|
|
76
|
-
"@portabletext/patches": "1.1.
|
|
76
|
+
"@portabletext/block-tools": "1.1.1",
|
|
77
|
+
"@portabletext/patches": "1.1.2"
|
|
77
78
|
},
|
|
78
79
|
"devDependencies": {
|
|
79
80
|
"@portabletext/toolkit": "^2.0.16",
|
|
80
|
-
"@sanity/diff-match-patch": "^3.
|
|
81
|
-
"@sanity/pkg-utils": "^7.0.
|
|
82
|
-
"@sanity/schema": "^3.
|
|
83
|
-
"@sanity/types": "^3.
|
|
81
|
+
"@sanity/diff-match-patch": "^3.2.0",
|
|
82
|
+
"@sanity/pkg-utils": "^7.0.2",
|
|
83
|
+
"@sanity/schema": "^3.71.0",
|
|
84
|
+
"@sanity/types": "^3.71.0",
|
|
84
85
|
"@testing-library/jest-dom": "^6.6.3",
|
|
85
86
|
"@testing-library/react": "^16.2.0",
|
|
86
87
|
"@types/debug": "^4.1.12",
|
|
@@ -91,11 +92,11 @@
|
|
|
91
92
|
"@typescript-eslint/eslint-plugin": "^8.18.1",
|
|
92
93
|
"@typescript-eslint/parser": "^8.18.1",
|
|
93
94
|
"@vitejs/plugin-react": "^4.3.4",
|
|
94
|
-
"@vitest/browser": "^
|
|
95
|
-
"@vitest/coverage-istanbul": "^
|
|
96
|
-
"babel-plugin-react-compiler": "19.0.0-beta-
|
|
95
|
+
"@vitest/browser": "^3.0.2",
|
|
96
|
+
"@vitest/coverage-istanbul": "^3.0.2",
|
|
97
|
+
"babel-plugin-react-compiler": "19.0.0-beta-decd7b8-20250118",
|
|
97
98
|
"eslint": "8.57.1",
|
|
98
|
-
"eslint-plugin-react-compiler": "19.0.0-beta-
|
|
99
|
+
"eslint-plugin-react-compiler": "19.0.0-beta-decd7b8-20250118",
|
|
99
100
|
"eslint-plugin-react-hooks": "^5.1.0",
|
|
100
101
|
"jsdom": "^26.0.0",
|
|
101
102
|
"react": "^19.0.0",
|
|
@@ -103,13 +104,13 @@
|
|
|
103
104
|
"rxjs": "^7.8.1",
|
|
104
105
|
"typescript": "5.7.3",
|
|
105
106
|
"vite": "^6.0.4",
|
|
106
|
-
"vitest": "^
|
|
107
|
+
"vitest": "^3.0.2",
|
|
107
108
|
"vitest-browser-react": "^0.0.4",
|
|
108
109
|
"racejar": "1.1.1"
|
|
109
110
|
},
|
|
110
111
|
"peerDependencies": {
|
|
111
|
-
"@sanity/schema": "^3.
|
|
112
|
-
"@sanity/types": "^3.
|
|
112
|
+
"@sanity/schema": "^3.71.0",
|
|
113
|
+
"@sanity/types": "^3.71.0",
|
|
113
114
|
"react": "^16.9 || ^17 || ^18 || ^19",
|
|
114
115
|
"rxjs": "^7.8.1"
|
|
115
116
|
},
|
|
@@ -129,8 +130,12 @@
|
|
|
129
130
|
"lint:fix": "biome lint --write .",
|
|
130
131
|
"test": "vitest --run",
|
|
131
132
|
"test:watch": "vitest",
|
|
132
|
-
"test:chromium": "vitest --run --project chromium",
|
|
133
|
-
"test:chromium:watch": "vitest --project chromium",
|
|
133
|
+
"test:chromium": "vitest --run --project \"browser (chromium)\"",
|
|
134
|
+
"test:chromium:watch": "vitest --project \"browser (chromium)\"",
|
|
135
|
+
"test:firefox": "vitest --run --project \"browser (firefox)\"",
|
|
136
|
+
"test:firefox:watch": "vitest --project \"browser (firefox)\"",
|
|
137
|
+
"test:webkit": "vitest --run --project \"browser (webkit)\"",
|
|
138
|
+
"test:webkit:watch": "vitest --project \"browser (webkit)\"",
|
|
134
139
|
"test:unit": "vitest --run --project unit",
|
|
135
140
|
"test:unit:watch": "vitest --project unit"
|
|
136
141
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {isEqual, uniq} from 'lodash'
|
|
2
|
+
import {Editor, Transforms} from 'slate'
|
|
3
|
+
import {isEqualToEmptyEditor, toSlateValue} from '../internal-utils/values'
|
|
4
|
+
import type {BehaviorActionImplementation} from './behavior.actions'
|
|
5
|
+
|
|
6
|
+
export const insertBlocksActionImplementation: BehaviorActionImplementation<
|
|
7
|
+
'insert.blocks'
|
|
8
|
+
> = ({context, action}) => {
|
|
9
|
+
const fragment = toSlateValue(action.blocks, {schemaTypes: context.schema})
|
|
10
|
+
|
|
11
|
+
if (!action.editor.selection) {
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
// Ensure that markDefs for any annotations inside this fragment are copied over to the focused text block.
|
|
15
|
+
const [focusBlock, focusPath] = Editor.node(
|
|
16
|
+
action.editor,
|
|
17
|
+
action.editor.selection,
|
|
18
|
+
{
|
|
19
|
+
depth: 1,
|
|
20
|
+
},
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
if (
|
|
24
|
+
action.editor.isTextBlock(focusBlock) &&
|
|
25
|
+
action.editor.isTextBlock(fragment[0])
|
|
26
|
+
) {
|
|
27
|
+
const {markDefs} = focusBlock
|
|
28
|
+
if (!isEqual(markDefs, fragment[0].markDefs)) {
|
|
29
|
+
Transforms.setNodes(
|
|
30
|
+
action.editor,
|
|
31
|
+
{
|
|
32
|
+
markDefs: uniq([
|
|
33
|
+
...(fragment[0].markDefs || []),
|
|
34
|
+
...(markDefs || []),
|
|
35
|
+
]),
|
|
36
|
+
},
|
|
37
|
+
{at: focusPath, mode: 'lowest', voids: false},
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const isPasteToEmptyEditor = isEqualToEmptyEditor(
|
|
43
|
+
action.editor.children,
|
|
44
|
+
context.schema,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if (isPasteToEmptyEditor) {
|
|
48
|
+
// Special case for pasting directly into an empty editor (a placeholder block).
|
|
49
|
+
// When pasting content starting with multiple empty blocks,
|
|
50
|
+
// `editor.insertFragment` can potentially duplicate the keys of
|
|
51
|
+
// the placeholder block because of operations that happen
|
|
52
|
+
// inside `editor.insertFragment` (involves an `insert_node` operation).
|
|
53
|
+
// However by splitting the placeholder block first in this situation we are good.
|
|
54
|
+
Transforms.splitNodes(action.editor, {at: [0, 0]})
|
|
55
|
+
action.editor.insertFragment(fragment)
|
|
56
|
+
Transforms.removeNodes(action.editor, {at: [0]})
|
|
57
|
+
} else {
|
|
58
|
+
// All other inserts
|
|
59
|
+
action.editor.insertFragment(fragment)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -28,7 +28,9 @@ import {KEY_TO_VALUE_ELEMENT} from '../internal-utils/weakMaps'
|
|
|
28
28
|
import type {PickFromUnion} from '../type-utils'
|
|
29
29
|
import {blockOffsetToSpanSelectionPoint} from '../utils/util.block-offset'
|
|
30
30
|
import {insertBlock} from './behavior.action-utils.insert-block'
|
|
31
|
+
import {dataTransferSetActionImplementation} from './behavior.action.data-transfer-set'
|
|
31
32
|
import {insertBlockObjectActionImplementation} from './behavior.action.insert-block-object'
|
|
33
|
+
import {insertBlocksActionImplementation} from './behavior.action.insert-blocks'
|
|
32
34
|
import {
|
|
33
35
|
insertBreakActionImplementation,
|
|
34
36
|
insertSoftBreakActionImplementation,
|
|
@@ -75,6 +77,7 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
|
|
|
75
77
|
'blur': ({action}) => {
|
|
76
78
|
ReactEditor.blur(action.editor)
|
|
77
79
|
},
|
|
80
|
+
'data transfer.set': dataTransferSetActionImplementation,
|
|
78
81
|
'decorator.add': addDecoratorActionImplementation,
|
|
79
82
|
'decorator.remove': removeDecoratorActionImplementation,
|
|
80
83
|
'decorator.toggle': toggleDecoratorActionImplementation,
|
|
@@ -143,6 +146,22 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
|
|
|
143
146
|
at: range,
|
|
144
147
|
})
|
|
145
148
|
},
|
|
149
|
+
'deserialization.failure': ({action}) => {
|
|
150
|
+
console.error(
|
|
151
|
+
`Deserialization of ${action.mimeType} failed with reason ${action.reason}`,
|
|
152
|
+
)
|
|
153
|
+
},
|
|
154
|
+
'deserialization.success': ({context, action}) => {
|
|
155
|
+
insertBlocksActionImplementation({
|
|
156
|
+
context,
|
|
157
|
+
action: {
|
|
158
|
+
type: 'insert.blocks',
|
|
159
|
+
blocks: action.data,
|
|
160
|
+
editor: action.editor,
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
},
|
|
164
|
+
'insert.blocks': insertBlocksActionImplementation,
|
|
146
165
|
'insert.block object': insertBlockObjectActionImplementation,
|
|
147
166
|
'insert.break': insertBreakActionImplementation,
|
|
148
167
|
'insert.inline object': insertInlineObjectActionImplementation,
|
|
@@ -260,6 +279,20 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
|
|
|
260
279
|
|
|
261
280
|
Transforms.select(action.editor, nextBlockPath)
|
|
262
281
|
},
|
|
282
|
+
'serialization.failure': ({action}) => {
|
|
283
|
+
console.error(
|
|
284
|
+
`Serialization of ${action.mimeType} failed with reason ${action.reason}`,
|
|
285
|
+
)
|
|
286
|
+
},
|
|
287
|
+
'serialization.success': ({context, action}) => {
|
|
288
|
+
dataTransferSetActionImplementation({
|
|
289
|
+
context,
|
|
290
|
+
action: {
|
|
291
|
+
...action,
|
|
292
|
+
type: 'data transfer.set',
|
|
293
|
+
},
|
|
294
|
+
})
|
|
295
|
+
},
|
|
263
296
|
'style.toggle': toggleStyleActionImplementation,
|
|
264
297
|
'style.add': addStyleActionImplementation,
|
|
265
298
|
'style.remove': removeStyleActionImplementation,
|
|
@@ -457,6 +490,13 @@ function performDefaultAction({
|
|
|
457
490
|
})
|
|
458
491
|
break
|
|
459
492
|
}
|
|
493
|
+
case 'data transfer.set': {
|
|
494
|
+
behaviorActionImplementations['data transfer.set']({
|
|
495
|
+
context,
|
|
496
|
+
action,
|
|
497
|
+
})
|
|
498
|
+
break
|
|
499
|
+
}
|
|
460
500
|
case 'decorator.toggle': {
|
|
461
501
|
behaviorActionImplementations['decorator.toggle']({
|
|
462
502
|
context,
|
|
@@ -478,6 +518,20 @@ function performDefaultAction({
|
|
|
478
518
|
})
|
|
479
519
|
break
|
|
480
520
|
}
|
|
521
|
+
case 'deserialization.failure': {
|
|
522
|
+
behaviorActionImplementations['deserialization.failure']({
|
|
523
|
+
context,
|
|
524
|
+
action,
|
|
525
|
+
})
|
|
526
|
+
break
|
|
527
|
+
}
|
|
528
|
+
case 'deserialization.success': {
|
|
529
|
+
behaviorActionImplementations['deserialization.success']({
|
|
530
|
+
context,
|
|
531
|
+
action,
|
|
532
|
+
})
|
|
533
|
+
break
|
|
534
|
+
}
|
|
481
535
|
case 'focus': {
|
|
482
536
|
behaviorActionImplementations.focus({
|
|
483
537
|
context,
|
|
@@ -485,6 +539,13 @@ function performDefaultAction({
|
|
|
485
539
|
})
|
|
486
540
|
break
|
|
487
541
|
}
|
|
542
|
+
case 'insert.blocks': {
|
|
543
|
+
behaviorActionImplementations['insert.blocks']({
|
|
544
|
+
context,
|
|
545
|
+
action,
|
|
546
|
+
})
|
|
547
|
+
break
|
|
548
|
+
}
|
|
488
549
|
case 'insert.block object': {
|
|
489
550
|
behaviorActionImplementations['insert.block object']({
|
|
490
551
|
context,
|
|
@@ -534,6 +595,20 @@ function performDefaultAction({
|
|
|
534
595
|
})
|
|
535
596
|
break
|
|
536
597
|
}
|
|
598
|
+
case 'serialization.failure': {
|
|
599
|
+
behaviorActionImplementations['serialization.failure']({
|
|
600
|
+
context,
|
|
601
|
+
action,
|
|
602
|
+
})
|
|
603
|
+
break
|
|
604
|
+
}
|
|
605
|
+
case 'serialization.success': {
|
|
606
|
+
behaviorActionImplementations['serialization.success']({
|
|
607
|
+
context,
|
|
608
|
+
action,
|
|
609
|
+
})
|
|
610
|
+
break
|
|
611
|
+
}
|
|
537
612
|
default: {
|
|
538
613
|
behaviorActionImplementations['style.toggle']({
|
|
539
614
|
context,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {defineBehavior, raise} from './behavior.types'
|
|
2
|
+
|
|
3
|
+
export const coreDeserializeBehavior = defineBehavior({
|
|
4
|
+
on: 'deserialize',
|
|
5
|
+
guard: ({context, event}) => {
|
|
6
|
+
const deserializeEvents = context.converters.flatMap((converter) => {
|
|
7
|
+
const data = event.dataTransfer.getData(converter.mimeType)
|
|
8
|
+
|
|
9
|
+
if (!data) {
|
|
10
|
+
return []
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return [
|
|
14
|
+
converter.deserialize({context, event: {type: 'deserialize', data}}),
|
|
15
|
+
]
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const firstSuccess = deserializeEvents.find(
|
|
19
|
+
(deserializeEvent) => deserializeEvent.type === 'deserialization.success',
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
if (!firstSuccess) {
|
|
23
|
+
return {
|
|
24
|
+
type: 'deserialization.failure',
|
|
25
|
+
mimeType: '*/*',
|
|
26
|
+
reason: deserializeEvents
|
|
27
|
+
.map((deserializeEvent) =>
|
|
28
|
+
deserializeEvent.type === 'deserialization.failure'
|
|
29
|
+
? deserializeEvent.reason
|
|
30
|
+
: '',
|
|
31
|
+
)
|
|
32
|
+
.join(', '),
|
|
33
|
+
} as const
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return firstSuccess
|
|
37
|
+
},
|
|
38
|
+
actions: [
|
|
39
|
+
({event}, deserializeEvent) => [
|
|
40
|
+
raise({
|
|
41
|
+
...deserializeEvent,
|
|
42
|
+
dataTransfer: event.dataTransfer,
|
|
43
|
+
}),
|
|
44
|
+
],
|
|
45
|
+
],
|
|
46
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {defineBehavior, raise} from './behavior.types'
|
|
2
|
+
|
|
3
|
+
export const coreSerializeBehaviors = {
|
|
4
|
+
'serialize': defineBehavior({
|
|
5
|
+
on: 'serialize',
|
|
6
|
+
guard: ({context, event}) => {
|
|
7
|
+
if (context.converters.length === 0) {
|
|
8
|
+
return false
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const serializeEvents = context.converters.map((converter) =>
|
|
12
|
+
converter.serialize({context, event}),
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
if (serializeEvents.length === 0) {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return serializeEvents
|
|
20
|
+
},
|
|
21
|
+
actions: [
|
|
22
|
+
({event}, serializeEvents) =>
|
|
23
|
+
serializeEvents.map((serializeEvent) =>
|
|
24
|
+
raise({
|
|
25
|
+
...serializeEvent,
|
|
26
|
+
dataTransfer: event.dataTransfer,
|
|
27
|
+
}),
|
|
28
|
+
),
|
|
29
|
+
],
|
|
30
|
+
}),
|
|
31
|
+
'serialization.success': defineBehavior({
|
|
32
|
+
on: 'serialization.success',
|
|
33
|
+
actions: [
|
|
34
|
+
({event}) => [
|
|
35
|
+
raise({
|
|
36
|
+
type: 'data transfer.set',
|
|
37
|
+
data: event.data,
|
|
38
|
+
dataTransfer: event.dataTransfer,
|
|
39
|
+
mimeType: event.mimeType,
|
|
40
|
+
}),
|
|
41
|
+
],
|
|
42
|
+
],
|
|
43
|
+
}),
|
|
44
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {coreBlockObjectBehaviors} from './behavior.core.block-objects'
|
|
2
2
|
import {coreDecoratorBehaviors} from './behavior.core.decorators'
|
|
3
|
+
import {coreDeserializeBehavior} from './behavior.core.deserialize'
|
|
3
4
|
import {coreListBehaviors} from './behavior.core.lists'
|
|
5
|
+
import {coreSerializeBehaviors} from './behavior.core.serialize'
|
|
4
6
|
import {defineBehavior} from './behavior.types'
|
|
5
7
|
|
|
6
8
|
const softReturn = defineBehavior({
|
|
@@ -17,6 +19,7 @@ export const coreBehaviors = [
|
|
|
17
19
|
coreDecoratorBehaviors.emShortcut,
|
|
18
20
|
coreDecoratorBehaviors.underlineShortcut,
|
|
19
21
|
coreDecoratorBehaviors.codeShortcut,
|
|
22
|
+
coreDeserializeBehavior,
|
|
20
23
|
coreBlockObjectBehaviors.arrowDownOnLonelyBlockObject,
|
|
21
24
|
coreBlockObjectBehaviors.arrowUpOnLonelyBlockObject,
|
|
22
25
|
coreBlockObjectBehaviors.breakingBlockObject,
|
|
@@ -27,6 +30,8 @@ export const coreBehaviors = [
|
|
|
27
30
|
coreListBehaviors.clearListOnEnter,
|
|
28
31
|
coreListBehaviors.indentListOnTab,
|
|
29
32
|
coreListBehaviors.unindentListOnShiftTab,
|
|
33
|
+
coreSerializeBehaviors.serialize,
|
|
34
|
+
coreSerializeBehaviors['serialization.success'],
|
|
30
35
|
]
|
|
31
36
|
|
|
32
37
|
/**
|
|
@@ -35,6 +40,8 @@ export const coreBehaviors = [
|
|
|
35
40
|
export const coreBehavior = {
|
|
36
41
|
softReturn,
|
|
37
42
|
decorators: coreDecoratorBehaviors,
|
|
43
|
+
deserialize: coreDeserializeBehavior,
|
|
38
44
|
blockObjects: coreBlockObjectBehaviors,
|
|
39
45
|
lists: coreListBehaviors,
|
|
46
|
+
...coreSerializeBehaviors,
|
|
40
47
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
KeyedSegment,
|
|
3
|
+
PortableTextBlock,
|
|
4
|
+
PortableTextTextBlock,
|
|
5
|
+
} from '@sanity/types'
|
|
2
6
|
import type {TextUnit} from 'slate'
|
|
3
7
|
import type {TextInsertTextOptions} from 'slate/dist/interfaces/transforms/text'
|
|
8
|
+
import type {ConverterEvent} from '../converters/converter'
|
|
4
9
|
import type {EditorContext} from '../editor/editor-snapshot'
|
|
10
|
+
import type {MIMEType} from '../internal-utils/mime-type'
|
|
5
11
|
import type {OmitFromUnion, PickFromUnion} from '../type-utils'
|
|
6
12
|
import type {EditorSelection, PortableTextSlateEditor} from '../types/editor'
|
|
7
13
|
|
|
@@ -25,6 +31,12 @@ export type SyntheticBehaviorEvent =
|
|
|
25
31
|
| {
|
|
26
32
|
type: 'blur'
|
|
27
33
|
}
|
|
34
|
+
| {
|
|
35
|
+
type: 'data transfer.set'
|
|
36
|
+
data: string
|
|
37
|
+
dataTransfer: DataTransfer
|
|
38
|
+
mimeType: MIMEType
|
|
39
|
+
}
|
|
28
40
|
| {
|
|
29
41
|
type: 'decorator.toggle'
|
|
30
42
|
decorator: string
|
|
@@ -40,6 +52,10 @@ export type SyntheticBehaviorEvent =
|
|
|
40
52
|
| {
|
|
41
53
|
type: 'focus'
|
|
42
54
|
}
|
|
55
|
+
| {
|
|
56
|
+
type: 'insert.blocks'
|
|
57
|
+
blocks: Array<PortableTextBlock>
|
|
58
|
+
}
|
|
43
59
|
| {
|
|
44
60
|
type: 'insert.block object'
|
|
45
61
|
placement: 'auto' | 'after' | 'before'
|
|
@@ -78,6 +94,14 @@ export type SyntheticBehaviorEvent =
|
|
|
78
94
|
type: 'style.toggle'
|
|
79
95
|
style: string
|
|
80
96
|
}
|
|
97
|
+
| (PickFromUnion<
|
|
98
|
+
ConverterEvent,
|
|
99
|
+
'type',
|
|
100
|
+
| 'deserialization.failure'
|
|
101
|
+
| 'deserialization.success'
|
|
102
|
+
| 'serialization.failure'
|
|
103
|
+
| 'serialization.success'
|
|
104
|
+
> & {dataTransfer: DataTransfer})
|
|
81
105
|
|
|
82
106
|
/**
|
|
83
107
|
* @beta
|
|
@@ -87,6 +111,10 @@ export type NativeBehaviorEvent =
|
|
|
87
111
|
type: 'copy'
|
|
88
112
|
data: DataTransfer
|
|
89
113
|
}
|
|
114
|
+
| {
|
|
115
|
+
type: 'deserialize'
|
|
116
|
+
dataTransfer: DataTransfer
|
|
117
|
+
}
|
|
90
118
|
| {
|
|
91
119
|
type: 'key.down'
|
|
92
120
|
keyboardEvent: Pick<
|
|
@@ -105,6 +133,11 @@ export type NativeBehaviorEvent =
|
|
|
105
133
|
type: 'paste'
|
|
106
134
|
data: DataTransfer
|
|
107
135
|
}
|
|
136
|
+
| {
|
|
137
|
+
type: 'serialize'
|
|
138
|
+
originEvent: 'copy' | 'cut' | 'unknown'
|
|
139
|
+
dataTransfer: DataTransfer
|
|
140
|
+
}
|
|
108
141
|
|
|
109
142
|
/**
|
|
110
143
|
* @beta
|
|
@@ -261,7 +294,11 @@ export type BehaviorEvent =
|
|
|
261
294
|
export type Behavior<
|
|
262
295
|
TBehaviorEventType extends BehaviorEvent['type'] = BehaviorEvent['type'],
|
|
263
296
|
TGuardResponse = true,
|
|
264
|
-
TBehaviorEvent extends BehaviorEvent =
|
|
297
|
+
TBehaviorEvent extends BehaviorEvent = PickFromUnion<
|
|
298
|
+
BehaviorEvent,
|
|
299
|
+
'type',
|
|
300
|
+
TBehaviorEventType
|
|
301
|
+
>,
|
|
265
302
|
> = {
|
|
266
303
|
/**
|
|
267
304
|
* The internal editor event that triggers this behavior.
|