@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.
Files changed (79) hide show
  1. package/README.md +3 -0
  2. package/lib/index.d.mts +1680 -12
  3. package/lib/index.d.ts +1680 -12
  4. package/lib/index.esm.js +310 -162
  5. package/lib/index.esm.js.map +1 -1
  6. package/lib/index.js +310 -163
  7. package/lib/index.js.map +1 -1
  8. package/lib/index.mjs +310 -162
  9. package/lib/index.mjs.map +1 -1
  10. package/package.json +25 -38
  11. package/src/editor/Editable.tsx +51 -50
  12. package/src/editor/PortableTextEditor.tsx +42 -26
  13. package/src/editor/__tests__/PortableTextEditor.test.tsx +11 -12
  14. package/src/editor/__tests__/PortableTextEditorTester.tsx +2 -5
  15. package/src/editor/__tests__/RangeDecorations.test.tsx +6 -7
  16. package/src/editor/__tests__/handleClick.test.tsx +27 -7
  17. package/src/editor/__tests__/insert-block.test.tsx +6 -6
  18. package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +8 -8
  19. package/src/editor/__tests__/self-solving.test.tsx +176 -0
  20. package/src/editor/components/Element.tsx +15 -17
  21. package/src/editor/components/Leaf.tsx +40 -35
  22. package/src/editor/components/SlateContainer.tsx +2 -2
  23. package/src/editor/components/Synchronizer.tsx +62 -34
  24. package/src/editor/editor-machine.ts +195 -0
  25. package/src/editor/hooks/usePortableTextEditor.ts +1 -1
  26. package/src/editor/hooks/usePortableTextEditorSelection.tsx +12 -14
  27. package/src/editor/hooks/useSyncValue.test.tsx +9 -9
  28. package/src/editor/hooks/useSyncValue.ts +16 -19
  29. package/src/editor/nodes/DefaultAnnotation.tsx +1 -2
  30. package/src/editor/nodes/DefaultObject.tsx +1 -1
  31. package/src/editor/plugins/__tests__/createWithInsertData.test.tsx +2 -5
  32. package/src/editor/plugins/__tests__/withEditableAPIDelete.test.tsx +28 -28
  33. package/src/editor/plugins/__tests__/withEditableAPIGetFragment.test.tsx +17 -17
  34. package/src/editor/plugins/__tests__/withEditableAPIInsert.test.tsx +8 -8
  35. package/src/editor/plugins/__tests__/withEditableAPISelectionsOverlapping.test.tsx +6 -6
  36. package/src/editor/plugins/__tests__/withPortableTextLists.test.tsx +2 -2
  37. package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +47 -49
  38. package/src/editor/plugins/__tests__/withPortableTextSelections.test.tsx +22 -11
  39. package/src/editor/plugins/__tests__/withUndoRedo.test.tsx +9 -9
  40. package/src/editor/plugins/createWithEditableAPI.ts +8 -8
  41. package/src/editor/plugins/createWithHotKeys.ts +8 -12
  42. package/src/editor/plugins/createWithInsertBreak.ts +4 -4
  43. package/src/editor/plugins/createWithInsertData.ts +11 -16
  44. package/src/editor/plugins/createWithMaxBlocks.ts +1 -1
  45. package/src/editor/plugins/createWithObjectKeys.ts +10 -3
  46. package/src/editor/plugins/createWithPatches.ts +9 -12
  47. package/src/editor/plugins/createWithPlaceholderBlock.ts +2 -2
  48. package/src/editor/plugins/createWithPortableTextBlockStyle.ts +13 -5
  49. package/src/editor/plugins/createWithPortableTextLists.ts +3 -4
  50. package/src/editor/plugins/createWithPortableTextMarkModel.ts +24 -10
  51. package/src/editor/plugins/createWithPortableTextSelections.ts +9 -10
  52. package/src/editor/plugins/createWithSchemaTypes.ts +13 -4
  53. package/src/editor/plugins/createWithUndoRedo.ts +3 -7
  54. package/src/editor/plugins/createWithUtils.ts +6 -6
  55. package/src/editor/plugins/index.ts +21 -11
  56. package/src/index.ts +9 -3
  57. package/src/types/editor.ts +33 -33
  58. package/src/types/options.ts +3 -3
  59. package/src/types/slate.ts +4 -4
  60. package/src/utils/__tests__/dmpToOperations.test.ts +4 -4
  61. package/src/utils/__tests__/operationToPatches.test.ts +62 -62
  62. package/src/utils/__tests__/patchToOperations.test.ts +40 -40
  63. package/src/utils/__tests__/ranges.test.ts +2 -2
  64. package/src/utils/__tests__/valueNormalization.test.tsx +14 -2
  65. package/src/utils/__tests__/values.test.ts +17 -17
  66. package/src/utils/applyPatch.ts +10 -12
  67. package/src/utils/getPortableTextMemberSchemaTypes.ts +8 -8
  68. package/src/utils/operationToPatches.ts +5 -9
  69. package/src/utils/paths.ts +5 -5
  70. package/src/utils/ranges.ts +4 -5
  71. package/src/utils/selection.ts +2 -2
  72. package/src/utils/ucs2Indices.ts +2 -2
  73. package/src/utils/validateValue.ts +3 -25
  74. package/src/utils/values.ts +7 -8
  75. package/src/utils/weakMaps.ts +2 -2
  76. package/src/utils/withChanges.ts +1 -1
  77. package/src/utils/withUndoRedo.ts +1 -1
  78. package/src/utils/withoutPatching.ts +1 -1
  79. 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 {type PortableTextBlock} from '@sanity/types'
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 {type EditorChange, type EditorSelection} from '../../types/editor'
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 = jest.fn()
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 = jest.fn()
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 = jest.fn()
180
+ const onChange: (change: EditorChange) => void = vi.fn()
181
181
 
182
182
  render(
183
183
  <PortableTextEditor
@@ -1,7 +1,7 @@
1
- import {describe, expect, it, jest} from '@jest/globals'
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 = jest.fn()
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 = jest.fn()
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 = jest.fn()
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 = jest.fn()
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 = jest.fn()
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 = jest.fn()
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
- /* eslint-disable complexity */
2
- /* eslint-disable max-statements */
3
- import {
4
- type Path,
5
- type PortableTextChild,
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
- type BlockRenderProps,
19
- type PortableTextMemberSchemaTypes,
20
- type RenderBlockFunction,
21
- type RenderChildFunction,
22
- type RenderListItemFunction,
23
- type RenderStyleFunction,
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
- type Path,
3
- type PortableTextObject,
4
- type PortableTextTextBlock,
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
- type BlockAnnotationRenderProps,
20
- type BlockChildRenderProps,
21
- type BlockDecoratorRenderProps,
22
- type PortableTextMemberSchemaTypes,
23
- type RenderAnnotationFunction,
24
- type RenderChildFunction,
25
- type RenderDecoratorFunction,
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
- const sub = portableTextEditor.change$.subscribe((next) => {
145
- if (next.type === 'blur') {
146
- setFocused(false)
147
- setSelected(false)
148
- return
149
- }
150
- if (next.type === '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)
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
- if (next.type === 'selection') {
159
+ setSelectedFromRange()
160
+ })
161
+
162
+ const onSelection = portableTextEditor.editorActor.on(
163
+ 'selection',
164
+ (event) => {
163
165
  if (
164
- next.selection &&
165
- isEqual(next.selection.focus.path, path) &&
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
- sub.unsubscribe()
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 {type PatchObservable} from '../../types/editor'
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 {type PortableTextEditor} from '../PortableTextEditor'
8
+ import type {PortableTextEditor} from '../PortableTextEditor'
9
9
 
10
10
  const debug = debugWithName('component:PortableTextEditor:SlateContainer')
11
11
 
@@ -1,12 +1,13 @@
1
- import {type Patch} from '@portabletext/patches'
2
- import {type PortableTextBlock} from '@sanity/types'
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 {type EditorChange, type EditorChanges} from '../../types/editor'
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
- change$: EditorChanges
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 {change$, getValue, onChange, value} = props
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
- change$.next({
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, change$])
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 = change$.subscribe((next: EditorChange): void => {
104
- switch (next.type) {
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(next.patch)
108
+ pendingPatches.current.push(event.patch)
108
109
  onFlushPendingPatchesThrottled()
109
- onChange(next)
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(next)
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
- }, [change$, onChange, onFlushPendingPatchesThrottled, slateEditor])
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
- }, [change$, syncValue, value])
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
- if (portableTextEditor.props.patches$) {
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
- window.removeEventListener('online', handleOnline)
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
- change$.next({type: 'loading', isLoading: false})
156
- change$.next({type: 'ready'})
184
+ editorActor.send({type: 'ready'})
157
185
  isInitialValueFromProps.current = false
158
186
  }
159
- }, [change$, syncValue, value])
187
+ }, [editorActor, syncValue, value])
160
188
 
161
189
  return null
162
190
  }