@portabletext/editor 2.3.7 → 2.4.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.
@@ -1,5 +1,5 @@
1
1
  import { Behavior, Editor, EditorEmittedEvent, EditorSchema } from "../_chunks-dts/behavior.types.action.js";
2
- import * as react22 from "react";
2
+ import * as react12 from "react";
3
3
  import React from "react";
4
4
  /**
5
5
  * @beta
@@ -181,7 +181,7 @@ type MarkdownPluginConfig = MarkdownBehaviorsConfig & {
181
181
  */
182
182
  declare function MarkdownPlugin(props: {
183
183
  config: MarkdownPluginConfig;
184
- }): react22.JSX.Element;
184
+ }): react12.JSX.Element;
185
185
  /**
186
186
  * @beta
187
187
  * Restrict the editor to one line. The plugin takes care of blocking
@@ -192,5 +192,5 @@ declare function MarkdownPlugin(props: {
192
192
  *
193
193
  * @deprecated Install the plugin from `@portabletext/plugin-one-line`
194
194
  */
195
- declare function OneLinePlugin(): react22.JSX.Element;
195
+ declare function OneLinePlugin(): react12.JSX.Element;
196
196
  export { BehaviorPlugin, DecoratorShortcutPlugin, EditorRefPlugin, EventListenerPlugin, MarkdownPlugin, type MarkdownPluginConfig, OneLinePlugin };
@@ -1,5 +1,5 @@
1
1
  import { BlockOffset, BlockPath, ChildPath, EditorContext, EditorSelection, EditorSelectionPoint } from "../_chunks-dts/behavior.types.action.js";
2
- import * as _sanity_types9 from "@sanity/types";
2
+ import * as _sanity_types8 from "@sanity/types";
3
3
  import { KeyedSegment, PortableTextBlock, PortableTextChild, PortableTextSpan, PortableTextTextBlock } from "@sanity/types";
4
4
  /**
5
5
  * @public
@@ -150,7 +150,7 @@ declare function mergeTextBlocks({
150
150
  context: Pick<EditorContext, 'keyGenerator' | 'schema'>;
151
151
  targetBlock: PortableTextTextBlock;
152
152
  incomingBlock: PortableTextTextBlock;
153
- }): PortableTextTextBlock<_sanity_types9.PortableTextObject | _sanity_types9.PortableTextSpan>;
153
+ }): PortableTextTextBlock<_sanity_types8.PortableTextObject | _sanity_types8.PortableTextSpan>;
154
154
  /**
155
155
  * @public
156
156
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "2.3.7",
3
+ "version": "2.4.0",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -79,16 +79,16 @@
79
79
  "slate-dom": "^0.117.4",
80
80
  "slate-react": "0.117.4",
81
81
  "xstate": "^5.20.2",
82
- "@portabletext/block-tools": "3.2.0",
83
- "@portabletext/keyboard-shortcuts": "1.1.1",
84
- "@portabletext/patches": "1.1.6",
85
- "@portabletext/schema": "1.0.0"
82
+ "@portabletext/block-tools": "^3.3.0",
83
+ "@portabletext/keyboard-shortcuts": "^1.1.1",
84
+ "@portabletext/schema": "^1.0.0",
85
+ "@portabletext/patches": "^1.1.6"
86
86
  },
87
87
  "devDependencies": {
88
88
  "@sanity/diff-match-patch": "^3.2.0",
89
89
  "@sanity/pkg-utils": "^7.11.1",
90
- "@sanity/schema": "^4.4.1",
91
- "@sanity/types": "^4.4.1",
90
+ "@sanity/schema": "^4.5.0",
91
+ "@sanity/types": "^4.5.0",
92
92
  "@testing-library/react": "^16.3.0",
93
93
  "@types/debug": "^4.1.12",
94
94
  "@types/lodash": "^4.17.16",
@@ -111,18 +111,18 @@
111
111
  "vite": "^7.0.6",
112
112
  "vitest": "^3.2.4",
113
113
  "vitest-browser-react": "^1.0.1",
114
- "@portabletext/sanity-bridge": "1.1.1",
115
- "racejar": "1.2.12"
114
+ "racejar": "1.2.12",
115
+ "@portabletext/sanity-bridge": "1.1.2"
116
116
  },
117
117
  "peerDependencies": {
118
- "@sanity/schema": "^4.4.1",
119
- "@sanity/types": "^4.4.1",
118
+ "@sanity/schema": "^4.5.0",
119
+ "@sanity/types": "^4.5.0",
120
120
  "react": "^18.3 || ^19",
121
121
  "rxjs": "^7.8.2",
122
- "@portabletext/sanity-bridge": "1.1.1"
122
+ "@portabletext/sanity-bridge": "^1.1.2"
123
123
  },
124
124
  "engines": {
125
- "node": ">=20.19"
125
+ "node": ">=20.19 <22 || >=22.12"
126
126
  },
127
127
  "publishConfig": {
128
128
  "access": "public"
@@ -0,0 +1,247 @@
1
+ import {isTextBlock} from '../internal-utils/parse-blocks'
2
+ import * as selectors from '../selectors'
3
+ import {getActiveDecorators} from '../selectors/selector.get-active-decorators'
4
+ import {getTextBlockText} from '../utils/util.get-text-block-text'
5
+ import {raise} from './behavior.types.action'
6
+ import {defineBehavior} from './behavior.types.behavior'
7
+
8
+ export const abstractDeserializeBehaviors = [
9
+ defineBehavior({
10
+ on: 'deserialize',
11
+ guard: ({event}) => {
12
+ const portableText = event.originEvent.originEvent.dataTransfer.getData(
13
+ 'application/x-portable-text',
14
+ )
15
+
16
+ if (portableText) {
17
+ return {
18
+ type: 'deserialize.data',
19
+ mimeType: 'application/x-portable-text',
20
+ data: portableText,
21
+ originEvent: event.originEvent,
22
+ } as const
23
+ }
24
+
25
+ const json =
26
+ event.originEvent.originEvent.dataTransfer.getData('application/json')
27
+
28
+ if (json) {
29
+ return {
30
+ type: 'deserialize.data',
31
+ mimeType: 'application/json',
32
+ data: json,
33
+ originEvent: event.originEvent,
34
+ } as const
35
+ }
36
+
37
+ const html =
38
+ event.originEvent.originEvent.dataTransfer.getData('text/html')
39
+
40
+ if (html) {
41
+ return {
42
+ type: 'deserialize.data',
43
+ mimeType: 'text/html',
44
+ data: html,
45
+ originEvent: event.originEvent,
46
+ } as const
47
+ }
48
+
49
+ const text =
50
+ event.originEvent.originEvent.dataTransfer.getData('text/plain')
51
+
52
+ if (text) {
53
+ return {
54
+ type: 'deserialize.data',
55
+ mimeType: 'text/plain',
56
+ data: text,
57
+ originEvent: event.originEvent,
58
+ } as const
59
+ }
60
+
61
+ return false
62
+ },
63
+ actions: [(_, deserializeEvent) => [raise(deserializeEvent)]],
64
+ }),
65
+ defineBehavior({
66
+ on: 'deserialize',
67
+ actions: [
68
+ ({event}) => [
69
+ raise({
70
+ type: 'deserialization.failure',
71
+ mimeType: '*/*',
72
+ reason: 'No Behavior was able to handle the incoming data',
73
+ originEvent: event.originEvent,
74
+ }),
75
+ ],
76
+ ],
77
+ }),
78
+ defineBehavior({
79
+ on: 'deserialize.data',
80
+ guard: ({snapshot, event}) => {
81
+ const converter = snapshot.context.converters.find(
82
+ (converter) => converter.mimeType === event.mimeType,
83
+ )
84
+
85
+ if (!converter) {
86
+ return false
87
+ }
88
+
89
+ return converter.deserialize({
90
+ snapshot,
91
+ event: {
92
+ type: 'deserialize',
93
+ data: event.data,
94
+ },
95
+ })
96
+ },
97
+ actions: [
98
+ ({event}, deserializeEvent) => [
99
+ raise({
100
+ ...deserializeEvent,
101
+ originEvent: event.originEvent,
102
+ }),
103
+ ],
104
+ ],
105
+ }),
106
+ /**
107
+ * If we are pasting text/plain into a text block then we can probably
108
+ * assume that the intended behavior is that the pasted text inherits
109
+ * formatting from the text it's pasted into.
110
+ */
111
+ defineBehavior({
112
+ on: 'deserialization.success',
113
+ guard: ({snapshot, event}) => {
114
+ const focusTextBlock = selectors.getFocusTextBlock(snapshot)
115
+
116
+ if (
117
+ focusTextBlock &&
118
+ event.mimeType === 'text/plain' &&
119
+ event.originEvent.type === 'clipboard.paste'
120
+ ) {
121
+ const activeDecorators = getActiveDecorators(snapshot)
122
+ const activeAnnotations = selectors.getActiveAnnotations(snapshot)
123
+
124
+ return {
125
+ activeAnnotations,
126
+ activeDecorators,
127
+ textRuns: event.data.flatMap((block) =>
128
+ isTextBlock(snapshot.context, block)
129
+ ? [getTextBlockText(block)]
130
+ : [],
131
+ ),
132
+ }
133
+ }
134
+
135
+ return false
136
+ },
137
+ actions: [
138
+ (_, {activeAnnotations, activeDecorators, textRuns}) =>
139
+ textRuns.flatMap((textRun, index) =>
140
+ index !== textRuns.length - 1
141
+ ? [
142
+ raise({
143
+ type: 'insert.span',
144
+ text: textRun,
145
+ decorators: activeDecorators,
146
+ annotations: activeAnnotations.map(
147
+ ({_key, _type, ...value}) => ({
148
+ name: _type,
149
+ value,
150
+ }),
151
+ ),
152
+ }),
153
+ raise({type: 'insert.break'}),
154
+ ]
155
+ : [
156
+ raise({
157
+ type: 'insert.span',
158
+ text: textRun,
159
+ decorators: activeDecorators,
160
+ annotations: activeAnnotations.map(
161
+ ({_key, _type, ...value}) => ({
162
+ name: _type,
163
+ value,
164
+ }),
165
+ ),
166
+ }),
167
+ ],
168
+ ),
169
+ ],
170
+ }),
171
+ defineBehavior({
172
+ on: 'deserialization.success',
173
+ actions: [
174
+ ({event}) => [
175
+ raise({
176
+ type: 'insert.blocks',
177
+ blocks: event.data,
178
+ placement: 'auto',
179
+ }),
180
+ ],
181
+ ],
182
+ }),
183
+ defineBehavior({
184
+ on: 'deserialization.failure',
185
+ guard: ({event}) => {
186
+ if (event.mimeType === 'application/x-portable-text') {
187
+ const json =
188
+ event.originEvent.originEvent.dataTransfer.getData('application/json')
189
+
190
+ if (json) {
191
+ return {
192
+ type: 'deserialize.data',
193
+ mimeType: 'application/json',
194
+ data: json,
195
+ originEvent: event.originEvent,
196
+ } as const
197
+ }
198
+ }
199
+
200
+ if (event.mimeType === 'application/json') {
201
+ const html =
202
+ event.originEvent.originEvent.dataTransfer.getData('text/html')
203
+
204
+ if (html) {
205
+ return {
206
+ type: 'deserialize.data',
207
+ mimeType: 'text/html',
208
+ data: html,
209
+ originEvent: event.originEvent,
210
+ } as const
211
+ }
212
+ }
213
+
214
+ if (event.mimeType === 'text/html') {
215
+ const text =
216
+ event.originEvent.originEvent.dataTransfer.getData('text/plain')
217
+
218
+ if (text) {
219
+ return {
220
+ type: 'deserialize.data',
221
+ mimeType: 'text/plain',
222
+ data: text,
223
+ originEvent: event.originEvent,
224
+ } as const
225
+ }
226
+ }
227
+
228
+ return false
229
+ },
230
+ actions: [(_, deserializeDataEvent) => [raise(deserializeDataEvent)]],
231
+ }),
232
+ defineBehavior({
233
+ on: 'deserialization.failure',
234
+ actions: [
235
+ ({event}) => [
236
+ {
237
+ type: 'effect',
238
+ effect: () => {
239
+ console.warn(
240
+ `Deserialization of ${event.mimeType} failed with reason "${event.reason}"`,
241
+ )
242
+ },
243
+ },
244
+ ],
245
+ ],
246
+ }),
247
+ ]
@@ -0,0 +1,91 @@
1
+ import {raise} from './behavior.types.action'
2
+ import {defineBehavior} from './behavior.types.behavior'
3
+
4
+ export const abstractSerializeBehaviors = [
5
+ defineBehavior({
6
+ on: 'serialize',
7
+ actions: [
8
+ ({event}) => [
9
+ raise({
10
+ type: 'serialize.data',
11
+ mimeType: 'application/x-portable-text',
12
+ originEvent: event.originEvent,
13
+ }),
14
+ raise({
15
+ type: 'serialize.data',
16
+ mimeType: 'application/json',
17
+ originEvent: event.originEvent,
18
+ }),
19
+ raise({
20
+ type: 'serialize.data',
21
+ mimeType: 'text/html',
22
+ originEvent: event.originEvent,
23
+ }),
24
+ raise({
25
+ type: 'serialize.data',
26
+ mimeType: 'text/plain',
27
+ originEvent: event.originEvent,
28
+ }),
29
+ ],
30
+ ],
31
+ }),
32
+ defineBehavior({
33
+ on: 'serialize.data',
34
+ guard: ({snapshot, event}) => {
35
+ const converter = snapshot.context.converters.find(
36
+ (converter) => converter.mimeType === event.mimeType,
37
+ )
38
+
39
+ if (!converter) {
40
+ return false
41
+ }
42
+
43
+ return converter.serialize({
44
+ snapshot,
45
+ event: {
46
+ type: 'serialize',
47
+ originEvent: event.originEvent.type,
48
+ },
49
+ })
50
+ },
51
+ actions: [
52
+ ({event}, serializeEvent) => [
53
+ raise({
54
+ ...serializeEvent,
55
+ originEvent: event.originEvent,
56
+ }),
57
+ ],
58
+ ],
59
+ }),
60
+ defineBehavior({
61
+ on: 'serialization.success',
62
+ actions: [
63
+ ({event}) => [
64
+ {
65
+ type: 'effect',
66
+ effect: () => {
67
+ event.originEvent.originEvent.dataTransfer.setData(
68
+ event.mimeType,
69
+ event.data,
70
+ )
71
+ },
72
+ },
73
+ ],
74
+ ],
75
+ }),
76
+ defineBehavior({
77
+ on: 'serialization.failure',
78
+ actions: [
79
+ ({event}) => [
80
+ {
81
+ type: 'effect',
82
+ effect: () => {
83
+ console.warn(
84
+ `Serialization of ${event.mimeType} failed with reason "${event.reason}"`,
85
+ )
86
+ },
87
+ },
88
+ ],
89
+ ],
90
+ }),
91
+ ]
@@ -1,108 +1,19 @@
1
- import type {ConverterEvent} from '../converters/converter.types'
2
- import {isTextBlock} from '../internal-utils/parse-blocks'
3
1
  import * as selectors from '../selectors'
4
- import {getActiveDecorators} from '../selectors/selector.get-active-decorators'
5
- import type {PickFromUnion} from '../type-utils'
6
- import {getTextBlockText} from '../utils'
7
2
  import {abstractAnnotationBehaviors} from './behavior.abstract.annotation'
8
3
  import {abstractDecoratorBehaviors} from './behavior.abstract.decorator'
9
4
  import {abstractDeleteBehaviors} from './behavior.abstract.delete'
5
+ import {abstractDeserializeBehaviors} from './behavior.abstract.deserialize'
10
6
  import {abstractInsertBehaviors} from './behavior.abstract.insert'
11
7
  import {abstractKeyboardBehaviors} from './behavior.abstract.keyboard'
12
8
  import {abstractListItemBehaviors} from './behavior.abstract.list-item'
13
9
  import {abstractMoveBehaviors} from './behavior.abstract.move'
14
10
  import {abstractSelectBehaviors} from './behavior.abstract.select'
11
+ import {abstractSerializeBehaviors} from './behavior.abstract.serialize'
15
12
  import {abstractSplitBehaviors} from './behavior.abstract.split'
16
13
  import {abstractStyleBehaviors} from './behavior.abstract.style'
17
14
  import {raise} from './behavior.types.action'
18
15
  import {defineBehavior} from './behavior.types.behavior'
19
16
 
20
- const raiseDeserializationSuccessOrFailure = defineBehavior({
21
- on: 'deserialize',
22
- guard: ({snapshot, event}) => {
23
- let success:
24
- | PickFromUnion<ConverterEvent, 'type', 'deserialization.success'>
25
- | undefined
26
- const failures: Array<
27
- PickFromUnion<ConverterEvent, 'type', 'deserialization.failure'>
28
- > = []
29
-
30
- for (const converter of snapshot.context.converters) {
31
- const data = event.originEvent.originEvent.dataTransfer.getData(
32
- converter.mimeType,
33
- )
34
-
35
- if (!data) {
36
- continue
37
- }
38
-
39
- const deserializeEvent = converter.deserialize({
40
- snapshot,
41
- event: {type: 'deserialize', data},
42
- })
43
-
44
- if (deserializeEvent.type === 'deserialization.success') {
45
- success = deserializeEvent
46
- break
47
- } else {
48
- failures.push(deserializeEvent)
49
- }
50
- }
51
-
52
- if (!success) {
53
- return {
54
- type: 'deserialization.failure',
55
- mimeType: '*/*',
56
- reason: failures.map((failure) => failure.reason).join(', '),
57
- } as const
58
- }
59
-
60
- return success
61
- },
62
- actions: [
63
- ({event}, deserializeEvent) => [
64
- raise({
65
- ...deserializeEvent,
66
- originEvent: event.originEvent,
67
- }),
68
- ],
69
- ],
70
- })
71
-
72
- const raiseSerializationSuccessOrFailure = defineBehavior({
73
- on: 'serialize',
74
- guard: ({snapshot, event}) => {
75
- if (snapshot.context.converters.length === 0) {
76
- return false
77
- }
78
-
79
- const serializeEvents = snapshot.context.converters.map((converter) =>
80
- converter.serialize({
81
- snapshot,
82
- event: {
83
- ...event,
84
- originEvent: event.originEvent.type,
85
- },
86
- }),
87
- )
88
-
89
- if (serializeEvents.length === 0) {
90
- return false
91
- }
92
-
93
- return serializeEvents
94
- },
95
- actions: [
96
- ({event}, serializeEvents) =>
97
- serializeEvents.map((serializeEvent) => {
98
- return raise({
99
- ...serializeEvent,
100
- originEvent: event.originEvent,
101
- })
102
- }),
103
- ],
104
- })
105
-
106
17
  export const abstractBehaviors = [
107
18
  defineBehavior({
108
19
  on: 'clipboard.copy',
@@ -168,130 +79,7 @@ export const abstractBehaviors = [
168
79
  ],
169
80
  ],
170
81
  }),
171
- defineBehavior({
172
- on: 'serialization.success',
173
- actions: [
174
- ({event}) => [
175
- {
176
- type: 'effect',
177
- effect: () => {
178
- event.originEvent.originEvent.dataTransfer.setData(
179
- event.mimeType,
180
- event.data,
181
- )
182
- },
183
- },
184
- ],
185
- ],
186
- }),
187
- defineBehavior({
188
- on: 'serialization.failure',
189
- actions: [
190
- ({event}) => [
191
- {
192
- type: 'effect',
193
- effect: () => {
194
- console.warn(
195
- `Serialization of ${event.mimeType} failed with reason "${event.reason}"`,
196
- )
197
- },
198
- },
199
- ],
200
- ],
201
- }),
202
-
203
- /**
204
- * If we are pasting text/plain into a text block then we can probably
205
- * assume that the intended behavior is that the pasted text inherits
206
- * formatting from the text it's pasted into.
207
- */
208
- defineBehavior({
209
- on: 'deserialization.success',
210
- guard: ({snapshot, event}) => {
211
- const focusTextBlock = selectors.getFocusTextBlock(snapshot)
212
82
 
213
- if (
214
- focusTextBlock &&
215
- event.mimeType === 'text/plain' &&
216
- event.originEvent.type === 'clipboard.paste'
217
- ) {
218
- const activeDecorators = getActiveDecorators(snapshot)
219
- const activeAnnotations = selectors.getActiveAnnotations(snapshot)
220
-
221
- return {
222
- activeAnnotations,
223
- activeDecorators,
224
- textRuns: event.data.flatMap((block) =>
225
- isTextBlock(snapshot.context, block)
226
- ? [getTextBlockText(block)]
227
- : [],
228
- ),
229
- }
230
- }
231
-
232
- return false
233
- },
234
- actions: [
235
- (_, {activeAnnotations, activeDecorators, textRuns}) =>
236
- textRuns.flatMap((textRun, index) =>
237
- index !== textRuns.length - 1
238
- ? [
239
- raise({
240
- type: 'insert.span',
241
- text: textRun,
242
- decorators: activeDecorators,
243
- annotations: activeAnnotations.map(
244
- ({_key, _type, ...value}) => ({
245
- name: _type,
246
- value,
247
- }),
248
- ),
249
- }),
250
- raise({type: 'insert.break'}),
251
- ]
252
- : [
253
- raise({
254
- type: 'insert.span',
255
- text: textRun,
256
- decorators: activeDecorators,
257
- annotations: activeAnnotations.map(
258
- ({_key, _type, ...value}) => ({
259
- name: _type,
260
- value,
261
- }),
262
- ),
263
- }),
264
- ],
265
- ),
266
- ],
267
- }),
268
- defineBehavior({
269
- on: 'deserialization.success',
270
- actions: [
271
- ({event}) => [
272
- raise({
273
- type: 'insert.blocks',
274
- blocks: event.data,
275
- placement: 'auto',
276
- }),
277
- ],
278
- ],
279
- }),
280
- defineBehavior({
281
- on: 'deserialization.failure',
282
- actions: [
283
- ({event}) => [
284
- {
285
- type: 'effect',
286
- effect: () => {
287
- console.warn(
288
- `Deserialization of ${event.mimeType} failed with reason "${event.reason}"`,
289
- )
290
- },
291
- },
292
- ],
293
- ],
294
- }),
295
83
  defineBehavior({
296
84
  on: 'clipboard.paste',
297
85
  guard: ({snapshot}) => {
@@ -338,13 +126,13 @@ export const abstractBehaviors = [
338
126
  ...abstractAnnotationBehaviors,
339
127
  ...abstractDecoratorBehaviors,
340
128
  ...abstractDeleteBehaviors,
129
+ ...abstractDeserializeBehaviors,
341
130
  ...abstractInsertBehaviors,
342
131
  ...abstractKeyboardBehaviors,
343
132
  ...abstractListItemBehaviors,
344
133
  ...abstractMoveBehaviors,
345
134
  ...abstractStyleBehaviors,
346
135
  ...abstractSelectBehaviors,
136
+ ...abstractSerializeBehaviors,
347
137
  ...abstractSplitBehaviors,
348
- raiseDeserializationSuccessOrFailure,
349
- raiseSerializationSuccessOrFailure,
350
138
  ]