@portabletext/editor 0.0.0
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/LICENSE +21 -0
- package/README.md +3 -0
- package/lib/index.d.mts +911 -0
- package/lib/index.d.ts +911 -0
- package/lib/index.esm.js +4896 -0
- package/lib/index.esm.js.map +1 -0
- package/lib/index.js +4874 -0
- package/lib/index.js.map +1 -0
- package/lib/index.mjs +4896 -0
- package/lib/index.mjs.map +1 -0
- package/package.json +119 -0
- package/src/editor/Editable.tsx +683 -0
- package/src/editor/PortableTextEditor.tsx +308 -0
- package/src/editor/__tests__/PortableTextEditor.test.tsx +386 -0
- package/src/editor/__tests__/PortableTextEditorTester.tsx +116 -0
- package/src/editor/__tests__/RangeDecorations.test.tsx +115 -0
- package/src/editor/__tests__/handleClick.test.tsx +218 -0
- package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +389 -0
- package/src/editor/__tests__/utils.ts +39 -0
- package/src/editor/components/DraggableBlock.tsx +287 -0
- package/src/editor/components/Element.tsx +279 -0
- package/src/editor/components/Leaf.tsx +288 -0
- package/src/editor/components/SlateContainer.tsx +81 -0
- package/src/editor/components/Synchronizer.tsx +190 -0
- package/src/editor/hooks/usePortableTextEditor.ts +23 -0
- package/src/editor/hooks/usePortableTextEditorKeyGenerator.ts +24 -0
- package/src/editor/hooks/usePortableTextEditorSelection.ts +22 -0
- package/src/editor/hooks/usePortableTextEditorValue.ts +16 -0
- package/src/editor/hooks/usePortableTextReadOnly.ts +20 -0
- package/src/editor/hooks/useSyncValue.test.tsx +125 -0
- package/src/editor/hooks/useSyncValue.ts +372 -0
- package/src/editor/nodes/DefaultAnnotation.tsx +16 -0
- package/src/editor/nodes/DefaultObject.tsx +15 -0
- package/src/editor/nodes/index.ts +189 -0
- package/src/editor/plugins/__tests__/withEditableAPIDelete.test.tsx +244 -0
- package/src/editor/plugins/__tests__/withEditableAPIGetFragment.test.tsx +142 -0
- package/src/editor/plugins/__tests__/withEditableAPIInsert.test.tsx +346 -0
- package/src/editor/plugins/__tests__/withEditableAPISelectionsOverlapping.test.tsx +162 -0
- package/src/editor/plugins/__tests__/withHotkeys.test.tsx +212 -0
- package/src/editor/plugins/__tests__/withInsertBreak.test.tsx +204 -0
- package/src/editor/plugins/__tests__/withPlaceholderBlock.test.tsx +133 -0
- package/src/editor/plugins/__tests__/withPortableTextLists.test.tsx +65 -0
- package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +1377 -0
- package/src/editor/plugins/__tests__/withPortableTextSelections.test.tsx +91 -0
- package/src/editor/plugins/__tests__/withUndoRedo.test.tsx +115 -0
- package/src/editor/plugins/createWithEditableAPI.ts +573 -0
- package/src/editor/plugins/createWithHotKeys.ts +304 -0
- package/src/editor/plugins/createWithInsertBreak.ts +45 -0
- package/src/editor/plugins/createWithInsertData.ts +359 -0
- package/src/editor/plugins/createWithMaxBlocks.ts +24 -0
- package/src/editor/plugins/createWithObjectKeys.ts +63 -0
- package/src/editor/plugins/createWithPatches.ts +274 -0
- package/src/editor/plugins/createWithPlaceholderBlock.ts +36 -0
- package/src/editor/plugins/createWithPortableTextBlockStyle.ts +91 -0
- package/src/editor/plugins/createWithPortableTextLists.ts +160 -0
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +441 -0
- package/src/editor/plugins/createWithPortableTextSelections.ts +65 -0
- package/src/editor/plugins/createWithSchemaTypes.ts +76 -0
- package/src/editor/plugins/createWithUndoRedo.ts +494 -0
- package/src/editor/plugins/createWithUtils.ts +81 -0
- package/src/editor/plugins/index.ts +155 -0
- package/src/index.ts +11 -0
- package/src/patch/PatchEvent.ts +33 -0
- package/src/patch/applyPatch.ts +29 -0
- package/src/patch/array.ts +89 -0
- package/src/patch/arrayInsert.ts +27 -0
- package/src/patch/object.ts +39 -0
- package/src/patch/patches.ts +53 -0
- package/src/patch/primitive.ts +43 -0
- package/src/patch/string.ts +51 -0
- package/src/types/editor.ts +576 -0
- package/src/types/options.ts +17 -0
- package/src/types/patch.ts +65 -0
- package/src/types/slate.ts +25 -0
- package/src/utils/__tests__/dmpToOperations.test.ts +181 -0
- package/src/utils/__tests__/operationToPatches.test.ts +421 -0
- package/src/utils/__tests__/patchToOperations.test.ts +293 -0
- package/src/utils/__tests__/ranges.test.ts +18 -0
- package/src/utils/__tests__/valueNormalization.test.tsx +62 -0
- package/src/utils/__tests__/values.test.ts +253 -0
- package/src/utils/applyPatch.ts +407 -0
- package/src/utils/bufferUntil.ts +15 -0
- package/src/utils/debug.ts +12 -0
- package/src/utils/getPortableTextMemberSchemaTypes.ts +100 -0
- package/src/utils/operationToPatches.ts +357 -0
- package/src/utils/patches.ts +36 -0
- package/src/utils/paths.ts +60 -0
- package/src/utils/ranges.ts +77 -0
- package/src/utils/schema.ts +8 -0
- package/src/utils/selection.ts +65 -0
- package/src/utils/ucs2Indices.ts +67 -0
- package/src/utils/validateValue.ts +394 -0
- package/src/utils/values.ts +208 -0
- package/src/utils/weakMaps.ts +24 -0
- package/src/utils/withChanges.ts +25 -0
- package/src/utils/withPreserveKeys.ts +14 -0
- package/src/utils/withoutPatching.ts +14 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/* eslint-disable max-statements */
|
|
2
|
+
/* eslint-disable complexity */
|
|
3
|
+
import {isPortableTextSpan, isPortableTextTextBlock} from '@sanity/types'
|
|
4
|
+
import {isHotkey} from 'is-hotkey-esm'
|
|
5
|
+
import {type KeyboardEvent} from 'react'
|
|
6
|
+
import {Editor, Node, Path, Range, Transforms} from 'slate'
|
|
7
|
+
import {type ReactEditor} from 'slate-react'
|
|
8
|
+
|
|
9
|
+
import {type PortableTextMemberSchemaTypes, type PortableTextSlateEditor} from '../../types/editor'
|
|
10
|
+
import {type HotkeyOptions} from '../../types/options'
|
|
11
|
+
import {type SlateTextBlock, type VoidElement} from '../../types/slate'
|
|
12
|
+
import {debugWithName} from '../../utils/debug'
|
|
13
|
+
import {type PortableTextEditor} from '../PortableTextEditor'
|
|
14
|
+
|
|
15
|
+
const debug = debugWithName('plugin:withHotKeys')
|
|
16
|
+
|
|
17
|
+
const DEFAULT_HOTKEYS: HotkeyOptions = {
|
|
18
|
+
marks: {
|
|
19
|
+
'mod+b': 'strong',
|
|
20
|
+
'mod+i': 'em',
|
|
21
|
+
'mod+u': 'underline',
|
|
22
|
+
"mod+'": 'code',
|
|
23
|
+
},
|
|
24
|
+
custom: {},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* This plugin takes care of all hotkeys in the editor
|
|
29
|
+
*
|
|
30
|
+
*/
|
|
31
|
+
export function createWithHotkeys(
|
|
32
|
+
types: PortableTextMemberSchemaTypes,
|
|
33
|
+
portableTextEditor: PortableTextEditor,
|
|
34
|
+
hotkeysFromOptions?: HotkeyOptions,
|
|
35
|
+
): (editor: PortableTextSlateEditor & ReactEditor) => any {
|
|
36
|
+
const reservedHotkeys = ['enter', 'tab', 'shift', 'delete', 'end']
|
|
37
|
+
const activeHotkeys = hotkeysFromOptions || DEFAULT_HOTKEYS // TODO: Merge where possible? A union?
|
|
38
|
+
return function withHotKeys(editor: PortableTextSlateEditor & ReactEditor) {
|
|
39
|
+
editor.pteWithHotKeys = (event: KeyboardEvent<HTMLDivElement>): void => {
|
|
40
|
+
// Wire up custom marks hotkeys
|
|
41
|
+
Object.keys(activeHotkeys).forEach((cat) => {
|
|
42
|
+
if (cat === 'marks') {
|
|
43
|
+
// eslint-disable-next-line guard-for-in
|
|
44
|
+
for (const hotkey in activeHotkeys[cat]) {
|
|
45
|
+
if (reservedHotkeys.includes(hotkey)) {
|
|
46
|
+
throw new Error(`The hotkey ${hotkey} is reserved!`)
|
|
47
|
+
}
|
|
48
|
+
if (isHotkey(hotkey, event.nativeEvent)) {
|
|
49
|
+
event.preventDefault()
|
|
50
|
+
const possibleMark = activeHotkeys[cat]
|
|
51
|
+
if (possibleMark) {
|
|
52
|
+
const mark = possibleMark[hotkey]
|
|
53
|
+
debug(`HotKey ${hotkey} to toggle ${mark}`)
|
|
54
|
+
editor.pteToggleMark(mark)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (cat === 'custom') {
|
|
60
|
+
// eslint-disable-next-line guard-for-in
|
|
61
|
+
for (const hotkey in activeHotkeys[cat]) {
|
|
62
|
+
if (reservedHotkeys.includes(hotkey)) {
|
|
63
|
+
throw new Error(`The hotkey ${hotkey} is reserved!`)
|
|
64
|
+
}
|
|
65
|
+
if (isHotkey(hotkey, event.nativeEvent)) {
|
|
66
|
+
const possibleCommand = activeHotkeys[cat]
|
|
67
|
+
if (possibleCommand) {
|
|
68
|
+
const command = possibleCommand[hotkey]
|
|
69
|
+
command(event, portableTextEditor)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const isEnter = isHotkey('enter', event.nativeEvent)
|
|
77
|
+
const isTab = isHotkey('tab', event.nativeEvent)
|
|
78
|
+
const isShiftEnter = isHotkey('shift+enter', event.nativeEvent)
|
|
79
|
+
const isShiftTab = isHotkey('shift+tab', event.nativeEvent)
|
|
80
|
+
const isBackspace = isHotkey('backspace', event.nativeEvent)
|
|
81
|
+
const isDelete = isHotkey('delete', event.nativeEvent)
|
|
82
|
+
const isArrowDown = isHotkey('down', event.nativeEvent)
|
|
83
|
+
const isArrowUp = isHotkey('up', event.nativeEvent)
|
|
84
|
+
|
|
85
|
+
// Check if the user is in a void block, in that case, add an empty text block below if there is no next block
|
|
86
|
+
if (isArrowDown && editor.selection) {
|
|
87
|
+
const focusBlock = Node.descendant(editor, editor.selection.focus.path.slice(0, 1)) as
|
|
88
|
+
| SlateTextBlock
|
|
89
|
+
| VoidElement
|
|
90
|
+
|
|
91
|
+
if (focusBlock && Editor.isVoid(editor, focusBlock)) {
|
|
92
|
+
const nextPath = Path.next(editor.selection.focus.path.slice(0, 1))
|
|
93
|
+
const nextBlock = Node.has(editor, nextPath)
|
|
94
|
+
if (!nextBlock) {
|
|
95
|
+
Transforms.insertNodes(editor, editor.pteCreateEmptyBlock(), {at: nextPath})
|
|
96
|
+
editor.onChange()
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (isArrowUp && editor.selection) {
|
|
102
|
+
const isFirstBlock = editor.selection.focus.path[0] === 0
|
|
103
|
+
const focusBlock = Node.descendant(editor, editor.selection.focus.path.slice(0, 1)) as
|
|
104
|
+
| SlateTextBlock
|
|
105
|
+
| VoidElement
|
|
106
|
+
|
|
107
|
+
if (isFirstBlock && focusBlock && Editor.isVoid(editor, focusBlock)) {
|
|
108
|
+
Transforms.insertNodes(editor, editor.pteCreateEmptyBlock(), {at: [0]})
|
|
109
|
+
Transforms.select(editor, {path: [0, 0], offset: 0})
|
|
110
|
+
editor.onChange()
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (
|
|
115
|
+
isBackspace &&
|
|
116
|
+
editor.selection &&
|
|
117
|
+
editor.selection.focus.path[0] === 0 &&
|
|
118
|
+
Range.isCollapsed(editor.selection)
|
|
119
|
+
) {
|
|
120
|
+
// If the block is text and we have a next block below, remove the current block
|
|
121
|
+
const focusBlock = Node.descendant(editor, editor.selection.focus.path.slice(0, 1)) as
|
|
122
|
+
| SlateTextBlock
|
|
123
|
+
| VoidElement
|
|
124
|
+
const nextPath = Path.next(editor.selection.focus.path.slice(0, 1))
|
|
125
|
+
const nextBlock = Node.has(editor, nextPath)
|
|
126
|
+
const isTextBlock = isPortableTextTextBlock(focusBlock)
|
|
127
|
+
const isEmptyFocusBlock =
|
|
128
|
+
isTextBlock && focusBlock.children.length === 1 && focusBlock.children?.[0]?.text === ''
|
|
129
|
+
|
|
130
|
+
if (nextBlock && isTextBlock && isEmptyFocusBlock) {
|
|
131
|
+
// Remove current block
|
|
132
|
+
event.preventDefault()
|
|
133
|
+
event.stopPropagation()
|
|
134
|
+
Transforms.removeNodes(editor, {match: (n) => n === focusBlock})
|
|
135
|
+
editor.onChange()
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Disallow deleting void blocks by backspace from another line.
|
|
140
|
+
// Otherwise it's so easy to delete the void block above when trying to delete text on
|
|
141
|
+
// the line below or above
|
|
142
|
+
if (
|
|
143
|
+
isBackspace &&
|
|
144
|
+
editor.selection &&
|
|
145
|
+
editor.selection.focus.path[0] > 0 &&
|
|
146
|
+
Range.isCollapsed(editor.selection)
|
|
147
|
+
) {
|
|
148
|
+
const prevPath = Path.previous(editor.selection.focus.path.slice(0, 1))
|
|
149
|
+
const prevBlock = Node.descendant(editor, prevPath) as SlateTextBlock | VoidElement
|
|
150
|
+
const focusBlock = Node.descendant(editor, editor.selection.focus.path.slice(0, 1))
|
|
151
|
+
if (
|
|
152
|
+
prevBlock &&
|
|
153
|
+
focusBlock &&
|
|
154
|
+
Editor.isVoid(editor, prevBlock) &&
|
|
155
|
+
editor.selection.focus.offset === 0
|
|
156
|
+
) {
|
|
157
|
+
debug('Preventing deleting void block above')
|
|
158
|
+
event.preventDefault()
|
|
159
|
+
event.stopPropagation()
|
|
160
|
+
|
|
161
|
+
const isTextBlock = isPortableTextTextBlock(focusBlock)
|
|
162
|
+
const isEmptyFocusBlock =
|
|
163
|
+
isTextBlock && focusBlock.children.length === 1 && focusBlock.children?.[0]?.text === ''
|
|
164
|
+
|
|
165
|
+
// If this is a not an text block or it is empty, simply remove it
|
|
166
|
+
if (!isTextBlock || isEmptyFocusBlock) {
|
|
167
|
+
Transforms.removeNodes(editor, {match: (n) => n === focusBlock})
|
|
168
|
+
Transforms.select(editor, prevPath)
|
|
169
|
+
|
|
170
|
+
editor.onChange()
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// If the focused block is a text node but it isn't empty, focus on the previous block
|
|
175
|
+
if (isTextBlock && !isEmptyFocusBlock) {
|
|
176
|
+
Transforms.select(editor, prevPath)
|
|
177
|
+
|
|
178
|
+
editor.onChange()
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (
|
|
186
|
+
isDelete &&
|
|
187
|
+
editor.selection &&
|
|
188
|
+
editor.selection.focus.offset === 0 &&
|
|
189
|
+
Range.isCollapsed(editor.selection) &&
|
|
190
|
+
editor.children[editor.selection.focus.path[0] + 1]
|
|
191
|
+
) {
|
|
192
|
+
const nextBlock = Node.descendant(
|
|
193
|
+
editor,
|
|
194
|
+
Path.next(editor.selection.focus.path.slice(0, 1)),
|
|
195
|
+
) as SlateTextBlock | VoidElement
|
|
196
|
+
const focusBlockPath = editor.selection.focus.path.slice(0, 1)
|
|
197
|
+
const focusBlock = Node.descendant(editor, focusBlockPath) as SlateTextBlock | VoidElement
|
|
198
|
+
|
|
199
|
+
if (
|
|
200
|
+
nextBlock &&
|
|
201
|
+
focusBlock &&
|
|
202
|
+
!Editor.isVoid(editor, focusBlock) &&
|
|
203
|
+
Editor.isVoid(editor, nextBlock)
|
|
204
|
+
) {
|
|
205
|
+
debug('Preventing deleting void block below')
|
|
206
|
+
event.preventDefault()
|
|
207
|
+
event.stopPropagation()
|
|
208
|
+
Transforms.removeNodes(editor, {match: (n) => n === focusBlock})
|
|
209
|
+
Transforms.select(editor, focusBlockPath)
|
|
210
|
+
editor.onChange()
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Tab for lists
|
|
216
|
+
// Only steal tab when we are on a plain text span or we are at the start of the line (fallback if the whole block is annotated or contains a single inline object)
|
|
217
|
+
// Otherwise tab is reserved for accessability for buttons etc.
|
|
218
|
+
if ((isTab || isShiftTab) && editor.selection) {
|
|
219
|
+
const [focusChild] = Editor.node(editor, editor.selection.focus, {depth: 2})
|
|
220
|
+
const [focusBlock] = isPortableTextSpan(focusChild)
|
|
221
|
+
? Editor.node(editor, editor.selection.focus, {depth: 1})
|
|
222
|
+
: []
|
|
223
|
+
const hasAnnotationFocus =
|
|
224
|
+
focusChild &&
|
|
225
|
+
isPortableTextTextBlock(focusBlock) &&
|
|
226
|
+
isPortableTextSpan(focusChild) &&
|
|
227
|
+
(focusChild.marks || ([] as string[])).filter((m) =>
|
|
228
|
+
(focusBlock.markDefs || []).map((def) => def._key).includes(m),
|
|
229
|
+
).length > 0
|
|
230
|
+
const [start] = Range.edges(editor.selection)
|
|
231
|
+
const atStartOfNode = Editor.isStart(editor, start, start.path)
|
|
232
|
+
|
|
233
|
+
if (
|
|
234
|
+
focusChild &&
|
|
235
|
+
isPortableTextSpan(focusChild) &&
|
|
236
|
+
(!hasAnnotationFocus || atStartOfNode) &&
|
|
237
|
+
editor.pteIncrementBlockLevels(isShiftTab)
|
|
238
|
+
) {
|
|
239
|
+
event.preventDefault()
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Deal with enter key combos
|
|
244
|
+
if (isEnter && !isShiftEnter && editor.selection) {
|
|
245
|
+
const focusBlockPath = editor.selection.focus.path.slice(0, 1)
|
|
246
|
+
const focusBlock = Node.descendant(editor, focusBlockPath) as SlateTextBlock | VoidElement
|
|
247
|
+
|
|
248
|
+
// List item enter key
|
|
249
|
+
if (editor.isListBlock(focusBlock)) {
|
|
250
|
+
if (editor.pteEndList()) {
|
|
251
|
+
event.preventDefault()
|
|
252
|
+
}
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Enter from another style than the first (default one)
|
|
257
|
+
if (
|
|
258
|
+
editor.isTextBlock(focusBlock) &&
|
|
259
|
+
focusBlock.style &&
|
|
260
|
+
focusBlock.style !== types.styles[0].value
|
|
261
|
+
) {
|
|
262
|
+
const [, end] = Range.edges(editor.selection)
|
|
263
|
+
const endAtEndOfNode = Editor.isEnd(editor, end, end.path)
|
|
264
|
+
if (endAtEndOfNode) {
|
|
265
|
+
Editor.insertNode(editor, editor.pteCreateEmptyBlock())
|
|
266
|
+
event.preventDefault()
|
|
267
|
+
editor.onChange()
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// Block object enter key
|
|
272
|
+
if (focusBlock && Editor.isVoid(editor, focusBlock)) {
|
|
273
|
+
Editor.insertNode(editor, editor.pteCreateEmptyBlock())
|
|
274
|
+
event.preventDefault()
|
|
275
|
+
editor.onChange()
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
// Default enter key behavior
|
|
279
|
+
event.preventDefault()
|
|
280
|
+
editor.insertBreak()
|
|
281
|
+
editor.onChange()
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Soft line breaks
|
|
285
|
+
if (isShiftEnter) {
|
|
286
|
+
event.preventDefault()
|
|
287
|
+
editor.insertText('\n')
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Undo/redo
|
|
292
|
+
if (isHotkey('mod+z', event.nativeEvent)) {
|
|
293
|
+
event.preventDefault()
|
|
294
|
+
editor.undo()
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
if (isHotkey('mod+y', event.nativeEvent) || isHotkey('mod+shift+z', event.nativeEvent)) {
|
|
298
|
+
event.preventDefault()
|
|
299
|
+
editor.redo()
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return editor
|
|
303
|
+
}
|
|
304
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {Editor, Node, Path, Range, Transforms} from 'slate'
|
|
2
|
+
|
|
3
|
+
import {type PortableTextMemberSchemaTypes, type PortableTextSlateEditor} from '../../types/editor'
|
|
4
|
+
import {type SlateTextBlock, type VoidElement} from '../../types/slate'
|
|
5
|
+
import {isEqualToEmptyEditor} from '../../utils/values'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Changes default behavior of insertBreak to insert a new block instead of splitting current when the cursor is at the
|
|
9
|
+
* start of the block.
|
|
10
|
+
*/
|
|
11
|
+
export function createWithInsertBreak(
|
|
12
|
+
types: PortableTextMemberSchemaTypes,
|
|
13
|
+
): (editor: PortableTextSlateEditor) => PortableTextSlateEditor {
|
|
14
|
+
return function withInsertBreak(editor: PortableTextSlateEditor): PortableTextSlateEditor {
|
|
15
|
+
const {insertBreak} = editor
|
|
16
|
+
|
|
17
|
+
editor.insertBreak = () => {
|
|
18
|
+
if (editor.selection) {
|
|
19
|
+
const focusBlockPath = editor.selection.focus.path.slice(0, 1)
|
|
20
|
+
const focusBlock = Node.descendant(editor, focusBlockPath) as SlateTextBlock | VoidElement
|
|
21
|
+
|
|
22
|
+
if (editor.isTextBlock(focusBlock)) {
|
|
23
|
+
// Enter from another style than the first (default one)
|
|
24
|
+
const [, end] = Range.edges(editor.selection)
|
|
25
|
+
// If it's at the start of block, we want to preserve the current block key and insert a new one in the current position instead of splitting the node.
|
|
26
|
+
const isEndAtStartOfNode = Editor.isStart(editor, end, end.path)
|
|
27
|
+
const isEmptyTextBlock = focusBlock && isEqualToEmptyEditor([focusBlock], types)
|
|
28
|
+
if (isEndAtStartOfNode && !isEmptyTextBlock) {
|
|
29
|
+
Editor.insertNode(editor, editor.pteCreateEmptyBlock())
|
|
30
|
+
const [nextBlockPath] = Path.next(focusBlockPath)
|
|
31
|
+
Transforms.select(editor, {
|
|
32
|
+
anchor: {path: [nextBlockPath, 0], offset: 0},
|
|
33
|
+
focus: {path: [nextBlockPath, 0], offset: 0},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
editor.onChange()
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
insertBreak()
|
|
42
|
+
}
|
|
43
|
+
return editor
|
|
44
|
+
}
|
|
45
|
+
}
|