@portabletext/editor 1.3.1 → 1.4.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.
- package/lib/index.d.mts +2634 -44
- package/lib/index.d.ts +2634 -44
- package/lib/index.esm.js +532 -377
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +531 -377
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +532 -377
- package/lib/index.mjs.map +1 -1
- package/package.json +9 -8
- package/src/editor/PortableTextEditor.tsx +101 -72
- package/src/editor/behavior/behavior.markdown.ts +203 -0
- package/src/editor/editor-machine.ts +16 -2
- package/src/editor/plugins/createWithEditableAPI.ts +1 -1
- package/src/editor/use-editor.ts +45 -0
- package/src/index.ts +14 -8
- package/src/types/editor.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -43,17 +43,19 @@
|
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@portabletext/patches": "1.1.0",
|
|
46
|
+
"@xstate/react": "^4.1.3",
|
|
46
47
|
"debug": "^4.3.4",
|
|
47
48
|
"is-hotkey-esm": "^1.0.0",
|
|
48
49
|
"lodash": "^4.17.21",
|
|
49
50
|
"react-compiler-runtime": "19.0.0-beta-6fc168f-20241025",
|
|
50
51
|
"slate": "0.110.2",
|
|
51
|
-
"slate-
|
|
52
|
+
"slate-dom": "^0.111.0",
|
|
53
|
+
"slate-react": "0.111.0",
|
|
52
54
|
"use-effect-event": "^1.0.2",
|
|
53
55
|
"xstate": "^5.18.2"
|
|
54
56
|
},
|
|
55
57
|
"devDependencies": {
|
|
56
|
-
"@babel/preset-env": "^7.
|
|
58
|
+
"@babel/preset-env": "^7.26.0",
|
|
57
59
|
"@babel/preset-react": "^7.25.9",
|
|
58
60
|
"@jest/globals": "^29.7.0",
|
|
59
61
|
"@jest/types": "^29.6.3",
|
|
@@ -64,7 +66,7 @@
|
|
|
64
66
|
"@sanity/pkg-utils": "^6.11.8",
|
|
65
67
|
"@sanity/schema": "^3.62.3",
|
|
66
68
|
"@sanity/types": "^3.62.3",
|
|
67
|
-
"@sanity/ui": "^2.8.
|
|
69
|
+
"@sanity/ui": "^2.8.18",
|
|
68
70
|
"@sanity/util": "^3.62.3",
|
|
69
71
|
"@testing-library/dom": "^10.4.0",
|
|
70
72
|
"@testing-library/jest-dom": "^6.6.3",
|
|
@@ -73,7 +75,7 @@
|
|
|
73
75
|
"@types/debug": "^4.1.5",
|
|
74
76
|
"@types/express": "^4.17.21",
|
|
75
77
|
"@types/express-ws": "^3.0.5",
|
|
76
|
-
"@types/lodash": "^4.17.
|
|
78
|
+
"@types/lodash": "^4.17.13",
|
|
77
79
|
"@types/node": "^18.19.8",
|
|
78
80
|
"@types/node-ipc": "^9.2.3",
|
|
79
81
|
"@types/react": "^18.3.12",
|
|
@@ -83,16 +85,15 @@
|
|
|
83
85
|
"@typescript-eslint/parser": "^8.12.2",
|
|
84
86
|
"@vitejs/plugin-react": "^4.3.3",
|
|
85
87
|
"@vitest/browser": "^2.1.4",
|
|
86
|
-
"@xstate/react": "^4.1.3",
|
|
87
88
|
"babel-plugin-react-compiler": "beta",
|
|
88
89
|
"dotenv": "^16.4.5",
|
|
89
|
-
"eslint": "8",
|
|
90
|
+
"eslint": "8.57.1",
|
|
90
91
|
"eslint-plugin-react-compiler": "beta",
|
|
91
92
|
"eslint-plugin-react-hooks": "^5.0.0",
|
|
92
93
|
"express": "^4.21.1",
|
|
93
94
|
"express-ws": "^5.0.2",
|
|
94
95
|
"jest": "^29.7.0",
|
|
95
|
-
"jest-dev-server": "^10.1.
|
|
96
|
+
"jest-dev-server": "^10.1.4",
|
|
96
97
|
"jest-environment-node": "^29.7.0",
|
|
97
98
|
"jsdom": "^25.0.1",
|
|
98
99
|
"node-ipc": "npm:@node-ipc/compat@9.2.5",
|
|
@@ -29,7 +29,6 @@ import type {
|
|
|
29
29
|
import {debugWithName} from '../utils/debug'
|
|
30
30
|
import {getPortableTextMemberSchemaTypes} from '../utils/getPortableTextMemberSchemaTypes'
|
|
31
31
|
import {compileType} from '../utils/schema'
|
|
32
|
-
import {coreBehaviors} from './behavior/behavior.core'
|
|
33
32
|
import {SlateContainer} from './components/SlateContainer'
|
|
34
33
|
import {Synchronizer} from './components/Synchronizer'
|
|
35
34
|
import {EditorActorContext} from './editor-actor-context'
|
|
@@ -38,6 +37,7 @@ import {PortableTextEditorContext} from './hooks/usePortableTextEditor'
|
|
|
38
37
|
import {PortableTextEditorSelectionProvider} from './hooks/usePortableTextEditorSelection'
|
|
39
38
|
import {PortableTextEditorReadOnlyContext} from './hooks/usePortableTextReadOnly'
|
|
40
39
|
import {defaultKeyGenerator} from './key-generator'
|
|
40
|
+
import type {Editor} from './use-editor'
|
|
41
41
|
|
|
42
42
|
const debug = debugWithName('component:PortableTextEditor')
|
|
43
43
|
|
|
@@ -46,58 +46,73 @@ const debug = debugWithName('component:PortableTextEditor')
|
|
|
46
46
|
*
|
|
47
47
|
* @public
|
|
48
48
|
*/
|
|
49
|
-
export type PortableTextEditorProps
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
export type PortableTextEditorProps<
|
|
50
|
+
TEditor extends Editor | undefined = undefined,
|
|
51
|
+
> = PropsWithChildren<
|
|
52
|
+
(TEditor extends Editor
|
|
53
|
+
? {
|
|
54
|
+
/**
|
|
55
|
+
* @alpha
|
|
56
|
+
*/
|
|
57
|
+
editor: TEditor
|
|
58
|
+
}
|
|
59
|
+
: {
|
|
60
|
+
editor?: undefined
|
|
54
61
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Function that gets called when the editor changes the value
|
|
64
|
+
*/
|
|
65
|
+
onChange: (change: EditorChange) => void
|
|
59
66
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Schema type for the portable text field
|
|
69
|
+
*/
|
|
70
|
+
schemaType: ArraySchemaType<PortableTextBlock> | ArrayDefinition
|
|
64
71
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Maximum number of blocks to allow within the editor
|
|
74
|
+
*/
|
|
75
|
+
maxBlocks?: number | string
|
|
69
76
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Function used to generate keys for array items (`_key`)
|
|
79
|
+
*/
|
|
80
|
+
keyGenerator?: () => string
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Observable of local and remote patches for the edited value.
|
|
84
|
+
*/
|
|
85
|
+
patches$?: PatchObservable
|
|
79
86
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Backward compatibility (renamed to patches$).
|
|
89
|
+
*/
|
|
90
|
+
incomingPatches$?: PatchObservable
|
|
91
|
+
}) & {
|
|
92
|
+
/**
|
|
93
|
+
* Whether or not the editor should be in read-only mode
|
|
94
|
+
*/
|
|
95
|
+
readOnly?: boolean
|
|
84
96
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
97
|
+
/**
|
|
98
|
+
* The current value of the portable text field
|
|
99
|
+
*/
|
|
100
|
+
value?: PortableTextBlock[]
|
|
89
101
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
102
|
+
/**
|
|
103
|
+
* A ref to the editor instance
|
|
104
|
+
*/
|
|
105
|
+
editorRef?: MutableRefObject<PortableTextEditor | null>
|
|
106
|
+
}
|
|
107
|
+
>
|
|
95
108
|
|
|
96
109
|
/**
|
|
97
110
|
* The main Portable Text Editor component.
|
|
98
111
|
* @public
|
|
99
112
|
*/
|
|
100
|
-
export class PortableTextEditor extends Component<
|
|
113
|
+
export class PortableTextEditor extends Component<
|
|
114
|
+
PortableTextEditorProps<Editor | undefined>
|
|
115
|
+
> {
|
|
101
116
|
public static displayName = 'PortableTextEditor'
|
|
102
117
|
/**
|
|
103
118
|
* An observable of all the editor changes.
|
|
@@ -116,35 +131,46 @@ export class PortableTextEditor extends Component<PortableTextEditorProps> {
|
|
|
116
131
|
constructor(props: PortableTextEditorProps) {
|
|
117
132
|
super(props)
|
|
118
133
|
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
|
|
134
|
+
if (props.editor) {
|
|
135
|
+
this.editorActor = props.editor
|
|
136
|
+
this.editorActor.start()
|
|
137
|
+
this.schemaTypes = this.editorActor.getSnapshot().context.schema
|
|
138
|
+
} else {
|
|
139
|
+
if (!props.schemaType) {
|
|
140
|
+
throw new Error('PortableTextEditor: missing "schemaType" property')
|
|
141
|
+
}
|
|
122
142
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
143
|
+
if (props.incomingPatches$) {
|
|
144
|
+
console.warn(
|
|
145
|
+
`The prop 'incomingPatches$' is deprecated and renamed to 'patches$'`,
|
|
146
|
+
)
|
|
147
|
+
}
|
|
128
148
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
149
|
+
this.schemaTypes = getPortableTextMemberSchemaTypes(
|
|
150
|
+
props.schemaType.hasOwnProperty('jsonType')
|
|
151
|
+
? props.schemaType
|
|
152
|
+
: compileType(props.schemaType),
|
|
153
|
+
)
|
|
134
154
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
155
|
+
this.editorActor =
|
|
156
|
+
props.editor ??
|
|
157
|
+
createActor(editorMachine, {
|
|
158
|
+
input: {
|
|
159
|
+
keyGenerator: props.keyGenerator || defaultKeyGenerator,
|
|
160
|
+
schema: this.schemaTypes,
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
this.editorActor.start()
|
|
164
|
+
}
|
|
143
165
|
}
|
|
144
166
|
|
|
145
167
|
componentDidUpdate(prevProps: PortableTextEditorProps) {
|
|
146
168
|
// Set up the schema type lookup table again if the source schema type changes
|
|
147
|
-
if (
|
|
169
|
+
if (
|
|
170
|
+
!this.props.editor &&
|
|
171
|
+
!prevProps.editor &&
|
|
172
|
+
this.props.schemaType !== prevProps.schemaType
|
|
173
|
+
) {
|
|
148
174
|
this.schemaTypes = getPortableTextMemberSchemaTypes(
|
|
149
175
|
this.props.schemaType.hasOwnProperty('jsonType')
|
|
150
176
|
? this.props.schemaType
|
|
@@ -175,22 +201,23 @@ export class PortableTextEditor extends Component<PortableTextEditorProps> {
|
|
|
175
201
|
}
|
|
176
202
|
|
|
177
203
|
render() {
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const maxBlocks =
|
|
182
|
-
typeof this.props.maxBlocks === 'undefined'
|
|
204
|
+
const maxBlocks = !this.props.editor
|
|
205
|
+
? typeof this.props.maxBlocks === 'undefined'
|
|
183
206
|
? undefined
|
|
184
207
|
: Number.parseInt(this.props.maxBlocks.toString(), 10) || undefined
|
|
208
|
+
: undefined
|
|
185
209
|
|
|
186
210
|
const readOnly = Boolean(this.props.readOnly)
|
|
211
|
+
const legacyPatches = !this.props.editor
|
|
212
|
+
? (this.props.incomingPatches$ ?? this.props.patches$)
|
|
213
|
+
: undefined
|
|
187
214
|
|
|
188
215
|
return (
|
|
189
216
|
<>
|
|
190
|
-
{
|
|
217
|
+
{legacyPatches ? (
|
|
191
218
|
<RoutePatchesObservableToEditorActor
|
|
192
219
|
editorActor={this.editorActor}
|
|
193
|
-
patches$={
|
|
220
|
+
patches$={legacyPatches}
|
|
194
221
|
/>
|
|
195
222
|
) : null}
|
|
196
223
|
<EditorActorContext.Provider value={this.editorActor}>
|
|
@@ -209,16 +236,18 @@ export class PortableTextEditor extends Component<PortableTextEditorProps> {
|
|
|
209
236
|
editorActor={this.editorActor}
|
|
210
237
|
getValue={this.getValue}
|
|
211
238
|
onChange={(change) => {
|
|
212
|
-
this.props.
|
|
239
|
+
if (!this.props.editor) {
|
|
240
|
+
this.props.onChange(change)
|
|
241
|
+
}
|
|
213
242
|
/**
|
|
214
243
|
* For backwards compatibility, we relay all changes to the
|
|
215
244
|
* `change$` Subject as well.
|
|
216
245
|
*/
|
|
217
246
|
this.change$.next(change)
|
|
218
247
|
}}
|
|
219
|
-
value={value}
|
|
248
|
+
value={this.props.value}
|
|
220
249
|
/>
|
|
221
|
-
{children}
|
|
250
|
+
{this.props.children}
|
|
222
251
|
</PortableTextEditorSelectionProvider>
|
|
223
252
|
</PortableTextEditorReadOnlyContext.Provider>
|
|
224
253
|
</PortableTextEditorContext.Provider>
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type {PortableTextMemberSchemaTypes} from '../../types/editor'
|
|
2
|
+
import {defineBehavior} from './behavior.types'
|
|
3
|
+
import {
|
|
4
|
+
getFocusSpan,
|
|
5
|
+
getFocusTextBlock,
|
|
6
|
+
selectionIsCollapsed,
|
|
7
|
+
} from './behavior.utils'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @alpha
|
|
11
|
+
*/
|
|
12
|
+
export type MarkdownBehaviorsConfig = {
|
|
13
|
+
mapDefaultStyle: (schema: PortableTextMemberSchemaTypes) => string | undefined
|
|
14
|
+
mapHeadingStyle: (
|
|
15
|
+
schema: PortableTextMemberSchemaTypes,
|
|
16
|
+
level: number,
|
|
17
|
+
) => string | undefined
|
|
18
|
+
mapBlockquoteStyle: (
|
|
19
|
+
schema: PortableTextMemberSchemaTypes,
|
|
20
|
+
) => string | undefined
|
|
21
|
+
mapUnorderedListStyle: (
|
|
22
|
+
schema: PortableTextMemberSchemaTypes,
|
|
23
|
+
) => string | undefined
|
|
24
|
+
mapOrderedListStyle: (
|
|
25
|
+
schema: PortableTextMemberSchemaTypes,
|
|
26
|
+
) => string | undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @alpha
|
|
31
|
+
*/
|
|
32
|
+
export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
33
|
+
const automaticStyleOnSpace = defineBehavior({
|
|
34
|
+
on: 'insert text',
|
|
35
|
+
guard: ({context, event}) => {
|
|
36
|
+
const isSpace = event.text === ' '
|
|
37
|
+
|
|
38
|
+
if (!isSpace) {
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const selectionCollapsed = selectionIsCollapsed(context)
|
|
43
|
+
const focusTextBlock = getFocusTextBlock(context)
|
|
44
|
+
const focusSpan = getFocusSpan(context)
|
|
45
|
+
|
|
46
|
+
if (!selectionCollapsed || !focusTextBlock || !focusSpan) {
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const looksLikeMarkdownHeading = /^#+/.test(focusSpan.node.text)
|
|
51
|
+
const headingStyle = config.mapHeadingStyle(
|
|
52
|
+
context.schema,
|
|
53
|
+
focusSpan.node.text.length,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const looksLikeMarkdownQuote = /^>/.test(focusSpan.node.text)
|
|
57
|
+
const blockquoteStyle = config.mapBlockquoteStyle(context.schema)
|
|
58
|
+
|
|
59
|
+
if (looksLikeMarkdownHeading && headingStyle !== undefined) {
|
|
60
|
+
return {focusTextBlock, focusSpan, style: headingStyle}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (looksLikeMarkdownQuote && blockquoteStyle !== undefined) {
|
|
64
|
+
return {focusTextBlock, focusSpan, style: blockquoteStyle}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return false
|
|
68
|
+
},
|
|
69
|
+
actions: [
|
|
70
|
+
() => [
|
|
71
|
+
{
|
|
72
|
+
type: 'insert text',
|
|
73
|
+
text: ' ',
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
(_, {focusTextBlock, focusSpan, style}) => [
|
|
77
|
+
{
|
|
78
|
+
type: 'set block',
|
|
79
|
+
style,
|
|
80
|
+
paths: [focusTextBlock.path],
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
type: 'delete',
|
|
84
|
+
selection: {
|
|
85
|
+
anchor: {path: focusSpan.path, offset: 0},
|
|
86
|
+
focus: {
|
|
87
|
+
path: focusSpan.path,
|
|
88
|
+
offset: focusSpan.node.text.length + 1,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
],
|
|
94
|
+
})
|
|
95
|
+
const clearStyleOnBackspace = defineBehavior({
|
|
96
|
+
on: 'delete backward',
|
|
97
|
+
guard: ({context}) => {
|
|
98
|
+
const selectionCollapsed = selectionIsCollapsed(context)
|
|
99
|
+
const focusTextBlock = getFocusTextBlock(context)
|
|
100
|
+
const focusSpan = getFocusSpan(context)
|
|
101
|
+
|
|
102
|
+
if (!selectionCollapsed || !focusTextBlock || !focusSpan) {
|
|
103
|
+
return false
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const defaultStyle = config.mapDefaultStyle(context.schema)
|
|
107
|
+
|
|
108
|
+
if (
|
|
109
|
+
defaultStyle &&
|
|
110
|
+
focusTextBlock.node.children.length === 1 &&
|
|
111
|
+
focusTextBlock.node.style !== config.mapDefaultStyle(context.schema) &&
|
|
112
|
+
focusSpan.node.text === ''
|
|
113
|
+
) {
|
|
114
|
+
return {defaultStyle, focusTextBlock}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return false
|
|
118
|
+
},
|
|
119
|
+
actions: [
|
|
120
|
+
(_, {defaultStyle, focusTextBlock}) => [
|
|
121
|
+
{
|
|
122
|
+
type: 'set block',
|
|
123
|
+
style: defaultStyle,
|
|
124
|
+
paths: [focusTextBlock.path],
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
],
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
const automaticListOnSpace = defineBehavior({
|
|
131
|
+
on: 'insert text',
|
|
132
|
+
guard: ({context, event}) => {
|
|
133
|
+
const isSpace = event.text === ' '
|
|
134
|
+
|
|
135
|
+
if (!isSpace) {
|
|
136
|
+
return false
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const selectionCollapsed = selectionIsCollapsed(context)
|
|
140
|
+
const focusTextBlock = getFocusTextBlock(context)
|
|
141
|
+
const focusSpan = getFocusSpan(context)
|
|
142
|
+
|
|
143
|
+
if (!selectionCollapsed || !focusTextBlock || !focusSpan) {
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const looksLikeUnorderedList = /^-/.test(focusSpan.node.text)
|
|
148
|
+
const unorderedListStyle = config.mapUnorderedListStyle(context.schema)
|
|
149
|
+
|
|
150
|
+
if (looksLikeUnorderedList && unorderedListStyle !== undefined) {
|
|
151
|
+
return {focusTextBlock, focusSpan, listItem: unorderedListStyle}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const looksLikeOrderedList = /^1./.test(focusSpan.node.text)
|
|
155
|
+
const orderedListStyle = config.mapOrderedListStyle(context.schema)
|
|
156
|
+
|
|
157
|
+
if (looksLikeOrderedList && orderedListStyle !== undefined) {
|
|
158
|
+
return {focusTextBlock, focusSpan, listItem: orderedListStyle}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return false
|
|
162
|
+
},
|
|
163
|
+
actions: [
|
|
164
|
+
() => [
|
|
165
|
+
{
|
|
166
|
+
type: 'insert text',
|
|
167
|
+
text: ' ',
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
(_, {focusTextBlock, focusSpan, listItem}) => [
|
|
171
|
+
{
|
|
172
|
+
type: 'unset block',
|
|
173
|
+
props: ['style'],
|
|
174
|
+
paths: [focusTextBlock.path],
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
type: 'set block',
|
|
178
|
+
listItem,
|
|
179
|
+
level: 1,
|
|
180
|
+
paths: [focusTextBlock.path],
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
type: 'delete',
|
|
184
|
+
selection: {
|
|
185
|
+
anchor: {path: focusSpan.path, offset: 0},
|
|
186
|
+
focus: {
|
|
187
|
+
path: focusSpan.path,
|
|
188
|
+
offset: focusSpan.node.text.length + 1,
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
],
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
const markdownBehaviors = [
|
|
197
|
+
automaticStyleOnSpace,
|
|
198
|
+
clearStyleOnBackspace,
|
|
199
|
+
automaticListOnSpace,
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
return markdownBehaviors
|
|
203
|
+
}
|
|
@@ -21,6 +21,7 @@ import {toPortableTextRange} from '../utils/ranges'
|
|
|
21
21
|
import {fromSlateValue} from '../utils/values'
|
|
22
22
|
import {KEY_TO_VALUE_ELEMENT} from '../utils/weakMaps'
|
|
23
23
|
import {performAction, performDefaultAction} from './behavior/behavior.actions'
|
|
24
|
+
import {coreBehaviors} from './behavior/behavior.core'
|
|
24
25
|
import type {
|
|
25
26
|
Behavior,
|
|
26
27
|
BehaviorAction,
|
|
@@ -91,6 +92,10 @@ type EditorEvent =
|
|
|
91
92
|
type: 'update schema'
|
|
92
93
|
schema: PortableTextMemberSchemaTypes
|
|
93
94
|
}
|
|
95
|
+
| {
|
|
96
|
+
type: 'update behaviors'
|
|
97
|
+
behaviors: Array<Behavior>
|
|
98
|
+
}
|
|
94
99
|
| EditorEmittedEvent
|
|
95
100
|
|
|
96
101
|
type EditorEmittedEvent =
|
|
@@ -139,12 +144,18 @@ export const editorMachine = setup({
|
|
|
139
144
|
events: {} as EditorEvent,
|
|
140
145
|
emitted: {} as EditorEmittedEvent,
|
|
141
146
|
input: {} as {
|
|
142
|
-
behaviors
|
|
147
|
+
behaviors?: Array<Behavior>
|
|
143
148
|
keyGenerator: () => string
|
|
144
149
|
schema: PortableTextMemberSchemaTypes
|
|
145
150
|
},
|
|
146
151
|
},
|
|
147
152
|
actions: {
|
|
153
|
+
'assign behaviors': assign({
|
|
154
|
+
behaviors: ({event}) => {
|
|
155
|
+
assertEvent(event, 'update behaviors')
|
|
156
|
+
return [...coreBehaviors, ...event.behaviors]
|
|
157
|
+
},
|
|
158
|
+
}),
|
|
148
159
|
'assign schema': assign({
|
|
149
160
|
schema: ({event}) => {
|
|
150
161
|
assertEvent(event, 'update schema')
|
|
@@ -263,7 +274,9 @@ export const editorMachine = setup({
|
|
|
263
274
|
}).createMachine({
|
|
264
275
|
id: 'editor',
|
|
265
276
|
context: ({input}) => ({
|
|
266
|
-
behaviors: input.behaviors
|
|
277
|
+
behaviors: input.behaviors
|
|
278
|
+
? [...coreBehaviors, ...input.behaviors]
|
|
279
|
+
: coreBehaviors,
|
|
267
280
|
keyGenerator: input.keyGenerator,
|
|
268
281
|
pendingEvents: [],
|
|
269
282
|
schema: input.schema,
|
|
@@ -286,6 +299,7 @@ export const editorMachine = setup({
|
|
|
286
299
|
'loading': {actions: emit({type: 'loading'})},
|
|
287
300
|
'patches': {actions: emit(({event}) => event)},
|
|
288
301
|
'done loading': {actions: emit({type: 'done loading'})},
|
|
302
|
+
'update behaviors': {actions: 'assign behaviors'},
|
|
289
303
|
'update schema': {actions: 'assign schema'},
|
|
290
304
|
'behavior event': {actions: 'handle behavior event'},
|
|
291
305
|
'behavior action intends': {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ArrayDefinition,
|
|
3
|
+
ArraySchemaType,
|
|
4
|
+
PortableTextBlock,
|
|
5
|
+
} from '@sanity/types'
|
|
6
|
+
import {useActorRef} from '@xstate/react'
|
|
7
|
+
import {getPortableTextMemberSchemaTypes} from '../utils/getPortableTextMemberSchemaTypes'
|
|
8
|
+
import {compileType} from '../utils/schema'
|
|
9
|
+
import type {Behavior} from './behavior/behavior.types'
|
|
10
|
+
import {editorMachine} from './editor-machine'
|
|
11
|
+
import {defaultKeyGenerator} from './key-generator'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @alpha
|
|
15
|
+
*/
|
|
16
|
+
export type EditorConfig = {
|
|
17
|
+
behaviors?: Array<Behavior>
|
|
18
|
+
keyGenerator?: () => string
|
|
19
|
+
schema: ArraySchemaType<PortableTextBlock> | ArrayDefinition
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @alpha
|
|
24
|
+
*/
|
|
25
|
+
export type Editor = ReturnType<typeof useEditor>
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @alpha
|
|
29
|
+
*/
|
|
30
|
+
export function useEditor(config: EditorConfig) {
|
|
31
|
+
const schema = getPortableTextMemberSchemaTypes(
|
|
32
|
+
config.schema.hasOwnProperty('jsonType')
|
|
33
|
+
? config.schema
|
|
34
|
+
: compileType(config.schema),
|
|
35
|
+
)
|
|
36
|
+
const editorActor = useActorRef(editorMachine, {
|
|
37
|
+
input: {
|
|
38
|
+
behaviors: config.behaviors,
|
|
39
|
+
keyGenerator: config.keyGenerator ?? defaultKeyGenerator,
|
|
40
|
+
schema,
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return editorActor
|
|
45
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
export type {Patch} from '@portabletext/patches'
|
|
2
|
-
export
|
|
3
|
-
Behavior,
|
|
4
|
-
BehaviorActionIntend,
|
|
5
|
-
BehaviorContext,
|
|
6
|
-
BehaviorEvent,
|
|
7
|
-
BehaviorGuard,
|
|
8
|
-
PickFromUnion,
|
|
9
|
-
BehaviorActionIntendSet,
|
|
2
|
+
export {
|
|
3
|
+
type Behavior,
|
|
4
|
+
type BehaviorActionIntend,
|
|
5
|
+
type BehaviorContext,
|
|
6
|
+
type BehaviorEvent,
|
|
7
|
+
type BehaviorGuard,
|
|
8
|
+
type PickFromUnion,
|
|
9
|
+
type BehaviorActionIntendSet,
|
|
10
|
+
defineBehavior,
|
|
10
11
|
} from './editor/behavior/behavior.types'
|
|
12
|
+
export {
|
|
13
|
+
createMarkdownBehaviors,
|
|
14
|
+
type MarkdownBehaviorsConfig,
|
|
15
|
+
} from './editor/behavior/behavior.markdown'
|
|
11
16
|
export {PortableTextEditable} from './editor/Editable'
|
|
12
17
|
export type {PortableTextEditableProps} from './editor/Editable'
|
|
13
18
|
export {
|
|
@@ -22,5 +27,6 @@ export {usePortableTextEditorSelection} from './editor/hooks/usePortableTextEdit
|
|
|
22
27
|
export {defaultKeyGenerator as keyGenerator} from './editor/key-generator'
|
|
23
28
|
export {PortableTextEditor} from './editor/PortableTextEditor'
|
|
24
29
|
export type {PortableTextEditorProps} from './editor/PortableTextEditor'
|
|
30
|
+
export {useEditor, type Editor, type EditorConfig} from './editor/use-editor'
|
|
25
31
|
export * from './types/editor'
|
|
26
32
|
export * from './types/options'
|
package/src/types/editor.ts
CHANGED
|
@@ -30,8 +30,8 @@ import type {
|
|
|
30
30
|
Node as SlateNode,
|
|
31
31
|
Operation as SlateOperation,
|
|
32
32
|
} from 'slate'
|
|
33
|
+
import type {DOMNode} from 'slate-dom'
|
|
33
34
|
import type {ReactEditor} from 'slate-react'
|
|
34
|
-
import type {DOMNode} from 'slate-react/dist/utils/dom'
|
|
35
35
|
import type {PortableTextEditableProps} from '../editor/Editable'
|
|
36
36
|
import type {PortableTextEditor} from '../editor/PortableTextEditor'
|
|
37
37
|
|