@kerebron/editor 0.4.27 → 0.4.29

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 (167) hide show
  1. package/esm/CoreEditor.d.ts +40 -0
  2. package/esm/CoreEditor.d.ts.map +1 -0
  3. package/esm/CoreEditor.js +224 -0
  4. package/esm/CoreEditor.js.map +1 -0
  5. package/esm/DummyEditorView.d.ts +60 -0
  6. package/esm/DummyEditorView.d.ts.map +1 -0
  7. package/esm/DummyEditorView.js +243 -0
  8. package/esm/DummyEditorView.js.map +1 -0
  9. package/esm/Extension.d.ts +31 -0
  10. package/esm/Extension.d.ts.map +1 -0
  11. package/esm/Extension.js +35 -0
  12. package/esm/Extension.js.map +1 -0
  13. package/esm/ExtensionManager.d.ts +32 -0
  14. package/esm/ExtensionManager.d.ts.map +1 -0
  15. package/esm/ExtensionManager.js +223 -0
  16. package/esm/ExtensionManager.js.map +1 -0
  17. package/esm/Mark.d.ts +23 -0
  18. package/esm/Mark.d.ts.map +1 -0
  19. package/esm/Mark.js +28 -0
  20. package/esm/Mark.js.map +1 -0
  21. package/esm/Node.d.ts +32 -0
  22. package/esm/Node.d.ts.map +1 -0
  23. package/esm/Node.js +37 -0
  24. package/esm/Node.js.map +1 -0
  25. package/esm/commands/CommandManager.d.ts +24 -0
  26. package/esm/commands/CommandManager.d.ts.map +1 -0
  27. package/esm/commands/CommandManager.js +101 -0
  28. package/esm/commands/CommandManager.js.map +1 -0
  29. package/esm/commands/baseCommandFactories.d.ts +3 -0
  30. package/esm/commands/baseCommandFactories.d.ts.map +1 -0
  31. package/esm/commands/baseCommandFactories.js +862 -0
  32. package/esm/commands/baseCommandFactories.js.map +1 -0
  33. package/esm/commands/createChainableState.d.ts +3 -0
  34. package/esm/commands/createChainableState.d.ts.map +1 -0
  35. package/esm/commands/createChainableState.js +30 -0
  36. package/esm/commands/createChainableState.js.map +1 -0
  37. package/esm/commands/keyCommandFactories.d.ts +3 -0
  38. package/esm/commands/keyCommandFactories.d.ts.map +1 -0
  39. package/esm/commands/keyCommandFactories.js +11 -0
  40. package/esm/commands/keyCommandFactories.js.map +1 -0
  41. package/esm/commands/mod.d.ts +7 -0
  42. package/esm/commands/mod.d.ts.map +1 -0
  43. package/esm/commands/mod.js +82 -0
  44. package/esm/commands/mod.js.map +1 -0
  45. package/esm/commands/replaceCommandFactories.d.ts +3 -0
  46. package/esm/commands/replaceCommandFactories.d.ts.map +1 -0
  47. package/esm/commands/replaceCommandFactories.js +95 -0
  48. package/esm/commands/replaceCommandFactories.js.map +1 -0
  49. package/esm/commands/types.d.ts +22 -0
  50. package/esm/commands/types.d.ts.map +1 -0
  51. package/esm/commands/types.js +2 -0
  52. package/esm/commands/types.js.map +1 -0
  53. package/esm/mod.d.ts +9 -0
  54. package/esm/mod.d.ts.map +1 -0
  55. package/esm/mod.js +9 -0
  56. package/esm/mod.js.map +1 -0
  57. package/esm/nodeToTreeString.d.ts +10 -0
  58. package/esm/nodeToTreeString.d.ts.map +1 -0
  59. package/esm/nodeToTreeString.js +75 -0
  60. package/esm/nodeToTreeString.js.map +1 -0
  61. package/esm/package.json +3 -0
  62. package/esm/plugins/TrackSelecionPlugin.d.ts +6 -0
  63. package/esm/plugins/TrackSelecionPlugin.d.ts.map +1 -0
  64. package/esm/plugins/TrackSelecionPlugin.js +25 -0
  65. package/esm/plugins/TrackSelecionPlugin.js.map +1 -0
  66. package/esm/plugins/input-rules/InputRulesPlugin.d.ts +25 -0
  67. package/esm/plugins/input-rules/InputRulesPlugin.d.ts.map +1 -0
  68. package/esm/plugins/input-rules/InputRulesPlugin.js +169 -0
  69. package/esm/plugins/input-rules/InputRulesPlugin.js.map +1 -0
  70. package/esm/plugins/input-rules/mod.d.ts +3 -0
  71. package/esm/plugins/input-rules/mod.d.ts.map +1 -0
  72. package/esm/plugins/input-rules/mod.js +3 -0
  73. package/esm/plugins/input-rules/mod.js.map +1 -0
  74. package/esm/plugins/input-rules/rulebuilders.d.ts +6 -0
  75. package/esm/plugins/input-rules/rulebuilders.d.ts.map +1 -0
  76. package/esm/plugins/input-rules/rulebuilders.js +65 -0
  77. package/esm/plugins/input-rules/rulebuilders.js.map +1 -0
  78. package/esm/plugins/keymap/keymap.d.ts +12 -0
  79. package/esm/plugins/keymap/keymap.d.ts.map +1 -0
  80. package/esm/plugins/keymap/keymap.js +126 -0
  81. package/esm/plugins/keymap/keymap.js.map +1 -0
  82. package/esm/plugins/keymap/mod.d.ts +2 -0
  83. package/esm/plugins/keymap/mod.d.ts.map +1 -0
  84. package/esm/plugins/keymap/mod.js +2 -0
  85. package/esm/plugins/keymap/mod.js.map +1 -0
  86. package/esm/plugins/keymap/w3c-keyname.d.ts +4 -0
  87. package/esm/plugins/keymap/w3c-keyname.d.ts.map +1 -0
  88. package/esm/plugins/keymap/w3c-keyname.js +125 -0
  89. package/esm/plugins/keymap/w3c-keyname.js.map +1 -0
  90. package/esm/search/mod.d.ts +3 -0
  91. package/esm/search/mod.d.ts.map +1 -0
  92. package/esm/search/mod.js +3 -0
  93. package/esm/search/mod.js.map +1 -0
  94. package/esm/search/query.d.ts +45 -0
  95. package/esm/search/query.d.ts.map +1 -0
  96. package/esm/search/query.js +335 -0
  97. package/esm/search/query.js.map +1 -0
  98. package/esm/search/search.d.ts +32 -0
  99. package/esm/search/search.d.ts.map +1 -0
  100. package/esm/search/search.js +200 -0
  101. package/esm/search/search.js.map +1 -0
  102. package/esm/types.d.ts +59 -0
  103. package/esm/types.d.ts.map +1 -0
  104. package/esm/types.js +2 -0
  105. package/esm/types.js.map +1 -0
  106. package/esm/ui.d.ts +15 -0
  107. package/esm/ui.d.ts.map +1 -0
  108. package/esm/ui.js +17 -0
  109. package/esm/ui.js.map +1 -0
  110. package/esm/utilities/SmartOutput.d.ts +41 -0
  111. package/esm/utilities/SmartOutput.d.ts.map +1 -0
  112. package/esm/utilities/SmartOutput.js +202 -0
  113. package/esm/utilities/SmartOutput.js.map +1 -0
  114. package/esm/utilities/createNodeFromContent.d.ts +9 -0
  115. package/esm/utilities/createNodeFromContent.d.ts.map +1 -0
  116. package/esm/utilities/createNodeFromContent.js +33 -0
  117. package/esm/utilities/createNodeFromContent.js.map +1 -0
  118. package/esm/utilities/getHtmlAttributes.d.ts +9 -0
  119. package/esm/utilities/getHtmlAttributes.d.ts.map +1 -0
  120. package/esm/utilities/getHtmlAttributes.js +48 -0
  121. package/esm/utilities/getHtmlAttributes.js.map +1 -0
  122. package/esm/utilities/getShadowRoot.d.ts +2 -0
  123. package/esm/utilities/getShadowRoot.d.ts.map +1 -0
  124. package/esm/utilities/getShadowRoot.js +17 -0
  125. package/esm/utilities/getShadowRoot.js.map +1 -0
  126. package/esm/utilities/mod.d.ts +6 -0
  127. package/esm/utilities/mod.d.ts.map +1 -0
  128. package/esm/utilities/mod.js +6 -0
  129. package/esm/utilities/mod.js.map +1 -0
  130. package/esm/utilities/toRawTextResult.d.ts +3 -0
  131. package/esm/utilities/toRawTextResult.d.ts.map +1 -0
  132. package/esm/utilities/toRawTextResult.js +22 -0
  133. package/esm/utilities/toRawTextResult.js.map +1 -0
  134. package/package.json +5 -2
  135. package/src/CoreEditor.ts +277 -0
  136. package/src/DummyEditorView.ts +403 -0
  137. package/src/Extension.ts +63 -0
  138. package/src/ExtensionManager.ts +328 -0
  139. package/src/Mark.ts +47 -0
  140. package/src/Node.ts +66 -0
  141. package/src/commands/CommandManager.ts +145 -0
  142. package/src/commands/baseCommandFactories.ts +1103 -0
  143. package/src/commands/createChainableState.ts +36 -0
  144. package/src/commands/keyCommandFactories.ts +26 -0
  145. package/src/commands/mod.ts +104 -0
  146. package/src/commands/replaceCommandFactories.ts +129 -0
  147. package/src/commands/types.ts +30 -0
  148. package/src/mod.ts +8 -0
  149. package/src/nodeToTreeString.ts +100 -0
  150. package/src/plugins/TrackSelecionPlugin.ts +27 -0
  151. package/src/plugins/input-rules/InputRulesPlugin.ts +242 -0
  152. package/src/plugins/input-rules/mod.ts +2 -0
  153. package/src/plugins/input-rules/rulebuilders.ts +88 -0
  154. package/src/plugins/keymap/keymap.ts +117 -0
  155. package/src/plugins/keymap/mod.ts +1 -0
  156. package/src/plugins/keymap/w3c-keyname.ts +123 -0
  157. package/src/search/mod.ts +2 -0
  158. package/src/search/query.ts +412 -0
  159. package/src/search/search.ts +284 -0
  160. package/src/types.ts +71 -0
  161. package/src/ui.ts +35 -0
  162. package/src/utilities/SmartOutput.ts +284 -0
  163. package/src/utilities/createNodeFromContent.ts +66 -0
  164. package/src/utilities/getHtmlAttributes.ts +68 -0
  165. package/src/utilities/getShadowRoot.ts +18 -0
  166. package/src/utilities/mod.ts +5 -0
  167. package/src/utilities/toRawTextResult.ts +27 -0
@@ -0,0 +1,862 @@
1
+ import { canJoin, canSplit, findWrapping, joinPoint, liftTarget, ReplaceAroundStep, ReplaceStep, replaceStep, } from 'prosemirror-transform';
2
+ import { Fragment, NodeRange, Slice, } from 'prosemirror-model';
3
+ import { AllSelection, NodeSelection, Selection, SelectionRange, TextSelection, } from 'prosemirror-state';
4
+ import { runInputRules, undoInputRuleCommand, } from '../plugins/input-rules/InputRulesPlugin.js';
5
+ /// Returns a command function that wraps the selection in a list with
6
+ /// the given type an attributes. If `dispatch` is null, only return a
7
+ /// value to indicate whether this is possible, but don't actually
8
+ /// perform the change.
9
+ const wrapInList = (listType, attrs = null) => {
10
+ return function (state, dispatch, view) {
11
+ let { $from, $to } = state.selection;
12
+ let range = $from.blockRange($to);
13
+ if (!range)
14
+ return false;
15
+ // let tr = dispatch ? state.tr : null;
16
+ const tr = state.tr;
17
+ if (!wrapRangeInList(tr, range, listType, attrs))
18
+ return false;
19
+ if (dispatch)
20
+ dispatch(tr.scrollIntoView());
21
+ return true;
22
+ };
23
+ };
24
+ /// Try to wrap the given node range in a list of the given type.
25
+ /// Return `true` when this is possible, `false` otherwise. When `tr`
26
+ /// is non-null, the wrapping is added to that transaction. When it is
27
+ /// `null`, the function only queries whether the wrapping is
28
+ /// possible.
29
+ const wrapRangeInList = (tr, range, listType, attrs = null) => {
30
+ const cmd = () => {
31
+ let doJoin = false, outerRange = range, doc = range.$from.doc;
32
+ // This is at the top of an existing list item
33
+ if (range.depth >= 2 &&
34
+ range.$from.node(range.depth - 1).type.compatibleContent(listType) &&
35
+ range.startIndex == 0) {
36
+ // Don't do anything if this is the top of the list
37
+ if (range.$from.index(range.depth - 1) == 0)
38
+ return false;
39
+ let $insert = doc.resolve(range.start - 2);
40
+ outerRange = new NodeRange($insert, $insert, range.depth);
41
+ if (range.endIndex < range.parent.childCount) {
42
+ range = new NodeRange(range.$from, doc.resolve(range.$to.end(range.depth)), range.depth);
43
+ }
44
+ doJoin = true;
45
+ }
46
+ let wrap = findWrapping(outerRange, listType, attrs, range);
47
+ if (!wrap)
48
+ return false;
49
+ if (tr)
50
+ doWrapInList(tr, range, wrap, doJoin, listType);
51
+ return true;
52
+ };
53
+ return cmd;
54
+ };
55
+ function doWrapInList(tr, range, wrappers, joinBefore, listType) {
56
+ let content = Fragment.empty;
57
+ for (let i = wrappers.length - 1; i >= 0; i--) {
58
+ content = Fragment.from(wrappers[i].type.create(wrappers[i].attrs, content));
59
+ }
60
+ tr.step(new ReplaceAroundStep(range.start - (joinBefore ? 2 : 0), range.end, range.start, range.end, new Slice(content, 0, 0), wrappers.length, true));
61
+ let found = 0;
62
+ for (let i = 0; i < wrappers.length; i++) {
63
+ if (wrappers[i].type == listType)
64
+ found = i + 1;
65
+ }
66
+ let splitDepth = wrappers.length - found;
67
+ let splitPos = range.start + wrappers.length - (joinBefore ? 2 : 0), parent = range.parent;
68
+ for (let i = range.startIndex, e = range.endIndex, first = true; i < e; i++, first = false) {
69
+ if (!first && canSplit(tr.doc, splitPos, splitDepth)) {
70
+ tr.split(splitPos, splitDepth);
71
+ splitPos += 2 * splitDepth;
72
+ }
73
+ splitPos += parent.child(i).nodeSize;
74
+ }
75
+ return tr;
76
+ }
77
+ /// Delete the selection, if there is one.
78
+ const deleteSelection = () => (state, dispatch) => {
79
+ if (state.selection.empty)
80
+ return false;
81
+ if (dispatch)
82
+ dispatch(state.tr.deleteSelection().scrollIntoView());
83
+ return true;
84
+ };
85
+ function atBlockStart(state, view) {
86
+ let { $cursor } = state.selection;
87
+ if (!$cursor ||
88
+ (view ? !view.endOfTextblock('backward', state) : $cursor.parentOffset > 0)) {
89
+ return null;
90
+ }
91
+ return $cursor;
92
+ }
93
+ /// If the selection is empty and at the start of a textblock, try to
94
+ /// reduce the distance between that block and the one before it—if
95
+ /// there's a block directly before it that can be joined, join them.
96
+ /// If not, try to move the selected block closer to the next one in
97
+ /// the document structure by lifting it out of its parent or moving it
98
+ /// into a parent of the previous block. Will use the view for accurate
99
+ /// (bidi-aware) start-of-textblock detection if given.
100
+ const joinBackward = () => (state, dispatch, view) => {
101
+ let $cursor = atBlockStart(state, view);
102
+ if (!$cursor)
103
+ return false;
104
+ let $cut = findCutBefore($cursor);
105
+ // If there is no node before this, try to lift
106
+ if (!$cut) {
107
+ let range = $cursor.blockRange(), target = range && liftTarget(range);
108
+ if (target == null)
109
+ return false;
110
+ if (dispatch)
111
+ dispatch(state.tr.lift(range, target).scrollIntoView());
112
+ return true;
113
+ }
114
+ let before = $cut.nodeBefore;
115
+ // Apply the joining algorithm
116
+ if (deleteBarrier(state, $cut, dispatch, -1))
117
+ return true;
118
+ // If the node below has no content and the node above is
119
+ // selectable, delete the node below and select the one above.
120
+ if ($cursor.parent.content.size == 0 &&
121
+ (textblockAt(before, 'end') || NodeSelection.isSelectable(before))) {
122
+ for (let depth = $cursor.depth;; depth--) {
123
+ let delStep = replaceStep(state.doc, $cursor.before(depth), $cursor.after(depth), Slice.empty);
124
+ if (delStep &&
125
+ delStep.slice.size <
126
+ delStep.to - delStep.from) {
127
+ if (dispatch) {
128
+ let tr = state.tr.step(delStep);
129
+ tr.setSelection(textblockAt(before, 'end')
130
+ ? Selection.findFrom(tr.doc.resolve(tr.mapping.map($cut.pos, -1)), -1)
131
+ : NodeSelection.create(tr.doc, $cut.pos - before.nodeSize));
132
+ dispatch(tr.scrollIntoView());
133
+ }
134
+ return true;
135
+ }
136
+ if (depth == 1 || $cursor.node(depth - 1).childCount > 1)
137
+ break;
138
+ }
139
+ }
140
+ // If the node before is an atom, delete it
141
+ if (before.isAtom && $cut.depth == $cursor.depth - 1) {
142
+ if (dispatch) {
143
+ dispatch(state.tr.delete($cut.pos - before.nodeSize, $cut.pos).scrollIntoView());
144
+ }
145
+ return true;
146
+ }
147
+ return false;
148
+ };
149
+ /// A more limited form of [`joinBackward`](#commands.joinBackward)
150
+ /// that only tries to join the current textblock to the one before
151
+ /// it, if the cursor is at the start of a textblock.
152
+ const joinTextblockBackward = () => (state, dispatch, view) => {
153
+ let $cursor = atBlockStart(state, view);
154
+ if (!$cursor)
155
+ return false;
156
+ let $cut = findCutBefore($cursor);
157
+ return $cut ? joinTextblocksAround(state, $cut, dispatch) : false;
158
+ };
159
+ /// A more limited form of [`joinForward`](#commands.joinForward)
160
+ /// that only tries to join the current textblock to the one after
161
+ /// it, if the cursor is at the end of a textblock.
162
+ const joinTextblockForward = () => (state, dispatch, view) => {
163
+ let $cursor = atBlockEnd(state, view);
164
+ if (!$cursor)
165
+ return false;
166
+ let $cut = findCutAfter($cursor);
167
+ return $cut ? joinTextblocksAround(state, $cut, dispatch) : false;
168
+ };
169
+ function joinTextblocksAround(state, $cut, dispatch) {
170
+ let before = $cut.nodeBefore, beforeText = before, beforePos = $cut.pos - 1;
171
+ for (; !beforeText.isTextblock; beforePos--) {
172
+ if (beforeText.type.spec.isolating)
173
+ return false;
174
+ let child = beforeText.lastChild;
175
+ if (!child)
176
+ return false;
177
+ beforeText = child;
178
+ }
179
+ let after = $cut.nodeAfter, afterText = after, afterPos = $cut.pos + 1;
180
+ for (; !afterText.isTextblock; afterPos++) {
181
+ if (afterText.type.spec.isolating)
182
+ return false;
183
+ let child = afterText.firstChild;
184
+ if (!child)
185
+ return false;
186
+ afterText = child;
187
+ }
188
+ let step = replaceStep(state.doc, beforePos, afterPos, Slice.empty);
189
+ if (!step || step.from != beforePos ||
190
+ step instanceof ReplaceStep && step.slice.size >= afterPos - beforePos)
191
+ return false;
192
+ if (dispatch) {
193
+ let tr = state.tr.step(step);
194
+ tr.setSelection(TextSelection.create(tr.doc, beforePos));
195
+ dispatch(tr.scrollIntoView());
196
+ }
197
+ return true;
198
+ }
199
+ function textblockAt(node, side, only = false) {
200
+ for (let scan = node; scan; scan = side == 'start' ? scan.firstChild : scan.lastChild) {
201
+ if (scan.isTextblock)
202
+ return true;
203
+ if (only && scan.childCount != 1)
204
+ return false;
205
+ }
206
+ return false;
207
+ }
208
+ /// When the selection is empty and at the start of a textblock, select
209
+ /// the node before that textblock, if possible. This is intended to be
210
+ /// bound to keys like backspace, after
211
+ /// [`joinBackward`](#commands.joinBackward) or other deleting
212
+ /// commands, as a fall-back behavior when the schema doesn't allow
213
+ /// deletion at the selected point.
214
+ const selectNodeBackward = () => (state, dispatch, view) => {
215
+ let { $head, empty } = state.selection, $cut = $head;
216
+ if (!empty)
217
+ return false;
218
+ if ($head.parent.isTextblock) {
219
+ if (view ? !view.endOfTextblock('backward', state) : $head.parentOffset > 0)
220
+ return false;
221
+ $cut = findCutBefore($head);
222
+ }
223
+ let node = $cut && $cut.nodeBefore;
224
+ if (!node || !NodeSelection.isSelectable(node))
225
+ return false;
226
+ if (dispatch) {
227
+ dispatch(state.tr.setSelection(NodeSelection.create(state.doc, $cut.pos - node.nodeSize)).scrollIntoView());
228
+ }
229
+ return true;
230
+ };
231
+ function findCutBefore($pos) {
232
+ if (!$pos.parent.type.spec.isolating) {
233
+ for (let i = $pos.depth - 1; i >= 0; i--) {
234
+ if ($pos.index(i) > 0) {
235
+ return $pos.doc.resolve($pos.before(i + 1));
236
+ }
237
+ if ($pos.node(i).type.spec.isolating)
238
+ break;
239
+ }
240
+ }
241
+ return null;
242
+ }
243
+ function atBlockEnd(state, view) {
244
+ let { $cursor } = state.selection;
245
+ if (!$cursor ||
246
+ (view
247
+ ? !view.endOfTextblock('forward', state)
248
+ : $cursor.parentOffset < $cursor.parent.content.size)) {
249
+ return null;
250
+ }
251
+ return $cursor;
252
+ }
253
+ /// If the selection is empty and the cursor is at the end of a
254
+ /// textblock, try to reduce or remove the boundary between that block
255
+ /// and the one after it, either by joining them or by moving the other
256
+ /// block closer to this one in the tree structure. Will use the view
257
+ /// for accurate start-of-textblock detection if given.
258
+ const joinForward = () => (state, dispatch, view) => {
259
+ let $cursor = atBlockEnd(state, view);
260
+ if (!$cursor)
261
+ return false;
262
+ let $cut = findCutAfter($cursor);
263
+ // If there is no node after this, there's nothing to do
264
+ if (!$cut)
265
+ return false;
266
+ let after = $cut.nodeAfter;
267
+ // Try the joining algorithm
268
+ if (deleteBarrier(state, $cut, dispatch, 1))
269
+ return true;
270
+ // If the node above has no content and the node below is
271
+ // selectable, delete the node above and select the one below.
272
+ if ($cursor.parent.content.size == 0 &&
273
+ (textblockAt(after, 'start') || NodeSelection.isSelectable(after))) {
274
+ let delStep = replaceStep(state.doc, $cursor.before(), $cursor.after(), Slice.empty);
275
+ if (delStep &&
276
+ delStep.slice.size <
277
+ delStep.to - delStep.from) {
278
+ if (dispatch) {
279
+ let tr = state.tr.step(delStep);
280
+ tr.setSelection(textblockAt(after, 'start')
281
+ ? Selection.findFrom(tr.doc.resolve(tr.mapping.map($cut.pos)), 1)
282
+ : NodeSelection.create(tr.doc, tr.mapping.map($cut.pos)));
283
+ dispatch(tr.scrollIntoView());
284
+ }
285
+ return true;
286
+ }
287
+ }
288
+ // If the next node is an atom, delete it
289
+ if (after.isAtom && $cut.depth == $cursor.depth - 1) {
290
+ if (dispatch) {
291
+ dispatch(state.tr.delete($cut.pos, $cut.pos + after.nodeSize).scrollIntoView());
292
+ }
293
+ return true;
294
+ }
295
+ return false;
296
+ };
297
+ /// When the selection is empty and at the end of a textblock, select
298
+ /// the node coming after that textblock, if possible. This is intended
299
+ /// to be bound to keys like delete, after
300
+ /// [`joinForward`](#commands.joinForward) and similar deleting
301
+ /// commands, to provide a fall-back behavior when the schema doesn't
302
+ /// allow deletion at the selected point.
303
+ const selectNodeForward = () => (state, dispatch, view) => {
304
+ let { $head, empty } = state.selection, $cut = $head;
305
+ if (!empty)
306
+ return false;
307
+ if ($head.parent.isTextblock) {
308
+ if (view
309
+ ? !view.endOfTextblock('forward', state)
310
+ : $head.parentOffset < $head.parent.content.size) {
311
+ return false;
312
+ }
313
+ $cut = findCutAfter($head);
314
+ }
315
+ let node = $cut && $cut.nodeAfter;
316
+ if (!node || !NodeSelection.isSelectable(node))
317
+ return false;
318
+ if (dispatch) {
319
+ dispatch(state.tr.setSelection(NodeSelection.create(state.doc, $cut.pos))
320
+ .scrollIntoView());
321
+ }
322
+ return true;
323
+ };
324
+ function findCutAfter($pos) {
325
+ if (!$pos.parent.type.spec.isolating) {
326
+ for (let i = $pos.depth - 1; i >= 0; i--) {
327
+ let parent = $pos.node(i);
328
+ if ($pos.index(i) + 1 < parent.childCount) {
329
+ return $pos.doc.resolve($pos.after(i + 1));
330
+ }
331
+ if (parent.type.spec.isolating) {
332
+ break;
333
+ }
334
+ }
335
+ }
336
+ return null;
337
+ }
338
+ /// Join the selected block or, if there is a text selection, the
339
+ /// closest ancestor block of the selection that can be joined, with
340
+ /// the sibling above it.
341
+ const joinUp = () => (state, dispatch) => {
342
+ let sel = state.selection, nodeSel = sel instanceof NodeSelection, point;
343
+ if (nodeSel) {
344
+ if (sel.node.isTextblock || !canJoin(state.doc, sel.from))
345
+ return false;
346
+ point = sel.from;
347
+ }
348
+ else {
349
+ point = joinPoint(state.doc, sel.from, -1);
350
+ if (point == null)
351
+ return false;
352
+ }
353
+ if (dispatch) {
354
+ let tr = state.tr.join(point);
355
+ if (nodeSel) {
356
+ tr.setSelection(NodeSelection.create(tr.doc, point - state.doc.resolve(point).nodeBefore.nodeSize));
357
+ }
358
+ dispatch(tr.scrollIntoView());
359
+ }
360
+ return true;
361
+ };
362
+ /// Join the selected block, or the closest ancestor of the selection
363
+ /// that can be joined, with the sibling after it.
364
+ const joinDown = () => (state, dispatch) => {
365
+ let sel = state.selection, point;
366
+ if (sel instanceof NodeSelection) {
367
+ if (sel.node.isTextblock || !canJoin(state.doc, sel.to))
368
+ return false;
369
+ point = sel.to;
370
+ }
371
+ else {
372
+ point = joinPoint(state.doc, sel.to, 1);
373
+ if (point == null)
374
+ return false;
375
+ }
376
+ if (dispatch) {
377
+ dispatch(state.tr.join(point).scrollIntoView());
378
+ }
379
+ return true;
380
+ };
381
+ /// Lift the selected block, or the closest ancestor block of the
382
+ /// selection that can be lifted, out of its parent node.
383
+ const lift = () => (state, dispatch) => {
384
+ let { $from, $to } = state.selection;
385
+ let range = $from.blockRange($to), target = range && liftTarget(range);
386
+ if (target == null)
387
+ return false;
388
+ if (dispatch)
389
+ dispatch(state.tr.lift(range, target).scrollIntoView());
390
+ return true;
391
+ };
392
+ /// If the selection is in a node whose type has a truthy
393
+ /// [`code`](#model.NodeSpec.code) property in its spec, replace the
394
+ /// selection with a newline character.
395
+ const newlineInCode = () => {
396
+ const cmd = (state, dispatch) => {
397
+ let { $head, $anchor } = state.selection;
398
+ if (!$head.parent.type.spec.code || !$head.sameParent($anchor)) {
399
+ return false;
400
+ }
401
+ if (dispatch)
402
+ dispatch(state.tr.insertText('\n').scrollIntoView());
403
+ return true;
404
+ };
405
+ cmd.displayName = 'newlineInCode()';
406
+ return cmd;
407
+ };
408
+ function defaultBlockAt(match) {
409
+ console.log('defaultBlockAt', match);
410
+ for (let i = 0; i < match.edgeCount; i++) {
411
+ let { type } = match.edge(i);
412
+ if (type.isTextblock && !type.hasRequiredAttrs())
413
+ return type;
414
+ }
415
+ return null;
416
+ }
417
+ /// When the selection is in a node with a truthy
418
+ /// [`code`](#model.NodeSpec.code) property in its spec, create a
419
+ /// default block after the code block, and move the cursor there.
420
+ const exitCode = () => (state, dispatch) => {
421
+ let { $head, $anchor } = state.selection;
422
+ if (!$head.parent.type.spec.code || !$head.sameParent($anchor))
423
+ return false;
424
+ let above = $head.node(-1), after = $head.indexAfter(-1), type = defaultBlockAt(above.contentMatchAt(after));
425
+ if (!type || !above.canReplaceWith(after, after, type))
426
+ return false;
427
+ if (dispatch) {
428
+ let pos = $head.after(), tr = state.tr.replaceWith(pos, pos, type.createAndFill());
429
+ tr.setSelection(Selection.near(tr.doc.resolve(pos), 1));
430
+ dispatch(tr.scrollIntoView());
431
+ }
432
+ return true;
433
+ };
434
+ /// If a block node is selected, create an empty paragraph before (if
435
+ /// it is its parent's first child) or after it.
436
+ const createParagraphNear = () => {
437
+ const cmd = (state, dispatch) => {
438
+ let sel = state.selection, { $from, $to } = sel;
439
+ if (sel instanceof AllSelection || $from.parent.inlineContent ||
440
+ $to.parent.inlineContent)
441
+ return false;
442
+ let type = defaultBlockAt($to.parent.contentMatchAt($to.indexAfter()));
443
+ if (!type || !type.isTextblock)
444
+ return false;
445
+ if (dispatch) {
446
+ let side = (!$from.parentOffset && $to.index() < $to.parent.childCount
447
+ ? $from
448
+ : $to)
449
+ .pos;
450
+ let tr = state.tr.insert(side, type.createAndFill());
451
+ tr.setSelection(TextSelection.create(tr.doc, side + 1));
452
+ dispatch(tr.scrollIntoView());
453
+ }
454
+ return true;
455
+ };
456
+ cmd.displayName = 'createParagraphNear()';
457
+ return cmd;
458
+ };
459
+ /// If the cursor is in an empty textblock that can be lifted, lift the
460
+ /// block.
461
+ const liftEmptyBlock = () => {
462
+ const cmd = (state, dispatch) => {
463
+ let { $cursor } = state.selection;
464
+ if (!$cursor || $cursor.parent.content.size)
465
+ return false;
466
+ if ($cursor.depth > 1 && $cursor.after() != $cursor.end(-1)) {
467
+ let before = $cursor.before();
468
+ if (canSplit(state.doc, before)) {
469
+ if (dispatch)
470
+ dispatch(state.tr.split(before).scrollIntoView());
471
+ return true;
472
+ }
473
+ }
474
+ let range = $cursor.blockRange(), target = range && liftTarget(range);
475
+ if (target == null)
476
+ return false;
477
+ if (dispatch)
478
+ dispatch(state.tr.lift(range, target).scrollIntoView());
479
+ return true;
480
+ };
481
+ cmd.displayName = 'liftEmptyBlock()';
482
+ return cmd;
483
+ };
484
+ /// Create a variant of [`splitBlock`](#commands.splitBlock) that uses
485
+ /// a custom function to determine the type of the newly split off block.
486
+ const splitBlockAs = (splitNode) => {
487
+ const cmd = (state, dispatch) => {
488
+ let { $from, $to } = state.selection;
489
+ if (state.selection instanceof NodeSelection && state.selection.node.isBlock) {
490
+ if (!$from.parentOffset || !canSplit(state.doc, $from.pos))
491
+ return false;
492
+ if (dispatch)
493
+ dispatch(state.tr.split($from.pos).scrollIntoView());
494
+ return true;
495
+ }
496
+ if (!$from.depth)
497
+ return false;
498
+ let types = [];
499
+ let splitDepth, deflt, atEnd = false, atStart = false;
500
+ for (let d = $from.depth;; d--) {
501
+ let node = $from.node(d);
502
+ if (node.isBlock) {
503
+ atEnd = $from.end(d) == $from.pos + ($from.depth - d);
504
+ atStart = $from.start(d) == $from.pos - ($from.depth - d);
505
+ deflt = defaultBlockAt($from.node(d - 1).contentMatchAt($from.indexAfter(d - 1)));
506
+ let splitType = splitNode && splitNode($to.parent, atEnd, $from);
507
+ types.unshift(splitType || (atEnd && deflt ? { type: deflt } : null));
508
+ splitDepth = d;
509
+ break;
510
+ }
511
+ else {
512
+ if (d == 1)
513
+ return false;
514
+ types.unshift(null);
515
+ }
516
+ }
517
+ let tr = state.tr;
518
+ if (state.selection instanceof TextSelection ||
519
+ state.selection instanceof AllSelection)
520
+ tr.deleteSelection();
521
+ let splitPos = tr.mapping.map($from.pos);
522
+ let can = canSplit(tr.doc, splitPos, types.length, types);
523
+ if (!can) {
524
+ types[0] = deflt ? { type: deflt } : null;
525
+ can = canSplit(tr.doc, splitPos, types.length, types);
526
+ }
527
+ tr.split(splitPos, types.length, types);
528
+ if (!atEnd && atStart && $from.node(splitDepth).type != deflt) {
529
+ let first = tr.mapping.map($from.before(splitDepth)), $first = tr.doc.resolve(first);
530
+ if (deflt &&
531
+ $from.node(splitDepth - 1).canReplaceWith($first.index(), $first.index() + 1, deflt)) {
532
+ tr.setNodeMarkup(tr.mapping.map($from.before(splitDepth)), deflt);
533
+ }
534
+ }
535
+ if (dispatch)
536
+ dispatch(tr.scrollIntoView());
537
+ return true;
538
+ };
539
+ cmd.displayName = 'splitBlockAs()';
540
+ return cmd;
541
+ };
542
+ /// Split the parent block of the selection. If the selection is a text
543
+ /// selection, also delete its content.
544
+ const splitBlock = () => splitBlockAs();
545
+ /// Acts like [`splitBlock`](#commands.splitBlock), but without
546
+ /// resetting the set of active marks at the cursor.
547
+ const splitBlockKeepMarks = () => (state, dispatch) => {
548
+ return splitBlockAs()(state, dispatch && ((tr) => {
549
+ let marks = state.storedMarks ||
550
+ (state.selection.$to.parentOffset && state.selection.$from.marks());
551
+ if (marks)
552
+ tr.ensureMarks(marks);
553
+ dispatch(tr);
554
+ }));
555
+ };
556
+ /// Move the selection to the node wrapping the current selection, if
557
+ /// any. (Will not select the document node.)
558
+ const selectParentNode = () => (state, dispatch) => {
559
+ let { $from, to } = state.selection, pos;
560
+ let same = $from.sharedDepth(to);
561
+ if (same == 0)
562
+ return false;
563
+ pos = $from.before(same);
564
+ if (dispatch) {
565
+ dispatch(state.tr.setSelection(NodeSelection.create(state.doc, pos)));
566
+ }
567
+ return true;
568
+ };
569
+ /// Select the whole document.
570
+ const selectAll = () => (state, dispatch) => {
571
+ if (dispatch)
572
+ dispatch(state.tr.setSelection(new AllSelection(state.doc)));
573
+ return true;
574
+ };
575
+ function joinMaybeClear(state, $pos, dispatch) {
576
+ let before = $pos.nodeBefore, after = $pos.nodeAfter, index = $pos.index();
577
+ if (!before || !after || !before.type.compatibleContent(after.type)) {
578
+ return false;
579
+ }
580
+ if (!before.content.size && $pos.parent.canReplace(index - 1, index)) {
581
+ if (dispatch) {
582
+ dispatch(state.tr.delete($pos.pos - before.nodeSize, $pos.pos).scrollIntoView());
583
+ }
584
+ return true;
585
+ }
586
+ if (!$pos.parent.canReplace(index, index + 1) ||
587
+ !(after.isTextblock || canJoin(state.doc, $pos.pos))) {
588
+ return false;
589
+ }
590
+ if (dispatch) {
591
+ dispatch(state.tr.join($pos.pos).scrollIntoView());
592
+ }
593
+ return true;
594
+ }
595
+ function deleteBarrier(state, $cut, dispatch, dir) {
596
+ let before = $cut.nodeBefore, after = $cut.nodeAfter, conn, match;
597
+ let isolated = before.type.spec.isolating || after.type.spec.isolating;
598
+ if (!isolated && joinMaybeClear(state, $cut, dispatch))
599
+ return true;
600
+ let canDelAfter = !isolated &&
601
+ $cut.parent.canReplace($cut.index(), $cut.index() + 1);
602
+ if (canDelAfter &&
603
+ (conn = (match = before.contentMatchAt(before.childCount)).findWrapping(after.type)) &&
604
+ match.matchType(conn[0] || after.type).validEnd) {
605
+ if (dispatch) {
606
+ let end = $cut.pos + after.nodeSize, wrap = Fragment.empty;
607
+ for (let i = conn.length - 1; i >= 0; i--) {
608
+ wrap = Fragment.from(conn[i].create(null, wrap));
609
+ }
610
+ wrap = Fragment.from(before.copy(wrap));
611
+ let tr = state.tr.step(new ReplaceAroundStep($cut.pos - 1, end, $cut.pos, end, new Slice(wrap, 1, 0), conn.length, true));
612
+ let $joinAt = tr.doc.resolve(end + 2 * conn.length);
613
+ if ($joinAt.nodeAfter && $joinAt.nodeAfter.type == before.type &&
614
+ canJoin(tr.doc, $joinAt.pos))
615
+ tr.join($joinAt.pos);
616
+ dispatch(tr.scrollIntoView());
617
+ }
618
+ return true;
619
+ }
620
+ let selAfter = after.type.spec.isolating || (dir > 0 && isolated)
621
+ ? null
622
+ : Selection.findFrom($cut, 1);
623
+ let range = selAfter && selAfter.$from.blockRange(selAfter.$to), target = range && liftTarget(range);
624
+ if (target != null && target >= $cut.depth) {
625
+ if (dispatch)
626
+ dispatch(state.tr.lift(range, target).scrollIntoView());
627
+ return true;
628
+ }
629
+ if (canDelAfter && textblockAt(after, 'start', true) &&
630
+ textblockAt(before, 'end')) {
631
+ let at = before, wrap = [];
632
+ for (;;) {
633
+ wrap.push(at);
634
+ if (at.isTextblock)
635
+ break;
636
+ at = at.lastChild;
637
+ }
638
+ let afterText = after, afterDepth = 1;
639
+ for (; !afterText.isTextblock; afterText = afterText.firstChild) {
640
+ afterDepth++;
641
+ }
642
+ if (at.canReplace(at.childCount, at.childCount, afterText.content)) {
643
+ if (dispatch) {
644
+ let end = Fragment.empty;
645
+ for (let i = wrap.length - 1; i >= 0; i--) {
646
+ end = Fragment.from(wrap[i].copy(end));
647
+ }
648
+ let tr = state.tr.step(new ReplaceAroundStep($cut.pos - wrap.length, $cut.pos + after.nodeSize, $cut.pos + afterDepth, $cut.pos + after.nodeSize - afterDepth, new Slice(end, wrap.length, 0), 0, true));
649
+ dispatch(tr.scrollIntoView());
650
+ }
651
+ return true;
652
+ }
653
+ }
654
+ return false;
655
+ }
656
+ function selectTextblockSide(side) {
657
+ return function (state, dispatch) {
658
+ let sel = state.selection, $pos = side < 0 ? sel.$from : sel.$to;
659
+ let depth = $pos.depth;
660
+ while ($pos.node(depth).isInline) {
661
+ if (!depth)
662
+ return false;
663
+ depth--;
664
+ }
665
+ if (!$pos.node(depth).isTextblock)
666
+ return false;
667
+ if (dispatch) {
668
+ dispatch(state.tr.setSelection(TextSelection.create(state.doc, side < 0 ? $pos.start(depth) : $pos.end(depth))));
669
+ }
670
+ return true;
671
+ };
672
+ }
673
+ // Parameterized commands
674
+ /// Wrap the selection in a node of the given type with the given
675
+ /// attributes.
676
+ const wrapIn = (nodeType, attrs = null) => {
677
+ return function (state, dispatch) {
678
+ let { $from, $to } = state.selection;
679
+ let range = $from.blockRange($to), wrapping = range && findWrapping(range, nodeType, attrs);
680
+ if (!wrapping)
681
+ return false;
682
+ if (dispatch)
683
+ dispatch(state.tr.wrap(range, wrapping).scrollIntoView());
684
+ return true;
685
+ };
686
+ };
687
+ /// Returns a command that tries to set the selected textblocks to the
688
+ /// given node type with the given attributes.
689
+ const setBlockType = (nodeType, attrs = null) => {
690
+ return function (state, dispatch) {
691
+ let applicable = false;
692
+ for (let i = 0; i < state.selection.ranges.length && !applicable; i++) {
693
+ let { $from: { pos: from }, $to: { pos: to } } = state.selection.ranges[i];
694
+ state.doc.nodesBetween(from, to, (node, pos) => {
695
+ if (applicable)
696
+ return false;
697
+ if (!node.isTextblock || node.hasMarkup(nodeType, attrs))
698
+ return;
699
+ if (node.type == nodeType) {
700
+ applicable = true;
701
+ }
702
+ else {
703
+ let $pos = state.doc.resolve(pos), index = $pos.index();
704
+ applicable = $pos.parent.canReplaceWith(index, index + 1, nodeType);
705
+ }
706
+ });
707
+ }
708
+ if (!applicable)
709
+ return false;
710
+ if (dispatch) {
711
+ let tr = state.tr;
712
+ for (let i = 0; i < state.selection.ranges.length; i++) {
713
+ let { $from: { pos: from }, $to: { pos: to } } = state.selection.ranges[i];
714
+ tr.setBlockType(from, to, nodeType, attrs);
715
+ }
716
+ dispatch(tr.scrollIntoView());
717
+ }
718
+ return true;
719
+ };
720
+ };
721
+ function markApplies(doc, ranges, type, enterAtoms) {
722
+ for (let i = 0; i < ranges.length; i++) {
723
+ let { $from, $to } = ranges[i];
724
+ let can = $from.depth == 0
725
+ ? doc.inlineContent && doc.type.allowsMarkType(type)
726
+ : false;
727
+ doc.nodesBetween($from.pos, $to.pos, (node, pos) => {
728
+ if (can ||
729
+ !enterAtoms && node.isAtom && node.isInline && pos >= $from.pos &&
730
+ pos + node.nodeSize <= $to.pos) {
731
+ return false;
732
+ }
733
+ can = node.inlineContent && node.type.allowsMarkType(type);
734
+ });
735
+ if (can)
736
+ return true;
737
+ }
738
+ return false;
739
+ }
740
+ function removeInlineAtoms(ranges) {
741
+ let result = [];
742
+ for (let i = 0; i < ranges.length; i++) {
743
+ let { $from, $to } = ranges[i];
744
+ $from.doc.nodesBetween($from.pos, $to.pos, (node, pos) => {
745
+ if (node.isAtom && node.content.size && node.isInline && pos >= $from.pos &&
746
+ pos + node.nodeSize <= $to.pos) {
747
+ if (pos + 1 > $from.pos) {
748
+ result.push(new SelectionRange($from, $from.doc.resolve(pos + 1)));
749
+ }
750
+ $from = $from.doc.resolve(pos + 1 + node.content.size);
751
+ return false;
752
+ }
753
+ });
754
+ if ($from.pos < $to.pos)
755
+ result.push(new SelectionRange($from, $to));
756
+ }
757
+ return result;
758
+ }
759
+ /// Create a command function that toggles the given mark with the
760
+ /// given attributes. Will return `false` when the current selection
761
+ /// doesn't support that mark. This will remove the mark if any marks
762
+ /// of that type exist in the selection, or add it otherwise. If the
763
+ /// selection is empty, this applies to the [stored
764
+ /// marks](#state.EditorState.storedMarks) instead of a range of the
765
+ /// document.
766
+ function toggleMark(markType, attrs = null, options) {
767
+ let removeWhenPresent = (options && options.removeWhenPresent) !== false;
768
+ let enterAtoms = (options && options.enterInlineAtoms) !== false;
769
+ let dropSpace = !(options && options.includeWhitespace);
770
+ return function (state, dispatch) {
771
+ let { empty, $cursor, ranges } = state.selection;
772
+ if ((empty && !$cursor) ||
773
+ !markApplies(state.doc, ranges, markType, enterAtoms))
774
+ return false;
775
+ if (dispatch) {
776
+ if ($cursor) {
777
+ if (markType.isInSet(state.storedMarks || $cursor.marks())) {
778
+ dispatch(state.tr.removeStoredMark(markType));
779
+ }
780
+ else {
781
+ dispatch(state.tr.addStoredMark(markType.create(attrs)));
782
+ }
783
+ }
784
+ else {
785
+ let add, tr = state.tr;
786
+ if (!enterAtoms)
787
+ ranges = removeInlineAtoms(ranges);
788
+ if (removeWhenPresent) {
789
+ add = !ranges.some((r) => state.doc.rangeHasMark(r.$from.pos, r.$to.pos, markType));
790
+ }
791
+ else {
792
+ add = !ranges.every((r) => {
793
+ let missing = false;
794
+ tr.doc.nodesBetween(r.$from.pos, r.$to.pos, (node, pos, parent) => {
795
+ if (missing)
796
+ return false;
797
+ missing = !markType.isInSet(node.marks) && !!parent &&
798
+ parent.type.allowsMarkType(markType) &&
799
+ !(node.isText &&
800
+ /^\s*$/.test(node.textBetween(Math.max(0, r.$from.pos - pos), Math.min(node.nodeSize, r.$to.pos - pos))));
801
+ });
802
+ return !missing;
803
+ });
804
+ }
805
+ for (let i = 0; i < ranges.length; i++) {
806
+ let { $from, $to } = ranges[i];
807
+ if (!add) {
808
+ tr.removeMark($from.pos, $to.pos, markType);
809
+ }
810
+ else {
811
+ let from = $from.pos, to = $to.pos, start = $from.nodeAfter, end = $to.nodeBefore;
812
+ let spaceStart = dropSpace && start && start.isText
813
+ ? /^\s*/.exec(start.text)[0].length
814
+ : 0;
815
+ let spaceEnd = dropSpace && end && end.isText
816
+ ? /\s*$/.exec(end.text)[0].length
817
+ : 0;
818
+ if (from + spaceStart < to) {
819
+ from += spaceStart;
820
+ to -= spaceEnd;
821
+ }
822
+ tr.addMark(from, to, markType.create(attrs));
823
+ }
824
+ }
825
+ dispatch(tr.scrollIntoView());
826
+ }
827
+ }
828
+ return true;
829
+ };
830
+ }
831
+ const undoInputRule = () => undoInputRuleCommand;
832
+ export const baseCommandFactories = {
833
+ wrapInList,
834
+ wrapRangeInList,
835
+ deleteSelection,
836
+ joinBackward,
837
+ joinTextblockBackward,
838
+ joinTextblockForward,
839
+ joinForward,
840
+ joinUp,
841
+ joinDown,
842
+ lift,
843
+ newlineInCode,
844
+ exitCode,
845
+ createParagraphNear,
846
+ liftEmptyBlock,
847
+ splitBlockAs,
848
+ splitBlock,
849
+ splitBlockKeepMarks,
850
+ selectAll,
851
+ selectParentNode,
852
+ selectNodeBackward,
853
+ selectNodeForward,
854
+ selectTextblockStart: () => selectTextblockSide(-1),
855
+ selectTextblockEnd: () => selectTextblockSide(1),
856
+ wrapIn,
857
+ setBlockType,
858
+ toggleMark,
859
+ undoInputRule,
860
+ runInputRules,
861
+ };
862
+ //# sourceMappingURL=baseCommandFactories.js.map