@portabletext/editor 2.3.0 → 2.3.2
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-dts/behavior.types.action.d.ts +122 -122
- package/lib/index.cjs +162 -2
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +164 -4
- package/lib/index.js.map +1 -1
- package/lib/plugins/index.d.ts +3 -3
- package/lib/utils/index.d.cts +2 -2
- package/lib/utils/index.d.ts +2 -2
- package/package.json +1 -1
- package/src/behaviors/behavior.core.lists.ts +149 -1
- package/src/behaviors/behavior.core.ts +1 -0
- package/src/converters/converter.text-plain.test.ts +7 -5
- package/src/editor/__tests__/PortableTextEditor.test.tsx +5 -14
- package/src/editor/__tests__/PortableTextEditorTester.tsx +23 -77
- package/src/editor/__tests__/RangeDecorations.test.tsx +2 -9
- package/src/editor/__tests__/insert-block.test.tsx +7 -28
- package/src/editor/__tests__/self-solving.test.tsx +5 -17
- package/src/editor/create-editor.ts +4 -1
- package/src/editor/editor-schema.ts +36 -3
- package/src/editor/plugins/__tests__/withEditableAPIDelete.test.tsx +8 -15
- package/src/editor/plugins/__tests__/withEditableAPIGetFragment.test.tsx +7 -11
- package/src/editor/plugins/__tests__/withEditableAPIInsert.test.tsx +81 -63
- package/src/editor/plugins/__tests__/withEditableAPISelectionsOverlapping.test.tsx +2 -9
- package/src/editor/plugins/__tests__/withPortableTextLists.test.tsx +3 -7
- package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +17 -27
- package/src/editor/plugins/__tests__/withPortableTextSelections.test.tsx +3 -7
- package/src/editor/plugins/__tests__/withUndoRedo.test.tsx +4 -9
- package/src/internal-utils/__tests__/valueNormalization.test.tsx +3 -7
- package/src/internal-utils/__tests__/values.test.ts +5 -6
- package/src/internal-utils/operation-to-patches.test.ts +17 -12
- package/src/plugins/plugin.internal.portable-text-editor-ref.tsx +16 -0
- package/src/editor/__tests__/sync-value.test.tsx +0 -154
package/lib/plugins/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Behavior, Editor, EditorEmittedEvent, EditorSchema } from "../_chunks-dts/behavior.types.action.js";
|
|
2
|
-
import * as
|
|
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
|
-
}):
|
|
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():
|
|
195
|
+
declare function OneLinePlugin(): react12.JSX.Element;
|
|
196
196
|
export { BehaviorPlugin, DecoratorShortcutPlugin, EditorRefPlugin, EventListenerPlugin, MarkdownPlugin, type MarkdownPluginConfig, OneLinePlugin };
|
package/lib/utils/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BlockOffset, BlockPath, ChildPath, EditorContext, EditorSelection, EditorSelectionPoint } from "../_chunks-dts/behavior.types.action.cjs";
|
|
2
|
-
import * as
|
|
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<
|
|
153
|
+
}): PortableTextTextBlock<_sanity_types8.PortableTextObject | _sanity_types8.PortableTextSpan>;
|
|
154
154
|
/**
|
|
155
155
|
* @public
|
|
156
156
|
*/
|
package/lib/utils/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BlockOffset, BlockPath, ChildPath, EditorContext, EditorSelection, EditorSelectionPoint } from "../_chunks-dts/behavior.types.action.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as _sanity_types6 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<
|
|
153
|
+
}): PortableTextTextBlock<_sanity_types6.PortableTextSpan | _sanity_types6.PortableTextObject>;
|
|
154
154
|
/**
|
|
155
155
|
* @public
|
|
156
156
|
*/
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import {isListBlock, isTextBlock} from '../internal-utils/parse-blocks'
|
|
2
2
|
import {defaultKeyboardShortcuts} from '../keyboard-shortcuts/default-keyboard-shortcuts'
|
|
3
3
|
import * as selectors from '../selectors'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getBlockEndPoint,
|
|
6
|
+
getBlockStartPoint,
|
|
7
|
+
isEqualSelectionPoints,
|
|
8
|
+
} from '../utils'
|
|
5
9
|
import {isAtTheBeginningOfBlock} from '../utils/util.at-the-beginning-of-block'
|
|
6
10
|
import {isEmptyTextBlock} from '../utils/util.is-empty-text-block'
|
|
11
|
+
import {sliceTextBlock} from '../utils/util.slice-text-block'
|
|
7
12
|
import {raise} from './behavior.types.action'
|
|
8
13
|
import {defineBehavior} from './behavior.types.behavior'
|
|
9
14
|
|
|
@@ -183,6 +188,148 @@ const mergeTextIntoListOnBackspace = defineBehavior({
|
|
|
183
188
|
],
|
|
184
189
|
})
|
|
185
190
|
|
|
191
|
+
/**
|
|
192
|
+
* When performing a delete operation where the start point of the operation is
|
|
193
|
+
* at the start of a list item and the end point of the operation is in another
|
|
194
|
+
* list item, we make sure the preserve the first list item. Otherwise, the
|
|
195
|
+
* default behavior would be to preserve the last item.
|
|
196
|
+
*/
|
|
197
|
+
const deletingListFromStart = defineBehavior({
|
|
198
|
+
on: 'delete',
|
|
199
|
+
guard: ({snapshot, event}) => {
|
|
200
|
+
const blocksToDelete = selectors.getSelectedBlocks({
|
|
201
|
+
...snapshot,
|
|
202
|
+
context: {
|
|
203
|
+
...snapshot.context,
|
|
204
|
+
selection: event.at,
|
|
205
|
+
},
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
if (blocksToDelete.length < 2) {
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const startBlock = blocksToDelete.at(0)?.node
|
|
213
|
+
const middleBlocks = blocksToDelete.slice(1, -1)
|
|
214
|
+
const endBlock = blocksToDelete.at(-1)?.node
|
|
215
|
+
|
|
216
|
+
if (
|
|
217
|
+
!isListBlock(snapshot.context, startBlock) ||
|
|
218
|
+
!isListBlock(snapshot.context, endBlock)
|
|
219
|
+
) {
|
|
220
|
+
// It's that any block in between isn't a list item, but the first and
|
|
221
|
+
// last blocks have to be list items for this Behavior to take effect.
|
|
222
|
+
return false
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const deleteStartPoint = selectors.getSelectionStartPoint({
|
|
226
|
+
...snapshot,
|
|
227
|
+
context: {
|
|
228
|
+
...snapshot.context,
|
|
229
|
+
selection: event.at,
|
|
230
|
+
},
|
|
231
|
+
})
|
|
232
|
+
const deleteEndPoint = selectors.getSelectionEndPoint({
|
|
233
|
+
...snapshot,
|
|
234
|
+
context: {
|
|
235
|
+
...snapshot.context,
|
|
236
|
+
selection: event.at,
|
|
237
|
+
},
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
if (!deleteStartPoint || !deleteEndPoint) {
|
|
241
|
+
return false
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const startBlockStartPoint = getBlockStartPoint({
|
|
245
|
+
context: snapshot.context,
|
|
246
|
+
block: {
|
|
247
|
+
node: startBlock,
|
|
248
|
+
path: [{_key: startBlock._key}],
|
|
249
|
+
},
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
if (!isEqualSelectionPoints(deleteStartPoint, startBlockStartPoint)) {
|
|
253
|
+
// If we aren't deleting from the beginning of the first list item, then
|
|
254
|
+
// there is no need to proceed. The default delete Behavior will suffice.
|
|
255
|
+
return false
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const startBlockEndPoint = getBlockEndPoint({
|
|
259
|
+
context: snapshot.context,
|
|
260
|
+
block: {
|
|
261
|
+
node: startBlock,
|
|
262
|
+
path: [{_key: startBlock._key}],
|
|
263
|
+
},
|
|
264
|
+
})
|
|
265
|
+
const endBlockEndPoint = getBlockEndPoint({
|
|
266
|
+
context: snapshot.context,
|
|
267
|
+
block: {
|
|
268
|
+
node: endBlock,
|
|
269
|
+
path: [{_key: endBlock._key}],
|
|
270
|
+
},
|
|
271
|
+
})
|
|
272
|
+
const slicedEndBlock = sliceTextBlock({
|
|
273
|
+
context: {
|
|
274
|
+
schema: snapshot.context.schema,
|
|
275
|
+
selection: {
|
|
276
|
+
anchor: deleteEndPoint,
|
|
277
|
+
focus: endBlockEndPoint,
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
block: endBlock,
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
startBlockStartPoint,
|
|
285
|
+
startBlockEndPoint,
|
|
286
|
+
middleBlocks,
|
|
287
|
+
endBlock,
|
|
288
|
+
slicedEndBlock,
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
actions: [
|
|
292
|
+
(
|
|
293
|
+
_,
|
|
294
|
+
{
|
|
295
|
+
startBlockStartPoint,
|
|
296
|
+
startBlockEndPoint,
|
|
297
|
+
middleBlocks,
|
|
298
|
+
endBlock,
|
|
299
|
+
slicedEndBlock,
|
|
300
|
+
},
|
|
301
|
+
) => [
|
|
302
|
+
// All block in between can safely be deleted.
|
|
303
|
+
...middleBlocks.map((block) =>
|
|
304
|
+
raise({type: 'delete.block', at: block.path}),
|
|
305
|
+
),
|
|
306
|
+
// The last block is deleted as well.
|
|
307
|
+
raise({type: 'delete.block', at: [{_key: endBlock._key}]}),
|
|
308
|
+
// But in case the delete operation didn't reach all the way to the end
|
|
309
|
+
// of it, we first place the caret at the end of the start block...
|
|
310
|
+
raise({
|
|
311
|
+
type: 'select',
|
|
312
|
+
at: {
|
|
313
|
+
anchor: startBlockEndPoint,
|
|
314
|
+
focus: startBlockEndPoint,
|
|
315
|
+
},
|
|
316
|
+
}),
|
|
317
|
+
// ...and insert the rest of the end block at the end of it.
|
|
318
|
+
raise({
|
|
319
|
+
type: 'insert.block',
|
|
320
|
+
block: slicedEndBlock,
|
|
321
|
+
placement: 'auto',
|
|
322
|
+
select: 'none',
|
|
323
|
+
}),
|
|
324
|
+
// And finally, we delete the original text of the start block.
|
|
325
|
+
raise({
|
|
326
|
+
type: 'delete',
|
|
327
|
+
at: {anchor: startBlockStartPoint, focus: startBlockEndPoint},
|
|
328
|
+
}),
|
|
329
|
+
],
|
|
330
|
+
],
|
|
331
|
+
})
|
|
332
|
+
|
|
186
333
|
/**
|
|
187
334
|
* Hitting Enter in an empty list item would create a new list item below by
|
|
188
335
|
* default. Instead, the list properties should be cleared.
|
|
@@ -521,6 +668,7 @@ export const coreListBehaviors = {
|
|
|
521
668
|
unindentListOnBackspace,
|
|
522
669
|
mergeTextIntoListOnDelete,
|
|
523
670
|
mergeTextIntoListOnBackspace,
|
|
671
|
+
deletingListFromStart,
|
|
524
672
|
clearListOnEnter,
|
|
525
673
|
indentListOnTab,
|
|
526
674
|
unindentListOnShiftTab,
|
|
@@ -24,6 +24,7 @@ export const coreBehaviorsConfig = [
|
|
|
24
24
|
coreListBehaviors.unindentListOnBackspace,
|
|
25
25
|
coreListBehaviors.mergeTextIntoListOnDelete,
|
|
26
26
|
coreListBehaviors.mergeTextIntoListOnBackspace,
|
|
27
|
+
coreListBehaviors.deletingListFromStart,
|
|
27
28
|
coreListBehaviors.clearListOnEnter,
|
|
28
29
|
coreListBehaviors.indentListOnTab,
|
|
29
30
|
coreListBehaviors.unindentListOnShiftTab,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type {PortableTextBlock, PortableTextTextBlock} from '@sanity/types'
|
|
2
2
|
import {expect, test} from 'vitest'
|
|
3
3
|
import type {EditorSelection} from '..'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
4
|
+
import {schemaDefinition} from '../editor/__tests__/PortableTextEditorTester'
|
|
5
|
+
import {
|
|
6
|
+
compileSchemaDefinition,
|
|
7
|
+
compileSchemaDefinitionToLegacySchema,
|
|
8
|
+
} from '../editor/editor-schema'
|
|
7
9
|
import type {SchemaDefinition} from '../editor/editor-schema-definition'
|
|
8
|
-
import {
|
|
10
|
+
import {defineSchema} from '../editor/editor-schema-definition'
|
|
9
11
|
import {createTestSnapshot} from '../internal-utils/create-test-snapshot'
|
|
10
12
|
import {createConverterTextPlain} from './converter.text-plain'
|
|
11
13
|
|
|
@@ -82,7 +84,7 @@ function createSnapshot({
|
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
const converterTextPlain = createConverterTextPlain(
|
|
85
|
-
|
|
87
|
+
compileSchemaDefinitionToLegacySchema(schemaDefinition),
|
|
86
88
|
)
|
|
87
89
|
|
|
88
90
|
test(converterTextPlain.serialize.name, () => {
|
|
@@ -5,11 +5,11 @@ import {describe, expect, it, vi} from 'vitest'
|
|
|
5
5
|
import type {EditorSelection} from '../..'
|
|
6
6
|
import {createTestKeyGenerator} from '../../internal-utils/test-key-generator'
|
|
7
7
|
import {PortableTextEditor} from '../PortableTextEditor'
|
|
8
|
-
import {PortableTextEditorTester
|
|
8
|
+
import {PortableTextEditorTester} from './PortableTextEditorTester'
|
|
9
9
|
|
|
10
10
|
const helloBlock: PortableTextBlock = {
|
|
11
11
|
_key: '123',
|
|
12
|
-
_type: '
|
|
12
|
+
_type: 'block',
|
|
13
13
|
markDefs: [],
|
|
14
14
|
children: [{_key: '567', _type: 'span', text: 'Hello', marks: []}],
|
|
15
15
|
}
|
|
@@ -26,7 +26,6 @@ describe('initialization', () => {
|
|
|
26
26
|
onChange={onChange}
|
|
27
27
|
renderPlaceholder={renderPlaceholder}
|
|
28
28
|
ref={editorRef}
|
|
29
|
-
schemaType={schemaType}
|
|
30
29
|
value={undefined}
|
|
31
30
|
/>,
|
|
32
31
|
)
|
|
@@ -54,7 +53,7 @@ describe('initialization', () => {
|
|
|
54
53
|
<div
|
|
55
54
|
class="pt-block pt-text-block pt-text-block-style-normal"
|
|
56
55
|
data-block-key="k0"
|
|
57
|
-
data-block-name="
|
|
56
|
+
data-block-name="block"
|
|
58
57
|
data-block-type="text"
|
|
59
58
|
data-slate-node="element"
|
|
60
59
|
data-style="normal"
|
|
@@ -100,7 +99,6 @@ describe('initialization', () => {
|
|
|
100
99
|
keyGenerator={createTestKeyGenerator()}
|
|
101
100
|
ref={editorRef}
|
|
102
101
|
onChange={onChange}
|
|
103
|
-
schemaType={schemaType}
|
|
104
102
|
value={initialValue}
|
|
105
103
|
/>,
|
|
106
104
|
)
|
|
@@ -133,7 +131,6 @@ describe('initialization', () => {
|
|
|
133
131
|
onChange={onChange}
|
|
134
132
|
ref={editorRef}
|
|
135
133
|
selection={initialSelection}
|
|
136
|
-
schemaType={schemaType}
|
|
137
134
|
value={initialValue}
|
|
138
135
|
/>,
|
|
139
136
|
)
|
|
@@ -178,7 +175,6 @@ describe('initialization', () => {
|
|
|
178
175
|
onChange={onChange}
|
|
179
176
|
ref={editorRef}
|
|
180
177
|
selection={initialSelection}
|
|
181
|
-
schemaType={schemaType}
|
|
182
178
|
value={initialValue}
|
|
183
179
|
/>,
|
|
184
180
|
)
|
|
@@ -212,7 +208,6 @@ describe('initialization', () => {
|
|
|
212
208
|
onChange={onChange}
|
|
213
209
|
ref={editorRef}
|
|
214
210
|
selection={newSelection}
|
|
215
|
-
schemaType={schemaType}
|
|
216
211
|
value={initialValue}
|
|
217
212
|
/>,
|
|
218
213
|
)
|
|
@@ -239,7 +234,6 @@ describe('initialization', () => {
|
|
|
239
234
|
onChange={onChange}
|
|
240
235
|
ref={editorRef}
|
|
241
236
|
selection={initialSelection}
|
|
242
|
-
schemaType={schemaType}
|
|
243
237
|
value={initialValue}
|
|
244
238
|
/>,
|
|
245
239
|
)
|
|
@@ -285,7 +279,6 @@ describe('initialization', () => {
|
|
|
285
279
|
onChange={onChange}
|
|
286
280
|
ref={editorRef}
|
|
287
281
|
selection={initialSelection}
|
|
288
|
-
schemaType={schemaType}
|
|
289
282
|
value={value}
|
|
290
283
|
/>,
|
|
291
284
|
)
|
|
@@ -318,7 +311,6 @@ describe('initialization', () => {
|
|
|
318
311
|
onChange={newOnChange}
|
|
319
312
|
ref={editorRef}
|
|
320
313
|
selection={initialSelection}
|
|
321
|
-
schemaType={schemaType}
|
|
322
314
|
value={value}
|
|
323
315
|
/>,
|
|
324
316
|
)
|
|
@@ -355,7 +347,7 @@ describe('initialization', () => {
|
|
|
355
347
|
helloBlock,
|
|
356
348
|
{
|
|
357
349
|
_key: 'abc',
|
|
358
|
-
_type: '
|
|
350
|
+
_type: 'block',
|
|
359
351
|
markDefs: [],
|
|
360
352
|
children: [{_key: 'def', _type: 'span', marks: []}],
|
|
361
353
|
},
|
|
@@ -371,7 +363,6 @@ describe('initialization', () => {
|
|
|
371
363
|
onChange={onChange}
|
|
372
364
|
ref={editorRef}
|
|
373
365
|
selection={initialSelection}
|
|
374
|
-
schemaType={schemaType}
|
|
375
366
|
value={initialValue}
|
|
376
367
|
/>,
|
|
377
368
|
)
|
|
@@ -396,7 +387,7 @@ describe('initialization', () => {
|
|
|
396
387
|
},
|
|
397
388
|
item: {
|
|
398
389
|
_key: 'abc',
|
|
399
|
-
_type: '
|
|
390
|
+
_type: 'block',
|
|
400
391
|
children: [
|
|
401
392
|
{
|
|
402
393
|
_key: 'def',
|
|
@@ -1,86 +1,34 @@
|
|
|
1
|
-
import {Schema} from '@sanity/schema'
|
|
2
|
-
import {defineArrayMember, defineField} from '@sanity/types'
|
|
3
1
|
import {forwardRef, useMemo, type ForwardedRef} from 'react'
|
|
4
2
|
import {vi} from 'vitest'
|
|
5
3
|
import {
|
|
4
|
+
defineSchema,
|
|
5
|
+
EditorProvider,
|
|
6
6
|
PortableTextEditable,
|
|
7
|
-
PortableTextEditor,
|
|
8
7
|
type PortableTextEditableProps,
|
|
8
|
+
type PortableTextEditor,
|
|
9
9
|
type PortableTextEditorProps,
|
|
10
|
+
type SchemaDefinition,
|
|
10
11
|
} from '../../index'
|
|
12
|
+
import {InternalChange$Plugin} from '../../plugins/plugin.internal.change-ref'
|
|
13
|
+
import {InternalPortableTextEditorRefPlugin} from '../../plugins/plugin.internal.portable-text-editor-ref'
|
|
11
14
|
|
|
12
|
-
const
|
|
13
|
-
name: '
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
const someObject = defineField({
|
|
19
|
-
type: 'object',
|
|
20
|
-
name: 'someObject',
|
|
21
|
-
fields: [{type: 'string', name: 'color'}],
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
const blockType = defineField({
|
|
25
|
-
type: 'block',
|
|
26
|
-
name: 'myTestBlockType',
|
|
27
|
-
styles: [
|
|
28
|
-
{title: 'Normal', value: 'normal'},
|
|
29
|
-
{title: 'H1', value: 'h1'},
|
|
30
|
-
{title: 'H2', value: 'h2'},
|
|
31
|
-
{title: 'H3', value: 'h3'},
|
|
32
|
-
{title: 'H4', value: 'h4'},
|
|
33
|
-
{title: 'H5', value: 'h5'},
|
|
34
|
-
{title: 'H6', value: 'h6'},
|
|
35
|
-
{title: 'Quote', value: 'blockquote'},
|
|
15
|
+
export const schemaDefinition = defineSchema({
|
|
16
|
+
decorators: [{name: 'strong'}],
|
|
17
|
+
blockObjects: [
|
|
18
|
+
{name: 'custom image', fields: [{name: 'src', type: 'string'}]},
|
|
36
19
|
],
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const portableTextType = defineArrayMember({
|
|
41
|
-
type: 'array',
|
|
42
|
-
name: 'body',
|
|
43
|
-
of: [blockType, someObject],
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
const colorAndLink = defineArrayMember({
|
|
47
|
-
type: 'array',
|
|
48
|
-
name: 'colorAndLink',
|
|
49
|
-
of: [
|
|
50
|
-
{
|
|
51
|
-
...blockType,
|
|
52
|
-
marks: {
|
|
53
|
-
annotations: [
|
|
54
|
-
{
|
|
55
|
-
name: 'link',
|
|
56
|
-
type: 'object',
|
|
57
|
-
fields: [{type: 'string', name: 'color'}],
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
name: 'color',
|
|
61
|
-
type: 'object',
|
|
62
|
-
fields: [{type: 'string', name: 'color'}],
|
|
63
|
-
},
|
|
64
|
-
],
|
|
65
|
-
},
|
|
66
|
-
},
|
|
20
|
+
inlineObjects: [
|
|
21
|
+
{name: 'someObject', fields: [{name: 'color', type: 'string'}]},
|
|
67
22
|
],
|
|
68
23
|
})
|
|
69
24
|
|
|
70
|
-
const schema = Schema.compile({
|
|
71
|
-
name: 'test',
|
|
72
|
-
types: [portableTextType, colorAndLink, imageType],
|
|
73
|
-
})
|
|
74
|
-
|
|
75
25
|
export const PortableTextEditorTester = forwardRef(
|
|
76
26
|
function PortableTextEditorTester(
|
|
77
|
-
props:
|
|
78
|
-
Omit<PortableTextEditorProps, 'type' | 'onChange' | 'value'>
|
|
79
|
-
> & {
|
|
27
|
+
props: {
|
|
80
28
|
onChange?: PortableTextEditorProps['onChange']
|
|
81
29
|
rangeDecorations?: PortableTextEditableProps['rangeDecorations']
|
|
82
30
|
renderPlaceholder?: PortableTextEditableProps['renderPlaceholder']
|
|
83
|
-
|
|
31
|
+
schemaDefinition?: SchemaDefinition
|
|
84
32
|
selection?: PortableTextEditableProps['selection']
|
|
85
33
|
value?: PortableTextEditorProps['value']
|
|
86
34
|
keyGenerator: PortableTextEditorProps['keyGenerator']
|
|
@@ -89,24 +37,22 @@ export const PortableTextEditorTester = forwardRef(
|
|
|
89
37
|
) {
|
|
90
38
|
const onChange = useMemo(() => props.onChange || vi.fn(), [props.onChange])
|
|
91
39
|
return (
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
40
|
+
<EditorProvider
|
|
41
|
+
initialConfig={{
|
|
42
|
+
schemaDefinition: props.schemaDefinition ?? schemaDefinition,
|
|
43
|
+
keyGenerator: props.keyGenerator,
|
|
44
|
+
initialValue: props.value,
|
|
45
|
+
}}
|
|
98
46
|
>
|
|
47
|
+
<InternalChange$Plugin onChange={onChange} />
|
|
48
|
+
<InternalPortableTextEditorRefPlugin ref={ref} />
|
|
99
49
|
<PortableTextEditable
|
|
100
50
|
selection={props.selection || undefined}
|
|
101
51
|
rangeDecorations={props.rangeDecorations}
|
|
102
52
|
renderPlaceholder={props.renderPlaceholder}
|
|
103
53
|
aria-describedby="desc_foo"
|
|
104
54
|
/>
|
|
105
|
-
</
|
|
55
|
+
</EditorProvider>
|
|
106
56
|
)
|
|
107
57
|
},
|
|
108
58
|
)
|
|
109
|
-
|
|
110
|
-
export const schemaType = schema.get('body')
|
|
111
|
-
|
|
112
|
-
export const schemaTypeWithColorAndLink = schema.get('colorAndLink')
|
|
@@ -5,11 +5,11 @@ import {describe, expect, it, vi} from 'vitest'
|
|
|
5
5
|
import type {RangeDecoration} from '../..'
|
|
6
6
|
import {createTestKeyGenerator} from '../../internal-utils/test-key-generator'
|
|
7
7
|
import type {PortableTextEditor} from '../PortableTextEditor'
|
|
8
|
-
import {PortableTextEditorTester
|
|
8
|
+
import {PortableTextEditorTester} from './PortableTextEditorTester'
|
|
9
9
|
|
|
10
10
|
const helloBlock: PortableTextBlock = {
|
|
11
11
|
_key: '123',
|
|
12
|
-
_type: '
|
|
12
|
+
_type: 'block',
|
|
13
13
|
markDefs: [],
|
|
14
14
|
children: [{_key: '567', _type: 'span', text: 'Hello', marks: []}],
|
|
15
15
|
}
|
|
@@ -44,7 +44,6 @@ describe('RangeDecorations', () => {
|
|
|
44
44
|
onChange={onChange}
|
|
45
45
|
rangeDecorations={rangeDecorations}
|
|
46
46
|
ref={editorRef}
|
|
47
|
-
schemaType={schemaType}
|
|
48
47
|
value={value}
|
|
49
48
|
/>,
|
|
50
49
|
),
|
|
@@ -71,7 +70,6 @@ describe('RangeDecorations', () => {
|
|
|
71
70
|
onChange={onChange}
|
|
72
71
|
rangeDecorations={rangeDecorations}
|
|
73
72
|
ref={editorRef}
|
|
74
|
-
schemaType={schemaType}
|
|
75
73
|
value={value}
|
|
76
74
|
/>,
|
|
77
75
|
)
|
|
@@ -95,7 +93,6 @@ describe('RangeDecorations', () => {
|
|
|
95
93
|
onChange={onChange}
|
|
96
94
|
rangeDecorations={rangeDecorations}
|
|
97
95
|
ref={editorRef}
|
|
98
|
-
schemaType={schemaType}
|
|
99
96
|
value={value}
|
|
100
97
|
/>,
|
|
101
98
|
)
|
|
@@ -122,7 +119,6 @@ describe('RangeDecorations', () => {
|
|
|
122
119
|
onChange={onChange}
|
|
123
120
|
rangeDecorations={rangeDecorations}
|
|
124
121
|
ref={editorRef}
|
|
125
|
-
schemaType={schemaType}
|
|
126
122
|
value={value}
|
|
127
123
|
/>,
|
|
128
124
|
)
|
|
@@ -150,7 +146,6 @@ describe('RangeDecorations', () => {
|
|
|
150
146
|
onChange={onChange}
|
|
151
147
|
rangeDecorations={rangeDecorations}
|
|
152
148
|
ref={editorRef}
|
|
153
|
-
schemaType={schemaType}
|
|
154
149
|
value={value}
|
|
155
150
|
/>,
|
|
156
151
|
)
|
|
@@ -178,7 +173,6 @@ describe('RangeDecorations', () => {
|
|
|
178
173
|
onChange={onChange}
|
|
179
174
|
rangeDecorations={rangeDecorations}
|
|
180
175
|
ref={editorRef}
|
|
181
|
-
schemaType={schemaType}
|
|
182
176
|
value={value}
|
|
183
177
|
/>,
|
|
184
178
|
)
|
|
@@ -206,7 +200,6 @@ describe('RangeDecorations', () => {
|
|
|
206
200
|
onChange={onChange}
|
|
207
201
|
rangeDecorations={rangeDecorations}
|
|
208
202
|
ref={editorRef}
|
|
209
|
-
schemaType={schemaType}
|
|
210
203
|
value={value}
|
|
211
204
|
/>,
|
|
212
205
|
)
|
|
@@ -1,22 +1,10 @@
|
|
|
1
|
-
import {Schema} from '@sanity/schema'
|
|
2
1
|
import type {PortableTextBlock} from '@sanity/types'
|
|
3
2
|
import {render, waitFor} from '@testing-library/react'
|
|
4
3
|
import {createRef, type RefObject} from 'react'
|
|
5
4
|
import {describe, expect, test, vi} from 'vitest'
|
|
6
5
|
import type {EditorChange, EditorSelection} from '../../types/editor'
|
|
7
|
-
import {PortableTextEditable} from '../Editable'
|
|
8
6
|
import {PortableTextEditor} from '../PortableTextEditor'
|
|
9
|
-
|
|
10
|
-
const schema = Schema.compile({
|
|
11
|
-
types: [
|
|
12
|
-
{
|
|
13
|
-
name: 'portable-text',
|
|
14
|
-
type: 'array',
|
|
15
|
-
of: [{type: 'block'}, {type: 'custom image'}],
|
|
16
|
-
},
|
|
17
|
-
{name: 'custom image', type: 'object'},
|
|
18
|
-
],
|
|
19
|
-
}).get('portable-text')
|
|
7
|
+
import {PortableTextEditorTester} from './PortableTextEditorTester'
|
|
20
8
|
|
|
21
9
|
describe(PortableTextEditor.insertBlock.name, () => {
|
|
22
10
|
test('Scenario: Inserting a custom block without a selection #1', async () => {
|
|
@@ -38,15 +26,12 @@ describe(PortableTextEditor.insertBlock.name, () => {
|
|
|
38
26
|
const onChange: (change: EditorChange) => void = vi.fn()
|
|
39
27
|
|
|
40
28
|
render(
|
|
41
|
-
<
|
|
29
|
+
<PortableTextEditorTester
|
|
42
30
|
ref={editorRef}
|
|
43
|
-
schemaType={schema}
|
|
44
31
|
value={initialValue}
|
|
45
32
|
keyGenerator={() => 'bb'}
|
|
46
33
|
onChange={onChange}
|
|
47
|
-
|
|
48
|
-
<PortableTextEditable />
|
|
49
|
-
</PortableTextEditor>,
|
|
34
|
+
/>,
|
|
50
35
|
)
|
|
51
36
|
|
|
52
37
|
// Given an empty text block
|
|
@@ -107,15 +92,12 @@ describe(PortableTextEditor.insertBlock.name, () => {
|
|
|
107
92
|
const onChange: (change: EditorChange) => void = vi.fn()
|
|
108
93
|
|
|
109
94
|
render(
|
|
110
|
-
<
|
|
95
|
+
<PortableTextEditorTester
|
|
111
96
|
ref={editorRef}
|
|
112
|
-
schemaType={schema}
|
|
113
97
|
value={initialValue}
|
|
114
98
|
keyGenerator={() => 'bb'}
|
|
115
99
|
onChange={onChange}
|
|
116
|
-
|
|
117
|
-
<PortableTextEditable />
|
|
118
|
-
</PortableTextEditor>,
|
|
100
|
+
/>,
|
|
119
101
|
)
|
|
120
102
|
|
|
121
103
|
// Given an non-empty text block
|
|
@@ -180,15 +162,12 @@ describe(PortableTextEditor.insertBlock.name, () => {
|
|
|
180
162
|
const onChange: (change: EditorChange) => void = vi.fn()
|
|
181
163
|
|
|
182
164
|
render(
|
|
183
|
-
<
|
|
165
|
+
<PortableTextEditorTester
|
|
184
166
|
ref={editorRef}
|
|
185
|
-
schemaType={schema}
|
|
186
167
|
value={initialValue}
|
|
187
168
|
keyGenerator={() => 'bc'}
|
|
188
169
|
onChange={onChange}
|
|
189
|
-
|
|
190
|
-
<PortableTextEditable />
|
|
191
|
-
</PortableTextEditor>,
|
|
170
|
+
/>,
|
|
192
171
|
)
|
|
193
172
|
|
|
194
173
|
// Given an empty text block followed by an image
|