@portabletext/editor 1.0.9 → 1.0.11
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 +17 -5
- package/lib/index.d.ts +17 -5
- package/lib/index.esm.js +25 -12
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +25 -12
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +25 -12
- package/lib/index.mjs.map +1 -1
- package/package.json +19 -20
- package/src/editor/PortableTextEditor.tsx +1 -6
- package/src/editor/__tests__/insert-block.test.tsx +229 -0
- package/src/editor/hooks/usePortableTextEditor.ts +1 -0
- package/src/editor/hooks/usePortableTextEditorKeyGenerator.ts +3 -0
- package/src/editor/hooks/usePortableTextEditorSelection.tsx +1 -0
- package/src/editor/plugins/createWithEditableAPI.ts +41 -10
- package/src/editor/plugins/createWithHotKeys.ts +5 -1
- package/src/types/editor.ts +7 -0
- package/src/types/options.ts +6 -0
- package/src/utils/bufferUntil.ts +0 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"src"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@portabletext/patches": "1.0
|
|
45
|
+
"@portabletext/patches": "1.1.0",
|
|
46
46
|
"debug": "^4.3.4",
|
|
47
47
|
"is-hotkey-esm": "^1.0.0",
|
|
48
48
|
"lodash": "^4.17.21",
|
|
@@ -50,32 +50,31 @@
|
|
|
50
50
|
"slate-react": "0.101.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
54
53
|
"@jest/globals": "^29.7.0",
|
|
55
|
-
"@playwright/test": "1.45.
|
|
54
|
+
"@playwright/test": "1.45.3",
|
|
56
55
|
"@portabletext/toolkit": "^2.0.15",
|
|
57
|
-
"@sanity/block-tools": "^3.
|
|
56
|
+
"@sanity/block-tools": "^3.52.4",
|
|
58
57
|
"@sanity/diff-match-patch": "^3.1.1",
|
|
59
58
|
"@sanity/eslint-config-i18n": "^1.1.0",
|
|
60
59
|
"@sanity/eslint-config-studio": "^4.0.0",
|
|
61
|
-
"@sanity/pkg-utils": "^6.10.
|
|
62
|
-
"@sanity/schema": "^3.
|
|
60
|
+
"@sanity/pkg-utils": "^6.10.8",
|
|
61
|
+
"@sanity/schema": "^3.52.4",
|
|
63
62
|
"@sanity/test": "0.0.1-alpha.1",
|
|
64
|
-
"@sanity/types": "^3.
|
|
65
|
-
"@sanity/ui": "^2.
|
|
66
|
-
"@sanity/util": "^3.
|
|
63
|
+
"@sanity/types": "^3.52.4",
|
|
64
|
+
"@sanity/ui": "^2.8.8",
|
|
65
|
+
"@sanity/util": "^3.52.4",
|
|
67
66
|
"@testing-library/react": "^13.4.0",
|
|
68
67
|
"@types/debug": "^4.1.5",
|
|
69
68
|
"@types/express": "^4.17.21",
|
|
70
69
|
"@types/express-ws": "^3.0.4",
|
|
71
|
-
"@types/lodash": "^4.17.
|
|
70
|
+
"@types/lodash": "^4.17.7",
|
|
72
71
|
"@types/node": "^18.19.8",
|
|
73
72
|
"@types/node-ipc": "^9.2.3",
|
|
74
73
|
"@types/react": "^18.3.3",
|
|
75
74
|
"@types/react-dom": "^18.3.0",
|
|
76
|
-
"@types/ws": "~8.5.
|
|
77
|
-
"@typescript-eslint/eslint-plugin": "^7.
|
|
78
|
-
"@typescript-eslint/parser": "^7.
|
|
75
|
+
"@types/ws": "~8.5.11",
|
|
76
|
+
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
|
77
|
+
"@typescript-eslint/parser": "^7.17.0",
|
|
79
78
|
"@vitejs/plugin-react": "^4.3.1",
|
|
80
79
|
"dotenv": "^16.4.5",
|
|
81
80
|
"eslint": "^8.57.0",
|
|
@@ -83,11 +82,11 @@
|
|
|
83
82
|
"eslint-config-sanity": "^7.1.2",
|
|
84
83
|
"eslint-import-resolver-typescript": "^3.6.1",
|
|
85
84
|
"eslint-plugin-import": "^2.29.1",
|
|
86
|
-
"eslint-plugin-prettier": "^5.1
|
|
87
|
-
"eslint-plugin-react-compiler": "0.0.0-experimental-
|
|
85
|
+
"eslint-plugin-prettier": "^5.2.1",
|
|
86
|
+
"eslint-plugin-react-compiler": "0.0.0-experimental-9ed098e-20240725",
|
|
88
87
|
"eslint-plugin-tsdoc": "^0.3.0",
|
|
89
88
|
"eslint-plugin-unicorn": "^54.0.0",
|
|
90
|
-
"eslint-plugin-unused-imports": "^4.0.
|
|
89
|
+
"eslint-plugin-unused-imports": "^4.0.1",
|
|
91
90
|
"express": "^4.19.2",
|
|
92
91
|
"express-ws": "^5.0.2",
|
|
93
92
|
"jest": "^29.7.0",
|
|
@@ -98,9 +97,9 @@
|
|
|
98
97
|
"react": "^18.3.1",
|
|
99
98
|
"react-dom": "^18.3.1",
|
|
100
99
|
"rxjs": "^7.8.1",
|
|
101
|
-
"styled-components": "^6.1.
|
|
102
|
-
"tsx": "^4.
|
|
103
|
-
"typescript": "
|
|
100
|
+
"styled-components": "^6.1.12",
|
|
101
|
+
"tsx": "^4.16.2",
|
|
102
|
+
"typescript": "5.5.4",
|
|
104
103
|
"vite": "^4.5.3"
|
|
105
104
|
},
|
|
106
105
|
"peerDependencies": {
|
|
@@ -36,11 +36,6 @@ import {PortableTextEditorReadOnlyContext} from './hooks/usePortableTextReadOnly
|
|
|
36
36
|
|
|
37
37
|
const debug = debugWithName('component:PortableTextEditor')
|
|
38
38
|
|
|
39
|
-
/**
|
|
40
|
-
* Props for the PortableTextEditor component
|
|
41
|
-
*
|
|
42
|
-
* @public
|
|
43
|
-
*/
|
|
44
39
|
/**
|
|
45
40
|
* Props for the PortableTextEditor component
|
|
46
41
|
*
|
|
@@ -115,7 +110,7 @@ export class PortableTextEditor extends Component<PortableTextEditorProps> {
|
|
|
115
110
|
super(props)
|
|
116
111
|
|
|
117
112
|
if (!props.schemaType) {
|
|
118
|
-
throw new Error('PortableTextEditor: missing "
|
|
113
|
+
throw new Error('PortableTextEditor: missing "schemaType" property')
|
|
119
114
|
}
|
|
120
115
|
|
|
121
116
|
if (props.incomingPatches$) {
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import {describe, expect, jest, test} from '@jest/globals'
|
|
2
|
+
import {Schema} from '@sanity/schema'
|
|
3
|
+
import {type PortableTextBlock} from '@sanity/types'
|
|
4
|
+
import {render, waitFor} from '@testing-library/react'
|
|
5
|
+
import {createRef, type RefObject} from 'react'
|
|
6
|
+
|
|
7
|
+
import {type EditorChange, type EditorSelection} from '../../types/editor'
|
|
8
|
+
import {PortableTextEditable} from '../Editable'
|
|
9
|
+
import {PortableTextEditor} from '../PortableTextEditor'
|
|
10
|
+
|
|
11
|
+
const schema = Schema.compile({
|
|
12
|
+
types: [
|
|
13
|
+
{name: 'portable-text', type: 'array', of: [{type: 'block'}, {type: 'image'}]},
|
|
14
|
+
{name: 'image', type: 'object'},
|
|
15
|
+
],
|
|
16
|
+
}).get('portable-text')
|
|
17
|
+
|
|
18
|
+
describe(PortableTextEditor.insertBlock.name, () => {
|
|
19
|
+
test('Scenario: Inserting a custom block without a selection #1', async () => {
|
|
20
|
+
const editorRef: RefObject<PortableTextEditor> = createRef()
|
|
21
|
+
const emptyTextBlock: PortableTextBlock = {
|
|
22
|
+
_key: 'ba',
|
|
23
|
+
_type: 'block',
|
|
24
|
+
children: [
|
|
25
|
+
{
|
|
26
|
+
_type: 'span',
|
|
27
|
+
_key: 'sa',
|
|
28
|
+
text: '',
|
|
29
|
+
marks: [],
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
style: 'normal',
|
|
33
|
+
}
|
|
34
|
+
const initialValue: Array<PortableTextBlock> = [emptyTextBlock]
|
|
35
|
+
const onChange: (change: EditorChange) => void = jest.fn()
|
|
36
|
+
|
|
37
|
+
render(
|
|
38
|
+
<PortableTextEditor
|
|
39
|
+
ref={editorRef}
|
|
40
|
+
schemaType={schema}
|
|
41
|
+
value={initialValue}
|
|
42
|
+
keyGenerator={() => 'bb'}
|
|
43
|
+
onChange={onChange}
|
|
44
|
+
>
|
|
45
|
+
<PortableTextEditable />
|
|
46
|
+
</PortableTextEditor>,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
// Given an empty text block
|
|
50
|
+
await waitFor(() => {
|
|
51
|
+
if (editorRef.current) {
|
|
52
|
+
expect(onChange).toHaveBeenCalledWith({type: 'value', value: initialValue})
|
|
53
|
+
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// And no selection
|
|
58
|
+
await waitFor(() => {
|
|
59
|
+
if (editorRef.current) {
|
|
60
|
+
expect(PortableTextEditor.getSelection(editorRef.current)).toBeNull()
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// When a new image is inserted
|
|
65
|
+
await waitFor(() => {
|
|
66
|
+
if (editorRef.current) {
|
|
67
|
+
const imageBlockType = editorRef.current.schemaTypes.blockObjects.find(
|
|
68
|
+
(object) => object.name === 'image',
|
|
69
|
+
)!
|
|
70
|
+
PortableTextEditor.insertBlock(editorRef.current, imageBlockType)
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// Then the empty text block is replaced with the new image
|
|
75
|
+
await waitFor(() => {
|
|
76
|
+
if (editorRef.current) {
|
|
77
|
+
expect(PortableTextEditor.getValue(editorRef.current)).toEqual([
|
|
78
|
+
{_key: 'bb', _type: 'image'},
|
|
79
|
+
])
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('Scenario: Inserting a custom block without a selection #2', async () => {
|
|
85
|
+
const editorRef: RefObject<PortableTextEditor> = createRef()
|
|
86
|
+
const nonEmptyTextBlock: PortableTextBlock = {
|
|
87
|
+
_key: 'ba',
|
|
88
|
+
_type: 'block',
|
|
89
|
+
children: [
|
|
90
|
+
{
|
|
91
|
+
_type: 'span',
|
|
92
|
+
_key: 'xs',
|
|
93
|
+
text: 'foo',
|
|
94
|
+
marks: [],
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
style: 'normal',
|
|
98
|
+
}
|
|
99
|
+
const initialValue: Array<PortableTextBlock> = [nonEmptyTextBlock]
|
|
100
|
+
const onChange: (change: EditorChange) => void = jest.fn()
|
|
101
|
+
|
|
102
|
+
render(
|
|
103
|
+
<PortableTextEditor
|
|
104
|
+
ref={editorRef}
|
|
105
|
+
schemaType={schema}
|
|
106
|
+
value={initialValue}
|
|
107
|
+
keyGenerator={() => 'bb'}
|
|
108
|
+
onChange={onChange}
|
|
109
|
+
>
|
|
110
|
+
<PortableTextEditable />
|
|
111
|
+
</PortableTextEditor>,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
// Given an non-empty text block
|
|
115
|
+
await waitFor(() => {
|
|
116
|
+
if (editorRef.current) {
|
|
117
|
+
expect(onChange).toHaveBeenCalledWith({type: 'value', value: initialValue})
|
|
118
|
+
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// And no selection
|
|
123
|
+
await waitFor(() => {
|
|
124
|
+
if (editorRef.current) {
|
|
125
|
+
expect(PortableTextEditor.getSelection(editorRef.current)).toBeNull()
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// When a new image is inserted
|
|
130
|
+
await waitFor(() => {
|
|
131
|
+
if (editorRef.current) {
|
|
132
|
+
const imageBlockType = editorRef.current.schemaTypes.blockObjects.find(
|
|
133
|
+
(object) => object.name === 'image',
|
|
134
|
+
)!
|
|
135
|
+
PortableTextEditor.insertBlock(editorRef.current, imageBlockType)
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
// Then the empty text block is replaced with the new image
|
|
140
|
+
await waitFor(() => {
|
|
141
|
+
if (editorRef.current) {
|
|
142
|
+
expect(PortableTextEditor.getValue(editorRef.current)).toEqual([
|
|
143
|
+
nonEmptyTextBlock,
|
|
144
|
+
{_key: 'bb', _type: 'image'},
|
|
145
|
+
])
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test('Scenario: Replacing an empty text block with a custom block', async () => {
|
|
151
|
+
const editorRef: RefObject<PortableTextEditor> = createRef()
|
|
152
|
+
const emptyTextBlock: PortableTextBlock = {
|
|
153
|
+
_key: 'ba',
|
|
154
|
+
_type: 'block',
|
|
155
|
+
children: [
|
|
156
|
+
{
|
|
157
|
+
_type: 'span',
|
|
158
|
+
_key: 'sa',
|
|
159
|
+
text: '',
|
|
160
|
+
marks: [],
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
style: 'normal',
|
|
164
|
+
}
|
|
165
|
+
const imageBlock: PortableTextBlock = {
|
|
166
|
+
_key: 'bb',
|
|
167
|
+
_type: 'image',
|
|
168
|
+
}
|
|
169
|
+
const initialValue: Array<PortableTextBlock> = [emptyTextBlock, imageBlock]
|
|
170
|
+
const onChange: (change: EditorChange) => void = jest.fn()
|
|
171
|
+
|
|
172
|
+
render(
|
|
173
|
+
<PortableTextEditor
|
|
174
|
+
ref={editorRef}
|
|
175
|
+
schemaType={schema}
|
|
176
|
+
value={initialValue}
|
|
177
|
+
keyGenerator={() => 'bc'}
|
|
178
|
+
onChange={onChange}
|
|
179
|
+
>
|
|
180
|
+
<PortableTextEditable />
|
|
181
|
+
</PortableTextEditor>,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
// Given an empty text block followed by an image
|
|
185
|
+
await waitFor(() => {
|
|
186
|
+
if (editorRef.current) {
|
|
187
|
+
expect(onChange).toHaveBeenCalledWith({type: 'value', value: initialValue})
|
|
188
|
+
expect(onChange).toHaveBeenCalledWith({type: 'ready'})
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
// And a selection in the empty text block
|
|
193
|
+
const initialSelection: EditorSelection = {
|
|
194
|
+
anchor: {path: [{_key: 'ba'}, 'children', {_key: 'sa'}], offset: 0},
|
|
195
|
+
focus: {path: [{_key: 'ba'}, 'children', {_key: 'sa'}], offset: 0},
|
|
196
|
+
backward: false,
|
|
197
|
+
}
|
|
198
|
+
await waitFor(() => {
|
|
199
|
+
if (editorRef.current) {
|
|
200
|
+
PortableTextEditor.select(editorRef.current, initialSelection)
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
await waitFor(() => {
|
|
204
|
+
if (editorRef.current) {
|
|
205
|
+
expect(onChange).toHaveBeenCalledWith({type: 'selection', selection: initialSelection})
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
// When a new image is inserted
|
|
210
|
+
await waitFor(() => {
|
|
211
|
+
if (editorRef.current) {
|
|
212
|
+
const imageBlockType = editorRef.current.schemaTypes.blockObjects.find(
|
|
213
|
+
(object) => object.name === 'image',
|
|
214
|
+
)!
|
|
215
|
+
PortableTextEditor.insertBlock(editorRef.current, imageBlockType)
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
// Then the empty text block is replaced with the new image
|
|
220
|
+
await waitFor(() => {
|
|
221
|
+
if (editorRef.current) {
|
|
222
|
+
expect(PortableTextEditor.getValue(editorRef.current)).toEqual([
|
|
223
|
+
{_key: 'bc', _type: 'image'},
|
|
224
|
+
{_key: 'bb', _type: 'image'},
|
|
225
|
+
])
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
})
|
|
229
|
+
})
|
|
@@ -8,6 +8,7 @@ import {type PortableTextEditor} from '../PortableTextEditor'
|
|
|
8
8
|
export const PortableTextEditorContext = createContext<PortableTextEditor | null>(null)
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
+
* @public
|
|
11
12
|
* Get the current editor object from the React context.
|
|
12
13
|
*/
|
|
13
14
|
export const usePortableTextEditor = (): PortableTextEditor => {
|
|
@@ -9,6 +9,7 @@ import {debugWithName} from '../../utils/debug'
|
|
|
9
9
|
const PortableTextEditorSelectionContext = createContext<EditorSelection | null>(null)
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
+
* @public
|
|
12
13
|
* Get the current editor selection from the React context.
|
|
13
14
|
*/
|
|
14
15
|
export const usePortableTextEditorSelection = (): EditorSelection => {
|
|
@@ -160,9 +160,6 @@ export function createWithEditableAPI(
|
|
|
160
160
|
)
|
|
161
161
|
},
|
|
162
162
|
insertBlock: (type: SchemaType, value?: {[prop: string]: any}): Path => {
|
|
163
|
-
if (!editor.selection) {
|
|
164
|
-
throw new Error('The editor has no selection')
|
|
165
|
-
}
|
|
166
163
|
const block = toSlateValue(
|
|
167
164
|
[
|
|
168
165
|
{
|
|
@@ -173,22 +170,52 @@ export function createWithEditableAPI(
|
|
|
173
170
|
],
|
|
174
171
|
portableTextEditor,
|
|
175
172
|
)[0] as unknown as Node
|
|
176
|
-
|
|
173
|
+
|
|
174
|
+
if (!editor.selection) {
|
|
175
|
+
const lastBlock = Array.from(
|
|
176
|
+
Editor.nodes(editor, {
|
|
177
|
+
match: (n) => !Editor.isEditor(n),
|
|
178
|
+
at: [],
|
|
179
|
+
reverse: true,
|
|
180
|
+
}),
|
|
181
|
+
)[0]
|
|
182
|
+
|
|
183
|
+
// If there is no selection, let's just insert the new block at the
|
|
184
|
+
// end of the document
|
|
185
|
+
Editor.insertNode(editor, block)
|
|
186
|
+
|
|
187
|
+
if (lastBlock && isEqualToEmptyEditor([lastBlock[0]], types)) {
|
|
188
|
+
// And if the last block was an empty text block, let's remove
|
|
189
|
+
// that too
|
|
190
|
+
Transforms.removeNodes(editor, {at: lastBlock[1]})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
editor.onChange()
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
toPortableTextRange(
|
|
197
|
+
fromSlateValue(editor.children, types.block.name, KEY_TO_VALUE_ELEMENT.get(editor)),
|
|
198
|
+
editor.selection,
|
|
199
|
+
types,
|
|
200
|
+
)?.focus.path ?? []
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const focusBlock = Array.from(
|
|
177
205
|
Editor.nodes(editor, {
|
|
178
206
|
at: editor.selection.focus.path.slice(0, 1),
|
|
179
207
|
match: (n) => n._type === types.block.name,
|
|
180
208
|
}),
|
|
181
|
-
)[0]
|
|
209
|
+
)[0]
|
|
182
210
|
|
|
183
|
-
|
|
211
|
+
Editor.insertNode(editor, block)
|
|
184
212
|
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
Transforms.removeNodes(editor, {at: editor.selection})
|
|
213
|
+
if (focusBlock && isEqualToEmptyEditor([focusBlock[0]], types)) {
|
|
214
|
+
Transforms.removeNodes(editor, {at: focusBlock[1]})
|
|
188
215
|
}
|
|
189
216
|
|
|
190
|
-
Editor.insertNode(editor, block)
|
|
191
217
|
editor.onChange()
|
|
218
|
+
|
|
192
219
|
return (
|
|
193
220
|
toPortableTextRange(
|
|
194
221
|
fromSlateValue(editor.children, types.block.name, KEY_TO_VALUE_ELEMENT.get(editor)),
|
|
@@ -308,6 +335,10 @@ export function createWithEditableAPI(
|
|
|
308
335
|
}),
|
|
309
336
|
]
|
|
310
337
|
|
|
338
|
+
if (spans.length === 0) {
|
|
339
|
+
return false
|
|
340
|
+
}
|
|
341
|
+
|
|
311
342
|
if (
|
|
312
343
|
spans.some(
|
|
313
344
|
([span]) => !isPortableTextSpan(span) || !span.marks || span.marks?.length === 0,
|
|
@@ -195,12 +195,16 @@ export function createWithHotkeys(
|
|
|
195
195
|
) as SlateTextBlock | VoidElement
|
|
196
196
|
const focusBlockPath = editor.selection.focus.path.slice(0, 1)
|
|
197
197
|
const focusBlock = Node.descendant(editor, focusBlockPath) as SlateTextBlock | VoidElement
|
|
198
|
+
const isTextBlock = isPortableTextTextBlock(focusBlock)
|
|
199
|
+
const isEmptyFocusBlock =
|
|
200
|
+
isTextBlock && focusBlock.children.length === 1 && focusBlock.children?.[0]?.text === ''
|
|
198
201
|
|
|
199
202
|
if (
|
|
200
203
|
nextBlock &&
|
|
201
204
|
focusBlock &&
|
|
202
205
|
!Editor.isVoid(editor, focusBlock) &&
|
|
203
|
-
Editor.isVoid(editor, nextBlock)
|
|
206
|
+
Editor.isVoid(editor, nextBlock) &&
|
|
207
|
+
isEmptyFocusBlock
|
|
204
208
|
) {
|
|
205
209
|
debug('Preventing deleting void block below')
|
|
206
210
|
event.preventDefault()
|
package/src/types/editor.ts
CHANGED
|
@@ -375,6 +375,9 @@ export type EditorChange =
|
|
|
375
375
|
| UnsetChange
|
|
376
376
|
| ValueChange
|
|
377
377
|
|
|
378
|
+
/**
|
|
379
|
+
* @beta
|
|
380
|
+
*/
|
|
378
381
|
export type EditorChanges = Subject<EditorChange>
|
|
379
382
|
|
|
380
383
|
/** @beta */
|
|
@@ -384,6 +387,10 @@ export type OnPasteResult =
|
|
|
384
387
|
path?: Path
|
|
385
388
|
}
|
|
386
389
|
| undefined
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* @beta
|
|
393
|
+
*/
|
|
387
394
|
export type OnPasteResultOrPromise = OnPasteResult | Promise<OnPasteResult>
|
|
388
395
|
|
|
389
396
|
/** @beta */
|
package/src/types/options.ts
CHANGED
|
@@ -3,6 +3,9 @@ import {type BaseSyntheticEvent} from 'react'
|
|
|
3
3
|
import {type PortableTextEditor} from '../editor/PortableTextEditor'
|
|
4
4
|
import {type PatchObservable} from './editor'
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
6
9
|
export type createEditorOptions = {
|
|
7
10
|
keyGenerator: () => string
|
|
8
11
|
patches$?: PatchObservable
|
|
@@ -11,6 +14,9 @@ export type createEditorOptions = {
|
|
|
11
14
|
maxBlocks?: number
|
|
12
15
|
}
|
|
13
16
|
|
|
17
|
+
/**
|
|
18
|
+
* @beta
|
|
19
|
+
*/
|
|
14
20
|
export type HotkeyOptions = {
|
|
15
21
|
marks?: Record<string, string>
|
|
16
22
|
custom?: Record<string, (event: BaseSyntheticEvent, editor: PortableTextEditor) => void>
|
package/src/utils/bufferUntil.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import {defer, EMPTY, type Observable, of, type OperatorFunction, switchMap, tap} from 'rxjs'
|
|
2
|
-
|
|
3
|
-
export function bufferUntil<T>(
|
|
4
|
-
emitWhen: (currentBuffer: T[]) => boolean,
|
|
5
|
-
): OperatorFunction<T, T[]> {
|
|
6
|
-
return (source: Observable<T>) =>
|
|
7
|
-
defer(() => {
|
|
8
|
-
let buffer: T[] = [] // custom buffer
|
|
9
|
-
return source.pipe(
|
|
10
|
-
tap((v) => buffer.push(v)), // add values to buffer
|
|
11
|
-
switchMap(() => (emitWhen(buffer) ? of(buffer) : EMPTY)), // emit the buffer when the condition is met
|
|
12
|
-
tap(() => (buffer = [])), // clear the buffer
|
|
13
|
-
)
|
|
14
|
-
})
|
|
15
|
-
}
|