@atlaskit/editor-plugin-list 0.2.0 → 1.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 (181) hide show
  1. package/.eslintrc.js +14 -0
  2. package/CHANGELOG.md +10 -0
  3. package/README.md +1 -1
  4. package/dist/cjs/actions/conversions.js +153 -0
  5. package/dist/cjs/actions/indent-list-items-selected.js +125 -0
  6. package/dist/cjs/actions/indent-list.js +49 -0
  7. package/dist/cjs/actions/join-list-items-forward.js +59 -0
  8. package/dist/cjs/actions/join-list-items-scenarios/index.js +40 -0
  9. package/dist/cjs/actions/join-list-items-scenarios/join-list-item-with-paragraph.js +88 -0
  10. package/dist/cjs/actions/join-list-items-scenarios/join-list-item-with-parent-nested-list.js +85 -0
  11. package/dist/cjs/actions/join-list-items-scenarios/join-nested-list-with-parent-list-item.js +79 -0
  12. package/dist/cjs/actions/join-list-items-scenarios/join-paragraph-with-list.js +45 -0
  13. package/dist/cjs/actions/join-list-items-scenarios/join-sibling-list-items.js +56 -0
  14. package/dist/cjs/actions/merge-lists.js +27 -0
  15. package/dist/cjs/actions/outdent-list-items-selected.js +291 -0
  16. package/dist/cjs/actions/wrap-and-join-lists.js +100 -0
  17. package/dist/cjs/commands/indent-list.js +71 -0
  18. package/dist/cjs/commands/index.js +350 -0
  19. package/dist/cjs/commands/isFirstChildOfParent.js +12 -0
  20. package/dist/cjs/commands/join-list-item-forward.js +61 -0
  21. package/dist/cjs/commands/listBackspace.js +284 -0
  22. package/dist/cjs/commands/outdent-list.js +70 -0
  23. package/dist/cjs/index.js +8 -1
  24. package/dist/cjs/messages.js +37 -0
  25. package/dist/cjs/plugin.js +133 -0
  26. package/dist/cjs/pm-plugins/input-rules/create-list-input-rule.js +63 -0
  27. package/dist/cjs/pm-plugins/input-rules/index.js +38 -0
  28. package/dist/cjs/pm-plugins/input-rules/wrapping-join-rule.js +60 -0
  29. package/dist/cjs/pm-plugins/keymap.js +27 -0
  30. package/dist/cjs/pm-plugins/main.js +166 -0
  31. package/dist/cjs/transforms.js +99 -0
  32. package/dist/cjs/types.js +4 -1
  33. package/dist/cjs/utils/analytics.js +22 -0
  34. package/dist/cjs/utils/find.js +68 -0
  35. package/dist/cjs/utils/indentation.js +22 -0
  36. package/dist/cjs/utils/mark.js +40 -0
  37. package/dist/cjs/utils/node.js +16 -0
  38. package/dist/cjs/utils/selection.js +95 -0
  39. package/dist/es2019/actions/conversions.js +160 -0
  40. package/dist/es2019/actions/indent-list-items-selected.js +124 -0
  41. package/dist/es2019/actions/indent-list.js +44 -0
  42. package/dist/es2019/actions/join-list-items-forward.js +54 -0
  43. package/dist/es2019/actions/join-list-items-scenarios/index.js +5 -0
  44. package/dist/es2019/actions/join-list-items-scenarios/join-list-item-with-paragraph.js +74 -0
  45. package/dist/es2019/actions/join-list-items-scenarios/join-list-item-with-parent-nested-list.js +77 -0
  46. package/dist/es2019/actions/join-list-items-scenarios/join-nested-list-with-parent-list-item.js +71 -0
  47. package/dist/es2019/actions/join-list-items-scenarios/join-paragraph-with-list.js +37 -0
  48. package/dist/es2019/actions/join-list-items-scenarios/join-sibling-list-items.js +48 -0
  49. package/dist/es2019/actions/merge-lists.js +24 -0
  50. package/dist/es2019/actions/outdent-list-items-selected.js +295 -0
  51. package/dist/es2019/actions/wrap-and-join-lists.js +93 -0
  52. package/dist/es2019/commands/indent-list.js +62 -0
  53. package/dist/es2019/commands/index.js +326 -0
  54. package/dist/es2019/commands/isFirstChildOfParent.js +7 -0
  55. package/dist/es2019/commands/join-list-item-forward.js +53 -0
  56. package/dist/es2019/commands/listBackspace.js +276 -0
  57. package/dist/es2019/commands/outdent-list.js +60 -0
  58. package/dist/es2019/index.js +1 -1
  59. package/dist/es2019/messages.js +29 -0
  60. package/dist/es2019/plugin.js +121 -0
  61. package/dist/es2019/pm-plugins/input-rules/create-list-input-rule.js +56 -0
  62. package/dist/es2019/pm-plugins/input-rules/index.js +35 -0
  63. package/dist/es2019/pm-plugins/input-rules/wrapping-join-rule.js +55 -0
  64. package/dist/es2019/pm-plugins/keymap.js +19 -0
  65. package/dist/es2019/pm-plugins/main.js +156 -0
  66. package/dist/es2019/transforms.js +101 -0
  67. package/dist/es2019/types.js +1 -1
  68. package/dist/es2019/utils/analytics.js +12 -0
  69. package/dist/es2019/utils/find.js +61 -0
  70. package/dist/es2019/utils/indentation.js +15 -0
  71. package/dist/es2019/utils/mark.js +30 -0
  72. package/dist/es2019/utils/node.js +12 -0
  73. package/dist/es2019/utils/selection.js +96 -0
  74. package/dist/esm/actions/conversions.js +147 -0
  75. package/dist/esm/actions/indent-list-items-selected.js +117 -0
  76. package/dist/esm/actions/indent-list.js +43 -0
  77. package/dist/esm/actions/join-list-items-forward.js +52 -0
  78. package/dist/esm/actions/join-list-items-scenarios/index.js +5 -0
  79. package/dist/esm/actions/join-list-items-scenarios/join-list-item-with-paragraph.js +81 -0
  80. package/dist/esm/actions/join-list-items-scenarios/join-list-item-with-parent-nested-list.js +78 -0
  81. package/dist/esm/actions/join-list-items-scenarios/join-nested-list-with-parent-list-item.js +72 -0
  82. package/dist/esm/actions/join-list-items-scenarios/join-paragraph-with-list.js +38 -0
  83. package/dist/esm/actions/join-list-items-scenarios/join-sibling-list-items.js +49 -0
  84. package/dist/esm/actions/merge-lists.js +21 -0
  85. package/dist/esm/actions/outdent-list-items-selected.js +283 -0
  86. package/dist/esm/actions/wrap-and-join-lists.js +94 -0
  87. package/dist/esm/commands/indent-list.js +63 -0
  88. package/dist/esm/commands/index.js +324 -0
  89. package/dist/esm/commands/isFirstChildOfParent.js +5 -0
  90. package/dist/esm/commands/join-list-item-forward.js +53 -0
  91. package/dist/esm/commands/listBackspace.js +275 -0
  92. package/dist/esm/commands/outdent-list.js +62 -0
  93. package/dist/esm/index.js +1 -1
  94. package/dist/esm/messages.js +29 -0
  95. package/dist/esm/plugin.js +126 -0
  96. package/dist/esm/pm-plugins/input-rules/create-list-input-rule.js +57 -0
  97. package/dist/esm/pm-plugins/input-rules/index.js +32 -0
  98. package/dist/esm/pm-plugins/input-rules/wrapping-join-rule.js +54 -0
  99. package/dist/esm/pm-plugins/keymap.js +19 -0
  100. package/dist/esm/pm-plugins/main.js +156 -0
  101. package/dist/esm/transforms.js +91 -0
  102. package/dist/esm/types.js +1 -1
  103. package/dist/esm/utils/analytics.js +12 -0
  104. package/dist/esm/utils/find.js +59 -0
  105. package/dist/esm/utils/indentation.js +15 -0
  106. package/dist/esm/utils/mark.js +33 -0
  107. package/dist/esm/utils/node.js +10 -0
  108. package/dist/esm/utils/selection.js +81 -0
  109. package/dist/types/actions/conversions.d.ts +6 -0
  110. package/dist/types/actions/indent-list-items-selected.d.ts +2 -0
  111. package/dist/types/actions/indent-list.d.ts +2 -0
  112. package/dist/types/actions/join-list-items-forward.d.ts +13 -0
  113. package/dist/types/actions/join-list-items-scenarios/index.d.ts +5 -0
  114. package/dist/types/actions/join-list-items-scenarios/join-list-item-with-paragraph.d.ts +9 -0
  115. package/dist/types/actions/join-list-items-scenarios/join-list-item-with-parent-nested-list.d.ts +9 -0
  116. package/dist/types/actions/join-list-items-scenarios/join-nested-list-with-parent-list-item.d.ts +9 -0
  117. package/dist/types/actions/join-list-items-scenarios/join-paragraph-with-list.d.ts +9 -0
  118. package/dist/types/actions/join-list-items-scenarios/join-sibling-list-items.d.ts +9 -0
  119. package/dist/types/actions/merge-lists.d.ts +7 -0
  120. package/dist/types/actions/outdent-list-items-selected.d.ts +3 -0
  121. package/dist/types/actions/wrap-and-join-lists.d.ts +17 -0
  122. package/dist/types/commands/indent-list.d.ts +6 -0
  123. package/dist/types/commands/index.d.ts +16 -0
  124. package/dist/types/commands/isFirstChildOfParent.d.ts +2 -0
  125. package/dist/types/commands/join-list-item-forward.d.ts +3 -0
  126. package/dist/types/commands/listBackspace.d.ts +10 -0
  127. package/dist/types/commands/outdent-list.d.ts +6 -0
  128. package/dist/types/index.d.ts +2 -1
  129. package/dist/types/messages.d.ts +27 -0
  130. package/dist/types/plugin.d.ts +2 -0
  131. package/dist/types/pm-plugins/input-rules/create-list-input-rule.d.ts +11 -0
  132. package/dist/types/pm-plugins/input-rules/index.d.ts +5 -0
  133. package/dist/types/pm-plugins/input-rules/wrapping-join-rule.d.ts +13 -0
  134. package/dist/types/pm-plugins/keymap.d.ts +5 -0
  135. package/dist/types/pm-plugins/main.d.ts +11 -0
  136. package/dist/types/transforms.d.ts +4 -0
  137. package/dist/types/types.d.ts +4 -6
  138. package/dist/types/utils/analytics.d.ts +5 -0
  139. package/dist/types/utils/find.d.ts +10 -0
  140. package/dist/types/utils/indentation.d.ts +2 -0
  141. package/dist/types/utils/mark.d.ts +8 -0
  142. package/dist/types/utils/node.d.ts +2 -0
  143. package/dist/types/utils/selection.d.ts +14 -0
  144. package/dist/types-ts4.5/actions/conversions.d.ts +6 -0
  145. package/dist/types-ts4.5/actions/indent-list-items-selected.d.ts +2 -0
  146. package/dist/types-ts4.5/actions/indent-list.d.ts +2 -0
  147. package/dist/types-ts4.5/actions/join-list-items-forward.d.ts +16 -0
  148. package/dist/types-ts4.5/actions/join-list-items-scenarios/index.d.ts +5 -0
  149. package/dist/types-ts4.5/actions/join-list-items-scenarios/join-list-item-with-paragraph.d.ts +9 -0
  150. package/dist/types-ts4.5/actions/join-list-items-scenarios/join-list-item-with-parent-nested-list.d.ts +9 -0
  151. package/dist/types-ts4.5/actions/join-list-items-scenarios/join-nested-list-with-parent-list-item.d.ts +9 -0
  152. package/dist/types-ts4.5/actions/join-list-items-scenarios/join-paragraph-with-list.d.ts +9 -0
  153. package/dist/types-ts4.5/actions/join-list-items-scenarios/join-sibling-list-items.d.ts +9 -0
  154. package/dist/types-ts4.5/actions/merge-lists.d.ts +7 -0
  155. package/dist/types-ts4.5/actions/outdent-list-items-selected.d.ts +3 -0
  156. package/dist/types-ts4.5/actions/wrap-and-join-lists.d.ts +17 -0
  157. package/dist/types-ts4.5/commands/indent-list.d.ts +6 -0
  158. package/dist/types-ts4.5/commands/index.d.ts +16 -0
  159. package/dist/types-ts4.5/commands/isFirstChildOfParent.d.ts +2 -0
  160. package/dist/types-ts4.5/commands/join-list-item-forward.d.ts +3 -0
  161. package/dist/types-ts4.5/commands/listBackspace.d.ts +13 -0
  162. package/dist/types-ts4.5/commands/outdent-list.d.ts +6 -0
  163. package/dist/types-ts4.5/index.d.ts +2 -1
  164. package/dist/types-ts4.5/messages.d.ts +27 -0
  165. package/dist/types-ts4.5/plugin.d.ts +2 -0
  166. package/dist/types-ts4.5/pm-plugins/input-rules/create-list-input-rule.d.ts +11 -0
  167. package/dist/types-ts4.5/pm-plugins/input-rules/index.d.ts +5 -0
  168. package/dist/types-ts4.5/pm-plugins/input-rules/wrapping-join-rule.d.ts +13 -0
  169. package/dist/types-ts4.5/pm-plugins/keymap.d.ts +5 -0
  170. package/dist/types-ts4.5/pm-plugins/main.d.ts +11 -0
  171. package/dist/types-ts4.5/transforms.d.ts +4 -0
  172. package/dist/types-ts4.5/types.d.ts +4 -6
  173. package/dist/types-ts4.5/utils/analytics.d.ts +5 -0
  174. package/dist/types-ts4.5/utils/find.d.ts +10 -0
  175. package/dist/types-ts4.5/utils/indentation.d.ts +2 -0
  176. package/dist/types-ts4.5/utils/mark.d.ts +8 -0
  177. package/dist/types-ts4.5/utils/node.d.ts +2 -0
  178. package/dist/types-ts4.5/utils/selection.d.ts +14 -0
  179. package/package.json +8 -5
  180. package/report.api.md +6 -2
  181. package/tmp/api-report-tmp.d.ts +4 -1
@@ -0,0 +1,160 @@
1
+ import { joinSiblingLists } from '@atlaskit/editor-common/lists';
2
+ import { GapCursorSelection } from '@atlaskit/editor-common/selection';
3
+ import { isEmptyParagraph, isListNode } from '@atlaskit/editor-common/utils';
4
+ import { NodeRange } from '@atlaskit/editor-prosemirror/model';
5
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
6
+ import { findWrapping } from '@atlaskit/editor-prosemirror/transform';
7
+ import { findParentNodeClosestToPos } from '@atlaskit/editor-prosemirror/utils';
8
+ import { findFirstParentListNode } from '../utils/find';
9
+ export function convertListType({
10
+ tr,
11
+ nextListNodeType
12
+ }) {
13
+ const {
14
+ doc,
15
+ selection: {
16
+ $from,
17
+ $to
18
+ }
19
+ } = tr;
20
+ let listRange;
21
+ if (tr.selection instanceof GapCursorSelection) {
22
+ var _$from$nodeAfter;
23
+ const nodeSize = ((_$from$nodeAfter = $from.nodeAfter) === null || _$from$nodeAfter === void 0 ? void 0 : _$from$nodeAfter.nodeSize) || 1;
24
+ listRange = $from.blockRange($from.doc.resolve($from.pos + nodeSize));
25
+ } else {
26
+ listRange = $from.blockRange($to, isListNode);
27
+ }
28
+ if (listRange) {
29
+ return convertSelectedList({
30
+ tr,
31
+ nextListNodeType
32
+ });
33
+ }
34
+ let nodeRangeAroundList = $from.blockRange($to);
35
+ if (!nodeRangeAroundList) {
36
+ return;
37
+ }
38
+ const parentNode = nodeRangeAroundList.parent;
39
+ const {
40
+ startIndex,
41
+ endIndex,
42
+ depth
43
+ } = nodeRangeAroundList;
44
+
45
+ // Checking for invalid nodes to prevent conversion
46
+ // eg. a panel cannot be wrapped in a list so return
47
+ // It will skip this check if the selection begins within a list
48
+ // This is to match the behaviour of the toolbar buttons being disabled
49
+ if (!findFirstParentListNode($from)) {
50
+ for (let i = startIndex; i < endIndex; i++) {
51
+ const position = nodeRangeAroundList.$from.posAtIndex(i, depth);
52
+ const resolvedPosition = doc.resolve(position);
53
+ const currentChild = parentNode.child(i);
54
+ const currentNodeRange = resolvedPosition.blockRange(tr.doc.resolve(position + currentChild.nodeSize));
55
+ if (currentNodeRange && !isListNode(currentChild) && !findWrapping(currentNodeRange, nextListNodeType)) {
56
+ return;
57
+ }
58
+ }
59
+ }
60
+
61
+ // Checking for any non list nodes and wrapping them in a list
62
+ // so they can be converted
63
+ tr.doc.nodesBetween(nodeRangeAroundList.start, nodeRangeAroundList.end, (node, pos) => {
64
+ // Skip over any nodes that are part of a list
65
+ if (findFirstParentListNode(tr.doc.resolve(tr.mapping.map(pos)))) {
66
+ return false;
67
+ }
68
+
69
+ // The following applies to suitable nodes that are not within a list
70
+ const currentNodeNotWrappedInList = node;
71
+ const isNotAnEmptyParagraphAndIsParagraphOrLeafNode = !isEmptyParagraph(currentNodeNotWrappedInList) && (!node.type.isBlock || node.type.name === 'paragraph');
72
+ if (isNotAnEmptyParagraphAndIsParagraphOrLeafNode && nodeRangeAroundList) {
73
+ const remainingNodeRange = new NodeRange(tr.doc.resolve(tr.mapping.map(pos)), tr.doc.resolve(tr.mapping.map(pos) + currentNodeNotWrappedInList.nodeSize), nodeRangeAroundList.depth);
74
+ convertAroundList({
75
+ tr,
76
+ nextListNodeType,
77
+ nodeRange: remainingNodeRange
78
+ });
79
+ return false;
80
+ }
81
+ });
82
+ convertSelectedList({
83
+ tr,
84
+ nextListNodeType
85
+ });
86
+ if (tr.docChanged) {
87
+ joinSiblingLists({
88
+ tr,
89
+ forceListType: nextListNodeType
90
+ });
91
+ }
92
+ }
93
+ const convertSelectedList = ({
94
+ tr,
95
+ nextListNodeType
96
+ }) => {
97
+ const {
98
+ selection,
99
+ selection: {
100
+ from,
101
+ to
102
+ }
103
+ } = tr;
104
+ const {
105
+ codeBlock
106
+ } = tr.doc.type.schema.nodes;
107
+ // get the positions of all the leaf nodes within the selection
108
+ const nodePositions = [];
109
+ if (selection instanceof TextSelection && selection.$cursor || selection instanceof GapCursorSelection) {
110
+ nodePositions.push(from);
111
+ } else {
112
+ // nodesBetween doesn't return leaf nodes that are outside of from and to
113
+ tr.doc.nodesBetween(from, to, (node, pos) => {
114
+ // isLeaf is false for empty codeBlock so adding additional check for childCount
115
+ if (!node.isLeaf && !(node.type === codeBlock && node.childCount === 0)) {
116
+ return true;
117
+ }
118
+ nodePositions.push(pos);
119
+ });
120
+ }
121
+
122
+ // use those positions to get the closest parent list nodes
123
+ nodePositions.reduce((acc, pos) => {
124
+ const closestParentListNode = findParentNodeClosestToPos(tr.doc.resolve(pos), isListNode);
125
+ if (!closestParentListNode) {
126
+ return acc;
127
+ }
128
+
129
+ // don't add duplicates if the parent has already been added into the array
130
+ const existingParent = acc.find(node => {
131
+ return node.pos === closestParentListNode.pos && node.start === closestParentListNode.start && node.depth === closestParentListNode.depth;
132
+ });
133
+ if (!existingParent) {
134
+ acc.push(closestParentListNode);
135
+ }
136
+ return acc;
137
+ }, []).forEach(item => {
138
+ tr.setNodeMarkup(item.pos, nextListNodeType);
139
+ });
140
+ };
141
+ const convertAroundList = ({
142
+ tr,
143
+ nextListNodeType,
144
+ nodeRange
145
+ }) => {
146
+ for (let i = nodeRange.endIndex - 1; i >= nodeRange.startIndex; i--) {
147
+ // @ts-ignore posAtIndex is a public API but has no type yet
148
+ const position = nodeRange.$from.posAtIndex(i, nodeRange.depth);
149
+ const resolvedPos = tr.doc.resolve(position + 1);
150
+ const range = resolvedPos.blockRange(resolvedPos);
151
+ if (!range) {
152
+ return;
153
+ }
154
+ const wrappings = findWrapping(range, nextListNodeType);
155
+ if (!range || !wrappings) {
156
+ return;
157
+ }
158
+ tr.wrap(range, wrappings);
159
+ }
160
+ };
@@ -0,0 +1,124 @@
1
+ import { normalizeListItemsSelection } from '@atlaskit/editor-common/lists';
2
+ import { GapCursorSelection } from '@atlaskit/editor-common/selection';
3
+ import { isListItemNode, isListNode } from '@atlaskit/editor-common/utils';
4
+ import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
5
+ import { NodeSelection, Selection, TextSelection } from '@atlaskit/editor-prosemirror/state';
6
+ import { findFirstParentListItemNode } from '../utils/find';
7
+ export const indentListItemsSelected = tr => {
8
+ const originalSelection = tr.selection;
9
+ const normalizedSelection = normalizeListItemsSelection({
10
+ selection: originalSelection,
11
+ doc: tr.doc
12
+ });
13
+ const {
14
+ $from,
15
+ $to
16
+ } = normalizedSelection;
17
+ const range = calculateRange({
18
+ selection: normalizedSelection
19
+ });
20
+ if (!range) {
21
+ return false;
22
+ }
23
+ const listItemsSelected = {
24
+ from: findFirstParentListItemNode($from),
25
+ to: findFirstParentListItemNode($to)
26
+ };
27
+ if (listItemsSelected.from === null || listItemsSelected.to === null) {
28
+ return null;
29
+ }
30
+ const resolvedPos = tr.doc.resolve(listItemsSelected.from.pos);
31
+ const listItemIndex = resolvedPos.index();
32
+ // @ts-ignore
33
+ const positionListItemPosition = resolvedPos.posAtIndex(listItemIndex - 1);
34
+ const previousListItem = tr.doc.nodeAt(positionListItemPosition);
35
+ if (!previousListItem || !isListItemNode(previousListItem)) {
36
+ return null;
37
+ }
38
+ if (isListItemNode(previousListItem) && listItemIndex === 0) {
39
+ return null;
40
+ }
41
+ const listItemSelectedCommonParent = range.parent;
42
+ const previousNestedList = isListNode(previousListItem.lastChild) ? previousListItem.lastChild : null;
43
+ const listNodeType = previousNestedList ? previousNestedList.type : listItemSelectedCommonParent.type;
44
+ const nestedList = listItemsSelected.to.node.lastChild;
45
+ const nestedItemsOffset = nestedList && isListNode(nestedList) ? nestedList.nodeSize : 0;
46
+ const from = listItemsSelected.from.pos;
47
+ const to = listItemsSelected.to.pos + listItemsSelected.to.node.nodeSize - nestedItemsOffset;
48
+ const [sliceSelected, nestedListItemsLeftover] = createIndentedListItemsSlice({
49
+ tr,
50
+ listNodeType,
51
+ range,
52
+ from,
53
+ to
54
+ });
55
+ const hasPreviousNestedList = Boolean(previousNestedList);
56
+ const start = from - 1;
57
+ tr.replaceRange(hasPreviousNestedList ? start - 1 : start, range.end, sliceSelected);
58
+ const leftoverContentPosition = tr.mapping.map(to) - 2;
59
+ if (nestedListItemsLeftover.openStart === 0) {
60
+ tr.insert(leftoverContentPosition, nestedListItemsLeftover.content);
61
+ } else {
62
+ tr.replace(leftoverContentPosition - nestedListItemsLeftover.openStart, leftoverContentPosition - nestedListItemsLeftover.openStart, nestedListItemsLeftover);
63
+ }
64
+ const nextSelection = calculateNewSelection({
65
+ originalSelection,
66
+ normalizedSelection,
67
+ tr,
68
+ hasPreviousNestedList
69
+ });
70
+ tr.setSelection(nextSelection);
71
+ };
72
+ const calculateRange = ({
73
+ selection
74
+ }) => {
75
+ const {
76
+ $from,
77
+ $to
78
+ } = selection;
79
+ const range = $from.blockRange($to, isListNode);
80
+ if (!range) {
81
+ return null;
82
+ }
83
+ return range;
84
+ };
85
+ const calculateNewSelection = ({
86
+ tr,
87
+ normalizedSelection,
88
+ originalSelection,
89
+ hasPreviousNestedList
90
+ }) => {
91
+ const offset = hasPreviousNestedList ? 2 : 0;
92
+ const {
93
+ $from,
94
+ $to
95
+ } = normalizedSelection;
96
+ if (normalizedSelection instanceof GapCursorSelection) {
97
+ const nextSelectionFrom = tr.doc.resolve($from.pos - offset);
98
+ return new GapCursorSelection(nextSelectionFrom, normalizedSelection.side);
99
+ }
100
+ if (originalSelection instanceof NodeSelection) {
101
+ return NodeSelection.create(tr.doc, $from.pos - offset);
102
+ }
103
+ const {
104
+ $from: nextSelectionFrom
105
+ } = Selection.near(tr.doc.resolve($from.pos - offset));
106
+ const {
107
+ $to: nextSelectionTo
108
+ } = Selection.near(tr.doc.resolve($to.pos - offset), -1);
109
+ return new TextSelection(nextSelectionFrom, nextSelectionTo);
110
+ };
111
+ const createIndentedListItemsSlice = ({
112
+ tr,
113
+ from,
114
+ to,
115
+ listNodeType,
116
+ range
117
+ }) => {
118
+ const listItemsSlice = tr.doc.slice(from, to - 2);
119
+ const listFragment = Fragment.from(listNodeType.create(null, listItemsSlice.content));
120
+ const nonSelectedListItemsSlice = tr.doc.slice(to, range.end - 2);
121
+ const openStart = tr.doc.slice(from - 1, range.end).openStart;
122
+ const slice = new Slice(listFragment, openStart, 0);
123
+ return [slice, nonSelectedListItemsSlice];
124
+ };
@@ -0,0 +1,44 @@
1
+ import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
2
+ import { ReplaceAroundStep } from '@atlaskit/editor-prosemirror/transform';
3
+
4
+ // adapted from https://github.com/ProseMirror/prosemirror-schema-list/blob/master/src/schema-list.js#L206:L231
5
+ export const indentList = tr => {
6
+ const {
7
+ $from,
8
+ $to
9
+ } = tr.selection;
10
+ const {
11
+ listItem
12
+ } = tr.doc.type.schema.nodes;
13
+ const range = $from.blockRange($to, node => !!node.childCount && !!node.firstChild && node.firstChild.type === listItem);
14
+ if (!range) {
15
+ return false;
16
+ }
17
+
18
+ // get the index of the selected list item in the list it is part of
19
+ const startIndex = range.startIndex;
20
+ if (startIndex === 0) {
21
+ return false;
22
+ }
23
+
24
+ // get the parent list of the list item(s) in the selected range
25
+ const parent = range.parent;
26
+
27
+ // get the list immediately before the selection start
28
+ const previousListItem = parent.child(startIndex - 1);
29
+ if (previousListItem.type !== listItem) {
30
+ return false;
31
+ }
32
+
33
+ // if that list was nested, join the selected list items into the same
34
+ // nested list; if not, create a new child list of the same type and
35
+ // nest it under the current level
36
+ const isPreviousListNested = previousListItem.lastChild && ['bulletList', 'orderedList'].includes(previousListItem.lastChild.type.name);
37
+ const inner = Fragment.from(isPreviousListNested ? listItem.create() : undefined);
38
+ const nextListNodeType = isPreviousListNested ? previousListItem.lastChild.type : parent.type;
39
+ const nextListNodeContent = Fragment.from(nextListNodeType.create(null, inner));
40
+ const slice = new Slice(Fragment.from(listItem.create(null, nextListNodeContent)), isPreviousListNested ? 3 : 1, 0);
41
+ const before = range.start;
42
+ const after = range.end;
43
+ tr.step(new ReplaceAroundStep(before - (isPreviousListNested ? 3 : 1), after, before, after, slice, 1, true));
44
+ };
@@ -0,0 +1,54 @@
1
+ import { LIST_TEXT_SCENARIOS } from '@atlaskit/editor-common/analytics';
2
+ import { isListItemNode, isListNode, isParagraphNode } from '@atlaskit/editor-common/utils';
3
+ import { isPosInsideList, isPosInsideParagraph } from '../utils/selection';
4
+ import { joinListItemWithParagraph, joinListItemWithParentNestedList, joinNestedListWithParentListItem, joinParagrapWithList, joinSiblingListItems } from './join-list-items-scenarios';
5
+ export const calcJoinListScenario = (walkNode, $head) => {
6
+ const {
7
+ $pos: $next,
8
+ foundNode: nextFoundNode
9
+ } = walkNode;
10
+ const headParent = $head.parent;
11
+ const headGrandParent = $head.node(-1);
12
+ const headInList = isPosInsideList($head);
13
+ const headInParagraph = isPosInsideParagraph($head);
14
+ const headInLastNonListChild = headGrandParent && headGrandParent.lastChild && (headGrandParent.lastChild === headParent || headGrandParent.childCount > 1 && headGrandParent.child(headGrandParent.childCount - 2) === headParent &&
15
+ //find the second last child if a list item may be the last child
16
+ isListNode(headGrandParent.lastChild));
17
+ const nextInList = isPosInsideList($next);
18
+ const nextInParagraph = isPosInsideParagraph($next);
19
+ if (!headInList && headInParagraph && nextInList) {
20
+ return [LIST_TEXT_SCENARIOS.JOIN_LIST_ITEM_WITH_PARAGRAPH, joinListItemWithParagraph];
21
+ }
22
+ if (!nextFoundNode || !headInList || !headInParagraph || !headInLastNonListChild) {
23
+ return false;
24
+ }
25
+ if (!nextInList && nextInParagraph) {
26
+ return [LIST_TEXT_SCENARIOS.JOIN_PARAGRAPH_WITH_LIST, joinParagrapWithList];
27
+ }
28
+ if (!nextInList) {
29
+ return false;
30
+ }
31
+ const nextNodeAfter = $next.nodeAfter;
32
+ const nextGrandParent = $next.node(-1);
33
+ const headGreatGrandParent = $head.node(-2);
34
+ const nextInListItem = isListItemNode($next.parent);
35
+ const nextNodeAfterListItem = isListItemNode(nextNodeAfter);
36
+ const nextListItemHasFirstChildParagraph = nextNodeAfter &&
37
+ //Redundant check but the linter complains otherwise
38
+ nextNodeAfterListItem && isParagraphNode(nextNodeAfter.firstChild);
39
+ if (!nextInListItem && nextListItemHasFirstChildParagraph) {
40
+ return [LIST_TEXT_SCENARIOS.JOIN_DESCENDANT_TO_PARENT, joinNestedListWithParentListItem];
41
+ }
42
+ if (!nextInListItem) {
43
+ return false;
44
+ }
45
+ const nextParentSiblingOfHeadParent = nextGrandParent && nextGrandParent === headGreatGrandParent;
46
+ const nextNodeAfterIsParagraph = isParagraphNode(nextNodeAfter);
47
+ if (!nextNodeAfterIsParagraph) {
48
+ return false;
49
+ }
50
+ if (nextParentSiblingOfHeadParent) {
51
+ return [LIST_TEXT_SCENARIOS.JOIN_SIBLINGS, joinSiblingListItems];
52
+ }
53
+ return [LIST_TEXT_SCENARIOS.JOIN_PARENT_SIBLING_TO_PARENT_CHILD, joinListItemWithParentNestedList];
54
+ };
@@ -0,0 +1,5 @@
1
+ export { joinParagrapWithList } from './join-paragraph-with-list';
2
+ export { joinSiblingListItems } from './join-sibling-list-items';
3
+ export { joinNestedListWithParentListItem } from './join-nested-list-with-parent-list-item';
4
+ export { joinListItemWithParentNestedList } from './join-list-item-with-parent-nested-list';
5
+ export { joinListItemWithParagraph } from './join-list-item-with-paragraph';
@@ -0,0 +1,74 @@
1
+ import { insertContentDeleteRange, isListNode } from '@atlaskit/editor-common/utils';
2
+ import { Fragment } from '@atlaskit/editor-prosemirror/model';
3
+ // Case for when a users selection is at the end of a paragraph, the paragraph
4
+ // is followed by a list, and they delete forward
5
+ export const joinListItemWithParagraph = ({
6
+ tr,
7
+ $next,
8
+ $head
9
+ }) => {
10
+ // For empty paragraphs before a list
11
+ if ($head.parent.content.size < 1) {
12
+ insertContentDeleteRange(tr, tr => tr.doc.resolve($head.pos), [], [[$head.pos - 1, $head.pos]]);
13
+ return true;
14
+ }
15
+ const paragraphPosition = $head.pos;
16
+ const list = tr.doc.nodeAt($next.pos - 1);
17
+ const firstListItem = tr.doc.nodeAt($next.pos);
18
+ if (!list || !firstListItem) {
19
+ return false;
20
+ }
21
+ const firstChildNodeOfFirstListItem = firstListItem.firstChild;
22
+ if (!firstChildNodeOfFirstListItem) {
23
+ return false;
24
+ }
25
+ const lastChildOfFirstListItem = firstListItem.lastChild;
26
+ const firstGrandchildOfFirstListItem = firstChildNodeOfFirstListItem.firstChild;
27
+ const firstListItemHasOneChildWithNoNestedLists = hasSingleChild(firstListItem) && firstChildNodeOfFirstListItem.childCount < 2 && $next.nodeAfter;
28
+ const firstListItemContainsParagraphAndNestedList = !hasSingleChild(firstListItem) && lastChildOfFirstListItem && isListNode(lastChildOfFirstListItem);
29
+ const insertions = [];
30
+ const deletions = [];
31
+
32
+ // For lists that only have one list item with no children - need to remove remaining list
33
+ if (hasSingleChild(list) && hasSingleChild(firstListItem) && $next.nodeAfter) {
34
+ deletions.push([tr.mapping.map($next.pos - 1), tr.mapping.map($next.pos + $next.nodeAfter.nodeSize + 1)]);
35
+ }
36
+
37
+ // For first list items that have a paragraph and a list
38
+ if (firstListItemContainsParagraphAndNestedList) {
39
+ const firstListItemNestedList = Fragment.from(lastChildOfFirstListItem.content);
40
+ insertions.push([firstListItemNestedList, tr.mapping.map($next.pos)]);
41
+ }
42
+
43
+ // For first list item has one child & no nested lists OR first list items that have a paragraph and a list
44
+ if (firstListItemHasOneChildWithNoNestedLists || firstListItemContainsParagraphAndNestedList) {
45
+ deletions.push([tr.mapping.map($next.pos), tr.mapping.map($next.pos + firstListItem.nodeSize - 1)]);
46
+ const firstListItemText = Fragment.from(firstChildNodeOfFirstListItem.content);
47
+ insertions.push([firstListItemText, paragraphPosition]);
48
+ insertContentDeleteRange(tr, tr => tr.doc.resolve($head.pos), insertions, deletions);
49
+ return true;
50
+ }
51
+
52
+ // For any first list items that have multiple children (eg. multiple paragraphs)
53
+ if (firstListItem.childCount > 1) {
54
+ insertions.push([Fragment.from(firstChildNodeOfFirstListItem.content), paragraphPosition]);
55
+ deletions.push([tr.mapping.map($next.pos + 1), tr.mapping.map($next.pos + firstChildNodeOfFirstListItem.nodeSize + 1)]);
56
+ insertContentDeleteRange(tr, tr => tr.doc.resolve($head.pos), insertions, deletions);
57
+ return true;
58
+ }
59
+
60
+ // For any remaining first list items that have a single child (eg. single paragraph, multiple lines of text)
61
+ if (firstGrandchildOfFirstListItem && firstGrandchildOfFirstListItem.type.name === 'hardBreak') {
62
+ const nodeSizeOfGrandchild = firstGrandchildOfFirstListItem ? firstGrandchildOfFirstListItem.nodeSize : 0;
63
+ deletions.push([tr.mapping.map($next.pos + 2), tr.mapping.map($next.pos + 2 + nodeSizeOfGrandchild)]);
64
+ } else {
65
+ insertions.push([Fragment.from(firstChildNodeOfFirstListItem.content), paragraphPosition]);
66
+ const nodeSizeOfFirstChild = firstChildNodeOfFirstListItem.nodeSize;
67
+ deletions.push([tr.mapping.map($next.pos), tr.mapping.map($next.pos + 2 + nodeSizeOfFirstChild)]);
68
+ }
69
+ insertContentDeleteRange(tr, tr => tr.doc.resolve($head.pos), insertions, deletions);
70
+ return true;
71
+ };
72
+ const hasSingleChild = node => {
73
+ return node.childCount === 1;
74
+ };
@@ -0,0 +1,77 @@
1
+ import { insertContentDeleteRange, isListNode } from '@atlaskit/editor-common/utils';
2
+ //Case for two adjacent list items with the first being of greater indentation
3
+ export const joinListItemWithParentNestedList = ({
4
+ tr,
5
+ $next,
6
+ $head
7
+ }) => {
8
+ /* CASE 4
9
+ * Initial Structure:
10
+ *
11
+ * List A {
12
+ * ListItem B {
13
+ * Paragraph C { text1 }
14
+ * ...Children D
15
+ * List E {
16
+ * ...
17
+ * List F { //May be multiple levels of lists
18
+ * ...Children G
19
+ * ListItem H { //Last node of the block
20
+ * ...Children I
21
+ * Paragraph J { text2 |$head||textInsertPos| } |childrenMInsertPos| //Cant have children since this ListItem is the last of the block
22
+ * }
23
+ * }
24
+ * ...
25
+ * |childrenOInsertPos| }
26
+ * }
27
+ * ListItem K { |$next|
28
+ * Paragraph L { text3 }
29
+ * ...Children M
30
+ * List? N {
31
+ * ...Children O
32
+ * }
33
+ * }
34
+ * }
35
+ *
36
+ * Converts to:
37
+ *
38
+ * List A {
39
+ * ListItem B {
40
+ * Paragraph C { text1 }
41
+ * ...Children D
42
+ * List E {
43
+ * ...
44
+ * List F {
45
+ * ...Children G
46
+ * ListItem H {
47
+ * ...Children I
48
+ * Paragraph J { text2text3 }
49
+ * ...Children M
50
+ * }
51
+ * }
52
+ * ...
53
+ * ...Children O
54
+ * }
55
+ * }
56
+ * }
57
+ *
58
+ */
59
+
60
+ const listItemK = $next.parent; //List must have at least one child
61
+ if (!listItemK.firstChild || !listItemK.lastChild) {
62
+ return false;
63
+ }
64
+ const beforeListItemK = $next.before();
65
+ const afterListItemB = $next.before();
66
+ const afterListItemK = $next.after();
67
+ const containsChildrenO = isListNode(listItemK.lastChild);
68
+ const textInsertPos = $head.pos;
69
+ const childrenMInsertPos = $head.pos + 1;
70
+ const childrenOInsertPos = afterListItemB - 2;
71
+ const textContent = listItemK.firstChild.content;
72
+ const childrenMContent = containsChildrenO ? listItemK.content.cut(listItemK.firstChild.nodeSize, listItemK.nodeSize - listItemK.lastChild.nodeSize - 2 //Get the position before
73
+ ) : listItemK.content.cut(listItemK.firstChild.nodeSize);
74
+ const childrenOContent = listItemK.lastChild.content;
75
+ insertContentDeleteRange(tr, tr => tr.doc.resolve(textInsertPos), containsChildrenO ? [[textContent, textInsertPos], [childrenMContent, childrenMInsertPos], [childrenOContent, childrenOInsertPos]] : [[textContent, textInsertPos], [childrenMContent, childrenMInsertPos]], [[beforeListItemK, afterListItemK]]);
76
+ return true;
77
+ };
@@ -0,0 +1,71 @@
1
+ import { insertContentDeleteRange, isListNode } from '@atlaskit/editor-common/utils';
2
+ //Case for two adjacent list items with the first being of lower indentation
3
+ export const joinNestedListWithParentListItem = ({
4
+ tr,
5
+ $next,
6
+ $head
7
+ }) => {
8
+ /* CASE 3
9
+ * Initial Structure:
10
+ *
11
+ * List A {
12
+ * ListItem B {
13
+ * ...Children C
14
+ * Paragraph D { text1 |$head||textInsertPos| } |childrenHInsertPos|
15
+ * List E { |$next||childrenJInsertPos|
16
+ * ListItem F {
17
+ * Paragraph G { text2 }
18
+ * ...Children H
19
+ * List? I {
20
+ * ...Children J
21
+ * }
22
+ * }
23
+ * ...Children K
24
+ * }
25
+ * }
26
+ * }
27
+ *
28
+ * Converts to:
29
+ *
30
+ * List A {
31
+ * ListItem B {
32
+ * ...Children C
33
+ * Paragraph D { text1text2 }
34
+ * ...Children H
35
+ * List E {
36
+ * ...Children J
37
+ * ...Children K
38
+ * }
39
+ * }
40
+ * }
41
+ *
42
+ */
43
+
44
+ const listE = $next.parent;
45
+ const listItemF = $next.nodeAfter; //We know next is before a ListItem. ListItem must have at least one child
46
+ if (!listItemF || !listItemF.lastChild) {
47
+ return false;
48
+ }
49
+ const paragraphG = listItemF.firstChild; //ListItem must have at least one child
50
+ if (!paragraphG) {
51
+ return false;
52
+ }
53
+ const beforeListE = $next.before();
54
+ const beforeListItemF = $next.pos;
55
+ const afterParagraphD = $head.after();
56
+ const afterListE = $next.after();
57
+ const afterListItemF = tr.doc.resolve($next.pos + 1).after(); //List must always have at least one listItem
58
+
59
+ const containsChildrenJ = isListNode(listItemF.lastChild);
60
+ const shouldRemoveListE = listE.childCount === 1 && !containsChildrenJ; //Assures no Children J and K
61
+
62
+ const textInsertPos = $head.pos;
63
+ const childrenHInsertPos = afterParagraphD;
64
+ const childrenJInsertPos = $next.pos;
65
+ const textContent = paragraphG.content;
66
+ const childrenHContent = containsChildrenJ ? listItemF.content.cut(paragraphG.nodeSize, listItemF.nodeSize - listItemF.lastChild.nodeSize - 2) : listItemF.content.cut(paragraphG.nodeSize); //If Children J doesn't exist then Children H will include the last node
67
+ const childrenJContent = listItemF.lastChild.content; //Will be invalid if there are no Children J but it will be unused
68
+
69
+ insertContentDeleteRange(tr, tr => tr.doc.resolve(textInsertPos), containsChildrenJ ? [[textContent, textInsertPos], [childrenHContent, childrenHInsertPos], [childrenJContent, childrenJInsertPos]] : [[textContent, textInsertPos], [childrenHContent, childrenHInsertPos]], [shouldRemoveListE ? [beforeListE, afterListE] : [beforeListItemF, afterListItemF]]);
70
+ return true;
71
+ };
@@ -0,0 +1,37 @@
1
+ import { insertContentDeleteRange } from '@atlaskit/editor-common/utils';
2
+ //Case for two adjacent nodes with the first being a list item and the last being a paragraph
3
+ export const joinParagrapWithList = ({
4
+ tr,
5
+ $next,
6
+ $head
7
+ }) => {
8
+ /* CASE 1
9
+ * Initial Structure:
10
+ *
11
+ * List A {
12
+ * ListItem B {
13
+ * ...Children C
14
+ * Paragraph D { text1 |$head||textInsertPos| }
15
+ * }
16
+ * }
17
+ * Paragraph E { |$next| text 2 }
18
+ *
19
+ * Converts to:
20
+ *
21
+ * List A {
22
+ * ListItem B {
23
+ * ...Children C
24
+ * Paragraph D { text1text2 }
25
+ * }
26
+ * }
27
+ *
28
+ */
29
+
30
+ const paragraphE = $next.parent;
31
+ const beforeParagraphE = $next.before();
32
+ const afterParagraphE = $next.after();
33
+ const textInsertPos = $head.pos;
34
+ const textContent = paragraphE.content;
35
+ insertContentDeleteRange(tr, tr => tr.doc.resolve(textInsertPos), [[textContent, textInsertPos]], [[beforeParagraphE, afterParagraphE]]);
36
+ return true;
37
+ };