@atlaskit/editor-plugin-selection-extension 10.0.0 → 10.0.2
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/CHANGELOG.md +16 -0
- package/dist/cjs/pm-plugins/utils/selection-helpers.js +119 -10
- package/dist/cjs/ui/toolbar/SelectionExtensionDropdownMenuButton.js +1 -1
- package/dist/es2019/pm-plugins/utils/selection-helpers.js +112 -9
- package/dist/es2019/ui/toolbar/SelectionExtensionDropdownMenuButton.js +1 -1
- package/dist/esm/pm-plugins/utils/selection-helpers.js +117 -9
- package/dist/esm/ui/toolbar/SelectionExtensionDropdownMenuButton.js +1 -1
- package/dist/types/pm-plugins/utils/selection-helpers.d.ts +12 -0
- package/dist/types-ts4.5/pm-plugins/utils/selection-helpers.d.ts +12 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-selection-extension
|
|
2
2
|
|
|
3
|
+
## 10.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`5d14fe3afd499`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/5d14fe3afd499) -
|
|
8
|
+
Implement `SelectionRanges` for multiple nodes selection to indicate actual selection ranges
|
|
9
|
+
- Updated dependencies
|
|
10
|
+
|
|
11
|
+
## 10.0.1
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- [`b429c01ce6af9`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/b429c01ce6af9) -
|
|
16
|
+
icon migration entry point update
|
|
17
|
+
- Updated dependencies
|
|
18
|
+
|
|
3
19
|
## 10.0.0
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
|
@@ -1,17 +1,118 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
3
4
|
Object.defineProperty(exports, "__esModule", {
|
|
4
5
|
value: true
|
|
5
6
|
});
|
|
6
|
-
exports.wrapNodesInDoc = exports.getSelectionInfoFromSameNode = exports.getSelectionInfo = exports.getCommonParentContainer = void 0;
|
|
7
|
+
exports.wrapNodesInDoc = exports.getSelectionInfoFromSameNode = exports.getSelectionInfo = exports.getCommonParentContainer = exports.buildSelectionRanges = void 0;
|
|
8
|
+
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
7
9
|
var _monitoring = require("@atlaskit/editor-common/monitoring");
|
|
8
10
|
var _model = require("@atlaskit/editor-prosemirror/model");
|
|
9
11
|
var LIST_ITEM_TYPES = new Set(['taskItem', 'decisionItem', 'listItem']);
|
|
10
12
|
var LIST_NODE_TYPES = new Set(['taskList', 'bulletList', 'orderedList', 'decisionList']);
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
|
-
*
|
|
15
|
+
* Build a JSON pointer path for a node within the selectedNode structure.
|
|
16
|
+
* @param pathIndices - Array of content indices representing the path to the node
|
|
14
17
|
*/
|
|
18
|
+
var buildJsonPointer = function buildJsonPointer(pathIndices) {
|
|
19
|
+
return pathIndices.map(function (index) {
|
|
20
|
+
return "/content/".concat(index);
|
|
21
|
+
}).join('');
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Build selection ranges for multi-node selections.
|
|
26
|
+
* This function traverses the selectedNode and creates JSON pointer-based ranges
|
|
27
|
+
* that describe what parts of the selectedNode are included in the selection.
|
|
28
|
+
*/
|
|
29
|
+
var buildSelectionRanges = exports.buildSelectionRanges = function buildSelectionRanges(selectedNode, selectedNodePos, $from, $to) {
|
|
30
|
+
var selectionStart = $from.pos;
|
|
31
|
+
var selectionEnd = $to.pos;
|
|
32
|
+
var tokenOffset = selectedNode.type.name === 'doc' ? 0 : 1;
|
|
33
|
+
var nodeStart = selectedNodePos + tokenOffset;
|
|
34
|
+
var nodeEnd = selectedNodePos + selectedNode.nodeSize - tokenOffset;
|
|
35
|
+
|
|
36
|
+
// If selection spans entire content, return undefined (complete block selection)
|
|
37
|
+
if (selectionStart <= nodeStart && selectionEnd >= nodeEnd) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
var selectionRanges = [];
|
|
41
|
+
|
|
42
|
+
// Traverse the selectedNode and find all nodes/text within the selection range
|
|
43
|
+
var _traverse = function traverse(node, nodePos, path) {
|
|
44
|
+
var nodeEndPos = nodePos + node.nodeSize;
|
|
45
|
+
|
|
46
|
+
// Skip nodes completely before or after selection
|
|
47
|
+
if (nodeEndPos <= selectionStart || nodePos >= selectionEnd) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (node.isText) {
|
|
51
|
+
var _node$text;
|
|
52
|
+
var textStart = nodePos;
|
|
53
|
+
var textEnd = nodePos + (((_node$text = node.text) === null || _node$text === void 0 ? void 0 : _node$text.length) || 0);
|
|
54
|
+
var rangeStart = Math.max(textStart, selectionStart);
|
|
55
|
+
var rangeEnd = Math.min(textEnd, selectionEnd);
|
|
56
|
+
if (rangeStart < rangeEnd) {
|
|
57
|
+
var pointer = "".concat(buildJsonPointer(path), "/text");
|
|
58
|
+
selectionRanges.push({
|
|
59
|
+
start: {
|
|
60
|
+
pointer: pointer,
|
|
61
|
+
position: rangeStart - textStart
|
|
62
|
+
},
|
|
63
|
+
end: {
|
|
64
|
+
pointer: pointer,
|
|
65
|
+
position: rangeEnd - textStart
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
} else if (node.content.size > 0) {
|
|
70
|
+
var contentStart = nodePos + 1;
|
|
71
|
+
var contentEnd = nodeEndPos - 1;
|
|
72
|
+
var isWholeBlockSelected = node.isBlock && !node.isTextblock && !LIST_NODE_TYPES.has(node.type.name) && selectionStart <= contentStart && selectionEnd >= contentEnd;
|
|
73
|
+
if (isWholeBlockSelected) {
|
|
74
|
+
var _pointer = buildJsonPointer(path);
|
|
75
|
+
selectionRanges.push({
|
|
76
|
+
start: {
|
|
77
|
+
pointer: _pointer
|
|
78
|
+
},
|
|
79
|
+
end: {
|
|
80
|
+
pointer: _pointer
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
} else {
|
|
84
|
+
// Traverse children for textblocks, lists, or partial selections
|
|
85
|
+
var _childPos = nodePos + 1;
|
|
86
|
+
for (var i = 0; i < node.content.childCount; i++) {
|
|
87
|
+
_traverse(node.content.child(i), _childPos, [].concat((0, _toConsumableArray2.default)(path), [i]));
|
|
88
|
+
_childPos += node.content.child(i).nodeSize;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} else if (nodePos >= selectionStart && nodeEndPos <= selectionEnd) {
|
|
92
|
+
// Handle leaf nodes (e.g., hardBreak, image)
|
|
93
|
+
var _pointer2 = buildJsonPointer(path);
|
|
94
|
+
selectionRanges.push({
|
|
95
|
+
start: {
|
|
96
|
+
pointer: _pointer2
|
|
97
|
+
},
|
|
98
|
+
end: {
|
|
99
|
+
pointer: _pointer2
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Traverse each child of the selectedNode
|
|
106
|
+
var childPos = nodeStart;
|
|
107
|
+
for (var i = 0; i < selectedNode.content.childCount; i++) {
|
|
108
|
+
var child = selectedNode.content.child(i);
|
|
109
|
+
_traverse(child, childPos, [i]);
|
|
110
|
+
childPos += child.nodeSize;
|
|
111
|
+
}
|
|
112
|
+
return selectionRanges.length > 0 ? selectionRanges : undefined;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** Find the depth of the deepest common ancestor node. */
|
|
15
116
|
var getCommonAncestorDepth = function getCommonAncestorDepth($from, $to) {
|
|
16
117
|
var minDepth = Math.min($from.depth, $to.depth);
|
|
17
118
|
for (var d = 0; d <= minDepth; d++) {
|
|
@@ -113,15 +214,19 @@ var getSelectionInfo = exports.getSelectionInfo = function getSelectionInfo(sele
|
|
|
113
214
|
parentNode = _getCommonParentConta.node,
|
|
114
215
|
parentNodePos = _getCommonParentConta.pos;
|
|
115
216
|
if (parentNode) {
|
|
217
|
+
var _selectionRanges = buildSelectionRanges(parentNode, parentNodePos, $from, $to);
|
|
116
218
|
return {
|
|
117
219
|
selectedNode: parentNode,
|
|
118
|
-
nodePos: parentNodePos
|
|
220
|
+
nodePos: parentNodePos,
|
|
221
|
+
selectionRanges: _selectionRanges
|
|
119
222
|
};
|
|
120
223
|
}
|
|
121
224
|
var nodePos = $from.before();
|
|
225
|
+
var _selectionRanges2 = buildSelectionRanges($from.node(), nodePos, $from, $to);
|
|
122
226
|
return {
|
|
123
227
|
selectedNode: $from.node(),
|
|
124
|
-
nodePos: nodePos
|
|
228
|
+
nodePos: nodePos,
|
|
229
|
+
selectionRanges: _selectionRanges2
|
|
125
230
|
};
|
|
126
231
|
}
|
|
127
232
|
|
|
@@ -134,24 +239,26 @@ var getSelectionInfo = exports.getSelectionInfo = function getSelectionInfo(sele
|
|
|
134
239
|
};
|
|
135
240
|
}
|
|
136
241
|
if (range.parent.type.name !== 'doc') {
|
|
137
|
-
//
|
|
242
|
+
// For lists, find topmost list parent; otherwise use immediate parent
|
|
138
243
|
if (LIST_NODE_TYPES.has(range.parent.type.name) || LIST_ITEM_TYPES.has(range.parent.type.name)) {
|
|
139
244
|
var _getCommonParentConta2 = getCommonParentContainer($from, $to),
|
|
140
245
|
topList = _getCommonParentConta2.node,
|
|
141
246
|
topListPos = _getCommonParentConta2.pos;
|
|
142
247
|
if (topList) {
|
|
248
|
+
var _selectionRanges3 = buildSelectionRanges(topList, topListPos, $from, $to);
|
|
143
249
|
return {
|
|
144
250
|
selectedNode: topList,
|
|
145
|
-
nodePos: topListPos
|
|
251
|
+
nodePos: topListPos,
|
|
252
|
+
selectionRanges: _selectionRanges3
|
|
146
253
|
};
|
|
147
254
|
}
|
|
148
255
|
}
|
|
149
|
-
|
|
150
|
-
// For non-list containers (panel, expand, etc.), return the immediate parent
|
|
151
256
|
var _nodePos = range.depth > 0 ? $from.before(range.depth) : 0;
|
|
257
|
+
var _selectionRanges4 = buildSelectionRanges(range.parent, _nodePos, $from, $to);
|
|
152
258
|
return {
|
|
153
259
|
selectedNode: range.parent,
|
|
154
|
-
nodePos: _nodePos
|
|
260
|
+
nodePos: _nodePos,
|
|
261
|
+
selectionRanges: _selectionRanges4
|
|
155
262
|
};
|
|
156
263
|
}
|
|
157
264
|
|
|
@@ -161,8 +268,10 @@ var getSelectionInfo = exports.getSelectionInfo = function getSelectionInfo(sele
|
|
|
161
268
|
nodes.push(range.parent.child(i));
|
|
162
269
|
}
|
|
163
270
|
var selectedNode = wrapNodesInDoc(schema, nodes);
|
|
271
|
+
var selectionRanges = buildSelectionRanges(selectedNode, range.start, $from, $to);
|
|
164
272
|
return {
|
|
165
273
|
selectedNode: selectedNode,
|
|
166
|
-
nodePos: range.start
|
|
274
|
+
nodePos: range.start,
|
|
275
|
+
selectionRanges: selectionRanges
|
|
167
276
|
};
|
|
168
277
|
};
|
|
@@ -10,7 +10,7 @@ var _reactIntlNext = require("react-intl-next");
|
|
|
10
10
|
var _messages = require("@atlaskit/editor-common/messages");
|
|
11
11
|
var _uiMenu = require("@atlaskit/editor-common/ui-menu");
|
|
12
12
|
var _apps = _interopRequireDefault(require("@atlaskit/icon/core/apps"));
|
|
13
|
-
var _chevronDown = _interopRequireDefault(require("@atlaskit/icon/core/
|
|
13
|
+
var _chevronDown = _interopRequireDefault(require("@atlaskit/icon/core/chevron-down"));
|
|
14
14
|
var SelectionExtensionDropdownMenuButtonComponent = function SelectionExtensionDropdownMenuButtonComponent(_ref) {
|
|
15
15
|
var onClick = _ref.onClick,
|
|
16
16
|
selected = _ref.selected,
|
|
@@ -4,8 +4,103 @@ const LIST_ITEM_TYPES = new Set(['taskItem', 'decisionItem', 'listItem']);
|
|
|
4
4
|
const LIST_NODE_TYPES = new Set(['taskList', 'bulletList', 'orderedList', 'decisionList']);
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Build a JSON pointer path for a node within the selectedNode structure.
|
|
8
|
+
* @param pathIndices - Array of content indices representing the path to the node
|
|
8
9
|
*/
|
|
10
|
+
const buildJsonPointer = pathIndices => pathIndices.map(index => `/content/${index}`).join('');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Build selection ranges for multi-node selections.
|
|
14
|
+
* This function traverses the selectedNode and creates JSON pointer-based ranges
|
|
15
|
+
* that describe what parts of the selectedNode are included in the selection.
|
|
16
|
+
*/
|
|
17
|
+
export const buildSelectionRanges = (selectedNode, selectedNodePos, $from, $to) => {
|
|
18
|
+
const selectionStart = $from.pos;
|
|
19
|
+
const selectionEnd = $to.pos;
|
|
20
|
+
const tokenOffset = selectedNode.type.name === 'doc' ? 0 : 1;
|
|
21
|
+
const nodeStart = selectedNodePos + tokenOffset;
|
|
22
|
+
const nodeEnd = selectedNodePos + selectedNode.nodeSize - tokenOffset;
|
|
23
|
+
|
|
24
|
+
// If selection spans entire content, return undefined (complete block selection)
|
|
25
|
+
if (selectionStart <= nodeStart && selectionEnd >= nodeEnd) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const selectionRanges = [];
|
|
29
|
+
|
|
30
|
+
// Traverse the selectedNode and find all nodes/text within the selection range
|
|
31
|
+
const traverse = (node, nodePos, path) => {
|
|
32
|
+
const nodeEndPos = nodePos + node.nodeSize;
|
|
33
|
+
|
|
34
|
+
// Skip nodes completely before or after selection
|
|
35
|
+
if (nodeEndPos <= selectionStart || nodePos >= selectionEnd) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (node.isText) {
|
|
39
|
+
var _node$text;
|
|
40
|
+
const textStart = nodePos;
|
|
41
|
+
const textEnd = nodePos + (((_node$text = node.text) === null || _node$text === void 0 ? void 0 : _node$text.length) || 0);
|
|
42
|
+
const rangeStart = Math.max(textStart, selectionStart);
|
|
43
|
+
const rangeEnd = Math.min(textEnd, selectionEnd);
|
|
44
|
+
if (rangeStart < rangeEnd) {
|
|
45
|
+
const pointer = `${buildJsonPointer(path)}/text`;
|
|
46
|
+
selectionRanges.push({
|
|
47
|
+
start: {
|
|
48
|
+
pointer,
|
|
49
|
+
position: rangeStart - textStart
|
|
50
|
+
},
|
|
51
|
+
end: {
|
|
52
|
+
pointer,
|
|
53
|
+
position: rangeEnd - textStart
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
} else if (node.content.size > 0) {
|
|
58
|
+
const contentStart = nodePos + 1;
|
|
59
|
+
const contentEnd = nodeEndPos - 1;
|
|
60
|
+
const isWholeBlockSelected = node.isBlock && !node.isTextblock && !LIST_NODE_TYPES.has(node.type.name) && selectionStart <= contentStart && selectionEnd >= contentEnd;
|
|
61
|
+
if (isWholeBlockSelected) {
|
|
62
|
+
const pointer = buildJsonPointer(path);
|
|
63
|
+
selectionRanges.push({
|
|
64
|
+
start: {
|
|
65
|
+
pointer
|
|
66
|
+
},
|
|
67
|
+
end: {
|
|
68
|
+
pointer
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
} else {
|
|
72
|
+
// Traverse children for textblocks, lists, or partial selections
|
|
73
|
+
let childPos = nodePos + 1;
|
|
74
|
+
for (let i = 0; i < node.content.childCount; i++) {
|
|
75
|
+
traverse(node.content.child(i), childPos, [...path, i]);
|
|
76
|
+
childPos += node.content.child(i).nodeSize;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} else if (nodePos >= selectionStart && nodeEndPos <= selectionEnd) {
|
|
80
|
+
// Handle leaf nodes (e.g., hardBreak, image)
|
|
81
|
+
const pointer = buildJsonPointer(path);
|
|
82
|
+
selectionRanges.push({
|
|
83
|
+
start: {
|
|
84
|
+
pointer
|
|
85
|
+
},
|
|
86
|
+
end: {
|
|
87
|
+
pointer
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Traverse each child of the selectedNode
|
|
94
|
+
let childPos = nodeStart;
|
|
95
|
+
for (let i = 0; i < selectedNode.content.childCount; i++) {
|
|
96
|
+
const child = selectedNode.content.child(i);
|
|
97
|
+
traverse(child, childPos, [i]);
|
|
98
|
+
childPos += child.nodeSize;
|
|
99
|
+
}
|
|
100
|
+
return selectionRanges.length > 0 ? selectionRanges : undefined;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/** Find the depth of the deepest common ancestor node. */
|
|
9
104
|
const getCommonAncestorDepth = ($from, $to) => {
|
|
10
105
|
const minDepth = Math.min($from.depth, $to.depth);
|
|
11
106
|
for (let d = 0; d <= minDepth; d++) {
|
|
@@ -112,15 +207,19 @@ export const getSelectionInfo = (selection, schema) => {
|
|
|
112
207
|
pos: parentNodePos
|
|
113
208
|
} = getCommonParentContainer($from, $to);
|
|
114
209
|
if (parentNode) {
|
|
210
|
+
const selectionRanges = buildSelectionRanges(parentNode, parentNodePos, $from, $to);
|
|
115
211
|
return {
|
|
116
212
|
selectedNode: parentNode,
|
|
117
|
-
nodePos: parentNodePos
|
|
213
|
+
nodePos: parentNodePos,
|
|
214
|
+
selectionRanges
|
|
118
215
|
};
|
|
119
216
|
}
|
|
120
217
|
const nodePos = $from.before();
|
|
218
|
+
const selectionRanges = buildSelectionRanges($from.node(), nodePos, $from, $to);
|
|
121
219
|
return {
|
|
122
220
|
selectedNode: $from.node(),
|
|
123
|
-
nodePos
|
|
221
|
+
nodePos,
|
|
222
|
+
selectionRanges
|
|
124
223
|
};
|
|
125
224
|
}
|
|
126
225
|
|
|
@@ -133,25 +232,27 @@ export const getSelectionInfo = (selection, schema) => {
|
|
|
133
232
|
};
|
|
134
233
|
}
|
|
135
234
|
if (range.parent.type.name !== 'doc') {
|
|
136
|
-
//
|
|
235
|
+
// For lists, find topmost list parent; otherwise use immediate parent
|
|
137
236
|
if (LIST_NODE_TYPES.has(range.parent.type.name) || LIST_ITEM_TYPES.has(range.parent.type.name)) {
|
|
138
237
|
const {
|
|
139
238
|
node: topList,
|
|
140
239
|
pos: topListPos
|
|
141
240
|
} = getCommonParentContainer($from, $to);
|
|
142
241
|
if (topList) {
|
|
242
|
+
const selectionRanges = buildSelectionRanges(topList, topListPos, $from, $to);
|
|
143
243
|
return {
|
|
144
244
|
selectedNode: topList,
|
|
145
|
-
nodePos: topListPos
|
|
245
|
+
nodePos: topListPos,
|
|
246
|
+
selectionRanges
|
|
146
247
|
};
|
|
147
248
|
}
|
|
148
249
|
}
|
|
149
|
-
|
|
150
|
-
// For non-list containers (panel, expand, etc.), return the immediate parent
|
|
151
250
|
const nodePos = range.depth > 0 ? $from.before(range.depth) : 0;
|
|
251
|
+
const selectionRanges = buildSelectionRanges(range.parent, nodePos, $from, $to);
|
|
152
252
|
return {
|
|
153
253
|
selectedNode: range.parent,
|
|
154
|
-
nodePos
|
|
254
|
+
nodePos,
|
|
255
|
+
selectionRanges
|
|
155
256
|
};
|
|
156
257
|
}
|
|
157
258
|
|
|
@@ -161,8 +262,10 @@ export const getSelectionInfo = (selection, schema) => {
|
|
|
161
262
|
nodes.push(range.parent.child(i));
|
|
162
263
|
}
|
|
163
264
|
const selectedNode = wrapNodesInDoc(schema, nodes);
|
|
265
|
+
const selectionRanges = buildSelectionRanges(selectedNode, range.start, $from, $to);
|
|
164
266
|
return {
|
|
165
267
|
selectedNode,
|
|
166
|
-
nodePos: range.start
|
|
268
|
+
nodePos: range.start,
|
|
269
|
+
selectionRanges
|
|
167
270
|
};
|
|
168
271
|
};
|
|
@@ -3,7 +3,7 @@ import { injectIntl } from 'react-intl-next';
|
|
|
3
3
|
import { selectionExtensionMessages } from '@atlaskit/editor-common/messages';
|
|
4
4
|
import { ToolbarButton } from '@atlaskit/editor-common/ui-menu';
|
|
5
5
|
import AppsIcon from '@atlaskit/icon/core/apps';
|
|
6
|
-
import ChevronDownIcon from '@atlaskit/icon/core/
|
|
6
|
+
import ChevronDownIcon from '@atlaskit/icon/core/chevron-down';
|
|
7
7
|
const SelectionExtensionDropdownMenuButtonComponent = ({
|
|
8
8
|
onClick,
|
|
9
9
|
selected,
|
|
@@ -1,11 +1,111 @@
|
|
|
1
|
+
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
1
2
|
import { logException } from '@atlaskit/editor-common/monitoring';
|
|
2
3
|
import { Fragment } from '@atlaskit/editor-prosemirror/model';
|
|
3
4
|
var LIST_ITEM_TYPES = new Set(['taskItem', 'decisionItem', 'listItem']);
|
|
4
5
|
var LIST_NODE_TYPES = new Set(['taskList', 'bulletList', 'orderedList', 'decisionList']);
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
+
* Build a JSON pointer path for a node within the selectedNode structure.
|
|
9
|
+
* @param pathIndices - Array of content indices representing the path to the node
|
|
8
10
|
*/
|
|
11
|
+
var buildJsonPointer = function buildJsonPointer(pathIndices) {
|
|
12
|
+
return pathIndices.map(function (index) {
|
|
13
|
+
return "/content/".concat(index);
|
|
14
|
+
}).join('');
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Build selection ranges for multi-node selections.
|
|
19
|
+
* This function traverses the selectedNode and creates JSON pointer-based ranges
|
|
20
|
+
* that describe what parts of the selectedNode are included in the selection.
|
|
21
|
+
*/
|
|
22
|
+
export var buildSelectionRanges = function buildSelectionRanges(selectedNode, selectedNodePos, $from, $to) {
|
|
23
|
+
var selectionStart = $from.pos;
|
|
24
|
+
var selectionEnd = $to.pos;
|
|
25
|
+
var tokenOffset = selectedNode.type.name === 'doc' ? 0 : 1;
|
|
26
|
+
var nodeStart = selectedNodePos + tokenOffset;
|
|
27
|
+
var nodeEnd = selectedNodePos + selectedNode.nodeSize - tokenOffset;
|
|
28
|
+
|
|
29
|
+
// If selection spans entire content, return undefined (complete block selection)
|
|
30
|
+
if (selectionStart <= nodeStart && selectionEnd >= nodeEnd) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
var selectionRanges = [];
|
|
34
|
+
|
|
35
|
+
// Traverse the selectedNode and find all nodes/text within the selection range
|
|
36
|
+
var _traverse = function traverse(node, nodePos, path) {
|
|
37
|
+
var nodeEndPos = nodePos + node.nodeSize;
|
|
38
|
+
|
|
39
|
+
// Skip nodes completely before or after selection
|
|
40
|
+
if (nodeEndPos <= selectionStart || nodePos >= selectionEnd) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (node.isText) {
|
|
44
|
+
var _node$text;
|
|
45
|
+
var textStart = nodePos;
|
|
46
|
+
var textEnd = nodePos + (((_node$text = node.text) === null || _node$text === void 0 ? void 0 : _node$text.length) || 0);
|
|
47
|
+
var rangeStart = Math.max(textStart, selectionStart);
|
|
48
|
+
var rangeEnd = Math.min(textEnd, selectionEnd);
|
|
49
|
+
if (rangeStart < rangeEnd) {
|
|
50
|
+
var pointer = "".concat(buildJsonPointer(path), "/text");
|
|
51
|
+
selectionRanges.push({
|
|
52
|
+
start: {
|
|
53
|
+
pointer: pointer,
|
|
54
|
+
position: rangeStart - textStart
|
|
55
|
+
},
|
|
56
|
+
end: {
|
|
57
|
+
pointer: pointer,
|
|
58
|
+
position: rangeEnd - textStart
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
} else if (node.content.size > 0) {
|
|
63
|
+
var contentStart = nodePos + 1;
|
|
64
|
+
var contentEnd = nodeEndPos - 1;
|
|
65
|
+
var isWholeBlockSelected = node.isBlock && !node.isTextblock && !LIST_NODE_TYPES.has(node.type.name) && selectionStart <= contentStart && selectionEnd >= contentEnd;
|
|
66
|
+
if (isWholeBlockSelected) {
|
|
67
|
+
var _pointer = buildJsonPointer(path);
|
|
68
|
+
selectionRanges.push({
|
|
69
|
+
start: {
|
|
70
|
+
pointer: _pointer
|
|
71
|
+
},
|
|
72
|
+
end: {
|
|
73
|
+
pointer: _pointer
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
} else {
|
|
77
|
+
// Traverse children for textblocks, lists, or partial selections
|
|
78
|
+
var _childPos = nodePos + 1;
|
|
79
|
+
for (var i = 0; i < node.content.childCount; i++) {
|
|
80
|
+
_traverse(node.content.child(i), _childPos, [].concat(_toConsumableArray(path), [i]));
|
|
81
|
+
_childPos += node.content.child(i).nodeSize;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} else if (nodePos >= selectionStart && nodeEndPos <= selectionEnd) {
|
|
85
|
+
// Handle leaf nodes (e.g., hardBreak, image)
|
|
86
|
+
var _pointer2 = buildJsonPointer(path);
|
|
87
|
+
selectionRanges.push({
|
|
88
|
+
start: {
|
|
89
|
+
pointer: _pointer2
|
|
90
|
+
},
|
|
91
|
+
end: {
|
|
92
|
+
pointer: _pointer2
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Traverse each child of the selectedNode
|
|
99
|
+
var childPos = nodeStart;
|
|
100
|
+
for (var i = 0; i < selectedNode.content.childCount; i++) {
|
|
101
|
+
var child = selectedNode.content.child(i);
|
|
102
|
+
_traverse(child, childPos, [i]);
|
|
103
|
+
childPos += child.nodeSize;
|
|
104
|
+
}
|
|
105
|
+
return selectionRanges.length > 0 ? selectionRanges : undefined;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** Find the depth of the deepest common ancestor node. */
|
|
9
109
|
var getCommonAncestorDepth = function getCommonAncestorDepth($from, $to) {
|
|
10
110
|
var minDepth = Math.min($from.depth, $to.depth);
|
|
11
111
|
for (var d = 0; d <= minDepth; d++) {
|
|
@@ -107,15 +207,19 @@ export var getSelectionInfo = function getSelectionInfo(selection, schema) {
|
|
|
107
207
|
parentNode = _getCommonParentConta.node,
|
|
108
208
|
parentNodePos = _getCommonParentConta.pos;
|
|
109
209
|
if (parentNode) {
|
|
210
|
+
var _selectionRanges = buildSelectionRanges(parentNode, parentNodePos, $from, $to);
|
|
110
211
|
return {
|
|
111
212
|
selectedNode: parentNode,
|
|
112
|
-
nodePos: parentNodePos
|
|
213
|
+
nodePos: parentNodePos,
|
|
214
|
+
selectionRanges: _selectionRanges
|
|
113
215
|
};
|
|
114
216
|
}
|
|
115
217
|
var nodePos = $from.before();
|
|
218
|
+
var _selectionRanges2 = buildSelectionRanges($from.node(), nodePos, $from, $to);
|
|
116
219
|
return {
|
|
117
220
|
selectedNode: $from.node(),
|
|
118
|
-
nodePos: nodePos
|
|
221
|
+
nodePos: nodePos,
|
|
222
|
+
selectionRanges: _selectionRanges2
|
|
119
223
|
};
|
|
120
224
|
}
|
|
121
225
|
|
|
@@ -128,24 +232,26 @@ export var getSelectionInfo = function getSelectionInfo(selection, schema) {
|
|
|
128
232
|
};
|
|
129
233
|
}
|
|
130
234
|
if (range.parent.type.name !== 'doc') {
|
|
131
|
-
//
|
|
235
|
+
// For lists, find topmost list parent; otherwise use immediate parent
|
|
132
236
|
if (LIST_NODE_TYPES.has(range.parent.type.name) || LIST_ITEM_TYPES.has(range.parent.type.name)) {
|
|
133
237
|
var _getCommonParentConta2 = getCommonParentContainer($from, $to),
|
|
134
238
|
topList = _getCommonParentConta2.node,
|
|
135
239
|
topListPos = _getCommonParentConta2.pos;
|
|
136
240
|
if (topList) {
|
|
241
|
+
var _selectionRanges3 = buildSelectionRanges(topList, topListPos, $from, $to);
|
|
137
242
|
return {
|
|
138
243
|
selectedNode: topList,
|
|
139
|
-
nodePos: topListPos
|
|
244
|
+
nodePos: topListPos,
|
|
245
|
+
selectionRanges: _selectionRanges3
|
|
140
246
|
};
|
|
141
247
|
}
|
|
142
248
|
}
|
|
143
|
-
|
|
144
|
-
// For non-list containers (panel, expand, etc.), return the immediate parent
|
|
145
249
|
var _nodePos = range.depth > 0 ? $from.before(range.depth) : 0;
|
|
250
|
+
var _selectionRanges4 = buildSelectionRanges(range.parent, _nodePos, $from, $to);
|
|
146
251
|
return {
|
|
147
252
|
selectedNode: range.parent,
|
|
148
|
-
nodePos: _nodePos
|
|
253
|
+
nodePos: _nodePos,
|
|
254
|
+
selectionRanges: _selectionRanges4
|
|
149
255
|
};
|
|
150
256
|
}
|
|
151
257
|
|
|
@@ -155,8 +261,10 @@ export var getSelectionInfo = function getSelectionInfo(selection, schema) {
|
|
|
155
261
|
nodes.push(range.parent.child(i));
|
|
156
262
|
}
|
|
157
263
|
var selectedNode = wrapNodesInDoc(schema, nodes);
|
|
264
|
+
var selectionRanges = buildSelectionRanges(selectedNode, range.start, $from, $to);
|
|
158
265
|
return {
|
|
159
266
|
selectedNode: selectedNode,
|
|
160
|
-
nodePos: range.start
|
|
267
|
+
nodePos: range.start,
|
|
268
|
+
selectionRanges: selectionRanges
|
|
161
269
|
};
|
|
162
270
|
};
|
|
@@ -3,7 +3,7 @@ import { injectIntl } from 'react-intl-next';
|
|
|
3
3
|
import { selectionExtensionMessages } from '@atlaskit/editor-common/messages';
|
|
4
4
|
import { ToolbarButton } from '@atlaskit/editor-common/ui-menu';
|
|
5
5
|
import AppsIcon from '@atlaskit/icon/core/apps';
|
|
6
|
-
import ChevronDownIcon from '@atlaskit/icon/core/
|
|
6
|
+
import ChevronDownIcon from '@atlaskit/icon/core/chevron-down';
|
|
7
7
|
var SelectionExtensionDropdownMenuButtonComponent = function SelectionExtensionDropdownMenuButtonComponent(_ref) {
|
|
8
8
|
var onClick = _ref.onClick,
|
|
9
9
|
selected = _ref.selected,
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { type Node as PMNode, type ResolvedPos, type Schema } from '@atlaskit/editor-prosemirror/model';
|
|
2
2
|
import type { TextSelection } from '@atlaskit/editor-prosemirror/state';
|
|
3
|
+
import type { SelectionRange } from '../../types';
|
|
4
|
+
/**
|
|
5
|
+
* Build selection ranges for multi-node selections.
|
|
6
|
+
* This function traverses the selectedNode and creates JSON pointer-based ranges
|
|
7
|
+
* that describe what parts of the selectedNode are included in the selection.
|
|
8
|
+
*/
|
|
9
|
+
export declare const buildSelectionRanges: (selectedNode: PMNode, selectedNodePos: number, $from: ResolvedPos, $to: ResolvedPos) => SelectionRange[] | undefined;
|
|
3
10
|
/**
|
|
4
11
|
* Find the closest parent container node that contains the selection.
|
|
5
12
|
* - For lists: returns the topmost list (to handle nested lists)
|
|
@@ -31,4 +38,9 @@ export declare const getSelectionInfoFromSameNode: (selection: TextSelection) =>
|
|
|
31
38
|
export declare const getSelectionInfo: (selection: TextSelection, schema: Schema) => {
|
|
32
39
|
selectedNode: PMNode;
|
|
33
40
|
nodePos: number;
|
|
41
|
+
selectionRanges: SelectionRange[] | undefined;
|
|
42
|
+
} | {
|
|
43
|
+
selectedNode: PMNode;
|
|
44
|
+
nodePos: number;
|
|
45
|
+
selectionRanges?: undefined;
|
|
34
46
|
};
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { type Node as PMNode, type ResolvedPos, type Schema } from '@atlaskit/editor-prosemirror/model';
|
|
2
2
|
import type { TextSelection } from '@atlaskit/editor-prosemirror/state';
|
|
3
|
+
import type { SelectionRange } from '../../types';
|
|
4
|
+
/**
|
|
5
|
+
* Build selection ranges for multi-node selections.
|
|
6
|
+
* This function traverses the selectedNode and creates JSON pointer-based ranges
|
|
7
|
+
* that describe what parts of the selectedNode are included in the selection.
|
|
8
|
+
*/
|
|
9
|
+
export declare const buildSelectionRanges: (selectedNode: PMNode, selectedNodePos: number, $from: ResolvedPos, $to: ResolvedPos) => SelectionRange[] | undefined;
|
|
3
10
|
/**
|
|
4
11
|
* Find the closest parent container node that contains the selection.
|
|
5
12
|
* - For lists: returns the topmost list (to handle nested lists)
|
|
@@ -31,4 +38,9 @@ export declare const getSelectionInfoFromSameNode: (selection: TextSelection) =>
|
|
|
31
38
|
export declare const getSelectionInfo: (selection: TextSelection, schema: Schema) => {
|
|
32
39
|
selectedNode: PMNode;
|
|
33
40
|
nodePos: number;
|
|
41
|
+
selectionRanges: SelectionRange[] | undefined;
|
|
42
|
+
} | {
|
|
43
|
+
selectedNode: PMNode;
|
|
44
|
+
nodePos: number;
|
|
45
|
+
selectionRanges?: undefined;
|
|
34
46
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-selection-extension",
|
|
3
|
-
"version": "10.0.
|
|
3
|
+
"version": "10.0.2",
|
|
4
4
|
"description": "editor-plugin-selection-extension plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -52,14 +52,14 @@
|
|
|
52
52
|
"@atlaskit/platform-feature-flags": "^1.1.0",
|
|
53
53
|
"@atlaskit/platform-feature-flags-react": "^0.4.0",
|
|
54
54
|
"@atlaskit/primitives": "^17.0.0",
|
|
55
|
-
"@atlaskit/tmp-editor-statsig": "^16.
|
|
55
|
+
"@atlaskit/tmp-editor-statsig": "^16.5.0",
|
|
56
56
|
"@babel/runtime": "^7.0.0",
|
|
57
57
|
"lodash": "^4.17.21",
|
|
58
58
|
"react-intl-next": "npm:react-intl@^5.18.1",
|
|
59
59
|
"uuid": "^3.1.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
|
-
"@atlaskit/editor-common": "^111.
|
|
62
|
+
"@atlaskit/editor-common": "^111.2.0",
|
|
63
63
|
"react": "^18.2.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|