@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.
Files changed (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/lib/index.d.mts +911 -0
  4. package/lib/index.d.ts +911 -0
  5. package/lib/index.esm.js +4896 -0
  6. package/lib/index.esm.js.map +1 -0
  7. package/lib/index.js +4874 -0
  8. package/lib/index.js.map +1 -0
  9. package/lib/index.mjs +4896 -0
  10. package/lib/index.mjs.map +1 -0
  11. package/package.json +119 -0
  12. package/src/editor/Editable.tsx +683 -0
  13. package/src/editor/PortableTextEditor.tsx +308 -0
  14. package/src/editor/__tests__/PortableTextEditor.test.tsx +386 -0
  15. package/src/editor/__tests__/PortableTextEditorTester.tsx +116 -0
  16. package/src/editor/__tests__/RangeDecorations.test.tsx +115 -0
  17. package/src/editor/__tests__/handleClick.test.tsx +218 -0
  18. package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +389 -0
  19. package/src/editor/__tests__/utils.ts +39 -0
  20. package/src/editor/components/DraggableBlock.tsx +287 -0
  21. package/src/editor/components/Element.tsx +279 -0
  22. package/src/editor/components/Leaf.tsx +288 -0
  23. package/src/editor/components/SlateContainer.tsx +81 -0
  24. package/src/editor/components/Synchronizer.tsx +190 -0
  25. package/src/editor/hooks/usePortableTextEditor.ts +23 -0
  26. package/src/editor/hooks/usePortableTextEditorKeyGenerator.ts +24 -0
  27. package/src/editor/hooks/usePortableTextEditorSelection.ts +22 -0
  28. package/src/editor/hooks/usePortableTextEditorValue.ts +16 -0
  29. package/src/editor/hooks/usePortableTextReadOnly.ts +20 -0
  30. package/src/editor/hooks/useSyncValue.test.tsx +125 -0
  31. package/src/editor/hooks/useSyncValue.ts +372 -0
  32. package/src/editor/nodes/DefaultAnnotation.tsx +16 -0
  33. package/src/editor/nodes/DefaultObject.tsx +15 -0
  34. package/src/editor/nodes/index.ts +189 -0
  35. package/src/editor/plugins/__tests__/withEditableAPIDelete.test.tsx +244 -0
  36. package/src/editor/plugins/__tests__/withEditableAPIGetFragment.test.tsx +142 -0
  37. package/src/editor/plugins/__tests__/withEditableAPIInsert.test.tsx +346 -0
  38. package/src/editor/plugins/__tests__/withEditableAPISelectionsOverlapping.test.tsx +162 -0
  39. package/src/editor/plugins/__tests__/withHotkeys.test.tsx +212 -0
  40. package/src/editor/plugins/__tests__/withInsertBreak.test.tsx +204 -0
  41. package/src/editor/plugins/__tests__/withPlaceholderBlock.test.tsx +133 -0
  42. package/src/editor/plugins/__tests__/withPortableTextLists.test.tsx +65 -0
  43. package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +1377 -0
  44. package/src/editor/plugins/__tests__/withPortableTextSelections.test.tsx +91 -0
  45. package/src/editor/plugins/__tests__/withUndoRedo.test.tsx +115 -0
  46. package/src/editor/plugins/createWithEditableAPI.ts +573 -0
  47. package/src/editor/plugins/createWithHotKeys.ts +304 -0
  48. package/src/editor/plugins/createWithInsertBreak.ts +45 -0
  49. package/src/editor/plugins/createWithInsertData.ts +359 -0
  50. package/src/editor/plugins/createWithMaxBlocks.ts +24 -0
  51. package/src/editor/plugins/createWithObjectKeys.ts +63 -0
  52. package/src/editor/plugins/createWithPatches.ts +274 -0
  53. package/src/editor/plugins/createWithPlaceholderBlock.ts +36 -0
  54. package/src/editor/plugins/createWithPortableTextBlockStyle.ts +91 -0
  55. package/src/editor/plugins/createWithPortableTextLists.ts +160 -0
  56. package/src/editor/plugins/createWithPortableTextMarkModel.ts +441 -0
  57. package/src/editor/plugins/createWithPortableTextSelections.ts +65 -0
  58. package/src/editor/plugins/createWithSchemaTypes.ts +76 -0
  59. package/src/editor/plugins/createWithUndoRedo.ts +494 -0
  60. package/src/editor/plugins/createWithUtils.ts +81 -0
  61. package/src/editor/plugins/index.ts +155 -0
  62. package/src/index.ts +11 -0
  63. package/src/patch/PatchEvent.ts +33 -0
  64. package/src/patch/applyPatch.ts +29 -0
  65. package/src/patch/array.ts +89 -0
  66. package/src/patch/arrayInsert.ts +27 -0
  67. package/src/patch/object.ts +39 -0
  68. package/src/patch/patches.ts +53 -0
  69. package/src/patch/primitive.ts +43 -0
  70. package/src/patch/string.ts +51 -0
  71. package/src/types/editor.ts +576 -0
  72. package/src/types/options.ts +17 -0
  73. package/src/types/patch.ts +65 -0
  74. package/src/types/slate.ts +25 -0
  75. package/src/utils/__tests__/dmpToOperations.test.ts +181 -0
  76. package/src/utils/__tests__/operationToPatches.test.ts +421 -0
  77. package/src/utils/__tests__/patchToOperations.test.ts +293 -0
  78. package/src/utils/__tests__/ranges.test.ts +18 -0
  79. package/src/utils/__tests__/valueNormalization.test.tsx +62 -0
  80. package/src/utils/__tests__/values.test.ts +253 -0
  81. package/src/utils/applyPatch.ts +407 -0
  82. package/src/utils/bufferUntil.ts +15 -0
  83. package/src/utils/debug.ts +12 -0
  84. package/src/utils/getPortableTextMemberSchemaTypes.ts +100 -0
  85. package/src/utils/operationToPatches.ts +357 -0
  86. package/src/utils/patches.ts +36 -0
  87. package/src/utils/paths.ts +60 -0
  88. package/src/utils/ranges.ts +77 -0
  89. package/src/utils/schema.ts +8 -0
  90. package/src/utils/selection.ts +65 -0
  91. package/src/utils/ucs2Indices.ts +67 -0
  92. package/src/utils/validateValue.ts +394 -0
  93. package/src/utils/values.ts +208 -0
  94. package/src/utils/weakMaps.ts +24 -0
  95. package/src/utils/withChanges.ts +25 -0
  96. package/src/utils/withPreserveKeys.ts +14 -0
  97. package/src/utils/withoutPatching.ts +14 -0
@@ -0,0 +1,274 @@
1
+ /* eslint-disable max-nested-callbacks */
2
+ import {type Subject} from 'rxjs'
3
+ import {
4
+ type Descendant,
5
+ Editor,
6
+ type InsertNodeOperation,
7
+ type InsertTextOperation,
8
+ type MergeNodeOperation,
9
+ type MoveNodeOperation,
10
+ type Operation,
11
+ type RemoveNodeOperation,
12
+ type RemoveTextOperation,
13
+ type SetNodeOperation,
14
+ type SplitNodeOperation,
15
+ } from 'slate'
16
+
17
+ import {insert, setIfMissing, unset} from '../../patch/PatchEvent'
18
+ import {
19
+ type EditorChange,
20
+ type PatchObservable,
21
+ type PortableTextMemberSchemaTypes,
22
+ type PortableTextSlateEditor,
23
+ } from '../../types/editor'
24
+ import {type Patch} from '../../types/patch'
25
+ import {createApplyPatch} from '../../utils/applyPatch'
26
+ import {debugWithName} from '../../utils/debug'
27
+ import {fromSlateValue, isEqualToEmptyEditor} from '../../utils/values'
28
+ import {IS_PROCESSING_REMOTE_CHANGES, KEY_TO_VALUE_ELEMENT} from '../../utils/weakMaps'
29
+ import {withRemoteChanges} from '../../utils/withChanges'
30
+ import {isPatching, PATCHING, withoutPatching} from '../../utils/withoutPatching'
31
+ import {withPreserveKeys} from '../../utils/withPreserveKeys'
32
+ import {withoutSaving} from './createWithUndoRedo'
33
+
34
+ const debug = debugWithName('plugin:withPatches')
35
+ const debugVerbose = false
36
+
37
+ export interface PatchFunctions {
38
+ insertNodePatch: (
39
+ editor: PortableTextSlateEditor,
40
+ operation: InsertNodeOperation,
41
+ previousChildren: Descendant[],
42
+ ) => Patch[]
43
+ insertTextPatch: (
44
+ editor: PortableTextSlateEditor,
45
+ operation: InsertTextOperation,
46
+ previousChildren: Descendant[],
47
+ ) => Patch[]
48
+ mergeNodePatch: (
49
+ editor: PortableTextSlateEditor,
50
+ operation: MergeNodeOperation,
51
+ previousChildren: Descendant[],
52
+ ) => Patch[]
53
+ moveNodePatch: (
54
+ editor: PortableTextSlateEditor,
55
+ operation: MoveNodeOperation,
56
+ previousChildren: Descendant[],
57
+ ) => Patch[]
58
+ removeNodePatch: (
59
+ editor: PortableTextSlateEditor,
60
+ operation: RemoveNodeOperation,
61
+ previousChildren: Descendant[],
62
+ ) => Patch[]
63
+ removeTextPatch: (
64
+ editor: PortableTextSlateEditor,
65
+ operation: RemoveTextOperation,
66
+ previousChildren: Descendant[],
67
+ ) => Patch[]
68
+ setNodePatch: (
69
+ editor: PortableTextSlateEditor,
70
+ operation: SetNodeOperation,
71
+ previousChildren: Descendant[],
72
+ ) => Patch[]
73
+ splitNodePatch: (
74
+ editor: PortableTextSlateEditor,
75
+ operation: SplitNodeOperation,
76
+ previousChildren: Descendant[],
77
+ ) => Patch[]
78
+ }
79
+
80
+ interface Options {
81
+ change$: Subject<EditorChange>
82
+ keyGenerator: () => string
83
+ patches$?: PatchObservable
84
+ patchFunctions: PatchFunctions
85
+ readOnly: boolean
86
+ schemaTypes: PortableTextMemberSchemaTypes
87
+ }
88
+
89
+ export function createWithPatches({
90
+ change$,
91
+ patches$,
92
+ patchFunctions,
93
+ readOnly,
94
+ schemaTypes,
95
+ }: Options): (editor: PortableTextSlateEditor) => PortableTextSlateEditor {
96
+ // The previous editor children are needed to figure out the _key of deleted nodes
97
+ // The editor.children would no longer contain that information if the node is already deleted.
98
+ let previousChildren: Descendant[]
99
+
100
+ const applyPatch = createApplyPatch(schemaTypes)
101
+
102
+ return function withPatches(editor: PortableTextSlateEditor) {
103
+ IS_PROCESSING_REMOTE_CHANGES.set(editor, false)
104
+ PATCHING.set(editor, true)
105
+ previousChildren = [...editor.children]
106
+
107
+ const {apply} = editor
108
+ let bufferedPatches: Patch[] = []
109
+
110
+ const handleBufferedRemotePatches = () => {
111
+ if (bufferedPatches.length === 0) {
112
+ return
113
+ }
114
+ const patches = bufferedPatches
115
+ bufferedPatches = []
116
+ let changed = false
117
+ withRemoteChanges(editor, () => {
118
+ Editor.withoutNormalizing(editor, () => {
119
+ withoutPatching(editor, () => {
120
+ withoutSaving(editor, () => {
121
+ withPreserveKeys(editor, () => {
122
+ patches.forEach((patch) => {
123
+ if (debug.enabled) debug(`Handling remote patch ${JSON.stringify(patch)}`)
124
+ changed = applyPatch(editor, patch)
125
+ })
126
+ })
127
+ })
128
+ })
129
+ })
130
+ if (changed) {
131
+ editor.normalize()
132
+ editor.onChange()
133
+ }
134
+ })
135
+ }
136
+
137
+ const handlePatches = ({patches}: {patches: Patch[]}) => {
138
+ const remotePatches = patches.filter((p) => p.origin !== 'local')
139
+ if (remotePatches.length === 0) {
140
+ return
141
+ }
142
+ bufferedPatches = bufferedPatches.concat(remotePatches)
143
+ handleBufferedRemotePatches()
144
+ }
145
+
146
+ if (patches$) {
147
+ editor.subscriptions.push(() => {
148
+ debug('Subscribing to patches$')
149
+ const sub = patches$.subscribe(handlePatches)
150
+ return () => {
151
+ debug('Unsubscribing to patches$')
152
+ sub.unsubscribe()
153
+ }
154
+ })
155
+ }
156
+
157
+ editor.apply = (operation: Operation): void | Editor => {
158
+ if (readOnly) {
159
+ apply(operation)
160
+ return editor
161
+ }
162
+ let patches: Patch[] = []
163
+
164
+ // Update previous children here before we apply
165
+ previousChildren = editor.children
166
+
167
+ const editorWasEmpty = isEqualToEmptyEditor(previousChildren, schemaTypes)
168
+
169
+ // Apply the operation
170
+ apply(operation)
171
+
172
+ const editorIsEmpty = isEqualToEmptyEditor(editor.children, schemaTypes)
173
+
174
+ if (!isPatching(editor)) {
175
+ if (debugVerbose && debug.enabled)
176
+ debug(`Editor is not producing patch for operation ${operation.type}`, operation)
177
+ return editor
178
+ }
179
+
180
+ // If the editor was empty and now isn't, insert the placeholder into it.
181
+ if (editorWasEmpty && !editorIsEmpty && operation.type !== 'set_selection') {
182
+ patches.push(insert(previousChildren, 'before', [0]))
183
+ }
184
+
185
+ switch (operation.type) {
186
+ case 'insert_text':
187
+ patches = [
188
+ ...patches,
189
+ ...patchFunctions.insertTextPatch(editor, operation, previousChildren),
190
+ ]
191
+ break
192
+ case 'remove_text':
193
+ patches = [
194
+ ...patches,
195
+ ...patchFunctions.removeTextPatch(editor, operation, previousChildren),
196
+ ]
197
+ break
198
+ case 'remove_node':
199
+ patches = [
200
+ ...patches,
201
+ ...patchFunctions.removeNodePatch(editor, operation, previousChildren),
202
+ ]
203
+ break
204
+ case 'split_node':
205
+ patches = [
206
+ ...patches,
207
+ ...patchFunctions.splitNodePatch(editor, operation, previousChildren),
208
+ ]
209
+ break
210
+ case 'insert_node':
211
+ patches = [
212
+ ...patches,
213
+ ...patchFunctions.insertNodePatch(editor, operation, previousChildren),
214
+ ]
215
+ break
216
+ case 'set_node':
217
+ patches = [
218
+ ...patches,
219
+ ...patchFunctions.setNodePatch(editor, operation, previousChildren),
220
+ ]
221
+ break
222
+ case 'merge_node':
223
+ patches = [
224
+ ...patches,
225
+ ...patchFunctions.mergeNodePatch(editor, operation, previousChildren),
226
+ ]
227
+ break
228
+ case 'move_node':
229
+ patches = [
230
+ ...patches,
231
+ ...patchFunctions.moveNodePatch(editor, operation, previousChildren),
232
+ ]
233
+ break
234
+ case 'set_selection':
235
+ default:
236
+ // Do nothing
237
+ }
238
+
239
+ // Unset the value if a operation made the editor empty
240
+ if (
241
+ !editorWasEmpty &&
242
+ editorIsEmpty &&
243
+ ['merge_node', 'set_node', 'remove_text', 'remove_node'].includes(operation.type)
244
+ ) {
245
+ patches = [...patches, unset([])]
246
+ change$.next({
247
+ type: 'unset',
248
+ previousValue: fromSlateValue(
249
+ previousChildren,
250
+ schemaTypes.block.name,
251
+ KEY_TO_VALUE_ELEMENT.get(editor),
252
+ ),
253
+ })
254
+ }
255
+
256
+ // Prepend patches with setIfMissing if going from empty editor to something involving a patch.
257
+ if (editorWasEmpty && patches.length > 0) {
258
+ patches = [setIfMissing([], []), ...patches]
259
+ }
260
+
261
+ // Emit all patches
262
+ if (patches.length > 0) {
263
+ patches.forEach((patch) => {
264
+ change$.next({
265
+ type: 'patch',
266
+ patch: {...patch, origin: 'local'},
267
+ })
268
+ })
269
+ }
270
+ return editor
271
+ }
272
+ return editor
273
+ }
274
+ }
@@ -0,0 +1,36 @@
1
+ import {Editor, Path} from 'slate'
2
+
3
+ import {type PortableTextSlateEditor} from '../../types/editor'
4
+ import {type SlateTextBlock, type VoidElement} from '../../types/slate'
5
+ import {debugWithName} from '../../utils/debug'
6
+
7
+ const debug = debugWithName('plugin:withPlaceholderBlock')
8
+
9
+ /**
10
+ * Keep a "placeholder" block present when the editor is empty
11
+ *
12
+ */
13
+ export function createWithPlaceholderBlock(): (
14
+ editor: PortableTextSlateEditor,
15
+ ) => PortableTextSlateEditor {
16
+ return function withPlaceholderBlock(editor: PortableTextSlateEditor): PortableTextSlateEditor {
17
+ const {apply} = editor
18
+
19
+ editor.apply = (op) => {
20
+ if (op.type === 'remove_node') {
21
+ const node = op.node as SlateTextBlock | VoidElement
22
+ if (op.path[0] === 0 && Editor.isVoid(editor, node)) {
23
+ // Check next path, if it exists, do nothing
24
+ const nextPath = Path.next(op.path)
25
+ // Is removing the first block which is a void (not a text block), add a new empty text block in it, if there is no other element in the next path
26
+ if (!editor.children[nextPath[0]]) {
27
+ debug('Adding placeholder block')
28
+ Editor.insertNode(editor, editor.pteCreateEmptyBlock())
29
+ }
30
+ }
31
+ }
32
+ apply(op)
33
+ }
34
+ return editor
35
+ }
36
+ }
@@ -0,0 +1,91 @@
1
+ import {Editor, type Node, Path, Text as SlateText, Transforms} from 'slate'
2
+
3
+ import {type PortableTextMemberSchemaTypes, type PortableTextSlateEditor} from '../../types/editor'
4
+ import {debugWithName} from '../../utils/debug'
5
+
6
+ const debug = debugWithName('plugin:withPortableTextBlockStyle')
7
+
8
+ export function createWithPortableTextBlockStyle(
9
+ types: PortableTextMemberSchemaTypes,
10
+ ): (editor: PortableTextSlateEditor) => PortableTextSlateEditor {
11
+ const defaultStyle = types.styles[0].value
12
+ return function withPortableTextBlockStyle(
13
+ editor: PortableTextSlateEditor,
14
+ ): PortableTextSlateEditor {
15
+ // Extend Slate's default normalization to reset split node to normal style
16
+ // if there is no text at the right end of the split.
17
+ const {normalizeNode} = editor
18
+ editor.normalizeNode = (nodeEntry) => {
19
+ normalizeNode(nodeEntry)
20
+ const [, path] = nodeEntry
21
+ for (const op of editor.operations) {
22
+ if (
23
+ op.type === 'split_node' &&
24
+ op.path.length === 1 &&
25
+ editor.isTextBlock(op.properties) &&
26
+ op.properties.style !== defaultStyle &&
27
+ op.path[0] === path[0] &&
28
+ !Path.equals(path, op.path)
29
+ ) {
30
+ const [child] = Editor.node(editor, [op.path[0] + 1, 0])
31
+ if (SlateText.isText(child) && child.text === '') {
32
+ debug(`Normalizing split node to ${defaultStyle} style`, op)
33
+ Transforms.setNodes(editor, {style: defaultStyle}, {at: [op.path[0] + 1], voids: false})
34
+ break
35
+ }
36
+ }
37
+ }
38
+ }
39
+ editor.pteHasBlockStyle = (style: string): boolean => {
40
+ if (!editor.selection) {
41
+ return false
42
+ }
43
+ const selectedBlocks = [
44
+ ...Editor.nodes(editor, {
45
+ at: editor.selection,
46
+ match: (node) => editor.isTextBlock(node) && node.style === style,
47
+ }),
48
+ ]
49
+ if (selectedBlocks.length > 0) {
50
+ return true
51
+ }
52
+ return false
53
+ }
54
+
55
+ editor.pteToggleBlockStyle = (blockStyle: string): void => {
56
+ if (!editor.selection) {
57
+ return
58
+ }
59
+ const selectedBlocks = [
60
+ ...Editor.nodes(editor, {
61
+ at: editor.selection,
62
+ match: (node) => editor.isTextBlock(node),
63
+ }),
64
+ ]
65
+ selectedBlocks.forEach(([node, path]) => {
66
+ if (editor.isTextBlock(node) && node.style === blockStyle) {
67
+ debug(`Unsetting block style '${blockStyle}'`)
68
+ Transforms.setNodes(editor, {...node, style: defaultStyle} as Partial<Node>, {
69
+ at: path,
70
+ })
71
+ } else {
72
+ if (blockStyle) {
73
+ debug(`Setting style '${blockStyle}'`)
74
+ } else {
75
+ debug('Setting default style', defaultStyle)
76
+ }
77
+ Transforms.setNodes(
78
+ editor,
79
+ {
80
+ ...node,
81
+ style: blockStyle || defaultStyle,
82
+ } as Partial<Node>,
83
+ {at: path},
84
+ )
85
+ }
86
+ })
87
+ editor.onChange()
88
+ }
89
+ return editor
90
+ }
91
+ }
@@ -0,0 +1,160 @@
1
+ import {Editor, Element, type Node, Text, Transforms} from 'slate'
2
+
3
+ import {type PortableTextMemberSchemaTypes, type PortableTextSlateEditor} from '../../types/editor'
4
+ import {debugWithName} from '../../utils/debug'
5
+
6
+ const debug = debugWithName('plugin:withPortableTextLists')
7
+ const MAX_LIST_LEVEL = 10
8
+
9
+ export function createWithPortableTextLists(types: PortableTextMemberSchemaTypes) {
10
+ return function withPortableTextLists(editor: PortableTextSlateEditor): PortableTextSlateEditor {
11
+ editor.pteToggleListItem = (listItemStyle: string) => {
12
+ const isActive = editor.pteHasListStyle(listItemStyle)
13
+ if (isActive) {
14
+ debug(`Remove list item '${listItemStyle}'`)
15
+ editor.pteUnsetListItem(listItemStyle)
16
+ } else {
17
+ debug(`Add list item '${listItemStyle}'`)
18
+ editor.pteSetListItem(listItemStyle)
19
+ }
20
+ }
21
+
22
+ editor.pteUnsetListItem = (listItemStyle: string) => {
23
+ if (!editor.selection) {
24
+ return
25
+ }
26
+ const selectedBlocks = [
27
+ ...Editor.nodes(editor, {
28
+ at: editor.selection,
29
+ match: (node) => Element.isElement(node) && node._type === types.block.name,
30
+ }),
31
+ ]
32
+ selectedBlocks.forEach(([node, path]) => {
33
+ if (editor.isListBlock(node)) {
34
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
35
+ const {listItem, level, ...rest} = node
36
+ const newNode = {
37
+ ...rest,
38
+ listItem: undefined,
39
+ level: undefined,
40
+ } as Partial<Node>
41
+ debug(`Unsetting list '${listItemStyle}'`)
42
+ Transforms.setNodes(editor, newNode, {at: path})
43
+ }
44
+ })
45
+ }
46
+
47
+ editor.pteSetListItem = (listItemStyle: string) => {
48
+ if (!editor.selection) {
49
+ return
50
+ }
51
+ const selectedBlocks = [
52
+ ...Editor.nodes(editor, {
53
+ at: editor.selection,
54
+ match: (node) => editor.isTextBlock(node),
55
+ }),
56
+ ]
57
+ selectedBlocks.forEach(([node, path]) => {
58
+ debug(`Setting list '${listItemStyle}'`)
59
+ Transforms.setNodes(
60
+ editor,
61
+ {
62
+ ...node,
63
+ level: 1,
64
+ listItem: listItemStyle || (types.lists[0] && types.lists[0].value),
65
+ } as Partial<Node>,
66
+ {at: path},
67
+ )
68
+ })
69
+ }
70
+
71
+ editor.pteEndList = () => {
72
+ if (!editor.selection) {
73
+ return false
74
+ }
75
+ const selectedBlocks = [
76
+ ...Editor.nodes(editor, {
77
+ at: editor.selection,
78
+ match: (node) =>
79
+ Element.isElement(node) &&
80
+ editor.isListBlock(node) &&
81
+ node.children.length === 1 &&
82
+ Text.isText(node.children[0]) &&
83
+ node.children[0].text === '',
84
+ }),
85
+ ]
86
+ if (selectedBlocks.length === 0) {
87
+ return false
88
+ }
89
+ selectedBlocks.forEach(([node, path]) => {
90
+ if (Element.isElement(node)) {
91
+ debug('Unset list')
92
+ Transforms.setNodes(
93
+ editor,
94
+ {
95
+ ...node,
96
+ level: undefined,
97
+ listItem: undefined,
98
+ },
99
+ {at: path},
100
+ )
101
+ }
102
+ })
103
+ return true // Note: we are exiting the plugin chain by not returning editor (or hotkey plugin 'enter' will fire)
104
+ }
105
+
106
+ editor.pteIncrementBlockLevels = (reverse?: boolean): boolean => {
107
+ if (!editor.selection) {
108
+ return false
109
+ }
110
+ const selectedBlocks = [
111
+ ...Editor.nodes(editor, {
112
+ at: editor.selection,
113
+ match: (node) => !!editor.isListBlock(node),
114
+ }),
115
+ ]
116
+ if (selectedBlocks.length === 0) {
117
+ return false
118
+ }
119
+ selectedBlocks.forEach(([node, path]) => {
120
+ if (editor.isListBlock(node)) {
121
+ let level = node.level || 1
122
+ if (reverse) {
123
+ level--
124
+ debug('Decrementing list level', Math.min(MAX_LIST_LEVEL, Math.max(1, level)))
125
+ } else {
126
+ level++
127
+ debug('Incrementing list level', Math.min(MAX_LIST_LEVEL, Math.max(1, level)))
128
+ }
129
+ Transforms.setNodes(
130
+ editor,
131
+ {level: Math.min(MAX_LIST_LEVEL, Math.max(1, level))},
132
+ {at: path},
133
+ )
134
+ }
135
+ })
136
+ return true
137
+ }
138
+
139
+ editor.pteHasListStyle = (listStyle: string): boolean => {
140
+ if (!editor.selection) {
141
+ return false
142
+ }
143
+ const selectedBlocks = [
144
+ ...Editor.nodes(editor, {
145
+ at: editor.selection,
146
+ match: (node) => editor.isTextBlock(node),
147
+ }),
148
+ ]
149
+
150
+ if (selectedBlocks.length > 0) {
151
+ return selectedBlocks.every(
152
+ ([node]) => editor.isListBlock(node) && node.listItem === listStyle,
153
+ )
154
+ }
155
+ return false
156
+ }
157
+
158
+ return editor
159
+ }
160
+ }