@portabletext/editor 1.1.0 → 1.1.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/README.md +3 -0
- package/lib/index.d.mts +1680 -12
- package/lib/index.d.ts +1680 -12
- package/lib/index.esm.js +310 -162
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +310 -163
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +310 -162
- package/lib/index.mjs.map +1 -1
- package/package.json +25 -38
- package/src/editor/Editable.tsx +51 -50
- package/src/editor/PortableTextEditor.tsx +42 -26
- package/src/editor/__tests__/PortableTextEditor.test.tsx +11 -12
- package/src/editor/__tests__/PortableTextEditorTester.tsx +2 -5
- package/src/editor/__tests__/RangeDecorations.test.tsx +6 -7
- package/src/editor/__tests__/handleClick.test.tsx +27 -7
- package/src/editor/__tests__/insert-block.test.tsx +6 -6
- package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +8 -8
- package/src/editor/__tests__/self-solving.test.tsx +176 -0
- package/src/editor/components/Element.tsx +15 -17
- package/src/editor/components/Leaf.tsx +40 -35
- package/src/editor/components/SlateContainer.tsx +2 -2
- package/src/editor/components/Synchronizer.tsx +62 -34
- package/src/editor/editor-machine.ts +195 -0
- package/src/editor/hooks/usePortableTextEditor.ts +1 -1
- package/src/editor/hooks/usePortableTextEditorSelection.tsx +12 -14
- package/src/editor/hooks/useSyncValue.test.tsx +9 -9
- package/src/editor/hooks/useSyncValue.ts +16 -19
- package/src/editor/nodes/DefaultAnnotation.tsx +1 -2
- package/src/editor/nodes/DefaultObject.tsx +1 -1
- package/src/editor/plugins/__tests__/createWithInsertData.test.tsx +2 -5
- package/src/editor/plugins/__tests__/withEditableAPIDelete.test.tsx +28 -28
- package/src/editor/plugins/__tests__/withEditableAPIGetFragment.test.tsx +17 -17
- package/src/editor/plugins/__tests__/withEditableAPIInsert.test.tsx +8 -8
- package/src/editor/plugins/__tests__/withEditableAPISelectionsOverlapping.test.tsx +6 -6
- package/src/editor/plugins/__tests__/withPortableTextLists.test.tsx +2 -2
- package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +47 -49
- package/src/editor/plugins/__tests__/withPortableTextSelections.test.tsx +22 -11
- package/src/editor/plugins/__tests__/withUndoRedo.test.tsx +9 -9
- package/src/editor/plugins/createWithEditableAPI.ts +8 -8
- package/src/editor/plugins/createWithHotKeys.ts +8 -12
- package/src/editor/plugins/createWithInsertBreak.ts +4 -4
- package/src/editor/plugins/createWithInsertData.ts +11 -16
- package/src/editor/plugins/createWithMaxBlocks.ts +1 -1
- package/src/editor/plugins/createWithObjectKeys.ts +10 -3
- package/src/editor/plugins/createWithPatches.ts +9 -12
- package/src/editor/plugins/createWithPlaceholderBlock.ts +2 -2
- package/src/editor/plugins/createWithPortableTextBlockStyle.ts +13 -5
- package/src/editor/plugins/createWithPortableTextLists.ts +3 -4
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +24 -10
- package/src/editor/plugins/createWithPortableTextSelections.ts +9 -10
- package/src/editor/plugins/createWithSchemaTypes.ts +13 -4
- package/src/editor/plugins/createWithUndoRedo.ts +3 -7
- package/src/editor/plugins/createWithUtils.ts +6 -6
- package/src/editor/plugins/index.ts +21 -11
- package/src/index.ts +9 -3
- package/src/types/editor.ts +33 -33
- package/src/types/options.ts +3 -3
- package/src/types/slate.ts +4 -4
- package/src/utils/__tests__/dmpToOperations.test.ts +4 -4
- package/src/utils/__tests__/operationToPatches.test.ts +62 -62
- package/src/utils/__tests__/patchToOperations.test.ts +40 -40
- package/src/utils/__tests__/ranges.test.ts +2 -2
- package/src/utils/__tests__/valueNormalization.test.tsx +14 -2
- package/src/utils/__tests__/values.test.ts +17 -17
- package/src/utils/applyPatch.ts +10 -12
- package/src/utils/getPortableTextMemberSchemaTypes.ts +8 -8
- package/src/utils/operationToPatches.ts +5 -9
- package/src/utils/paths.ts +5 -5
- package/src/utils/ranges.ts +4 -5
- package/src/utils/selection.ts +2 -2
- package/src/utils/ucs2Indices.ts +2 -2
- package/src/utils/validateValue.ts +3 -25
- package/src/utils/values.ts +7 -8
- package/src/utils/weakMaps.ts +2 -2
- package/src/utils/withChanges.ts +1 -1
- package/src/utils/withUndoRedo.ts +1 -1
- package/src/utils/withoutPatching.ts +1 -1
- package/src/editor/__tests__/utils.ts +0 -45
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {describe, expect, jest, test} from '@jest/globals'
|
|
2
1
|
import {Schema} from '@sanity/schema'
|
|
3
|
-
import
|
|
2
|
+
import type {PortableTextBlock} from '@sanity/types'
|
|
4
3
|
import {render, waitFor} from '@testing-library/react'
|
|
5
4
|
import {createRef, type RefObject} from 'react'
|
|
6
|
-
import {
|
|
5
|
+
import {describe, expect, test, vi} from 'vitest'
|
|
6
|
+
import type {EditorChange, EditorSelection} from '../../types/editor'
|
|
7
7
|
import {PortableTextEditable} from '../Editable'
|
|
8
8
|
import {PortableTextEditor} from '../PortableTextEditor'
|
|
9
9
|
|
|
@@ -35,7 +35,7 @@ describe(PortableTextEditor.insertBlock.name, () => {
|
|
|
35
35
|
style: 'normal',
|
|
36
36
|
}
|
|
37
37
|
const initialValue: Array<PortableTextBlock> = [emptyTextBlock]
|
|
38
|
-
const onChange: (change: EditorChange) => void =
|
|
38
|
+
const onChange: (change: EditorChange) => void = vi.fn()
|
|
39
39
|
|
|
40
40
|
render(
|
|
41
41
|
<PortableTextEditor
|
|
@@ -104,7 +104,7 @@ describe(PortableTextEditor.insertBlock.name, () => {
|
|
|
104
104
|
style: 'normal',
|
|
105
105
|
}
|
|
106
106
|
const initialValue: Array<PortableTextBlock> = [nonEmptyTextBlock]
|
|
107
|
-
const onChange: (change: EditorChange) => void =
|
|
107
|
+
const onChange: (change: EditorChange) => void = vi.fn()
|
|
108
108
|
|
|
109
109
|
render(
|
|
110
110
|
<PortableTextEditor
|
|
@@ -177,7 +177,7 @@ describe(PortableTextEditor.insertBlock.name, () => {
|
|
|
177
177
|
_type: 'image',
|
|
178
178
|
}
|
|
179
179
|
const initialValue: Array<PortableTextBlock> = [emptyTextBlock, imageBlock]
|
|
180
|
-
const onChange: (change: EditorChange) => void =
|
|
180
|
+
const onChange: (change: EditorChange) => void = vi.fn()
|
|
181
181
|
|
|
182
182
|
render(
|
|
183
183
|
<PortableTextEditor
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {type PortableTextBlock} from '@sanity/types'
|
|
1
|
+
import type {PortableTextBlock} from '@sanity/types'
|
|
3
2
|
import {render, waitFor} from '@testing-library/react'
|
|
4
3
|
import {createRef, type RefObject} from 'react'
|
|
4
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
5
5
|
import {PortableTextEditor} from '../PortableTextEditor'
|
|
6
6
|
import {PortableTextEditorTester, schemaType} from './PortableTextEditorTester'
|
|
7
7
|
|
|
@@ -24,7 +24,7 @@ describe('when PTE would display warnings, instead it self solves', () => {
|
|
|
24
24
|
},
|
|
25
25
|
]
|
|
26
26
|
|
|
27
|
-
const onChange =
|
|
27
|
+
const onChange = vi.fn()
|
|
28
28
|
render(
|
|
29
29
|
<PortableTextEditorTester
|
|
30
30
|
onChange={onChange}
|
|
@@ -80,7 +80,7 @@ describe('when PTE would display warnings, instead it self solves', () => {
|
|
|
80
80
|
},
|
|
81
81
|
]
|
|
82
82
|
|
|
83
|
-
const onChange =
|
|
83
|
+
const onChange = vi.fn()
|
|
84
84
|
render(
|
|
85
85
|
<PortableTextEditorTester
|
|
86
86
|
onChange={onChange}
|
|
@@ -137,7 +137,7 @@ describe('when PTE would display warnings, instead it self solves', () => {
|
|
|
137
137
|
},
|
|
138
138
|
]
|
|
139
139
|
|
|
140
|
-
const onChange =
|
|
140
|
+
const onChange = vi.fn()
|
|
141
141
|
render(
|
|
142
142
|
<PortableTextEditorTester
|
|
143
143
|
onChange={onChange}
|
|
@@ -209,7 +209,7 @@ describe('when PTE would display warnings, instead it self solves', () => {
|
|
|
209
209
|
},
|
|
210
210
|
]
|
|
211
211
|
|
|
212
|
-
const onChange =
|
|
212
|
+
const onChange = vi.fn()
|
|
213
213
|
render(
|
|
214
214
|
<PortableTextEditorTester
|
|
215
215
|
onChange={onChange}
|
|
@@ -273,7 +273,7 @@ describe('when PTE would display warnings, instead it self solves', () => {
|
|
|
273
273
|
},
|
|
274
274
|
]
|
|
275
275
|
|
|
276
|
-
const onChange =
|
|
276
|
+
const onChange = vi.fn()
|
|
277
277
|
render(
|
|
278
278
|
<PortableTextEditorTester
|
|
279
279
|
onChange={onChange}
|
|
@@ -316,7 +316,7 @@ describe('when PTE would display warnings, instead it self solves', () => {
|
|
|
316
316
|
const editorRef: RefObject<PortableTextEditor> = createRef()
|
|
317
317
|
const initialValue = [] as PortableTextBlock[]
|
|
318
318
|
|
|
319
|
-
const onChange =
|
|
319
|
+
const onChange = vi.fn()
|
|
320
320
|
render(
|
|
321
321
|
<PortableTextEditorTester
|
|
322
322
|
onChange={onChange}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import type {JSONValue, Patch} from '@portabletext/patches'
|
|
2
|
+
import {Schema} from '@sanity/schema'
|
|
3
|
+
import type {PortableTextBlock, PortableTextSpan} from '@sanity/types'
|
|
4
|
+
import {render, waitFor} from '@testing-library/react'
|
|
5
|
+
import {createRef, type ComponentProps, type RefObject} from 'react'
|
|
6
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
7
|
+
import {getTextSelection} from '../../../e2e-tests/__tests__/gherkin-step-helpers'
|
|
8
|
+
import {PortableTextEditable} from '../Editable'
|
|
9
|
+
import {PortableTextEditor} from '../PortableTextEditor'
|
|
10
|
+
|
|
11
|
+
const schema = Schema.compile({
|
|
12
|
+
types: [
|
|
13
|
+
{
|
|
14
|
+
name: 'portable-text',
|
|
15
|
+
type: 'array',
|
|
16
|
+
of: [{type: 'block'}, {type: 'image'}],
|
|
17
|
+
},
|
|
18
|
+
{name: 'image', type: 'object'},
|
|
19
|
+
],
|
|
20
|
+
}).get('portable-text')
|
|
21
|
+
type OnChange = ComponentProps<typeof PortableTextEditor>['onChange']
|
|
22
|
+
|
|
23
|
+
function block(
|
|
24
|
+
props?: Partial<Omit<PortableTextBlock, '_type'>>,
|
|
25
|
+
): PortableTextBlock {
|
|
26
|
+
return {
|
|
27
|
+
_type: 'block',
|
|
28
|
+
...(props ?? {}),
|
|
29
|
+
} as PortableTextBlock
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function span(
|
|
33
|
+
props?: Partial<Omit<PortableTextSpan, '_type'>>,
|
|
34
|
+
): PortableTextSpan {
|
|
35
|
+
return {
|
|
36
|
+
_type: 'span',
|
|
37
|
+
...(props ?? {}),
|
|
38
|
+
} as PortableTextSpan
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe('Feature: Self-solving', () => {
|
|
42
|
+
it('Scenario: Missing .markDefs and .marks are added after the editor is made dirty', async () => {
|
|
43
|
+
const editorRef: RefObject<PortableTextEditor> = createRef()
|
|
44
|
+
const onChange = vi.fn<OnChange>()
|
|
45
|
+
const initialValue = [
|
|
46
|
+
block({
|
|
47
|
+
_key: 'b1',
|
|
48
|
+
children: [
|
|
49
|
+
span({
|
|
50
|
+
_key: 's1',
|
|
51
|
+
text: 'foo',
|
|
52
|
+
}),
|
|
53
|
+
],
|
|
54
|
+
style: 'normal',
|
|
55
|
+
}),
|
|
56
|
+
]
|
|
57
|
+
const spanPatch: Patch = {
|
|
58
|
+
type: 'set',
|
|
59
|
+
path: [{_key: 'b1'}, 'children', {_key: 's1'}, 'marks'],
|
|
60
|
+
value: [],
|
|
61
|
+
origin: 'local',
|
|
62
|
+
}
|
|
63
|
+
const blockPatch: Patch = {
|
|
64
|
+
type: 'set',
|
|
65
|
+
path: [{_key: 'b1'}],
|
|
66
|
+
value: block({
|
|
67
|
+
_key: 'b1',
|
|
68
|
+
children: [
|
|
69
|
+
span({
|
|
70
|
+
_key: 's1',
|
|
71
|
+
text: 'foo',
|
|
72
|
+
marks: [],
|
|
73
|
+
}),
|
|
74
|
+
],
|
|
75
|
+
style: 'normal',
|
|
76
|
+
markDefs: [],
|
|
77
|
+
}) as JSONValue,
|
|
78
|
+
origin: 'local',
|
|
79
|
+
}
|
|
80
|
+
const strongPatch: Patch = {
|
|
81
|
+
type: 'set',
|
|
82
|
+
path: [{_key: 'b1'}, 'children', {_key: 's1'}, 'marks'],
|
|
83
|
+
value: ['strong'],
|
|
84
|
+
origin: 'local',
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
render(
|
|
88
|
+
<PortableTextEditor
|
|
89
|
+
ref={editorRef}
|
|
90
|
+
schemaType={schema}
|
|
91
|
+
value={initialValue}
|
|
92
|
+
onChange={onChange}
|
|
93
|
+
>
|
|
94
|
+
<PortableTextEditable />
|
|
95
|
+
</PortableTextEditor>,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
await waitFor(() => {
|
|
99
|
+
if (editorRef.current) {
|
|
100
|
+
expect(onChange).toHaveBeenNthCalledWith(1, {
|
|
101
|
+
type: 'value',
|
|
102
|
+
value: initialValue,
|
|
103
|
+
})
|
|
104
|
+
expect(onChange).toHaveBeenNthCalledWith(2, {
|
|
105
|
+
type: 'ready',
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
await waitFor(() => {
|
|
111
|
+
if (editorRef.current) {
|
|
112
|
+
PortableTextEditor.select(
|
|
113
|
+
editorRef.current,
|
|
114
|
+
getTextSelection(initialValue, 'foo'),
|
|
115
|
+
)
|
|
116
|
+
PortableTextEditor.toggleMark(editorRef.current, 'strong')
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
await waitFor(() => {
|
|
121
|
+
if (editorRef.current) {
|
|
122
|
+
expect(onChange).toHaveBeenNthCalledWith(3, {
|
|
123
|
+
type: 'selection',
|
|
124
|
+
selection: {
|
|
125
|
+
...getTextSelection(initialValue, 'foo'),
|
|
126
|
+
backward: false,
|
|
127
|
+
},
|
|
128
|
+
})
|
|
129
|
+
expect(onChange).toHaveBeenNthCalledWith(4, {
|
|
130
|
+
type: 'patch',
|
|
131
|
+
patch: spanPatch,
|
|
132
|
+
})
|
|
133
|
+
expect(onChange).toHaveBeenNthCalledWith(5, {
|
|
134
|
+
type: 'patch',
|
|
135
|
+
patch: blockPatch,
|
|
136
|
+
})
|
|
137
|
+
expect(onChange).toHaveBeenNthCalledWith(6, {
|
|
138
|
+
type: 'patch',
|
|
139
|
+
patch: strongPatch,
|
|
140
|
+
})
|
|
141
|
+
expect(onChange).toHaveBeenNthCalledWith(7, {
|
|
142
|
+
type: 'selection',
|
|
143
|
+
selection: {
|
|
144
|
+
...getTextSelection(initialValue, 'foo'),
|
|
145
|
+
backward: false,
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
expect(onChange).toHaveBeenNthCalledWith(8, {
|
|
149
|
+
type: 'selection',
|
|
150
|
+
selection: {
|
|
151
|
+
...getTextSelection(initialValue, 'foo'),
|
|
152
|
+
backward: false,
|
|
153
|
+
},
|
|
154
|
+
})
|
|
155
|
+
expect(onChange).toHaveBeenNthCalledWith(9, {
|
|
156
|
+
type: 'mutation',
|
|
157
|
+
patches: [spanPatch, blockPatch, strongPatch],
|
|
158
|
+
snapshot: [
|
|
159
|
+
block({
|
|
160
|
+
_key: 'b1',
|
|
161
|
+
children: [
|
|
162
|
+
span({
|
|
163
|
+
_key: 's1',
|
|
164
|
+
text: 'foo',
|
|
165
|
+
marks: ['strong'],
|
|
166
|
+
}),
|
|
167
|
+
],
|
|
168
|
+
style: 'normal',
|
|
169
|
+
markDefs: [],
|
|
170
|
+
}),
|
|
171
|
+
],
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
})
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
type PortableTextObject,
|
|
7
|
-
type PortableTextTextBlock,
|
|
1
|
+
import type {
|
|
2
|
+
Path,
|
|
3
|
+
PortableTextChild,
|
|
4
|
+
PortableTextObject,
|
|
5
|
+
PortableTextTextBlock,
|
|
8
6
|
} from '@sanity/types'
|
|
9
7
|
import {useMemo, useRef, type FunctionComponent, type ReactElement} from 'react'
|
|
10
8
|
import {Editor, Range, Element as SlateElement} from 'slate'
|
|
@@ -14,13 +12,13 @@ import {
|
|
|
14
12
|
useSlateStatic,
|
|
15
13
|
type RenderElementProps,
|
|
16
14
|
} from 'slate-react'
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
import type {
|
|
16
|
+
BlockRenderProps,
|
|
17
|
+
PortableTextMemberSchemaTypes,
|
|
18
|
+
RenderBlockFunction,
|
|
19
|
+
RenderChildFunction,
|
|
20
|
+
RenderListItemFunction,
|
|
21
|
+
RenderStyleFunction,
|
|
24
22
|
} from '../../types/editor'
|
|
25
23
|
import {debugWithName} from '../../utils/debug'
|
|
26
24
|
import {fromSlateValue} from '../../utils/values'
|
|
@@ -91,7 +89,7 @@ export const Element: FunctionComponent<ElementProps> = ({
|
|
|
91
89
|
|
|
92
90
|
let renderedBlock = children
|
|
93
91
|
|
|
94
|
-
let className
|
|
92
|
+
let className: string | undefined
|
|
95
93
|
|
|
96
94
|
const blockPath: Path = useMemo(() => [{_key: element._key}], [element])
|
|
97
95
|
|
|
@@ -180,7 +178,7 @@ export const Element: FunctionComponent<ElementProps> = ({
|
|
|
180
178
|
editorElementRef: blockRef,
|
|
181
179
|
})
|
|
182
180
|
}
|
|
183
|
-
let level
|
|
181
|
+
let level: number | undefined
|
|
184
182
|
if (isListItem) {
|
|
185
183
|
if (typeof element.level === 'number') {
|
|
186
184
|
level = element.level
|
|
@@ -276,7 +274,7 @@ export const Element: FunctionComponent<ElementProps> = ({
|
|
|
276
274
|
schemaTypes.block.name,
|
|
277
275
|
KEY_TO_VALUE_ELEMENT.get(editor),
|
|
278
276
|
)[0]
|
|
279
|
-
let renderedBlockFromProps
|
|
277
|
+
let renderedBlockFromProps: JSX.Element | undefined
|
|
280
278
|
if (renderBlock) {
|
|
281
279
|
const _props: Omit<BlockRenderProps, 'type'> = Object.defineProperty(
|
|
282
280
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import type {
|
|
2
|
+
Path,
|
|
3
|
+
PortableTextObject,
|
|
4
|
+
PortableTextTextBlock,
|
|
5
5
|
} from '@sanity/types'
|
|
6
6
|
import {isEqual, uniq} from 'lodash'
|
|
7
7
|
import {
|
|
@@ -15,14 +15,14 @@ import {
|
|
|
15
15
|
} from 'react'
|
|
16
16
|
import {Text} from 'slate'
|
|
17
17
|
import {useSelected, type RenderLeafProps} from 'slate-react'
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
import type {
|
|
19
|
+
BlockAnnotationRenderProps,
|
|
20
|
+
BlockChildRenderProps,
|
|
21
|
+
BlockDecoratorRenderProps,
|
|
22
|
+
PortableTextMemberSchemaTypes,
|
|
23
|
+
RenderAnnotationFunction,
|
|
24
|
+
RenderChildFunction,
|
|
25
|
+
RenderDecoratorFunction,
|
|
26
26
|
} from '../../types/editor'
|
|
27
27
|
import {debugWithName} from '../../utils/debug'
|
|
28
28
|
import {usePortableTextEditor} from '../hooks/usePortableTextEditor'
|
|
@@ -141,28 +141,30 @@ export const Leaf = (props: LeafProps) => {
|
|
|
141
141
|
if (!shouldTrackSelectionAndFocus) {
|
|
142
142
|
return undefined
|
|
143
143
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
setSelectedFromRange()
|
|
160
|
-
return
|
|
144
|
+
|
|
145
|
+
const onBlur = portableTextEditor.editorActor.on('blur', () => {
|
|
146
|
+
setFocused(false)
|
|
147
|
+
setSelected(false)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const onFocus = portableTextEditor.editorActor.on('focus', () => {
|
|
151
|
+
const sel = PortableTextEditor.getSelection(portableTextEditor)
|
|
152
|
+
if (
|
|
153
|
+
sel &&
|
|
154
|
+
isEqual(sel.focus.path, path) &&
|
|
155
|
+
PortableTextEditor.isCollapsedSelection(portableTextEditor)
|
|
156
|
+
) {
|
|
157
|
+
setFocused(true)
|
|
161
158
|
}
|
|
162
|
-
|
|
159
|
+
setSelectedFromRange()
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
const onSelection = portableTextEditor.editorActor.on(
|
|
163
|
+
'selection',
|
|
164
|
+
(event) => {
|
|
163
165
|
if (
|
|
164
|
-
|
|
165
|
-
isEqual(
|
|
166
|
+
event.selection &&
|
|
167
|
+
isEqual(event.selection.focus.path, path) &&
|
|
166
168
|
PortableTextEditor.isCollapsedSelection(portableTextEditor)
|
|
167
169
|
) {
|
|
168
170
|
setFocused(true)
|
|
@@ -170,10 +172,13 @@ export const Leaf = (props: LeafProps) => {
|
|
|
170
172
|
setFocused(false)
|
|
171
173
|
}
|
|
172
174
|
setSelectedFromRange()
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
+
},
|
|
176
|
+
)
|
|
177
|
+
|
|
175
178
|
return () => {
|
|
176
|
-
|
|
179
|
+
onBlur.unsubscribe()
|
|
180
|
+
onFocus.unsubscribe()
|
|
181
|
+
onSelection.unsubscribe()
|
|
177
182
|
}
|
|
178
183
|
}, [
|
|
179
184
|
path,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {useEffect, useMemo, useState, type PropsWithChildren} from 'react'
|
|
2
2
|
import {createEditor} from 'slate'
|
|
3
3
|
import {Slate, withReact} from 'slate-react'
|
|
4
|
-
import
|
|
4
|
+
import type {PatchObservable} from '../../types/editor'
|
|
5
5
|
import {debugWithName} from '../../utils/debug'
|
|
6
6
|
import {KEY_TO_SLATE_ELEMENT, KEY_TO_VALUE_ELEMENT} from '../../utils/weakMaps'
|
|
7
7
|
import {withPlugins} from '../plugins'
|
|
8
|
-
import
|
|
8
|
+
import type {PortableTextEditor} from '../PortableTextEditor'
|
|
9
9
|
|
|
10
10
|
const debug = debugWithName('component:PortableTextEditor:SlateContainer')
|
|
11
11
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import type {Patch} from '@portabletext/patches'
|
|
2
|
+
import type {PortableTextBlock} from '@sanity/types'
|
|
3
3
|
import {throttle} from 'lodash'
|
|
4
4
|
import {useCallback, useEffect, useMemo, useRef} from 'react'
|
|
5
5
|
import {Editor} from 'slate'
|
|
6
6
|
import {useSlate} from 'slate-react'
|
|
7
|
-
import
|
|
7
|
+
import type {EditorChange} from '../../types/editor'
|
|
8
8
|
import {debugWithName} from '../../utils/debug'
|
|
9
9
|
import {IS_PROCESSING_LOCAL_CHANGES} from '../../utils/weakMaps'
|
|
10
|
+
import type {EditorActor} from '../editor-machine'
|
|
10
11
|
import {usePortableTextEditor} from '../hooks/usePortableTextEditor'
|
|
11
12
|
import {usePortableTextEditorKeyGenerator} from '../hooks/usePortableTextEditorKeyGenerator'
|
|
12
13
|
import {usePortableTextEditorReadOnlyStatus} from '../hooks/usePortableTextReadOnly'
|
|
@@ -23,7 +24,7 @@ const FLUSH_PATCHES_THROTTLED_MS = process.env.NODE_ENV === 'test' ? 500 : 1000
|
|
|
23
24
|
* @internal
|
|
24
25
|
*/
|
|
25
26
|
export interface SynchronizerProps {
|
|
26
|
-
|
|
27
|
+
editorActor: EditorActor
|
|
27
28
|
getValue: () => Array<PortableTextBlock> | undefined
|
|
28
29
|
onChange: (change: EditorChange) => void
|
|
29
30
|
value: Array<PortableTextBlock> | undefined
|
|
@@ -37,12 +38,12 @@ export function Synchronizer(props: SynchronizerProps) {
|
|
|
37
38
|
const portableTextEditor = usePortableTextEditor()
|
|
38
39
|
const keyGenerator = usePortableTextEditorKeyGenerator()
|
|
39
40
|
const readOnly = usePortableTextEditorReadOnlyStatus()
|
|
40
|
-
const {
|
|
41
|
+
const {editorActor, getValue, onChange, value} = props
|
|
41
42
|
const pendingPatches = useRef<Patch[]>([])
|
|
42
43
|
|
|
43
44
|
const syncValue = useSyncValue({
|
|
45
|
+
editorActor,
|
|
44
46
|
keyGenerator,
|
|
45
|
-
onChange,
|
|
46
47
|
portableTextEditor,
|
|
47
48
|
readOnly,
|
|
48
49
|
})
|
|
@@ -60,7 +61,7 @@ export function Synchronizer(props: SynchronizerProps) {
|
|
|
60
61
|
debug(`Patches:\n${JSON.stringify(pendingPatches.current, null, 2)}`)
|
|
61
62
|
}
|
|
62
63
|
const snapshot = getValue()
|
|
63
|
-
|
|
64
|
+
editorActor.send({
|
|
64
65
|
type: 'mutation',
|
|
65
66
|
patches: pendingPatches.current,
|
|
66
67
|
snapshot,
|
|
@@ -68,7 +69,7 @@ export function Synchronizer(props: SynchronizerProps) {
|
|
|
68
69
|
pendingPatches.current = []
|
|
69
70
|
}
|
|
70
71
|
IS_PROCESSING_LOCAL_CHANGES.set(slateEditor, false)
|
|
71
|
-
}, [slateEditor, getValue
|
|
72
|
+
}, [editorActor, slateEditor, getValue])
|
|
72
73
|
|
|
73
74
|
const onFlushPendingPatchesThrottled = useMemo(() => {
|
|
74
75
|
return throttle(
|
|
@@ -99,50 +100,78 @@ export function Synchronizer(props: SynchronizerProps) {
|
|
|
99
100
|
|
|
100
101
|
// Subscribe to, and handle changes from the editor
|
|
101
102
|
useEffect(() => {
|
|
102
|
-
debug('Subscribing to editor changes
|
|
103
|
-
const sub =
|
|
104
|
-
switch (
|
|
103
|
+
debug('Subscribing to editor changes')
|
|
104
|
+
const sub = editorActor.on('*', (event) => {
|
|
105
|
+
switch (event.type) {
|
|
105
106
|
case 'patch':
|
|
106
107
|
IS_PROCESSING_LOCAL_CHANGES.set(slateEditor, true)
|
|
107
|
-
pendingPatches.current.push(
|
|
108
|
+
pendingPatches.current.push(event.patch)
|
|
108
109
|
onFlushPendingPatchesThrottled()
|
|
109
|
-
onChange(
|
|
110
|
+
onChange(event)
|
|
110
111
|
break
|
|
112
|
+
case 'loading': {
|
|
113
|
+
onChange({type: 'loading', isLoading: true})
|
|
114
|
+
break
|
|
115
|
+
}
|
|
116
|
+
case 'done loading': {
|
|
117
|
+
onChange({type: 'loading', isLoading: false})
|
|
118
|
+
break
|
|
119
|
+
}
|
|
120
|
+
case 'offline': {
|
|
121
|
+
onChange({type: 'connection', value: 'offline'})
|
|
122
|
+
break
|
|
123
|
+
}
|
|
124
|
+
case 'online': {
|
|
125
|
+
onChange({type: 'connection', value: 'online'})
|
|
126
|
+
break
|
|
127
|
+
}
|
|
128
|
+
case 'value changed': {
|
|
129
|
+
onChange({type: 'value', value: event.value})
|
|
130
|
+
break
|
|
131
|
+
}
|
|
132
|
+
case 'invalid value': {
|
|
133
|
+
onChange({
|
|
134
|
+
type: 'invalidValue',
|
|
135
|
+
resolution: event.resolution,
|
|
136
|
+
value: event.value,
|
|
137
|
+
})
|
|
138
|
+
break
|
|
139
|
+
}
|
|
140
|
+
case 'error': {
|
|
141
|
+
onChange({
|
|
142
|
+
...event,
|
|
143
|
+
level: 'warning',
|
|
144
|
+
})
|
|
145
|
+
break
|
|
146
|
+
}
|
|
111
147
|
default:
|
|
112
|
-
onChange(
|
|
148
|
+
onChange(event)
|
|
113
149
|
}
|
|
114
150
|
})
|
|
115
151
|
return () => {
|
|
116
|
-
debug('Unsubscribing to changes
|
|
152
|
+
debug('Unsubscribing to changes')
|
|
117
153
|
sub.unsubscribe()
|
|
118
154
|
}
|
|
119
|
-
}, [
|
|
155
|
+
}, [editorActor, onFlushPendingPatchesThrottled, slateEditor])
|
|
120
156
|
|
|
121
157
|
// Sync the value when going online
|
|
122
158
|
const handleOnline = useCallback(() => {
|
|
123
159
|
debug('Editor is online, syncing from props.value')
|
|
124
|
-
change$.next({type: 'connection', value: 'online'})
|
|
125
160
|
syncValue(value)
|
|
126
|
-
}, [
|
|
127
|
-
|
|
128
|
-
const handleOffline = useCallback(() => {
|
|
129
|
-
debug('Editor is offline')
|
|
130
|
-
change$.next({type: 'connection', value: 'offline'})
|
|
131
|
-
}, [change$])
|
|
161
|
+
}, [syncValue, value])
|
|
132
162
|
|
|
133
163
|
// Notify about window online and offline status changes
|
|
134
164
|
useEffect(() => {
|
|
135
|
-
|
|
136
|
-
window.addEventListener('online', handleOnline)
|
|
137
|
-
window.addEventListener('offline', handleOffline)
|
|
138
|
-
}
|
|
139
|
-
return () => {
|
|
165
|
+
const subscription = editorActor.on('online', () => {
|
|
140
166
|
if (portableTextEditor.props.patches$) {
|
|
141
|
-
|
|
142
|
-
window.removeEventListener('offline', handleOffline)
|
|
167
|
+
handleOnline()
|
|
143
168
|
}
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
return () => {
|
|
172
|
+
subscription.unsubscribe()
|
|
144
173
|
}
|
|
145
|
-
})
|
|
174
|
+
}, [editorActor])
|
|
146
175
|
|
|
147
176
|
// This hook must be set up after setting up the subscription above, or it will not pick up validation errors from the useSyncValue hook.
|
|
148
177
|
// This will cause the editor to not be able to signal a validation error and offer invalid value resolution of the initial value.
|
|
@@ -152,11 +181,10 @@ export function Synchronizer(props: SynchronizerProps) {
|
|
|
152
181
|
syncValue(value)
|
|
153
182
|
// Signal that we have our first value, and are ready to roll.
|
|
154
183
|
if (isInitialValueFromProps.current) {
|
|
155
|
-
|
|
156
|
-
change$.next({type: 'ready'})
|
|
184
|
+
editorActor.send({type: 'ready'})
|
|
157
185
|
isInitialValueFromProps.current = false
|
|
158
186
|
}
|
|
159
|
-
}, [
|
|
187
|
+
}, [editorActor, syncValue, value])
|
|
160
188
|
|
|
161
189
|
return null
|
|
162
190
|
}
|