@portabletext/editor 1.10.2 → 1.11.1

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.
@@ -0,0 +1,81 @@
1
+ import React, {useMemo} from 'react'
2
+ import {Slate} from 'slate-react'
3
+ import {Synchronizer} from './components/Synchronizer'
4
+ import {EditorActorContext} from './editor-actor-context'
5
+ import {PortableTextEditorContext} from './hooks/usePortableTextEditor'
6
+ import {PortableTextEditorSelectionProvider} from './hooks/usePortableTextEditorSelection'
7
+ import {
8
+ PortableTextEditor,
9
+ RouteEventsToChanges,
10
+ type PortableTextEditorProps,
11
+ } from './PortableTextEditor'
12
+ import {useEditor, type Editor, type EditorConfig} from './use-editor'
13
+
14
+ const EditorContext = React.createContext<Editor | undefined>(undefined)
15
+
16
+ /**
17
+ * @alpha
18
+ */
19
+ export type EditorProviderProps = {
20
+ config: EditorConfig
21
+ children?: React.ReactNode
22
+ }
23
+
24
+ /**
25
+ * @alpha
26
+ */
27
+ export function EditorProvider(props: EditorProviderProps) {
28
+ const editor = useEditor(props.config)
29
+ const editorActor = editor._internal.editorActor
30
+ const slateEditor = editor._internal.slateEditor
31
+ const editable = editor.editable
32
+ const portableTextEditor = useMemo(
33
+ () =>
34
+ new PortableTextEditor({
35
+ editor,
36
+ } as unknown as PortableTextEditorProps),
37
+ [editor],
38
+ )
39
+
40
+ return (
41
+ <EditorContext.Provider value={editor}>
42
+ <RouteEventsToChanges
43
+ editorActor={editorActor}
44
+ onChange={(change) => {
45
+ portableTextEditor.change$.next(change)
46
+ }}
47
+ />
48
+ <Synchronizer
49
+ editorActor={editorActor}
50
+ getValue={editable.getValue}
51
+ portableTextEditor={portableTextEditor}
52
+ slateEditor={slateEditor.instance}
53
+ />
54
+ <EditorActorContext.Provider value={editorActor}>
55
+ <Slate
56
+ editor={slateEditor.instance}
57
+ initialValue={slateEditor.initialValue}
58
+ >
59
+ <PortableTextEditorContext.Provider value={portableTextEditor}>
60
+ <PortableTextEditorSelectionProvider editorActor={editorActor}>
61
+ {props.children}
62
+ </PortableTextEditorSelectionProvider>
63
+ </PortableTextEditorContext.Provider>
64
+ </Slate>
65
+ </EditorActorContext.Provider>
66
+ </EditorContext.Provider>
67
+ )
68
+ }
69
+
70
+ /**
71
+ * @alpha
72
+ */
73
+ export function useEditorContext() {
74
+ const editor = React.useContext(EditorContext)
75
+
76
+ if (!editor) {
77
+ throw new Error('No Editor set. Use EditorProvider to set one.')
78
+ }
79
+
80
+ return editor
81
+ }
@@ -2,7 +2,6 @@ import type {PortableTextBlock} from '@sanity/types'
2
2
  import {debounce, isEqual} from 'lodash'
3
3
  import {useCallback, useMemo, useRef} from 'react'
4
4
  import {Editor, Text, Transforms, type Descendant, type Node} from 'slate'
5
- import {useSlate} from 'slate-react'
6
5
  import type {PortableTextSlateEditor} from '../../types/editor'
7
6
  import {debugWithName} from '../../utils/debug'
8
7
  import {validateValue} from '../../utils/validateValue'
@@ -26,6 +25,7 @@ export interface UseSyncValueProps {
26
25
  editorActor: EditorActor
27
26
  portableTextEditor: PortableTextEditor
28
27
  readOnly: boolean
28
+ slateEditor: PortableTextSlateEditor
29
29
  }
30
30
 
31
31
  const CURRENT_VALUE = new WeakMap<
@@ -51,10 +51,9 @@ export function useSyncValue(
51
51
  value: PortableTextBlock[] | undefined,
52
52
  userCallbackFn?: () => void,
53
53
  ) => void {
54
- const {editorActor, portableTextEditor, readOnly} = props
54
+ const {editorActor, portableTextEditor, readOnly, slateEditor} = props
55
55
  const schemaTypes = editorActor.getSnapshot().context.schema
56
56
  const previousValue = useRef<PortableTextBlock[] | undefined>()
57
- const slateEditor = useSlate()
58
57
  const updateValueFunctionRef =
59
58
  useRef<(value: PortableTextBlock[] | undefined) => void>()
60
59
 
@@ -14,6 +14,7 @@ export function createWithMaxBlocks(editorActor: EditorActor) {
14
14
  const {apply} = editor
15
15
  editor.apply = (operation) => {
16
16
  if (editorActor.getSnapshot().context.readOnly) {
17
+ apply(operation)
17
18
  return
18
19
  }
19
20
 
@@ -22,6 +22,7 @@ export function createWithPlaceholderBlock(
22
22
 
23
23
  editor.apply = (op) => {
24
24
  if (editorActor.getSnapshot().context.readOnly) {
25
+ apply(op)
25
26
  return
26
27
  }
27
28
 
@@ -115,6 +115,7 @@ export function createWithUndoRedo(
115
115
  const {apply} = editor
116
116
  editor.apply = (op: Operation) => {
117
117
  if (editorActor.getSnapshot().context.readOnly) {
118
+ apply(op)
118
119
  return
119
120
  }
120
121
 
@@ -21,11 +21,6 @@ export interface OriginalEditorFunctions {
21
21
  normalizeNode: (entry: NodeEntry<Node>) => void
22
22
  }
23
23
 
24
- const originalFnMap = new WeakMap<
25
- PortableTextSlateEditor,
26
- OriginalEditorFunctions
27
- >()
28
-
29
24
  type PluginsOptions = {
30
25
  editorActor: EditorActor
31
26
  subscriptions: Array<() => () => void>
@@ -38,17 +33,6 @@ export const withPlugins = <T extends Editor>(
38
33
  const e = editor as T & PortableTextSlateEditor
39
34
  const {editorActor} = options
40
35
  const schemaTypes = editorActor.getSnapshot().context.schema
41
- if (e.destroy) {
42
- e.destroy()
43
- } else {
44
- // Save a copy of the original editor functions here before they were changed by plugins.
45
- // We will put them back when .destroy is called (see below).
46
- originalFnMap.set(e, {
47
- apply: e.apply,
48
- onChange: e.onChange,
49
- normalizeNode: e.normalizeNode,
50
- })
51
- }
52
36
  const operationToPatches = createOperationToPatches(schemaTypes)
53
37
  const withObjectKeys = createWithObjectKeys(editorActor, schemaTypes)
54
38
  const withSchemaTypes = createWithSchemaTypes({
@@ -92,17 +76,6 @@ export const withPlugins = <T extends Editor>(
92
76
  options.subscriptions,
93
77
  )
94
78
 
95
- e.destroy = () => {
96
- const originalFunctions = originalFnMap.get(e)
97
- if (!originalFunctions) {
98
- throw new Error('Could not find pristine versions of editor functions')
99
- }
100
- e.apply = originalFunctions.apply
101
- e.history = {undos: [], redos: []}
102
- e.normalizeNode = originalFunctions.normalizeNode
103
- e.onChange = originalFunctions.onChange
104
- }
105
-
106
79
  // Ordering is important here, selection dealing last, data manipulation in the middle and core model stuff first.
107
80
  return withEventListeners(
108
81
  withSchemaTypes(
@@ -3,7 +3,15 @@ import type {
3
3
  ArraySchemaType,
4
4
  PortableTextBlock,
5
5
  } from '@sanity/types'
6
- import {useActorRef, useSelector} from '@xstate/react'
6
+ import {useActorRef} from '@xstate/react'
7
+ import {useCallback, useMemo} from 'react'
8
+ import {
9
+ createActor,
10
+ type ActorRef,
11
+ type EventObject,
12
+ type Snapshot,
13
+ } from 'xstate'
14
+ import type {EditableAPI} from '../types/editor'
7
15
  import {getPortableTextMemberSchemaTypes} from '../utils/getPortableTextMemberSchemaTypes'
8
16
  import {compileType} from '../utils/schema'
9
17
  import type {Behavior, PickFromUnion} from './behavior/behavior.types'
@@ -12,9 +20,11 @@ import {compileSchemaDefinition, type SchemaDefinition} from './define-schema'
12
20
  import {
13
21
  editorMachine,
14
22
  type EditorActor,
23
+ type EditorEmittedEvent,
15
24
  type InternalEditorEvent,
16
25
  } from './editor-machine'
17
26
  import {defaultKeyGenerator} from './key-generator'
27
+ import {createEditableAPI} from './plugins/createWithEditableAPI'
18
28
 
19
29
  /**
20
30
  * @alpha
@@ -22,6 +32,8 @@ import {defaultKeyGenerator} from './key-generator'
22
32
  export type EditorConfig = {
23
33
  behaviors?: Array<Behavior>
24
34
  keyGenerator?: () => string
35
+ maxBlocks?: number
36
+ readOnly?: boolean
25
37
  initialValue?: Array<PortableTextBlock>
26
38
  } & (
27
39
  | {
@@ -53,8 +65,8 @@ export type EditorEvent = PickFromUnion<
53
65
  */
54
66
  export type Editor = {
55
67
  send: (event: EditorEvent) => void
56
- on: EditorActor['on']
57
- readOnly: boolean
68
+ on: ActorRef<Snapshot<unknown>, EventObject, EditorEmittedEvent>['on']
69
+ editable: EditableAPI
58
70
  _internal: {
59
71
  editorActor: EditorActor
60
72
  slateEditor: SlateEditor
@@ -64,33 +76,90 @@ export type Editor = {
64
76
  /**
65
77
  * @alpha
66
78
  */
67
- export function useEditor(config: EditorConfig): Editor {
68
- const editorActor = useActorRef(editorMachine, {
69
- input: {
70
- behaviors: config.behaviors,
71
- keyGenerator: config.keyGenerator ?? defaultKeyGenerator,
72
- schema: config.schemaDefinition
73
- ? compileSchemaDefinition(config.schemaDefinition)
74
- : getPortableTextMemberSchemaTypes(
75
- config.schema.hasOwnProperty('jsonType')
76
- ? config.schema
77
- : compileType(config.schema),
78
- ),
79
- value: config.initialValue,
80
- },
79
+ export function createEditor(config: EditorConfig): Editor {
80
+ const editorActor = createActor(editorMachine, {
81
+ input: editorConfigToMachineInput(config),
81
82
  })
83
+
84
+ editorActor.start()
85
+
82
86
  const slateEditor = createSlateEditor({editorActor})
83
- const readOnly = useSelector(editorActor, (s) => s.context.readOnly)
87
+ const editable = createEditableAPI(slateEditor.instance, editorActor)
84
88
 
85
89
  return {
86
90
  send: (event) => {
87
91
  editorActor.send(event)
88
92
  },
89
- on: (event, listener) => editorActor.on(event, listener),
90
- readOnly,
93
+ on: (event, listener) =>
94
+ editorActor.on(
95
+ event,
96
+ // @ts-ignore
97
+ listener,
98
+ ),
99
+ editable,
91
100
  _internal: {
92
101
  editorActor,
93
102
  slateEditor,
94
103
  },
95
104
  }
96
105
  }
106
+
107
+ /**
108
+ * @alpha
109
+ */
110
+ export function useEditor(config: EditorConfig): Editor {
111
+ const editorActor = useActorRef(editorMachine, {
112
+ input: editorConfigToMachineInput(config),
113
+ })
114
+ const slateEditor = createSlateEditor({editorActor})
115
+ const editable = useMemo(
116
+ () => createEditableAPI(slateEditor.instance, editorActor),
117
+ [slateEditor.instance, editorActor],
118
+ )
119
+ const send = useCallback(
120
+ (event: EditorEvent) => {
121
+ editorActor.send(event)
122
+ },
123
+ [editorActor],
124
+ )
125
+ const on = useCallback<Editor['on']>(
126
+ (event, listener) =>
127
+ editorActor.on(
128
+ event,
129
+ // @ts-ignore
130
+ listener,
131
+ ),
132
+ [editorActor],
133
+ )
134
+ const editor: Editor = useMemo(
135
+ () => ({
136
+ send,
137
+ on,
138
+ editable,
139
+ _internal: {
140
+ editorActor,
141
+ slateEditor,
142
+ },
143
+ }),
144
+ [send, on, editable, editorActor, slateEditor],
145
+ )
146
+
147
+ return editor
148
+ }
149
+
150
+ function editorConfigToMachineInput(config: EditorConfig) {
151
+ return {
152
+ behaviors: config.behaviors,
153
+ keyGenerator: config.keyGenerator ?? defaultKeyGenerator,
154
+ maxBlocks: config.maxBlocks,
155
+ readOnly: config.readOnly,
156
+ schema: config.schemaDefinition
157
+ ? compileSchemaDefinition(config.schemaDefinition)
158
+ : getPortableTextMemberSchemaTypes(
159
+ config.schema.hasOwnProperty('jsonType')
160
+ ? config.schema
161
+ : compileType(config.schema),
162
+ ),
163
+ value: config.initialValue,
164
+ } as const
165
+ }
package/src/index.ts CHANGED
@@ -1,14 +1,14 @@
1
1
  export type {Patch} from '@portabletext/patches'
2
2
  export type {PortableTextBlock, PortableTextChild} from '@sanity/types'
3
3
  export {coreBehavior, coreBehaviors} from './editor/behavior/behavior.core'
4
- export {
5
- createMarkdownBehaviors,
6
- type MarkdownBehaviorsConfig,
7
- } from './editor/behavior/behavior.markdown'
8
4
  export {
9
5
  createLinkBehaviors,
10
6
  type LinkBehaviorsConfig,
11
7
  } from './editor/behavior/behavior.links'
8
+ export {
9
+ createMarkdownBehaviors,
10
+ type MarkdownBehaviorsConfig,
11
+ } from './editor/behavior/behavior.markdown'
12
12
  export {
13
13
  defineBehavior,
14
14
  type Behavior,
@@ -17,6 +17,7 @@ export {
17
17
  type BehaviorContext,
18
18
  type BehaviorEvent,
19
19
  type BehaviorGuard,
20
+ type OmitFromUnion,
20
21
  type PickFromUnion,
21
22
  } from './editor/behavior/behavior.types'
22
23
  export type {SlateEditor} from './editor/create-slate-editor'
@@ -27,15 +28,22 @@ export {
27
28
  } from './editor/define-schema'
28
29
  export {PortableTextEditable} from './editor/Editable'
29
30
  export type {PortableTextEditableProps} from './editor/Editable'
31
+ export {EditorEventListener} from './editor/editor-event-listener'
30
32
  export {
31
33
  editorMachine,
32
34
  type EditorActor,
35
+ type EditorEmittedEvent,
33
36
  type InternalEditorEmittedEvent,
34
37
  type InternalEditorEvent,
35
38
  type MutationEvent,
36
39
  type PatchEvent,
37
40
  type PatchesEvent,
38
41
  } from './editor/editor-machine'
42
+ export {
43
+ EditorProvider,
44
+ useEditorContext,
45
+ type EditorProviderProps,
46
+ } from './editor/editor-provider'
39
47
  export {usePortableTextEditor} from './editor/hooks/usePortableTextEditor'
40
48
  export {usePortableTextEditorSelection} from './editor/hooks/usePortableTextEditorSelection'
41
49
  export {defaultKeyGenerator as keyGenerator} from './editor/key-generator'
@@ -124,7 +124,6 @@ export type EditorSelection = {
124
124
  export interface PortableTextSlateEditor extends ReactEditor {
125
125
  _key: 'editor'
126
126
  _type: 'editor'
127
- destroy: () => void
128
127
  createPlaceholderBlock: () => Descendant
129
128
  editable: EditableAPI
130
129
  history: History