@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.
- package/.eslintrc.js +14 -0
- package/CHANGELOG.md +10 -0
- package/README.md +1 -1
- package/dist/cjs/actions/conversions.js +153 -0
- package/dist/cjs/actions/indent-list-items-selected.js +125 -0
- package/dist/cjs/actions/indent-list.js +49 -0
- package/dist/cjs/actions/join-list-items-forward.js +59 -0
- package/dist/cjs/actions/join-list-items-scenarios/index.js +40 -0
- package/dist/cjs/actions/join-list-items-scenarios/join-list-item-with-paragraph.js +88 -0
- package/dist/cjs/actions/join-list-items-scenarios/join-list-item-with-parent-nested-list.js +85 -0
- package/dist/cjs/actions/join-list-items-scenarios/join-nested-list-with-parent-list-item.js +79 -0
- package/dist/cjs/actions/join-list-items-scenarios/join-paragraph-with-list.js +45 -0
- package/dist/cjs/actions/join-list-items-scenarios/join-sibling-list-items.js +56 -0
- package/dist/cjs/actions/merge-lists.js +27 -0
- package/dist/cjs/actions/outdent-list-items-selected.js +291 -0
- package/dist/cjs/actions/wrap-and-join-lists.js +100 -0
- package/dist/cjs/commands/indent-list.js +71 -0
- package/dist/cjs/commands/index.js +350 -0
- package/dist/cjs/commands/isFirstChildOfParent.js +12 -0
- package/dist/cjs/commands/join-list-item-forward.js +61 -0
- package/dist/cjs/commands/listBackspace.js +284 -0
- package/dist/cjs/commands/outdent-list.js +70 -0
- package/dist/cjs/index.js +8 -1
- package/dist/cjs/messages.js +37 -0
- package/dist/cjs/plugin.js +133 -0
- package/dist/cjs/pm-plugins/input-rules/create-list-input-rule.js +63 -0
- package/dist/cjs/pm-plugins/input-rules/index.js +38 -0
- package/dist/cjs/pm-plugins/input-rules/wrapping-join-rule.js +60 -0
- package/dist/cjs/pm-plugins/keymap.js +27 -0
- package/dist/cjs/pm-plugins/main.js +166 -0
- package/dist/cjs/transforms.js +99 -0
- package/dist/cjs/types.js +4 -1
- package/dist/cjs/utils/analytics.js +22 -0
- package/dist/cjs/utils/find.js +68 -0
- package/dist/cjs/utils/indentation.js +22 -0
- package/dist/cjs/utils/mark.js +40 -0
- package/dist/cjs/utils/node.js +16 -0
- package/dist/cjs/utils/selection.js +95 -0
- package/dist/es2019/actions/conversions.js +160 -0
- package/dist/es2019/actions/indent-list-items-selected.js +124 -0
- package/dist/es2019/actions/indent-list.js +44 -0
- package/dist/es2019/actions/join-list-items-forward.js +54 -0
- package/dist/es2019/actions/join-list-items-scenarios/index.js +5 -0
- package/dist/es2019/actions/join-list-items-scenarios/join-list-item-with-paragraph.js +74 -0
- package/dist/es2019/actions/join-list-items-scenarios/join-list-item-with-parent-nested-list.js +77 -0
- package/dist/es2019/actions/join-list-items-scenarios/join-nested-list-with-parent-list-item.js +71 -0
- package/dist/es2019/actions/join-list-items-scenarios/join-paragraph-with-list.js +37 -0
- package/dist/es2019/actions/join-list-items-scenarios/join-sibling-list-items.js +48 -0
- package/dist/es2019/actions/merge-lists.js +24 -0
- package/dist/es2019/actions/outdent-list-items-selected.js +295 -0
- package/dist/es2019/actions/wrap-and-join-lists.js +93 -0
- package/dist/es2019/commands/indent-list.js +62 -0
- package/dist/es2019/commands/index.js +326 -0
- package/dist/es2019/commands/isFirstChildOfParent.js +7 -0
- package/dist/es2019/commands/join-list-item-forward.js +53 -0
- package/dist/es2019/commands/listBackspace.js +276 -0
- package/dist/es2019/commands/outdent-list.js +60 -0
- package/dist/es2019/index.js +1 -1
- package/dist/es2019/messages.js +29 -0
- package/dist/es2019/plugin.js +121 -0
- package/dist/es2019/pm-plugins/input-rules/create-list-input-rule.js +56 -0
- package/dist/es2019/pm-plugins/input-rules/index.js +35 -0
- package/dist/es2019/pm-plugins/input-rules/wrapping-join-rule.js +55 -0
- package/dist/es2019/pm-plugins/keymap.js +19 -0
- package/dist/es2019/pm-plugins/main.js +156 -0
- package/dist/es2019/transforms.js +101 -0
- package/dist/es2019/types.js +1 -1
- package/dist/es2019/utils/analytics.js +12 -0
- package/dist/es2019/utils/find.js +61 -0
- package/dist/es2019/utils/indentation.js +15 -0
- package/dist/es2019/utils/mark.js +30 -0
- package/dist/es2019/utils/node.js +12 -0
- package/dist/es2019/utils/selection.js +96 -0
- package/dist/esm/actions/conversions.js +147 -0
- package/dist/esm/actions/indent-list-items-selected.js +117 -0
- package/dist/esm/actions/indent-list.js +43 -0
- package/dist/esm/actions/join-list-items-forward.js +52 -0
- package/dist/esm/actions/join-list-items-scenarios/index.js +5 -0
- package/dist/esm/actions/join-list-items-scenarios/join-list-item-with-paragraph.js +81 -0
- package/dist/esm/actions/join-list-items-scenarios/join-list-item-with-parent-nested-list.js +78 -0
- package/dist/esm/actions/join-list-items-scenarios/join-nested-list-with-parent-list-item.js +72 -0
- package/dist/esm/actions/join-list-items-scenarios/join-paragraph-with-list.js +38 -0
- package/dist/esm/actions/join-list-items-scenarios/join-sibling-list-items.js +49 -0
- package/dist/esm/actions/merge-lists.js +21 -0
- package/dist/esm/actions/outdent-list-items-selected.js +283 -0
- package/dist/esm/actions/wrap-and-join-lists.js +94 -0
- package/dist/esm/commands/indent-list.js +63 -0
- package/dist/esm/commands/index.js +324 -0
- package/dist/esm/commands/isFirstChildOfParent.js +5 -0
- package/dist/esm/commands/join-list-item-forward.js +53 -0
- package/dist/esm/commands/listBackspace.js +275 -0
- package/dist/esm/commands/outdent-list.js +62 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/messages.js +29 -0
- package/dist/esm/plugin.js +126 -0
- package/dist/esm/pm-plugins/input-rules/create-list-input-rule.js +57 -0
- package/dist/esm/pm-plugins/input-rules/index.js +32 -0
- package/dist/esm/pm-plugins/input-rules/wrapping-join-rule.js +54 -0
- package/dist/esm/pm-plugins/keymap.js +19 -0
- package/dist/esm/pm-plugins/main.js +156 -0
- package/dist/esm/transforms.js +91 -0
- package/dist/esm/types.js +1 -1
- package/dist/esm/utils/analytics.js +12 -0
- package/dist/esm/utils/find.js +59 -0
- package/dist/esm/utils/indentation.js +15 -0
- package/dist/esm/utils/mark.js +33 -0
- package/dist/esm/utils/node.js +10 -0
- package/dist/esm/utils/selection.js +81 -0
- package/dist/types/actions/conversions.d.ts +6 -0
- package/dist/types/actions/indent-list-items-selected.d.ts +2 -0
- package/dist/types/actions/indent-list.d.ts +2 -0
- package/dist/types/actions/join-list-items-forward.d.ts +13 -0
- package/dist/types/actions/join-list-items-scenarios/index.d.ts +5 -0
- package/dist/types/actions/join-list-items-scenarios/join-list-item-with-paragraph.d.ts +9 -0
- package/dist/types/actions/join-list-items-scenarios/join-list-item-with-parent-nested-list.d.ts +9 -0
- package/dist/types/actions/join-list-items-scenarios/join-nested-list-with-parent-list-item.d.ts +9 -0
- package/dist/types/actions/join-list-items-scenarios/join-paragraph-with-list.d.ts +9 -0
- package/dist/types/actions/join-list-items-scenarios/join-sibling-list-items.d.ts +9 -0
- package/dist/types/actions/merge-lists.d.ts +7 -0
- package/dist/types/actions/outdent-list-items-selected.d.ts +3 -0
- package/dist/types/actions/wrap-and-join-lists.d.ts +17 -0
- package/dist/types/commands/indent-list.d.ts +6 -0
- package/dist/types/commands/index.d.ts +16 -0
- package/dist/types/commands/isFirstChildOfParent.d.ts +2 -0
- package/dist/types/commands/join-list-item-forward.d.ts +3 -0
- package/dist/types/commands/listBackspace.d.ts +10 -0
- package/dist/types/commands/outdent-list.d.ts +6 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/messages.d.ts +27 -0
- package/dist/types/plugin.d.ts +2 -0
- package/dist/types/pm-plugins/input-rules/create-list-input-rule.d.ts +11 -0
- package/dist/types/pm-plugins/input-rules/index.d.ts +5 -0
- package/dist/types/pm-plugins/input-rules/wrapping-join-rule.d.ts +13 -0
- package/dist/types/pm-plugins/keymap.d.ts +5 -0
- package/dist/types/pm-plugins/main.d.ts +11 -0
- package/dist/types/transforms.d.ts +4 -0
- package/dist/types/types.d.ts +4 -6
- package/dist/types/utils/analytics.d.ts +5 -0
- package/dist/types/utils/find.d.ts +10 -0
- package/dist/types/utils/indentation.d.ts +2 -0
- package/dist/types/utils/mark.d.ts +8 -0
- package/dist/types/utils/node.d.ts +2 -0
- package/dist/types/utils/selection.d.ts +14 -0
- package/dist/types-ts4.5/actions/conversions.d.ts +6 -0
- package/dist/types-ts4.5/actions/indent-list-items-selected.d.ts +2 -0
- package/dist/types-ts4.5/actions/indent-list.d.ts +2 -0
- package/dist/types-ts4.5/actions/join-list-items-forward.d.ts +16 -0
- package/dist/types-ts4.5/actions/join-list-items-scenarios/index.d.ts +5 -0
- package/dist/types-ts4.5/actions/join-list-items-scenarios/join-list-item-with-paragraph.d.ts +9 -0
- package/dist/types-ts4.5/actions/join-list-items-scenarios/join-list-item-with-parent-nested-list.d.ts +9 -0
- package/dist/types-ts4.5/actions/join-list-items-scenarios/join-nested-list-with-parent-list-item.d.ts +9 -0
- package/dist/types-ts4.5/actions/join-list-items-scenarios/join-paragraph-with-list.d.ts +9 -0
- package/dist/types-ts4.5/actions/join-list-items-scenarios/join-sibling-list-items.d.ts +9 -0
- package/dist/types-ts4.5/actions/merge-lists.d.ts +7 -0
- package/dist/types-ts4.5/actions/outdent-list-items-selected.d.ts +3 -0
- package/dist/types-ts4.5/actions/wrap-and-join-lists.d.ts +17 -0
- package/dist/types-ts4.5/commands/indent-list.d.ts +6 -0
- package/dist/types-ts4.5/commands/index.d.ts +16 -0
- package/dist/types-ts4.5/commands/isFirstChildOfParent.d.ts +2 -0
- package/dist/types-ts4.5/commands/join-list-item-forward.d.ts +3 -0
- package/dist/types-ts4.5/commands/listBackspace.d.ts +13 -0
- package/dist/types-ts4.5/commands/outdent-list.d.ts +6 -0
- package/dist/types-ts4.5/index.d.ts +2 -1
- package/dist/types-ts4.5/messages.d.ts +27 -0
- package/dist/types-ts4.5/plugin.d.ts +2 -0
- package/dist/types-ts4.5/pm-plugins/input-rules/create-list-input-rule.d.ts +11 -0
- package/dist/types-ts4.5/pm-plugins/input-rules/index.d.ts +5 -0
- package/dist/types-ts4.5/pm-plugins/input-rules/wrapping-join-rule.d.ts +13 -0
- package/dist/types-ts4.5/pm-plugins/keymap.d.ts +5 -0
- package/dist/types-ts4.5/pm-plugins/main.d.ts +11 -0
- package/dist/types-ts4.5/transforms.d.ts +4 -0
- package/dist/types-ts4.5/types.d.ts +4 -6
- package/dist/types-ts4.5/utils/analytics.d.ts +5 -0
- package/dist/types-ts4.5/utils/find.d.ts +10 -0
- package/dist/types-ts4.5/utils/indentation.d.ts +2 -0
- package/dist/types-ts4.5/utils/mark.d.ts +8 -0
- package/dist/types-ts4.5/utils/node.d.ts +2 -0
- package/dist/types-ts4.5/utils/selection.d.ts +14 -0
- package/package.json +8 -5
- package/report.api.md +6 -2
- package/tmp/api-report-tmp.d.ts +4 -1
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
|
|
2
|
+
import { findCutBefore } from '@atlaskit/editor-common/commands';
|
|
3
|
+
import { getCommonListAnalyticsAttributes, moveTargetIntoList } from '@atlaskit/editor-common/lists';
|
|
4
|
+
import { GapCursorSelection } from '@atlaskit/editor-common/selection';
|
|
5
|
+
import { filterCommand as filter, hasVisibleContent, isEmptySelectionAtStart } from '@atlaskit/editor-common/utils';
|
|
6
|
+
import { chainCommands } from '@atlaskit/editor-prosemirror/commands';
|
|
7
|
+
import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
|
|
8
|
+
import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
|
|
9
|
+
import { findPositionOfNodeBefore, hasParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
|
|
10
|
+
import { convertListType } from '../actions/conversions';
|
|
11
|
+
import { wrapInListAndJoin } from '../actions/wrap-and-join-lists';
|
|
12
|
+
import { liftFollowingList, liftNodeSelectionList, liftTextSelectionList } from '../transforms';
|
|
13
|
+
import { sanitiseMarksInSelection } from '../utils/mark';
|
|
14
|
+
import { canJoinToPreviousListItem, isInsideListItem, selectionContainsList } from '../utils/selection';
|
|
15
|
+
import { indentList } from './indent-list';
|
|
16
|
+
import { isFirstChildOfParent } from './isFirstChildOfParent';
|
|
17
|
+
import { joinListItemForward } from './join-list-item-forward';
|
|
18
|
+
import { listBackspace } from './listBackspace';
|
|
19
|
+
import { outdentList } from './outdent-list';
|
|
20
|
+
export { outdentList, indentList };
|
|
21
|
+
export const enterKeyCommand = editorAnalyticsAPI => featureFlags => (state, dispatch) => {
|
|
22
|
+
const {
|
|
23
|
+
selection
|
|
24
|
+
} = state;
|
|
25
|
+
if (selection.empty) {
|
|
26
|
+
const {
|
|
27
|
+
$from
|
|
28
|
+
} = selection;
|
|
29
|
+
const {
|
|
30
|
+
listItem,
|
|
31
|
+
codeBlock
|
|
32
|
+
} = state.schema.nodes;
|
|
33
|
+
const wrapper = $from.node($from.depth - 1);
|
|
34
|
+
if (wrapper && wrapper.type === listItem) {
|
|
35
|
+
/** Check if the wrapper has any visible content */
|
|
36
|
+
const wrapperHasContent = hasVisibleContent(wrapper);
|
|
37
|
+
if (!wrapperHasContent) {
|
|
38
|
+
return outdentList(editorAnalyticsAPI)(INPUT_METHOD.KEYBOARD, featureFlags)(state, dispatch);
|
|
39
|
+
} else if (!hasParentNodeOfType(codeBlock)(selection)) {
|
|
40
|
+
return splitListItem(listItem)(state, dispatch);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
};
|
|
46
|
+
export const backspaceKeyCommand = editorAnalyticsAPI => featureFlags => (state, dispatch) => {
|
|
47
|
+
return chainCommands(listBackspace(editorAnalyticsAPI),
|
|
48
|
+
// if we're at the start of a list item, we need to either backspace
|
|
49
|
+
// directly to an empty list item above, or outdent this node
|
|
50
|
+
filter([isEmptySelectionAtStart,
|
|
51
|
+
// list items might have multiple paragraphs; only do this at the first one
|
|
52
|
+
isFirstChildOfParent, isInsideListItem], chainCommands(deletePreviousEmptyListItem, outdentList(editorAnalyticsAPI)(INPUT_METHOD.KEYBOARD, featureFlags))),
|
|
53
|
+
// if we're just inside a paragraph node (or gapcursor is shown) and backspace, then try to join
|
|
54
|
+
// the text to the previous list item, if one exists
|
|
55
|
+
filter([isEmptySelectionAtStart, canJoinToPreviousListItem], joinToPreviousListItem))(state, dispatch);
|
|
56
|
+
};
|
|
57
|
+
export const deleteKeyCommand = editorAnalyticsAPI => joinListItemForward(editorAnalyticsAPI);
|
|
58
|
+
|
|
59
|
+
// Get the depth of the nearest ancestor list
|
|
60
|
+
export const rootListDepth = (pos, nodes) => {
|
|
61
|
+
const {
|
|
62
|
+
bulletList,
|
|
63
|
+
orderedList,
|
|
64
|
+
listItem
|
|
65
|
+
} = nodes;
|
|
66
|
+
let depth;
|
|
67
|
+
for (let i = pos.depth - 1; i > 0; i--) {
|
|
68
|
+
const node = pos.node(i);
|
|
69
|
+
if (node.type === bulletList || node.type === orderedList) {
|
|
70
|
+
depth = i;
|
|
71
|
+
}
|
|
72
|
+
if (node.type !== bulletList && node.type !== orderedList && node.type !== listItem) {
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return depth;
|
|
77
|
+
};
|
|
78
|
+
function untoggleSelectedList(tr) {
|
|
79
|
+
const {
|
|
80
|
+
selection
|
|
81
|
+
} = tr;
|
|
82
|
+
const depth = rootListDepth(selection.$to, tr.doc.type.schema.nodes);
|
|
83
|
+
tr = liftFollowingList(selection.$to.pos, selection.$to.end(depth), depth || 0, tr);
|
|
84
|
+
if (selection instanceof NodeSelection || selection instanceof GapCursorSelection) {
|
|
85
|
+
return liftNodeSelectionList(selection, tr);
|
|
86
|
+
}
|
|
87
|
+
return liftTextSelectionList(selection, tr);
|
|
88
|
+
}
|
|
89
|
+
export const toggleList = editorAnalyticsAPI => (inputMethod, listType) => {
|
|
90
|
+
return function (state, dispatch) {
|
|
91
|
+
let tr = state.tr;
|
|
92
|
+
const listInsideSelection = selectionContainsList(tr);
|
|
93
|
+
const listNodeType = state.schema.nodes[listType];
|
|
94
|
+
const actionSubjectId = listType === 'bulletList' ? ACTION_SUBJECT_ID.FORMAT_LIST_BULLET : ACTION_SUBJECT_ID.FORMAT_LIST_NUMBER;
|
|
95
|
+
if (listInsideSelection) {
|
|
96
|
+
const {
|
|
97
|
+
selection
|
|
98
|
+
} = state;
|
|
99
|
+
|
|
100
|
+
// for gap cursor or node selection - list is expected 1 level up (listItem -> list)
|
|
101
|
+
// for text selection - list is expected 2 levels up (paragraph -> listItem -> list)
|
|
102
|
+
const positionDiff = selection instanceof GapCursorSelection || selection instanceof NodeSelection ? 1 : 2;
|
|
103
|
+
const fromNode = selection.$from.node(selection.$from.depth - positionDiff);
|
|
104
|
+
const toNode = selection.$to.node(selection.$to.depth - positionDiff);
|
|
105
|
+
const transformedFrom = listInsideSelection.type.name === 'bulletList' ? ACTION_SUBJECT_ID.FORMAT_LIST_BULLET : ACTION_SUBJECT_ID.FORMAT_LIST_NUMBER;
|
|
106
|
+
if ((fromNode === null || fromNode === void 0 ? void 0 : fromNode.type.name) === listType && (toNode === null || toNode === void 0 ? void 0 : toNode.type.name) === listType) {
|
|
107
|
+
let tr = state.tr;
|
|
108
|
+
untoggleSelectedList(tr);
|
|
109
|
+
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({
|
|
110
|
+
action: ACTION.CONVERTED,
|
|
111
|
+
actionSubject: ACTION_SUBJECT.LIST,
|
|
112
|
+
actionSubjectId: ACTION_SUBJECT_ID.TEXT,
|
|
113
|
+
eventType: EVENT_TYPE.TRACK,
|
|
114
|
+
attributes: {
|
|
115
|
+
...getCommonListAnalyticsAttributes(state),
|
|
116
|
+
transformedFrom,
|
|
117
|
+
inputMethod
|
|
118
|
+
}
|
|
119
|
+
})(tr);
|
|
120
|
+
if (dispatch) {
|
|
121
|
+
dispatch(tr);
|
|
122
|
+
}
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
convertListType({
|
|
126
|
+
tr,
|
|
127
|
+
nextListNodeType: listNodeType
|
|
128
|
+
});
|
|
129
|
+
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({
|
|
130
|
+
action: ACTION.CONVERTED,
|
|
131
|
+
actionSubject: ACTION_SUBJECT.LIST,
|
|
132
|
+
actionSubjectId,
|
|
133
|
+
eventType: EVENT_TYPE.TRACK,
|
|
134
|
+
attributes: {
|
|
135
|
+
...getCommonListAnalyticsAttributes(state),
|
|
136
|
+
transformedFrom,
|
|
137
|
+
inputMethod
|
|
138
|
+
}
|
|
139
|
+
})(tr);
|
|
140
|
+
} else {
|
|
141
|
+
// Need to have this before wrapInList so the wrapping is done with valid content
|
|
142
|
+
// For example, if trying to convert centre or right aligned paragraphs to lists
|
|
143
|
+
sanitiseMarksInSelection(tr, listNodeType);
|
|
144
|
+
wrapInListAndJoin(listNodeType, tr);
|
|
145
|
+
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({
|
|
146
|
+
action: ACTION.INSERTED,
|
|
147
|
+
actionSubject: ACTION_SUBJECT.LIST,
|
|
148
|
+
actionSubjectId,
|
|
149
|
+
eventType: EVENT_TYPE.TRACK,
|
|
150
|
+
attributes: {
|
|
151
|
+
inputMethod
|
|
152
|
+
}
|
|
153
|
+
})(tr);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// If document wasn't changed, return false from the command to indicate that the
|
|
157
|
+
// editing action failed
|
|
158
|
+
if (!tr.docChanged) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
if (dispatch) {
|
|
162
|
+
dispatch(tr);
|
|
163
|
+
}
|
|
164
|
+
return true;
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
export const toggleBulletList = editorAnalyticsAPI => (view, inputMethod = INPUT_METHOD.TOOLBAR) => {
|
|
168
|
+
return toggleList(editorAnalyticsAPI)(inputMethod, 'bulletList')(view.state, view.dispatch);
|
|
169
|
+
};
|
|
170
|
+
export const toggleOrderedList = editorAnalyticsAPI => (view, inputMethod = INPUT_METHOD.TOOLBAR) => {
|
|
171
|
+
return toggleList(editorAnalyticsAPI)(inputMethod, 'orderedList')(view.state, view.dispatch);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Implementation taken and modified for our needs from PM
|
|
176
|
+
* @param itemType Node
|
|
177
|
+
* Splits the list items, specific implementation take from PM
|
|
178
|
+
*/
|
|
179
|
+
function splitListItem(itemType) {
|
|
180
|
+
return function (state, dispatch) {
|
|
181
|
+
const ref = state.selection;
|
|
182
|
+
const $from = ref.$from;
|
|
183
|
+
const $to = ref.$to;
|
|
184
|
+
const node = ref.node;
|
|
185
|
+
if (node && node.isBlock || $from.depth < 2 || !$from.sameParent($to)) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
const grandParent = $from.node(-1);
|
|
189
|
+
if (grandParent.type !== itemType) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
/** --> The following line changed from the original PM implementation to allow list additions with multiple paragraphs */
|
|
193
|
+
if (
|
|
194
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
195
|
+
grandParent.content.content.length <= 1 && $from.parent.content.size === 0 && !(grandParent.content.size === 0)) {
|
|
196
|
+
// In an empty block. If this is a nested list, the wrapping
|
|
197
|
+
// list item should be split. Otherwise, bail out and let next
|
|
198
|
+
// command handle lifting.
|
|
199
|
+
if ($from.depth === 2 || $from.node(-3).type !== itemType || $from.index(-2) !== $from.node(-2).childCount - 1) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
if (dispatch) {
|
|
203
|
+
let wrap = Fragment.empty;
|
|
204
|
+
const keepItem = $from.index(-1) > 0;
|
|
205
|
+
// Build a fragment containing empty versions of the structure
|
|
206
|
+
// from the outer list item to the parent node of the cursor
|
|
207
|
+
for (let d = $from.depth - (keepItem ? 1 : 2); d >= $from.depth - 3; d--) {
|
|
208
|
+
wrap = Fragment.from($from.node(d).copy(wrap));
|
|
209
|
+
}
|
|
210
|
+
// Add a second list item with an empty default start node
|
|
211
|
+
wrap = wrap.append(Fragment.from(itemType.createAndFill()));
|
|
212
|
+
const tr$1 = state.tr.replace($from.before(keepItem ? undefined : -1), $from.after(-3), new Slice(wrap, keepItem ? 3 : 2, 2));
|
|
213
|
+
tr$1.setSelection(
|
|
214
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
215
|
+
state.selection.constructor.near(tr$1.doc.resolve($from.pos + (keepItem ? 3 : 2))));
|
|
216
|
+
dispatch(tr$1.scrollIntoView());
|
|
217
|
+
}
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
const nextType = $to.pos === $from.end() ? grandParent.contentMatchAt(0).defaultType : null;
|
|
221
|
+
const tr = state.tr.delete($from.pos, $to.pos);
|
|
222
|
+
const types = nextType && [null, {
|
|
223
|
+
type: nextType
|
|
224
|
+
}];
|
|
225
|
+
if (dispatch) {
|
|
226
|
+
dispatch(tr.split($from.pos, 2, types !== null && types !== void 0 ? types : undefined).scrollIntoView());
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const deletePreviousEmptyListItem = (state, dispatch) => {
|
|
232
|
+
const {
|
|
233
|
+
$from
|
|
234
|
+
} = state.selection;
|
|
235
|
+
const {
|
|
236
|
+
listItem
|
|
237
|
+
} = state.schema.nodes;
|
|
238
|
+
const $cut = findCutBefore($from);
|
|
239
|
+
if (!$cut || !$cut.nodeBefore || !($cut.nodeBefore.type === listItem)) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
const previousListItemEmpty = $cut.nodeBefore.childCount === 1 && $cut.nodeBefore.firstChild.nodeSize <= 2;
|
|
243
|
+
if (previousListItemEmpty) {
|
|
244
|
+
const {
|
|
245
|
+
tr
|
|
246
|
+
} = state;
|
|
247
|
+
if (dispatch) {
|
|
248
|
+
dispatch(tr.delete($cut.pos - $cut.nodeBefore.nodeSize, $from.pos).scrollIntoView());
|
|
249
|
+
}
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
return false;
|
|
253
|
+
};
|
|
254
|
+
const joinToPreviousListItem = (state, dispatch) => {
|
|
255
|
+
const {
|
|
256
|
+
$from
|
|
257
|
+
} = state.selection;
|
|
258
|
+
const {
|
|
259
|
+
paragraph,
|
|
260
|
+
listItem,
|
|
261
|
+
codeBlock,
|
|
262
|
+
bulletList,
|
|
263
|
+
orderedList
|
|
264
|
+
} = state.schema.nodes;
|
|
265
|
+
const isGapCursorShown = state.selection instanceof GapCursorSelection;
|
|
266
|
+
const $cutPos = isGapCursorShown ? state.doc.resolve($from.pos + 1) : $from;
|
|
267
|
+
let $cut = findCutBefore($cutPos);
|
|
268
|
+
if (!$cut) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// see if the containing node is a list
|
|
273
|
+
if ($cut.nodeBefore && [bulletList, orderedList].indexOf($cut.nodeBefore.type) > -1) {
|
|
274
|
+
// and the node after this is a paragraph or a codeBlock
|
|
275
|
+
if ($cut.nodeAfter && ($cut.nodeAfter.type === paragraph || $cut.nodeAfter.type === codeBlock)) {
|
|
276
|
+
// find the nearest paragraph that precedes this node
|
|
277
|
+
let $lastNode = $cut.doc.resolve($cut.pos - 1);
|
|
278
|
+
while ($lastNode.parent.type !== paragraph) {
|
|
279
|
+
$lastNode = state.doc.resolve($lastNode.pos - 1);
|
|
280
|
+
}
|
|
281
|
+
let {
|
|
282
|
+
tr
|
|
283
|
+
} = state;
|
|
284
|
+
if (isGapCursorShown) {
|
|
285
|
+
const nodeBeforePos = findPositionOfNodeBefore(tr.selection);
|
|
286
|
+
if (typeof nodeBeforePos !== 'number') {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
// append the codeblock to the list node
|
|
290
|
+
const list = $cut.nodeBefore.copy($cut.nodeBefore.content.append(Fragment.from(listItem.createChecked({}, $cut.nodeAfter))));
|
|
291
|
+
tr.replaceWith(nodeBeforePos, $from.pos + $cut.nodeAfter.nodeSize, list);
|
|
292
|
+
} else {
|
|
293
|
+
const step = moveTargetIntoList({
|
|
294
|
+
insertPosition: $lastNode.pos,
|
|
295
|
+
$target: $cut
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// ED-13966: check if the step will cause an ProseMirror error
|
|
299
|
+
// if there's an error don't apply the step as it will might lead into a data loss.
|
|
300
|
+
// It doesn't play well with media being a leaf node.
|
|
301
|
+
const stepResult = state.tr.maybeStep(step);
|
|
302
|
+
if (stepResult.failed) {
|
|
303
|
+
return false;
|
|
304
|
+
} else {
|
|
305
|
+
tr = state.tr.step(step);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// find out if there's now another list following and join them
|
|
310
|
+
// as in, [list, p, list] => [list with p, list], and we want [joined list]
|
|
311
|
+
let $postCut = tr.doc.resolve(tr.mapping.map($cut.pos + $cut.nodeAfter.nodeSize));
|
|
312
|
+
if ($postCut.nodeBefore && $postCut.nodeAfter && $postCut.nodeBefore.type === $postCut.nodeAfter.type && [bulletList, orderedList].indexOf($postCut.nodeBefore.type) > -1) {
|
|
313
|
+
tr = tr.join($postCut.pos);
|
|
314
|
+
}
|
|
315
|
+
if (dispatch) {
|
|
316
|
+
var _tr$doc$resolve$nodeB;
|
|
317
|
+
if (!((_tr$doc$resolve$nodeB = tr.doc.resolve($lastNode.pos).nodeBefore) !== null && _tr$doc$resolve$nodeB !== void 0 && _tr$doc$resolve$nodeB.isBlock) || tr.doc.resolve($lastNode.pos).nodeBefore === null) {
|
|
318
|
+
tr = tr.setSelection(TextSelection.near(tr.doc.resolve(tr.mapping.map($cut.pos)), -1));
|
|
319
|
+
}
|
|
320
|
+
dispatch(tr.scrollIntoView());
|
|
321
|
+
}
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return false;
|
|
326
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { GapCursorSelection } from '@atlaskit/editor-common/selection';
|
|
2
|
+
export const isFirstChildOfParent = state => {
|
|
3
|
+
const {
|
|
4
|
+
$from
|
|
5
|
+
} = state.selection;
|
|
6
|
+
return $from.depth > 1 ? state.selection instanceof GapCursorSelection && $from.parentOffset === 0 || $from.index($from.depth - 1) === 0 : true;
|
|
7
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, DELETE_DIRECTION, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
|
|
2
|
+
import { isEmptySelectionAtEnd, walkNextNode } from '@atlaskit/editor-common/utils';
|
|
3
|
+
import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
|
|
4
|
+
import { calcJoinListScenario } from '../actions/join-list-items-forward';
|
|
5
|
+
export const joinListItemForward = editorAnalyticsAPI => (state, dispatch) => {
|
|
6
|
+
const {
|
|
7
|
+
tr,
|
|
8
|
+
selection: {
|
|
9
|
+
$head
|
|
10
|
+
}
|
|
11
|
+
} = state;
|
|
12
|
+
const walkNode = walkNextNode($head);
|
|
13
|
+
if (!isEmptySelectionAtEnd(state)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const scenarios = calcJoinListScenario(walkNode, $head);
|
|
17
|
+
if (!scenarios) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const [scenario, action] = scenarios;
|
|
21
|
+
const result = action({
|
|
22
|
+
tr,
|
|
23
|
+
$next: walkNode.$pos,
|
|
24
|
+
$head: $head
|
|
25
|
+
});
|
|
26
|
+
if (!result) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const {
|
|
30
|
+
bulletList,
|
|
31
|
+
orderedList
|
|
32
|
+
} = state.schema.nodes;
|
|
33
|
+
const listParent = findParentNodeOfType([bulletList, orderedList])(tr.selection);
|
|
34
|
+
let actionSubjectId = ACTION_SUBJECT_ID.FORMAT_LIST_BULLET;
|
|
35
|
+
if (listParent && listParent.node.type === orderedList) {
|
|
36
|
+
actionSubjectId = ACTION_SUBJECT_ID.FORMAT_LIST_NUMBER;
|
|
37
|
+
}
|
|
38
|
+
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({
|
|
39
|
+
action: ACTION.LIST_ITEM_JOINED,
|
|
40
|
+
actionSubject: ACTION_SUBJECT.LIST,
|
|
41
|
+
actionSubjectId,
|
|
42
|
+
eventType: EVENT_TYPE.TRACK,
|
|
43
|
+
attributes: {
|
|
44
|
+
inputMethod: INPUT_METHOD.KEYBOARD,
|
|
45
|
+
direction: DELETE_DIRECTION.FORWARD,
|
|
46
|
+
scenario
|
|
47
|
+
}
|
|
48
|
+
})(tr);
|
|
49
|
+
if (dispatch) {
|
|
50
|
+
dispatch(tr);
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
};
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, DELETE_DIRECTION, EVENT_TYPE, INPUT_METHOD, LIST_TEXT_SCENARIOS } from '@atlaskit/editor-common/analytics';
|
|
2
|
+
import { insertContentDeleteRange, isEmptySelectionAtStart, isListNode, isParagraphNode, walkPrevNode } from '@atlaskit/editor-common/utils';
|
|
3
|
+
import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
|
|
4
|
+
import { isPosInsideList, isPosInsideParagraph } from '../utils/selection';
|
|
5
|
+
//Cases below refer to the cases found in this document: https://product-fabric.atlassian.net/wiki/spaces/E/pages/1146954996/List+Backspace+and+Delete+Behaviour
|
|
6
|
+
//Case for two adjacent list items of the same indentation
|
|
7
|
+
const listBackspaceCase2 = (tr, dispatch, $prev, $head) => {
|
|
8
|
+
/* CASE 2
|
|
9
|
+
* Initial Structure:
|
|
10
|
+
*
|
|
11
|
+
* List A {
|
|
12
|
+
* ListItem B {
|
|
13
|
+
* ...Children C
|
|
14
|
+
* Paragraph D { text1 |textInsertPos| } //Cant have children since that would be Case 4
|
|
15
|
+
* |$prev||childrenGInsertPos| }
|
|
16
|
+
* ListItem E {
|
|
17
|
+
* Paragraph F { |$head| text2 }
|
|
18
|
+
* ...Children G
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* Converts to:
|
|
23
|
+
*
|
|
24
|
+
* List A {
|
|
25
|
+
* ListItem B {
|
|
26
|
+
* ...Children C
|
|
27
|
+
* Paragraph C { text1text2 }
|
|
28
|
+
* ...Children G
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
*
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const listItemE = $head.node(-1); //Head is inside listItem E so it must have a first and last child
|
|
35
|
+
if (!listItemE.firstChild) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const beforeListItemE = $head.before(-1);
|
|
39
|
+
const afterListItemE = $head.after(-1);
|
|
40
|
+
const textInsertPos = $prev.pos - 1; //Paragraph D must be directly behind $prev otherwise it would be case 4
|
|
41
|
+
const childrenGInsertPos = $prev.pos;
|
|
42
|
+
const textContent = $head.parent.content;
|
|
43
|
+
const childrenGContent = listItemE.content.cut(listItemE.firstChild.nodeSize);
|
|
44
|
+
insertContentDeleteRange(tr, tr => tr.doc.resolve(textInsertPos), [[textContent, textInsertPos], [childrenGContent, childrenGInsertPos]], [[beforeListItemE, afterListItemE]]);
|
|
45
|
+
if (dispatch) {
|
|
46
|
+
dispatch(tr);
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
//Case for two adjacent list items with the first being of lower indentation
|
|
52
|
+
const listBackspaceCase3 = (tr, dispatch, $prev, $head) => {
|
|
53
|
+
/* CASE 3
|
|
54
|
+
* Initial Structure:
|
|
55
|
+
*
|
|
56
|
+
* List A {
|
|
57
|
+
* ListItem B {
|
|
58
|
+
* ...Children C
|
|
59
|
+
* Paragraph D { text1 |$prev||textInsertPos| } |childrenHInsertPos|
|
|
60
|
+
* List E { |childrenJInsertPos|
|
|
61
|
+
* ListItem F {
|
|
62
|
+
* Paragraph G { |$head| text2 }
|
|
63
|
+
* ...Children H
|
|
64
|
+
* List? I {
|
|
65
|
+
* ...Children J
|
|
66
|
+
* }
|
|
67
|
+
* }
|
|
68
|
+
* ...Children K
|
|
69
|
+
* }
|
|
70
|
+
* }
|
|
71
|
+
* }
|
|
72
|
+
*
|
|
73
|
+
* Converts to:
|
|
74
|
+
*
|
|
75
|
+
* List A {
|
|
76
|
+
* ListItem B {
|
|
77
|
+
* ...Children C
|
|
78
|
+
* Paragraph D { text1text2 }
|
|
79
|
+
* ...Children H
|
|
80
|
+
* List E {
|
|
81
|
+
* ...Children J
|
|
82
|
+
* ...Children K
|
|
83
|
+
* }
|
|
84
|
+
* }
|
|
85
|
+
* }
|
|
86
|
+
*
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
const listE = $head.node(-2);
|
|
90
|
+
const listItemF = $head.node(-1); //Head is inside listItem F so it must have a first and last child
|
|
91
|
+
if (!listItemF.firstChild || !listItemF.lastChild) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
const beforeListE = $head.before(-2);
|
|
95
|
+
const beforeListItemF = $head.before(-1);
|
|
96
|
+
const afterParagraphD = $prev.after();
|
|
97
|
+
const afterListE = $head.after(-2);
|
|
98
|
+
const afterListItemF = $head.after(-1);
|
|
99
|
+
const startListE = $head.start(-2);
|
|
100
|
+
const containsChildrenJ = isListNode(listItemF.lastChild);
|
|
101
|
+
const shouldRemoveListE = listE.childCount === 1 && !containsChildrenJ; //Assures no Children J and K
|
|
102
|
+
const textInsertPos = $prev.pos;
|
|
103
|
+
const childrenHInsertPos = afterParagraphD;
|
|
104
|
+
const childrenJInsertPos = startListE;
|
|
105
|
+
const textContent = $head.parent.content;
|
|
106
|
+
const childrenHContent = containsChildrenJ ? listItemF.content.cut(listItemF.firstChild.nodeSize, listItemF.nodeSize - listItemF.lastChild.nodeSize - 2) : listItemF.content.cut(listItemF.firstChild.nodeSize); //If Children J doesn't exist then Children H will include the last node
|
|
107
|
+
const childrenJContent = listItemF.lastChild.content; //Will be invalid if there are no Children J but it will be unused
|
|
108
|
+
|
|
109
|
+
insertContentDeleteRange(tr, tr => tr.doc.resolve(textInsertPos), containsChildrenJ ? [[textContent, textInsertPos], [childrenHContent, childrenHInsertPos], [childrenJContent, childrenJInsertPos]] : [[textContent, textInsertPos], [childrenHContent, childrenHInsertPos]], [shouldRemoveListE ? [beforeListE, afterListE] : [beforeListItemF, afterListItemF]]);
|
|
110
|
+
if (dispatch) {
|
|
111
|
+
dispatch(tr);
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
//Case for two adjacent list items with the first being of greater indentation
|
|
117
|
+
const listBackspaceCase4 = (tr, dispatch, $prev, $head, $last) => {
|
|
118
|
+
/* CASE 4
|
|
119
|
+
* Initial Structure:
|
|
120
|
+
*
|
|
121
|
+
* List A {
|
|
122
|
+
* ListItem B {
|
|
123
|
+
* Paragraph C { text1 }
|
|
124
|
+
* ...Children D
|
|
125
|
+
* List E {
|
|
126
|
+
* ...
|
|
127
|
+
* List F { //May be multiple levels of lists
|
|
128
|
+
* ...Children G
|
|
129
|
+
* ListItem H { //Last node of the block
|
|
130
|
+
* ...Children I
|
|
131
|
+
* Paragraph J { text2 |$last||textInsertPos| } |childrenMInsertPos| //Cant have children since this ListItem is the last of the block
|
|
132
|
+
* }
|
|
133
|
+
* }
|
|
134
|
+
* ...
|
|
135
|
+
* |childrenOInsertPosition| }
|
|
136
|
+
* |$prev| }
|
|
137
|
+
* ListItem K {
|
|
138
|
+
* Paragraph L { |$head| text3 }
|
|
139
|
+
* ...Children M
|
|
140
|
+
* List? N {
|
|
141
|
+
* ...Children O
|
|
142
|
+
* }
|
|
143
|
+
* }
|
|
144
|
+
* }
|
|
145
|
+
*
|
|
146
|
+
* Converts to:
|
|
147
|
+
*
|
|
148
|
+
* List A {
|
|
149
|
+
* ListItem B {
|
|
150
|
+
* Paragraph C { text1 }
|
|
151
|
+
* ...Children D
|
|
152
|
+
* List E {
|
|
153
|
+
* ...
|
|
154
|
+
* List F {
|
|
155
|
+
* ...Children G
|
|
156
|
+
* ListItem H {
|
|
157
|
+
* ...Children I
|
|
158
|
+
* Paragraph J { text2text3 }
|
|
159
|
+
* ...Children M
|
|
160
|
+
* }
|
|
161
|
+
* }
|
|
162
|
+
* ...
|
|
163
|
+
* ...Children O
|
|
164
|
+
* }
|
|
165
|
+
* }
|
|
166
|
+
* }
|
|
167
|
+
*
|
|
168
|
+
*/
|
|
169
|
+
|
|
170
|
+
if (!$last) {
|
|
171
|
+
//Exit if an invalid last was given as a parameter
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
const listItemK = $head.node(-1); //Head is inside listItem K so it must have a first and last child
|
|
175
|
+
if (!listItemK.firstChild || !listItemK.lastChild) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
const paragraphL = $head.parent;
|
|
179
|
+
const beforeListItemK = $head.before(-1);
|
|
180
|
+
const afterParagraphJ = $last.after();
|
|
181
|
+
const afterListItemK = $head.after(-1);
|
|
182
|
+
const containsChildrenO = isListNode(listItemK.lastChild);
|
|
183
|
+
const textInsertPos = $last.pos;
|
|
184
|
+
const childrenMInsertPos = afterParagraphJ;
|
|
185
|
+
const childrenOInsertPos = $prev.pos - 1; //Last item of listItem B must be a list therefore we can simply decrement $prev to get there
|
|
186
|
+
|
|
187
|
+
const textContent = paragraphL.content;
|
|
188
|
+
const childrenMContent = containsChildrenO ? listItemK.content.cut(listItemK.firstChild.nodeSize, listItemK.nodeSize - listItemK.lastChild.nodeSize - 2) : listItemK.content.cut(listItemK.firstChild.nodeSize);
|
|
189
|
+
const childrenOContent = listItemK.lastChild.content;
|
|
190
|
+
insertContentDeleteRange(tr, tr => tr.doc.resolve(textInsertPos), containsChildrenO ? [[textContent, textInsertPos], [childrenMContent, childrenMInsertPos], [childrenOContent, childrenOInsertPos]] : [[textContent, textInsertPos], [childrenMContent, childrenMInsertPos]], [[beforeListItemK, afterListItemK]]);
|
|
191
|
+
if (dispatch) {
|
|
192
|
+
dispatch(tr);
|
|
193
|
+
}
|
|
194
|
+
return true;
|
|
195
|
+
};
|
|
196
|
+
const BACKSPACE_COMMANDS = {
|
|
197
|
+
[LIST_TEXT_SCENARIOS.JOIN_SIBLINGS]: listBackspaceCase2,
|
|
198
|
+
[LIST_TEXT_SCENARIOS.JOIN_DESCENDANT_TO_PARENT]: listBackspaceCase3,
|
|
199
|
+
[LIST_TEXT_SCENARIOS.JOIN_TO_SIBLING_DESCENDANT]: listBackspaceCase4
|
|
200
|
+
};
|
|
201
|
+
export const calcJoinListScenario = (walkNode, $head, tr) => {
|
|
202
|
+
const {
|
|
203
|
+
$pos: $prev,
|
|
204
|
+
foundNode: prevFoundNode
|
|
205
|
+
} = walkNode;
|
|
206
|
+
const prevInList = isPosInsideList($prev);
|
|
207
|
+
const headInParagraph = isPosInsideParagraph($head);
|
|
208
|
+
const headInFirstChild = $head.index(-1) === 0;
|
|
209
|
+
const headInList = isPosInsideList($head);
|
|
210
|
+
|
|
211
|
+
//Must be at the start of the selection of the first child in the listItem
|
|
212
|
+
|
|
213
|
+
if (!prevFoundNode || !prevInList || !headInParagraph || !headInFirstChild || !headInList) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
const prevInParagraph = isPosInsideParagraph($prev);
|
|
217
|
+
if (prevInParagraph) {
|
|
218
|
+
return [LIST_TEXT_SCENARIOS.JOIN_DESCENDANT_TO_PARENT, null];
|
|
219
|
+
}
|
|
220
|
+
const prevParentLastChildIsList = $prev.parent.lastChild && isListNode($prev.parent.lastChild);
|
|
221
|
+
const prevParentLastChildIsParagraph = isParagraphNode($prev.parent.lastChild);
|
|
222
|
+
|
|
223
|
+
// Will search for the possible last node for case 4 (where the list could be indented multiple times)
|
|
224
|
+
// $last is required to determine whether we are in case 2 or 4
|
|
225
|
+
let $last = tr.doc.resolve($prev.pos);
|
|
226
|
+
let lastFoundNode;
|
|
227
|
+
do {
|
|
228
|
+
let walkNode = walkPrevNode($last);
|
|
229
|
+
$last = walkNode.$pos;
|
|
230
|
+
lastFoundNode = walkNode.foundNode;
|
|
231
|
+
} while (lastFoundNode && !$last.parent.isTextblock);
|
|
232
|
+
const lastInParagraph = isPosInsideParagraph($last);
|
|
233
|
+
if (lastFoundNode && prevParentLastChildIsList && lastInParagraph) {
|
|
234
|
+
return [LIST_TEXT_SCENARIOS.JOIN_TO_SIBLING_DESCENDANT, $last];
|
|
235
|
+
} else if (prevParentLastChildIsParagraph) {
|
|
236
|
+
return [LIST_TEXT_SCENARIOS.JOIN_SIBLINGS, null];
|
|
237
|
+
}
|
|
238
|
+
return false;
|
|
239
|
+
};
|
|
240
|
+
export const listBackspace = editorAnalyticsAPI => (state, dispatch) => {
|
|
241
|
+
const {
|
|
242
|
+
tr,
|
|
243
|
+
selection: {
|
|
244
|
+
$head
|
|
245
|
+
}
|
|
246
|
+
} = state;
|
|
247
|
+
const walkNode = walkPrevNode($head);
|
|
248
|
+
if (!isEmptySelectionAtStart(state)) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
const scenario = calcJoinListScenario(walkNode, $head, tr);
|
|
252
|
+
if (!scenario) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
const {
|
|
256
|
+
bulletList,
|
|
257
|
+
orderedList
|
|
258
|
+
} = state.schema.nodes;
|
|
259
|
+
const listParent = findParentNodeOfType([bulletList, orderedList])(tr.selection);
|
|
260
|
+
let actionSubjectId = ACTION_SUBJECT_ID.FORMAT_LIST_BULLET;
|
|
261
|
+
if (listParent && listParent.node.type === orderedList) {
|
|
262
|
+
actionSubjectId = ACTION_SUBJECT_ID.FORMAT_LIST_NUMBER;
|
|
263
|
+
}
|
|
264
|
+
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({
|
|
265
|
+
action: ACTION.LIST_ITEM_JOINED,
|
|
266
|
+
actionSubject: ACTION_SUBJECT.LIST,
|
|
267
|
+
actionSubjectId,
|
|
268
|
+
eventType: EVENT_TYPE.TRACK,
|
|
269
|
+
attributes: {
|
|
270
|
+
inputMethod: INPUT_METHOD.KEYBOARD,
|
|
271
|
+
direction: DELETE_DIRECTION.BACKWARD,
|
|
272
|
+
scenario: scenario[0]
|
|
273
|
+
}
|
|
274
|
+
})(tr);
|
|
275
|
+
return BACKSPACE_COMMANDS[scenario[0]](tr, dispatch, walkNode.$pos, $head, scenario[1]);
|
|
276
|
+
};
|