@portabletext/editor 1.21.5 → 1.22.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.map +1 -1
- package/lib/_chunks-cjs/selector.get-text-before.cjs +4 -4
- package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -1
- package/lib/_chunks-cjs/{util.get-block-start-point.cjs → util.reverse-selection.cjs} +12 -12
- package/lib/_chunks-cjs/util.reverse-selection.cjs.map +1 -0
- package/lib/_chunks-cjs/util.slice-blocks.cjs +75 -0
- package/lib/_chunks-cjs/util.slice-blocks.cjs.map +1 -0
- package/lib/_chunks-es/behavior.core.js.map +1 -1
- package/lib/_chunks-es/selector.get-text-before.js +1 -1
- package/lib/_chunks-es/{util.get-block-start-point.js → util.reverse-selection.js} +12 -12
- package/lib/_chunks-es/util.reverse-selection.js.map +1 -0
- package/lib/_chunks-es/util.slice-blocks.js +76 -0
- package/lib/_chunks-es/util.slice-blocks.js.map +1 -0
- package/lib/behaviors/index.cjs.map +1 -1
- package/lib/behaviors/index.d.cts +57 -0
- package/lib/behaviors/index.d.ts +57 -0
- package/lib/behaviors/index.js.map +1 -1
- package/lib/index.cjs +32 -19
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +130 -0
- package/lib/index.d.ts +130 -0
- package/lib/index.js +33 -20
- package/lib/index.js.map +1 -1
- package/lib/selectors/index.cjs +10 -4
- package/lib/selectors/index.cjs.map +1 -1
- package/lib/selectors/index.d.cts +5 -0
- package/lib/selectors/index.d.ts +5 -0
- package/lib/selectors/index.js +9 -2
- package/lib/selectors/index.js.map +1 -1
- package/lib/utils/index.cjs +4 -3
- package/lib/utils/index.cjs.map +1 -1
- package/lib/utils/index.d.cts +11 -0
- package/lib/utils/index.d.ts +11 -0
- package/lib/utils/index.js +3 -1
- package/lib/utils/index.js.map +1 -1
- package/package.json +11 -11
- package/src/behaviors/behavior.markdown.ts +42 -0
- package/src/behaviors/behavior.types.ts +15 -0
- package/src/editor/Editable.tsx +17 -0
- package/src/editor/define-schema.ts +24 -1
- package/src/editor/editor-event-listener.tsx +45 -0
- package/src/editor/editor-machine.ts +6 -26
- package/src/editor/editor-provider.tsx +27 -0
- package/src/editor/editor-selector.ts +21 -0
- package/src/editor/editor-snapshot.ts +37 -1
- package/src/editor/plugins/createWithInsertData.ts +6 -7
- package/src/selectors/index.ts +1 -0
- package/src/selectors/selector.get-selected-slice.ts +12 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/util.slice-blocks.test.ts +163 -0
- package/src/utils/util.slice-blocks.ts +143 -0
- package/lib/_chunks-cjs/util.get-block-start-point.cjs.map +0 -1
- package/lib/_chunks-es/util.get-block-start-point.js.map +0 -1
|
@@ -5,6 +5,51 @@ import {useEditor} from './editor-provider'
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @public
|
|
8
|
+
* Listen for events emitted by the editor. Must be used inside `EditorProvider`. Events available include:
|
|
9
|
+
* - 'blurred'
|
|
10
|
+
* - 'done loading'
|
|
11
|
+
* - 'editable'
|
|
12
|
+
* - 'error'
|
|
13
|
+
* - 'focused'
|
|
14
|
+
* - 'invalid value'
|
|
15
|
+
* - 'loading'
|
|
16
|
+
* - 'mutation'
|
|
17
|
+
* - 'patch'
|
|
18
|
+
* - 'read only'
|
|
19
|
+
* - 'ready'
|
|
20
|
+
* - 'selection'
|
|
21
|
+
* - 'value changed'
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* Listen and log events.
|
|
25
|
+
* ```tsx
|
|
26
|
+
* import {EditorEventListener, EditorProvider} from '@portabletext/editor'
|
|
27
|
+
*
|
|
28
|
+
* function MyComponent() {
|
|
29
|
+
* return (
|
|
30
|
+
* <EditorProvider>
|
|
31
|
+
* <EditorEventListener
|
|
32
|
+
* on={(event) => {
|
|
33
|
+
* console.log(event)
|
|
34
|
+
* }
|
|
35
|
+
* } />
|
|
36
|
+
* { ... }
|
|
37
|
+
* </EditorProvider>
|
|
38
|
+
* )
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
* @example
|
|
42
|
+
* Handle events when there is a mutation.
|
|
43
|
+
* ```tsx
|
|
44
|
+
* <EditorEventListener
|
|
45
|
+
* on={(event) => {
|
|
46
|
+
* if (event.type === 'mutation') {
|
|
47
|
+
* console.log('Value changed:', event.snapshot)
|
|
48
|
+
* }
|
|
49
|
+
* }}
|
|
50
|
+
* />
|
|
51
|
+
* ```
|
|
52
|
+
* @group Components
|
|
8
53
|
*/
|
|
9
54
|
export function EditorEventListener(props: {
|
|
10
55
|
on: (event: EditorEmittedEvent) => void
|
|
@@ -20,9 +20,6 @@ import {
|
|
|
20
20
|
type NativeBehaviorEvent,
|
|
21
21
|
type SyntheticBehaviorEvent,
|
|
22
22
|
} from '../behaviors/behavior.types'
|
|
23
|
-
import {toPortableTextRange} from '../internal-utils/ranges'
|
|
24
|
-
import {fromSlateValue} from '../internal-utils/values'
|
|
25
|
-
import {KEY_TO_VALUE_ELEMENT} from '../internal-utils/weakMaps'
|
|
26
23
|
import type {OmitFromUnion, PickFromUnion} from '../type-utils'
|
|
27
24
|
import type {
|
|
28
25
|
EditorSelection,
|
|
@@ -30,8 +27,7 @@ import type {
|
|
|
30
27
|
PortableTextSlateEditor,
|
|
31
28
|
} from '../types/editor'
|
|
32
29
|
import type {EditorSchema} from './define-schema'
|
|
33
|
-
import
|
|
34
|
-
import {getActiveDecorators} from './get-active-decorators'
|
|
30
|
+
import {createEditorSnapshot} from './editor-snapshot'
|
|
35
31
|
import {withApplyingBehaviorActions} from './with-applying-behavior-actions'
|
|
36
32
|
|
|
37
33
|
export * from 'xstate/guards'
|
|
@@ -342,27 +338,11 @@ export const editorMachine = setup({
|
|
|
342
338
|
return
|
|
343
339
|
}
|
|
344
340
|
|
|
345
|
-
const
|
|
346
|
-
event.editor
|
|
347
|
-
context.schema.block.name,
|
|
348
|
-
KEY_TO_VALUE_ELEMENT.get(event.editor),
|
|
349
|
-
)
|
|
350
|
-
const selection = toPortableTextRange(
|
|
351
|
-
value,
|
|
352
|
-
event.editor.selection,
|
|
353
|
-
context.schema,
|
|
354
|
-
)
|
|
355
|
-
|
|
356
|
-
const editorContext = {
|
|
357
|
-
activeDecorators: getActiveDecorators({
|
|
358
|
-
schema: context.schema,
|
|
359
|
-
slateEditorInstance: event.editor,
|
|
360
|
-
}),
|
|
341
|
+
const editorSnapshot = createEditorSnapshot({
|
|
342
|
+
editor: event.editor,
|
|
361
343
|
keyGenerator: context.keyGenerator,
|
|
362
344
|
schema: context.schema,
|
|
363
|
-
|
|
364
|
-
value,
|
|
365
|
-
} satisfies EditorContext
|
|
345
|
+
})
|
|
366
346
|
|
|
367
347
|
let behaviorOverwritten = false
|
|
368
348
|
|
|
@@ -370,7 +350,7 @@ export const editorMachine = setup({
|
|
|
370
350
|
const shouldRun =
|
|
371
351
|
eventBehavior.guard === undefined ||
|
|
372
352
|
eventBehavior.guard({
|
|
373
|
-
context:
|
|
353
|
+
context: editorSnapshot.context,
|
|
374
354
|
event: event.behaviorEvent,
|
|
375
355
|
})
|
|
376
356
|
|
|
@@ -380,7 +360,7 @@ export const editorMachine = setup({
|
|
|
380
360
|
|
|
381
361
|
const actionIntendSets = eventBehavior.actions.map((actionSet) =>
|
|
382
362
|
actionSet(
|
|
383
|
-
{context:
|
|
363
|
+
{context: editorSnapshot.context, event: event.behaviorEvent},
|
|
384
364
|
shouldRun,
|
|
385
365
|
),
|
|
386
366
|
)
|
|
@@ -23,6 +23,21 @@ export type EditorProviderProps = {
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* @public
|
|
26
|
+
* The EditorProvider component is used to set up the editor context and configure the Portable Text Editor.
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* import {EditorProvider} from '@portabletext/editor'
|
|
30
|
+
*
|
|
31
|
+
* function App() {
|
|
32
|
+
* return (
|
|
33
|
+
* <EditorProvider initialConfig={{ ... }} >
|
|
34
|
+
* ...
|
|
35
|
+
* </EditorProvider>
|
|
36
|
+
* )
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
* ```
|
|
40
|
+
* @group Components
|
|
26
41
|
*/
|
|
27
42
|
export function EditorProvider(props: EditorProviderProps) {
|
|
28
43
|
const editor = useCreateEditor(props.initialConfig)
|
|
@@ -66,6 +81,18 @@ export function EditorProvider(props: EditorProviderProps) {
|
|
|
66
81
|
|
|
67
82
|
/**
|
|
68
83
|
* @public
|
|
84
|
+
* Get the current editor context from the `EditorProvider`.
|
|
85
|
+
* Must be used inside the `EditorProvider` component.
|
|
86
|
+
* @returns The current editor object.
|
|
87
|
+
* @example
|
|
88
|
+
* ```tsx
|
|
89
|
+
* import { useEditor } from '@portabletext/editor'
|
|
90
|
+
*
|
|
91
|
+
* function MyComponent() {
|
|
92
|
+
* const editor = useEditor()
|
|
93
|
+
* }
|
|
94
|
+
* ```
|
|
95
|
+
* @group Hooks
|
|
69
96
|
*/
|
|
70
97
|
export function useEditor() {
|
|
71
98
|
const editor = React.useContext(EditorContext)
|
|
@@ -17,6 +17,27 @@ export type EditorSelector<TSelected> = (snapshot: EditorSnapshot) => TSelected
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* @public
|
|
20
|
+
* Hook to select a value from the editor state.
|
|
21
|
+
* @example
|
|
22
|
+
* Pass a selector as the second argument
|
|
23
|
+
* ```tsx
|
|
24
|
+
* import { useEditorSelector } from '@portabletext/editor'
|
|
25
|
+
*
|
|
26
|
+
* function MyComponent(editor) {
|
|
27
|
+
* const value = useEditorSelector(editor, selector)
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
* @example
|
|
31
|
+
* Pass an inline selector as the second argument.
|
|
32
|
+
* In this case, use the editor context to obtain the schema.
|
|
33
|
+
* ```tsx
|
|
34
|
+
* import { useEditorSelector } from '@portabletext/editor'
|
|
35
|
+
*
|
|
36
|
+
* function MyComponent(editor) {
|
|
37
|
+
* const schema = useEditorSelector(editor, (snapshot) => snapshot.context.schema)
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
* @group Hooks
|
|
20
41
|
*/
|
|
21
42
|
export function useEditorSelector<TSelected>(
|
|
22
43
|
editor: Editor,
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type {PortableTextBlock} from '@sanity/types'
|
|
2
|
-
import
|
|
2
|
+
import {toPortableTextRange} from '../internal-utils/ranges'
|
|
3
|
+
import {fromSlateValue} from '../internal-utils/values'
|
|
4
|
+
import {KEY_TO_VALUE_ELEMENT} from '../internal-utils/weakMaps'
|
|
5
|
+
import type {EditorSelection, PortableTextSlateEditor} from '../types/editor'
|
|
3
6
|
import type {EditorSchema} from './define-schema'
|
|
7
|
+
import {getActiveDecorators} from './get-active-decorators'
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
10
|
* @public
|
|
@@ -19,3 +23,35 @@ export type EditorContext = {
|
|
|
19
23
|
export type EditorSnapshot = {
|
|
20
24
|
context: EditorContext
|
|
21
25
|
}
|
|
26
|
+
|
|
27
|
+
export function createEditorSnapshot({
|
|
28
|
+
editor,
|
|
29
|
+
keyGenerator,
|
|
30
|
+
schema,
|
|
31
|
+
}: {
|
|
32
|
+
editor: PortableTextSlateEditor
|
|
33
|
+
keyGenerator: () => string
|
|
34
|
+
schema: EditorSchema
|
|
35
|
+
}) {
|
|
36
|
+
const value = fromSlateValue(
|
|
37
|
+
editor.children,
|
|
38
|
+
schema.block.name,
|
|
39
|
+
KEY_TO_VALUE_ELEMENT.get(editor),
|
|
40
|
+
)
|
|
41
|
+
const selection = toPortableTextRange(value, editor.selection, schema)
|
|
42
|
+
|
|
43
|
+
const context = {
|
|
44
|
+
activeDecorators: getActiveDecorators({
|
|
45
|
+
schema,
|
|
46
|
+
slateEditorInstance: editor,
|
|
47
|
+
}),
|
|
48
|
+
keyGenerator,
|
|
49
|
+
schema,
|
|
50
|
+
selection,
|
|
51
|
+
value,
|
|
52
|
+
} satisfies EditorContext
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
context,
|
|
56
|
+
} satisfies EditorSnapshot
|
|
57
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {htmlToBlocks
|
|
1
|
+
import {htmlToBlocks} from '@portabletext/block-tools'
|
|
2
2
|
import type {PortableTextBlock, PortableTextChild} from '@sanity/types'
|
|
3
3
|
import {isEqual, uniq} from 'lodash'
|
|
4
4
|
import {Editor, Range, Transforms, type Descendant, type Node} from 'slate'
|
|
@@ -194,9 +194,8 @@ export function createWithInsertData(
|
|
|
194
194
|
if (html) {
|
|
195
195
|
portableText = htmlToBlocks(html, schemaTypes.portableText, {
|
|
196
196
|
unstable_whitespaceOnPasteMode: whitespaceOnPasteMode,
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
) as PortableTextBlock[]
|
|
197
|
+
keyGenerator: editorActor.getSnapshot().context.keyGenerator,
|
|
198
|
+
}) as PortableTextBlock[]
|
|
200
199
|
fragment = toSlateValue(portableText, {schemaTypes})
|
|
201
200
|
insertedType = 'HTML'
|
|
202
201
|
|
|
@@ -214,9 +213,9 @@ export function createWithInsertData(
|
|
|
214
213
|
)
|
|
215
214
|
.join('')
|
|
216
215
|
const textToHtml = `<html><body>${blocks}</body></html>`
|
|
217
|
-
portableText = htmlToBlocks(textToHtml, schemaTypes.portableText
|
|
218
|
-
|
|
219
|
-
) as PortableTextBlock[]
|
|
216
|
+
portableText = htmlToBlocks(textToHtml, schemaTypes.portableText, {
|
|
217
|
+
keyGenerator: editorActor.getSnapshot().context.keyGenerator,
|
|
218
|
+
}) as PortableTextBlock[]
|
|
220
219
|
fragment = toSlateValue(portableText, {
|
|
221
220
|
schemaTypes,
|
|
222
221
|
})
|
package/src/selectors/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type {
|
|
|
9
9
|
export type {EditorSchema} from '../editor/define-schema'
|
|
10
10
|
export {getActiveListItem} from './selector.get-active-list-item'
|
|
11
11
|
export {getActiveStyle} from './selector.get-active-style'
|
|
12
|
+
export {getSelectedSlice} from './selector.get-selected-slice'
|
|
12
13
|
export {getSelectedSpans} from './selector.get-selected-spans'
|
|
13
14
|
export {getSelectionText} from './selector.get-selection-text'
|
|
14
15
|
export {getBlockTextBefore} from './selector.get-text-before'
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type {PortableTextBlock} from '@sanity/types'
|
|
2
|
+
import type {EditorSelector} from '../editor/editor-selector'
|
|
3
|
+
import {sliceBlocks} from '../utils'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
export const getSelectedSlice: EditorSelector<Array<PortableTextBlock>> = ({
|
|
9
|
+
context,
|
|
10
|
+
}) => {
|
|
11
|
+
return sliceBlocks({blocks: context.value, selection: context.selection})
|
|
12
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -9,3 +9,4 @@ export {getTextBlockText} from './util.get-text-block-text'
|
|
|
9
9
|
export {isEmptyTextBlock} from './util.is-empty-text-block'
|
|
10
10
|
export {isKeyedSegment} from './util.is-keyed-segment'
|
|
11
11
|
export {reverseSelection} from './util.reverse-selection'
|
|
12
|
+
export {sliceBlocks} from './util.slice-blocks'
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import type {PortableTextBlock, PortableTextTextBlock} from '@sanity/types'
|
|
2
|
+
import {describe, expect, test} from 'vitest'
|
|
3
|
+
import {sliceBlocks} from './util.slice-blocks'
|
|
4
|
+
|
|
5
|
+
const textBlock1: PortableTextTextBlock = {
|
|
6
|
+
_type: 'block',
|
|
7
|
+
_key: 'b1',
|
|
8
|
+
children: [
|
|
9
|
+
{
|
|
10
|
+
_type: 'span',
|
|
11
|
+
_key: 's1',
|
|
12
|
+
text: 'foo',
|
|
13
|
+
marks: ['strong'],
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
_type: 'span',
|
|
17
|
+
_key: 's2',
|
|
18
|
+
text: 'bar',
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
}
|
|
22
|
+
const textBlock2: PortableTextTextBlock = {
|
|
23
|
+
_type: 'block',
|
|
24
|
+
_key: 'b3',
|
|
25
|
+
children: [
|
|
26
|
+
{
|
|
27
|
+
_type: 'span',
|
|
28
|
+
_key: 's3',
|
|
29
|
+
text: 'baz',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const blocks: Array<PortableTextBlock> = [
|
|
35
|
+
textBlock1,
|
|
36
|
+
{
|
|
37
|
+
_type: 'image',
|
|
38
|
+
_key: 'b2',
|
|
39
|
+
},
|
|
40
|
+
textBlock2,
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
describe(sliceBlocks.name, () => {
|
|
44
|
+
test('sensible defaults', () => {
|
|
45
|
+
expect(sliceBlocks({blocks: [], selection: null})).toEqual([])
|
|
46
|
+
expect(sliceBlocks({blocks, selection: null})).toEqual([])
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('slicing a single block', () => {
|
|
50
|
+
expect(
|
|
51
|
+
sliceBlocks({
|
|
52
|
+
blocks,
|
|
53
|
+
selection: {
|
|
54
|
+
anchor: {
|
|
55
|
+
path: [{_key: 'b1'}, 'children', {_key: 's1'}],
|
|
56
|
+
offset: 0,
|
|
57
|
+
},
|
|
58
|
+
focus: {
|
|
59
|
+
path: [{_key: 'b1'}, 'children', {_key: 's1'}],
|
|
60
|
+
offset: 3,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
).toEqual([
|
|
65
|
+
{
|
|
66
|
+
...textBlock1,
|
|
67
|
+
children: [textBlock1.children[0]],
|
|
68
|
+
},
|
|
69
|
+
])
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('slicing a single span', () => {
|
|
73
|
+
expect(
|
|
74
|
+
sliceBlocks({
|
|
75
|
+
blocks,
|
|
76
|
+
selection: {
|
|
77
|
+
anchor: {
|
|
78
|
+
path: [{_key: 'b1'}, 'children', {_key: 's1'}],
|
|
79
|
+
offset: 1,
|
|
80
|
+
},
|
|
81
|
+
focus: {
|
|
82
|
+
path: [{_key: 'b1'}, 'children', {_key: 's1'}],
|
|
83
|
+
offset: 2,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
).toEqual([
|
|
88
|
+
{
|
|
89
|
+
...textBlock1,
|
|
90
|
+
children: [
|
|
91
|
+
{
|
|
92
|
+
...textBlock1.children[0],
|
|
93
|
+
text: 'o',
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
])
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('ending selection on a block object', () => {
|
|
101
|
+
expect(
|
|
102
|
+
sliceBlocks({
|
|
103
|
+
blocks,
|
|
104
|
+
selection: {
|
|
105
|
+
anchor: {
|
|
106
|
+
path: [{_key: 'b1'}, 'children', {_key: 's1'}],
|
|
107
|
+
offset: 3,
|
|
108
|
+
},
|
|
109
|
+
focus: {
|
|
110
|
+
path: [{_key: 'b2'}],
|
|
111
|
+
offset: 0,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
}),
|
|
115
|
+
).toEqual([
|
|
116
|
+
{
|
|
117
|
+
...textBlock1,
|
|
118
|
+
children: [
|
|
119
|
+
{
|
|
120
|
+
...textBlock1.children[0],
|
|
121
|
+
text: '',
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
blocks[1],
|
|
126
|
+
])
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('starting and ending mid-span', () => {
|
|
130
|
+
expect(
|
|
131
|
+
sliceBlocks({
|
|
132
|
+
blocks,
|
|
133
|
+
selection: {
|
|
134
|
+
anchor: {
|
|
135
|
+
path: [{_key: 'b1'}, 'children', {_key: 's1'}],
|
|
136
|
+
offset: 2,
|
|
137
|
+
},
|
|
138
|
+
focus: {path: [{_key: 'b3'}, 'children', {_key: 's3'}], offset: 1},
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
141
|
+
).toEqual([
|
|
142
|
+
{
|
|
143
|
+
...textBlock1,
|
|
144
|
+
children: [
|
|
145
|
+
{
|
|
146
|
+
...textBlock1.children[0],
|
|
147
|
+
text: 'o',
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
blocks[1],
|
|
152
|
+
{
|
|
153
|
+
...textBlock2,
|
|
154
|
+
children: [
|
|
155
|
+
{
|
|
156
|
+
...textBlock2.children[0],
|
|
157
|
+
text: 'az',
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
])
|
|
162
|
+
})
|
|
163
|
+
})
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isKeySegment,
|
|
3
|
+
isPortableTextSpan,
|
|
4
|
+
isPortableTextTextBlock,
|
|
5
|
+
type PortableTextBlock,
|
|
6
|
+
} from '@sanity/types'
|
|
7
|
+
import type {EditorSelection} from '../selectors'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
export function sliceBlocks({
|
|
13
|
+
blocks,
|
|
14
|
+
selection,
|
|
15
|
+
}: {
|
|
16
|
+
blocks: Array<PortableTextBlock>
|
|
17
|
+
selection: EditorSelection
|
|
18
|
+
}): Array<PortableTextBlock> {
|
|
19
|
+
const slice: Array<PortableTextBlock> = []
|
|
20
|
+
|
|
21
|
+
if (!selection) {
|
|
22
|
+
return slice
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let startBlock: PortableTextBlock | undefined
|
|
26
|
+
const middleBlocks: PortableTextBlock[] = []
|
|
27
|
+
let endBlock: PortableTextBlock | undefined
|
|
28
|
+
|
|
29
|
+
const startPoint = selection.backward ? selection.focus : selection.anchor
|
|
30
|
+
const endPoint = selection.backward ? selection.anchor : selection.focus
|
|
31
|
+
|
|
32
|
+
const startBlockKey = isKeySegment(startPoint.path[0])
|
|
33
|
+
? startPoint.path[0]._key
|
|
34
|
+
: undefined
|
|
35
|
+
const endBlockKey = isKeySegment(endPoint.path[0])
|
|
36
|
+
? endPoint.path[0]._key
|
|
37
|
+
: undefined
|
|
38
|
+
const startChildKey = isKeySegment(startPoint.path[2])
|
|
39
|
+
? startPoint.path[2]._key
|
|
40
|
+
: undefined
|
|
41
|
+
const endChildKey = isKeySegment(endPoint.path[2])
|
|
42
|
+
? endPoint.path[2]._key
|
|
43
|
+
: undefined
|
|
44
|
+
|
|
45
|
+
if (!startBlockKey || !endBlockKey) {
|
|
46
|
+
return slice
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const block of blocks) {
|
|
50
|
+
if (block._key === startBlockKey) {
|
|
51
|
+
if (isPortableTextTextBlock(block) && startChildKey) {
|
|
52
|
+
for (const child of block.children) {
|
|
53
|
+
if (child._key === startChildKey) {
|
|
54
|
+
if (isPortableTextSpan(child)) {
|
|
55
|
+
const text =
|
|
56
|
+
child._key === endChildKey
|
|
57
|
+
? child.text.slice(startPoint.offset, endPoint.offset)
|
|
58
|
+
: child.text.slice(startPoint.offset)
|
|
59
|
+
|
|
60
|
+
startBlock = {
|
|
61
|
+
...block,
|
|
62
|
+
children: [
|
|
63
|
+
{
|
|
64
|
+
...child,
|
|
65
|
+
text,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
}
|
|
69
|
+
break
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
startBlock = {
|
|
73
|
+
...block,
|
|
74
|
+
children: [child],
|
|
75
|
+
}
|
|
76
|
+
break
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (startBlock && isPortableTextTextBlock(startBlock)) {
|
|
80
|
+
startBlock.children.push(child)
|
|
81
|
+
|
|
82
|
+
if (
|
|
83
|
+
block._key === endBlockKey &&
|
|
84
|
+
endChildKey &&
|
|
85
|
+
child._key === endChildKey
|
|
86
|
+
) {
|
|
87
|
+
break
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (startBlockKey === endBlockKey) {
|
|
93
|
+
break
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
startBlock = block
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (block._key === endBlockKey) {
|
|
103
|
+
if (isPortableTextTextBlock(block) && endBlockKey) {
|
|
104
|
+
endBlock = {
|
|
105
|
+
...block,
|
|
106
|
+
children: [],
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const child of block.children) {
|
|
110
|
+
if (endBlock && isPortableTextTextBlock(endBlock)) {
|
|
111
|
+
if (child._key === endChildKey && isPortableTextSpan(child)) {
|
|
112
|
+
endBlock.children.push({
|
|
113
|
+
...child,
|
|
114
|
+
text: child.text.slice(endPoint.offset),
|
|
115
|
+
})
|
|
116
|
+
break
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
endBlock.children.push(child)
|
|
120
|
+
|
|
121
|
+
if (endChildKey && child._key === endChildKey) {
|
|
122
|
+
break
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
continue
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
endBlock = block
|
|
131
|
+
|
|
132
|
+
break
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
middleBlocks.push(block)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return [
|
|
139
|
+
...(startBlock ? [startBlock] : []),
|
|
140
|
+
...middleBlocks,
|
|
141
|
+
...(endBlock ? [endBlock] : []),
|
|
142
|
+
]
|
|
143
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"util.get-block-start-point.cjs","sources":["../../src/utils/util.reverse-selection.ts","../../src/utils/util.get-block-start-point.ts"],"sourcesContent":["import type {EditorSelection} from '../types/editor'\n\n/**\n * @public\n */\nexport function reverseSelection(\n selection: NonNullable<EditorSelection>,\n): NonNullable<EditorSelection> {\n if (selection.backward) {\n return {\n anchor: selection.focus,\n focus: selection.anchor,\n backward: false,\n }\n }\n\n return {\n anchor: selection.focus,\n focus: selection.anchor,\n backward: true,\n }\n}\n","import {\n isPortableTextTextBlock,\n type KeyedSegment,\n type PortableTextBlock,\n} from '@sanity/types'\nimport type {EditorSelectionPoint} from '../types/editor'\n\n/**\n * @public\n */\nexport function getBlockStartPoint({\n node,\n path,\n}: {\n node: PortableTextBlock\n path: [KeyedSegment]\n}): EditorSelectionPoint {\n if (isPortableTextTextBlock(node)) {\n return {\n path: [...path, 'children', {_key: node.children[0]._key}],\n offset: 0,\n }\n }\n\n return {\n path,\n offset: 0,\n }\n}\n"],"names":["reverseSelection","selection","backward","anchor","focus","getBlockStartPoint","node","path","isPortableTextTextBlock","_key","children","offset"],"mappings":";;AAKO,SAASA,iBACdC,WAC8B;AAC9B,SAAIA,UAAUC,WACL;AAAA,IACLC,QAAQF,UAAUG;AAAAA,IAClBA,OAAOH,UAAUE;AAAAA,IACjBD,UAAU;AAAA,EAAA,IAIP;AAAA,IACLC,QAAQF,UAAUG;AAAAA,IAClBA,OAAOH,UAAUE;AAAAA,IACjBD,UAAU;AAAA,EACZ;AACF;ACXO,SAASG,mBAAmB;AAAA,EACjCC;AAAAA,EACAC;AAIF,GAAyB;AACnBC,SAAAA,MAAAA,wBAAwBF,IAAI,IACvB;AAAA,IACLC,MAAM,CAAC,GAAGA,MAAM,YAAY;AAAA,MAACE,MAAMH,KAAKI,SAAS,CAAC,EAAED;AAAAA,IAAAA,CAAK;AAAA,IACzDE,QAAQ;AAAA,EAAA,IAIL;AAAA,IACLJ;AAAAA,IACAI,QAAQ;AAAA,EACV;AACF;;;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"util.get-block-start-point.js","sources":["../../src/utils/util.reverse-selection.ts","../../src/utils/util.get-block-start-point.ts"],"sourcesContent":["import type {EditorSelection} from '../types/editor'\n\n/**\n * @public\n */\nexport function reverseSelection(\n selection: NonNullable<EditorSelection>,\n): NonNullable<EditorSelection> {\n if (selection.backward) {\n return {\n anchor: selection.focus,\n focus: selection.anchor,\n backward: false,\n }\n }\n\n return {\n anchor: selection.focus,\n focus: selection.anchor,\n backward: true,\n }\n}\n","import {\n isPortableTextTextBlock,\n type KeyedSegment,\n type PortableTextBlock,\n} from '@sanity/types'\nimport type {EditorSelectionPoint} from '../types/editor'\n\n/**\n * @public\n */\nexport function getBlockStartPoint({\n node,\n path,\n}: {\n node: PortableTextBlock\n path: [KeyedSegment]\n}): EditorSelectionPoint {\n if (isPortableTextTextBlock(node)) {\n return {\n path: [...path, 'children', {_key: node.children[0]._key}],\n offset: 0,\n }\n }\n\n return {\n path,\n offset: 0,\n }\n}\n"],"names":["reverseSelection","selection","backward","anchor","focus","getBlockStartPoint","node","path","isPortableTextTextBlock","_key","children","offset"],"mappings":";AAKO,SAASA,iBACdC,WAC8B;AAC9B,SAAIA,UAAUC,WACL;AAAA,IACLC,QAAQF,UAAUG;AAAAA,IAClBA,OAAOH,UAAUE;AAAAA,IACjBD,UAAU;AAAA,EAAA,IAIP;AAAA,IACLC,QAAQF,UAAUG;AAAAA,IAClBA,OAAOH,UAAUE;AAAAA,IACjBD,UAAU;AAAA,EACZ;AACF;ACXO,SAASG,mBAAmB;AAAA,EACjCC;AAAAA,EACAC;AAIF,GAAyB;AACnBC,SAAAA,wBAAwBF,IAAI,IACvB;AAAA,IACLC,MAAM,CAAC,GAAGA,MAAM,YAAY;AAAA,MAACE,MAAMH,KAAKI,SAAS,CAAC,EAAED;AAAAA,IAAAA,CAAK;AAAA,IACzDE,QAAQ;AAAA,EAAA,IAIL;AAAA,IACLJ;AAAAA,IACAI,QAAQ;AAAA,EACV;AACF;"}
|