@atlaskit/editor-plugin-paste-options-toolbar 9.1.5 → 9.1.7
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 +19 -0
- package/dist/cjs/pm-plugins/util/format-handlers.js +1 -1
- package/dist/cjs/ui/on-paste-actions-menu/PasteActionsMenu.js +133 -13
- package/dist/cjs/ui/on-paste-actions-menu/PasteActionsMenuContent.js +16 -13
- package/dist/cjs/ui/on-paste-actions-menu/PasteMenuComponents.js +45 -15
- package/dist/cjs/ui/on-paste-actions-menu/PasteOptionsDropdownButton.js +52 -0
- package/dist/es2019/pm-plugins/util/format-handlers.js +1 -1
- package/dist/es2019/ui/on-paste-actions-menu/PasteActionsMenu.js +131 -14
- package/dist/es2019/ui/on-paste-actions-menu/PasteActionsMenuContent.js +17 -14
- package/dist/es2019/ui/on-paste-actions-menu/PasteMenuComponents.js +45 -13
- package/dist/es2019/ui/on-paste-actions-menu/PasteOptionsDropdownButton.js +47 -0
- package/dist/esm/pm-plugins/util/format-handlers.js +1 -1
- package/dist/esm/ui/on-paste-actions-menu/PasteActionsMenu.js +132 -14
- package/dist/esm/ui/on-paste-actions-menu/PasteActionsMenuContent.js +17 -14
- package/dist/esm/ui/on-paste-actions-menu/PasteMenuComponents.js +45 -15
- package/dist/esm/ui/on-paste-actions-menu/PasteOptionsDropdownButton.js +46 -0
- package/dist/types/ui/on-paste-actions-menu/PasteActionsMenu.d.ts +38 -4
- package/dist/types/ui/on-paste-actions-menu/PasteActionsMenuContent.d.ts +2 -1
- package/dist/types/ui/on-paste-actions-menu/PasteOptionsDropdownButton.d.ts +19 -0
- package/dist/types-ts4.5/ui/on-paste-actions-menu/PasteActionsMenu.d.ts +38 -4
- package/dist/types-ts4.5/ui/on-paste-actions-menu/PasteActionsMenuContent.d.ts +2 -1
- package/dist/types-ts4.5/ui/on-paste-actions-menu/PasteOptionsDropdownButton.d.ts +19 -0
- package/package.json +6 -5
|
@@ -4,7 +4,7 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
4
4
|
import React, { useCallback, useEffect, useRef } from 'react';
|
|
5
5
|
import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
|
|
6
6
|
import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
|
|
7
|
-
import { EditorToolbarProvider, PASTE_MENU } from '@atlaskit/editor-common/toolbar';
|
|
7
|
+
import { AI_PASTE_MENU_SECTION, EditorToolbarProvider, PASTE_MENU } from '@atlaskit/editor-common/toolbar';
|
|
8
8
|
import { findOverflowScrollParent, Popup } from '@atlaskit/editor-common/ui';
|
|
9
9
|
import { withReactEditorViewOuterListeners } from '@atlaskit/editor-common/ui-react';
|
|
10
10
|
import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils';
|
|
@@ -69,10 +69,76 @@ export function getVisualEndBottom(editorView, pasteEndPos, tableAfterPos) {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
72
|
+
* Finds the DOM element for the nearest block-level ProseMirror ancestor of
|
|
73
|
+
* the given document position. Uses ProseMirror's schema (`node.isBlock`)
|
|
74
|
+
* rather than CSS display properties, so the check is always in sync with the
|
|
75
|
+
* document model.
|
|
76
|
+
*
|
|
77
|
+
* Returns `null` if no block ancestor can be resolved to a DOM element.
|
|
78
|
+
*/
|
|
79
|
+
export function findBlockAncestorDOM(editorView, pos) {
|
|
80
|
+
try {
|
|
81
|
+
var $pos = editorView.state.doc.resolve(pos);
|
|
82
|
+
// Walk up the document tree from the resolved position's innermost
|
|
83
|
+
// node towards the root. $pos.node(depth) gives the ancestor at each
|
|
84
|
+
// depth; $pos.start(depth) gives the position just inside that ancestor,
|
|
85
|
+
// so `$pos.start(depth) - 1` is the position of the ancestor node itself
|
|
86
|
+
// (which is what nodeDOM expects).
|
|
87
|
+
for (var depth = $pos.depth; depth >= 0; depth--) {
|
|
88
|
+
var node = $pos.node(depth);
|
|
89
|
+
if (node.isBlock) {
|
|
90
|
+
var domNode = editorView.nodeDOM($pos.start(depth) - 1);
|
|
91
|
+
if (domNode instanceof HTMLElement) {
|
|
92
|
+
return domNode;
|
|
93
|
+
}
|
|
94
|
+
// depth 0 is the doc node — nodeDOM(–1) won't work, so try
|
|
95
|
+
// the editor's own DOM element as a fallback.
|
|
96
|
+
if (depth === 0 && editorView.dom instanceof HTMLElement) {
|
|
97
|
+
return editorView.dom;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch (_unused2) {
|
|
102
|
+
// Position may be out of range after a concurrent edit — fall through.
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Positions the paste menu inline, immediately to the right of the cursor
|
|
109
|
+
* at the paste end position, vertically centered with the line.
|
|
110
|
+
* Used for short pastes without AI actions.
|
|
111
|
+
*/
|
|
112
|
+
export function onInlinePositionCalculated(editorView, pasteEndPos, targetElement, popupContentRef) {
|
|
113
|
+
return function (position) {
|
|
114
|
+
var _popupContentRef$curr, _popupContentRef$curr2, _position$top, _position$left;
|
|
115
|
+
var endCoords = editorView.coordsAtPos(pasteEndPos);
|
|
116
|
+
var targetRect = targetElement.getBoundingClientRect();
|
|
117
|
+
|
|
118
|
+
// Vertical: center the menu with the line at the paste end position.
|
|
119
|
+
var lineHeight = endCoords.bottom - endCoords.top;
|
|
120
|
+
var lineMidpoint = endCoords.top + lineHeight / 2;
|
|
121
|
+
var menuHeight = (_popupContentRef$curr = (_popupContentRef$curr2 = popupContentRef.current) === null || _popupContentRef$curr2 === void 0 ? void 0 : _popupContentRef$curr2.getBoundingClientRect().height) !== null && _popupContentRef$curr !== void 0 ? _popupContentRef$curr : lineHeight;
|
|
122
|
+
var menuTop = lineMidpoint - menuHeight / 2;
|
|
123
|
+
var topDelta = menuTop - (targetRect.top + targetRect.height);
|
|
124
|
+
var adjustedTop = ((_position$top = position.top) !== null && _position$top !== void 0 ? _position$top : 0) + topDelta;
|
|
125
|
+
|
|
126
|
+
// Horizontal: position to the right of the cursor
|
|
127
|
+
var leftDelta = endCoords.right - targetRect.right;
|
|
128
|
+
var adjustedLeft = ((_position$left = position.left) !== null && _position$left !== void 0 ? _position$left : 0) + leftDelta;
|
|
129
|
+
return _objectSpread(_objectSpread({}, position), {}, {
|
|
130
|
+
top: adjustedTop,
|
|
131
|
+
left: adjustedLeft
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Adjusts the position of the paste menu so that:
|
|
138
|
+
*
|
|
139
|
+
* **Vertical:** The menu aligns with the top of the pasted content using the
|
|
140
|
+
* exact coordinates at the paste start position, and sticks to the top of the
|
|
141
|
+
* scroll container when the pasted content scrolls above the visible area.
|
|
76
142
|
*
|
|
77
143
|
* The Popup uses alignY="bottom", which positions the popup below the target
|
|
78
144
|
* element's bottom edge. This override:
|
|
@@ -83,21 +149,31 @@ export function getVisualEndBottom(editorView, pasteEndPos, tableAfterPos) {
|
|
|
83
149
|
* to the scroll container's top edge (sticky-top).
|
|
84
150
|
* 3. Stops sticking once the entire pasted range (pasteEndPos) has scrolled
|
|
85
151
|
* above the visible area.
|
|
152
|
+
*
|
|
153
|
+
* **Horizontal:** When the target element is an inline element (e.g. a mark
|
|
154
|
+
* wrapper like `<strong>`, or an inline node like an emoji), the Popup's
|
|
155
|
+
* `alignX="end"` would place the menu at the right edge of that narrow
|
|
156
|
+
* element. This override resolves the nearest block-level ProseMirror
|
|
157
|
+
* ancestor (using `node.isBlock` from the document schema) and re-anchors
|
|
158
|
+
* the horizontal position to its right edge, so the menu consistently
|
|
159
|
+
* appears at the right side of the content area.
|
|
86
160
|
*/
|
|
87
161
|
export function onPositionCalculated(editorView, pasteStartPos, pasteEndPos, targetElement, scrollableElement) {
|
|
88
162
|
// Pre-compute once per render to avoid doc.resolve() on every scroll frame.
|
|
89
163
|
var tableAfterPos = resolveTableAfterPos(editorView, pasteEndPos);
|
|
164
|
+
var blockAncestorDOM = findBlockAncestorDOM(editorView, pasteStartPos);
|
|
90
165
|
return function (position) {
|
|
91
|
-
var _position$
|
|
166
|
+
var _position$top2;
|
|
92
167
|
var startCoords = editorView.coordsAtPos(pasteStartPos);
|
|
93
168
|
var endBottom = getVisualEndBottom(editorView, pasteEndPos, tableAfterPos);
|
|
94
169
|
var targetRect = targetElement.getBoundingClientRect();
|
|
95
170
|
|
|
171
|
+
// ── Vertical adjustment ──────────────────────────────────────────
|
|
96
172
|
// The Popup places the menu at the target's bottom edge by default.
|
|
97
173
|
// We shift it up so it aligns with the paste start position.
|
|
98
174
|
// Both coordinates are in viewport space, so the delta is offset-parent agnostic.
|
|
99
175
|
var topDelta = startCoords.top - (targetRect.top + targetRect.height);
|
|
100
|
-
var adjustedTop = ((_position$
|
|
176
|
+
var adjustedTop = ((_position$top2 = position.top) !== null && _position$top2 !== void 0 ? _position$top2 : 0) + topDelta;
|
|
101
177
|
|
|
102
178
|
// Sticky-top: clamp to the scroll container's top edge when the paste
|
|
103
179
|
// start has scrolled above the visible area, but only while some pasted
|
|
@@ -108,8 +184,28 @@ export function onPositionCalculated(editorView, pasteStartPos, pasteEndPos, tar
|
|
|
108
184
|
adjustedTop += scrollContainerTop - startCoords.top + PASTE_MENU_GAP_TOP;
|
|
109
185
|
}
|
|
110
186
|
}
|
|
187
|
+
|
|
188
|
+
// ── Horizontal adjustment ────────────────────────────────────────
|
|
189
|
+
// When pasted content starts with a mark (bold, italic, link …) or
|
|
190
|
+
// an inline node (emoji, smart link, inline image …),
|
|
191
|
+
// findDomRefAtPos returns the narrow inline wrapper element. The
|
|
192
|
+
// Popup's alignX="end" then places the menu at that element's right
|
|
193
|
+
// edge instead of the content area's right edge. We correct this by
|
|
194
|
+
// resolving the nearest block-level ProseMirror ancestor and
|
|
195
|
+
// re-anchoring to its right edge.
|
|
196
|
+
var adjustedLeft = position.left;
|
|
197
|
+
if (blockAncestorDOM && blockAncestorDOM !== targetElement) {
|
|
198
|
+
var _position$left2;
|
|
199
|
+
var blockRect = blockAncestorDOM.getBoundingClientRect();
|
|
200
|
+
// Shift left by the difference between the block's right edge and
|
|
201
|
+
// the inline target's right edge. This mirrors what alignX="end"
|
|
202
|
+
// would have computed if the target were the block element.
|
|
203
|
+
var leftDelta = blockRect.right - targetRect.right;
|
|
204
|
+
adjustedLeft = ((_position$left2 = position.left) !== null && _position$left2 !== void 0 ? _position$left2 : 0) + leftDelta;
|
|
205
|
+
}
|
|
111
206
|
return _objectSpread(_objectSpread({}, position), {}, {
|
|
112
|
-
top: adjustedTop
|
|
207
|
+
top: adjustedTop,
|
|
208
|
+
left: adjustedLeft
|
|
113
209
|
});
|
|
114
210
|
};
|
|
115
211
|
}
|
|
@@ -129,8 +225,8 @@ export var PasteActionsMenu = function PasteActionsMenu(_ref) {
|
|
|
129
225
|
}),
|
|
130
226
|
lastContentPasted = _useSharedPluginState.lastContentPasted;
|
|
131
227
|
var prevShowToolbarRef = useRef(false);
|
|
228
|
+
var popupContentRef = useRef(null);
|
|
132
229
|
useEffect(function () {
|
|
133
|
-
var _lastContentPasted$te, _lastContentPasted$te2;
|
|
134
230
|
if (!lastContentPasted) {
|
|
135
231
|
hideToolbar()(editorView.state, editorView.dispatch);
|
|
136
232
|
return;
|
|
@@ -153,7 +249,7 @@ export var PasteActionsMenu = function PasteActionsMenu(_ref) {
|
|
|
153
249
|
pasteAncestorNodeNames.push($pos.node(depth).type.name);
|
|
154
250
|
}
|
|
155
251
|
}
|
|
156
|
-
var legacyVisible = isToolbarVisible(editorView.state, lastContentPasted)
|
|
252
|
+
var legacyVisible = isToolbarVisible(editorView.state, lastContentPasted);
|
|
157
253
|
showToolbar(lastContentPasted, selectedOption, legacyVisible, pasteAncestorNodeNames)(editorView.state, editorView.dispatch);
|
|
158
254
|
}, [lastContentPasted, editorView]);
|
|
159
255
|
var _useSharedPluginState2 = useSharedPluginStateWithSelector(api, ['pasteOptionsToolbarPlugin'], function (states) {
|
|
@@ -217,16 +313,36 @@ export var PasteActionsMenu = function PasteActionsMenu(_ref) {
|
|
|
217
313
|
var effectiveScrollableElement = overflowScrollParent || scrollableElement;
|
|
218
314
|
var pasteMenuComponents = (_api$uiControlRegistr3 = api === null || api === void 0 || (_api$uiControlRegistr4 = api.uiControlRegistry) === null || _api$uiControlRegistr4 === void 0 ? void 0 : _api$uiControlRegistr4.actions.getComponents(PASTE_MENU.key)) !== null && _api$uiControlRegistr3 !== void 0 ? _api$uiControlRegistr3 : [];
|
|
219
315
|
var anyComponentVisible = hasVisibleButton(pasteMenuComponents);
|
|
316
|
+
|
|
317
|
+
// Two positioning modes:
|
|
318
|
+
// 1. Inline: no AI actions visible — menu appears to the right of the cursor,
|
|
319
|
+
// vertically centered with the text line.
|
|
320
|
+
// 2. Block-anchored: AI actions are visible — menu appears at the right edge
|
|
321
|
+
// of the content block, aligned with paste start.
|
|
322
|
+
var hasVisibleAiActions = getVisibleKeys(
|
|
323
|
+
// eslint-disable-next-line @atlassian/perf-linting/no-expensive-computations-in-render -- pasteMenuComponents changes by reference each render; filter is small (< 10 items)
|
|
324
|
+
pasteMenuComponents.filter(function (c) {
|
|
325
|
+
var _c$parents;
|
|
326
|
+
return c.type === 'menu-item' && ((_c$parents = c.parents) === null || _c$parents === void 0 ? void 0 : _c$parents.some(function (p) {
|
|
327
|
+
return p.key === AI_PASTE_MENU_SECTION.key;
|
|
328
|
+
}));
|
|
329
|
+
}), ['menu-item']).length > 0;
|
|
330
|
+
var useInlinePosition = !hasVisibleAiActions;
|
|
220
331
|
if (!isToolbarShown) {
|
|
221
332
|
return null;
|
|
222
333
|
}
|
|
223
334
|
if (!anyComponentVisible) {
|
|
224
335
|
return null;
|
|
225
336
|
}
|
|
226
|
-
var target = getTargetElement(editorView, pasteStartPos);
|
|
337
|
+
var target = getTargetElement(editorView, useInlinePosition ? pasteEndPos : pasteStartPos);
|
|
227
338
|
if (!target) {
|
|
228
339
|
return null;
|
|
229
340
|
}
|
|
341
|
+
|
|
342
|
+
// Choose positioning strategy based on whether the menu appears inline
|
|
343
|
+
// (right of cursor for short pastes) or anchored to the block ancestor
|
|
344
|
+
// (right side of content area for longer pastes / AI actions).
|
|
345
|
+
var positionCalculator = useInlinePosition ? onInlinePositionCalculated(editorView, pasteEndPos, target, popupContentRef) : onPositionCalculated(editorView, pasteStartPos, pasteEndPos, target, effectiveScrollableElement);
|
|
230
346
|
return /*#__PURE__*/React.createElement(PopupWithListeners, {
|
|
231
347
|
target: target,
|
|
232
348
|
mountTo: mountTo,
|
|
@@ -235,9 +351,10 @@ export var PasteActionsMenu = function PasteActionsMenu(_ref) {
|
|
|
235
351
|
minPopupMargin: PASTE_MENU_GAP_HORIZONTAL,
|
|
236
352
|
zIndex: akEditorFloatingPanelZIndex,
|
|
237
353
|
alignX: "end",
|
|
238
|
-
alignY:
|
|
354
|
+
alignY: useInlinePosition ? 'top' : 'bottom'
|
|
355
|
+
/* eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) */,
|
|
239
356
|
offset: [PASTE_MENU_GAP_HORIZONTAL, 0],
|
|
240
|
-
onPositionCalculated:
|
|
357
|
+
onPositionCalculated: positionCalculator,
|
|
241
358
|
handleClickOutside: handleClickOutside,
|
|
242
359
|
handleEscapeKeydown: handleDismiss
|
|
243
360
|
}, /*#__PURE__*/React.createElement(EditorToolbarProvider, {
|
|
@@ -248,6 +365,7 @@ export var PasteActionsMenu = function PasteActionsMenu(_ref) {
|
|
|
248
365
|
}, /*#__PURE__*/React.createElement(PasteActionsMenuContent, {
|
|
249
366
|
onMouseDown: preventEditorFocusLoss,
|
|
250
367
|
onMouseEnter: handleMouseEnter,
|
|
251
|
-
components: pasteMenuComponents
|
|
368
|
+
components: pasteMenuComponents,
|
|
369
|
+
contentRef: popupContentRef
|
|
252
370
|
}))));
|
|
253
371
|
};
|
|
@@ -1,34 +1,37 @@
|
|
|
1
1
|
/* PasteActionsMenuContent.tsx generated by @compiled/babel-plugin v0.39.1 */
|
|
2
2
|
import "./PasteActionsMenuContent.compiled.css";
|
|
3
3
|
import { ax, ix } from "@compiled/react/runtime";
|
|
4
|
-
import React, { useContext } from 'react';
|
|
5
|
-
import {
|
|
6
|
-
import { pasteOptionsToolbarMessages as messages } from '@atlaskit/editor-common/messages';
|
|
4
|
+
import React, { useCallback, useContext } from 'react';
|
|
5
|
+
import { PASTE_MENU } from '@atlaskit/editor-common/toolbar';
|
|
7
6
|
import { OutsideClickTargetRefContext } from '@atlaskit/editor-common/ui-react';
|
|
8
|
-
import { ToolbarDropdownItemSection } from '@atlaskit/editor-toolbar';
|
|
9
7
|
import { SurfaceRenderer } from '@atlaskit/editor-ui-control-model';
|
|
10
8
|
import { Box } from '@atlaskit/primitives/compiled';
|
|
11
9
|
var styles = {
|
|
12
10
|
container: "_2rko12b0 _bfhk1bhr _16qs130s"
|
|
13
11
|
};
|
|
12
|
+
var pasteMenuSurface = {
|
|
13
|
+
type: PASTE_MENU.type,
|
|
14
|
+
key: PASTE_MENU.key
|
|
15
|
+
};
|
|
14
16
|
export var PasteActionsMenuContent = function PasteActionsMenuContent(_ref) {
|
|
15
17
|
var onMouseDown = _ref.onMouseDown,
|
|
16
18
|
onMouseEnter = _ref.onMouseEnter,
|
|
17
|
-
components = _ref.components
|
|
19
|
+
components = _ref.components,
|
|
20
|
+
contentRef = _ref.contentRef;
|
|
18
21
|
var setOutsideClickTargetRef = useContext(OutsideClickTargetRefContext);
|
|
19
|
-
var
|
|
22
|
+
var mergedRef = useCallback(function (node) {
|
|
23
|
+
setOutsideClickTargetRef === null || setOutsideClickTargetRef === void 0 || setOutsideClickTargetRef(node);
|
|
24
|
+
if (contentRef) {
|
|
25
|
+
contentRef.current = node;
|
|
26
|
+
}
|
|
27
|
+
}, [setOutsideClickTargetRef, contentRef]);
|
|
20
28
|
return /*#__PURE__*/React.createElement(Box, {
|
|
21
|
-
ref:
|
|
29
|
+
ref: mergedRef,
|
|
22
30
|
xcss: styles.container,
|
|
23
31
|
onMouseDown: onMouseDown,
|
|
24
32
|
onMouseEnter: onMouseEnter
|
|
25
|
-
}, /*#__PURE__*/React.createElement(ToolbarDropdownItemSection, {
|
|
26
|
-
title: intl.formatMessage(messages.pasteMenuActionsTitle)
|
|
27
33
|
}, /*#__PURE__*/React.createElement(SurfaceRenderer, {
|
|
28
|
-
surface:
|
|
29
|
-
type: 'menu',
|
|
30
|
-
key: 'paste-menu'
|
|
31
|
-
},
|
|
34
|
+
surface: pasteMenuSurface,
|
|
32
35
|
components: components
|
|
33
|
-
}))
|
|
36
|
+
}));
|
|
34
37
|
};
|
|
@@ -8,12 +8,14 @@ import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks'
|
|
|
8
8
|
import { pasteOptionsToolbarMessages as messages } from '@atlaskit/editor-common/messages';
|
|
9
9
|
import { useEditorToolbar, PASTE_MENU, PASTE_MENU_SECTION, PASTE_NESTED_MENU, PASTE_MENU_NESTED_SECTION, PASTE_RICH_TEXT_MENU_ITEM, PASTE_MARKDOWN_MENU_ITEM, PASTE_PLAIN_TEXT_MENU_ITEM, PASTE_MENU_RANK, PASTE_MENU_SECTION_RANK, PASTE_NESTED_MENU_RANK, PASTE_MENU_NESTED_SECTION_RANK, AI_PASTE_MENU_SECTION } from '@atlaskit/editor-common/toolbar';
|
|
10
10
|
import { ToolbarDropdownItem, ToolbarDropdownItemSection, ToolbarNestedDropdownMenu } from '@atlaskit/editor-toolbar';
|
|
11
|
+
import ChevronDownIcon from '@atlaskit/icon/core/chevron-down';
|
|
11
12
|
import ChevronRightIcon from '@atlaskit/icon/core/chevron-right';
|
|
12
13
|
import ClipboardIcon from '@atlaskit/icon/core/clipboard';
|
|
13
14
|
import { Box } from '@atlaskit/primitives/compiled';
|
|
14
15
|
import { changeToMarkdownWithAnalytics, changeToPlainTextWithAnalytics, changeToRichTextWithAnalytics } from '../../editor-commands/commands';
|
|
15
16
|
import { ToolbarDropdownOption } from '../../types/types';
|
|
16
17
|
import { getVisibleKeys } from './hasVisibleButton';
|
|
18
|
+
import { PasteOptionsDropdownButton } from './PasteOptionsDropdownButton';
|
|
17
19
|
var nestedMenuStyles = {
|
|
18
20
|
narrowSection: "_10gv1lit"
|
|
19
21
|
};
|
|
@@ -91,21 +93,49 @@ var PasteMenuItem = function PasteMenuItem(_ref) {
|
|
|
91
93
|
}, displayLabel);
|
|
92
94
|
};
|
|
93
95
|
var PasteOptionsNestedMenu = function PasteOptionsNestedMenu(_ref2) {
|
|
94
|
-
var children = _ref2.children
|
|
96
|
+
var children = _ref2.children,
|
|
97
|
+
hasVisibleAiActions = _ref2.hasVisibleAiActions;
|
|
95
98
|
var intl = useIntl();
|
|
99
|
+
var label = intl.formatMessage(messages.pasteMenuActionsPasteAs);
|
|
100
|
+
if (!hasVisibleAiActions) {
|
|
101
|
+
return /*#__PURE__*/React.createElement(PasteOptionsDropdownButton, {
|
|
102
|
+
elemBefore: /*#__PURE__*/React.createElement(ClipboardIcon, {
|
|
103
|
+
size: "small",
|
|
104
|
+
label: ""
|
|
105
|
+
}),
|
|
106
|
+
elemAfter: /*#__PURE__*/React.createElement(ChevronDownIcon, {
|
|
107
|
+
size: "small",
|
|
108
|
+
label: ""
|
|
109
|
+
}),
|
|
110
|
+
label: label,
|
|
111
|
+
testId: "paste-options-nested-menu",
|
|
112
|
+
tooltipContent: label
|
|
113
|
+
}, children);
|
|
114
|
+
}
|
|
96
115
|
return /*#__PURE__*/React.createElement(ToolbarNestedDropdownMenu, {
|
|
97
116
|
elemBefore: /*#__PURE__*/React.createElement(ClipboardIcon, {
|
|
98
117
|
size: "small",
|
|
99
|
-
label:
|
|
118
|
+
label: label
|
|
100
119
|
}),
|
|
101
120
|
elemAfter: /*#__PURE__*/React.createElement(ChevronRightIcon, {
|
|
102
121
|
size: "small",
|
|
103
|
-
label:
|
|
122
|
+
label: ""
|
|
104
123
|
}),
|
|
105
124
|
testId: "paste-options-nested-menu",
|
|
106
|
-
text:
|
|
125
|
+
text: label
|
|
107
126
|
}, children);
|
|
108
127
|
};
|
|
128
|
+
var getHasVisibleAiActions = function getHasVisibleAiActions(api) {
|
|
129
|
+
var _api$uiControlRegistr, _api$uiControlRegistr2;
|
|
130
|
+
var allComponents = (_api$uiControlRegistr = api === null || api === void 0 || (_api$uiControlRegistr2 = api.uiControlRegistry) === null || _api$uiControlRegistr2 === void 0 ? void 0 : _api$uiControlRegistr2.actions.getComponents(PASTE_MENU.key)) !== null && _api$uiControlRegistr !== void 0 ? _api$uiControlRegistr : [];
|
|
131
|
+
var aiMenuItems = allComponents.filter(function (c) {
|
|
132
|
+
var _c$parents;
|
|
133
|
+
return c.type === 'menu-item' && ((_c$parents = c.parents) === null || _c$parents === void 0 ? void 0 : _c$parents.some(function (p) {
|
|
134
|
+
return p.key === AI_PASTE_MENU_SECTION.key;
|
|
135
|
+
}));
|
|
136
|
+
});
|
|
137
|
+
return getVisibleKeys(aiMenuItems, ['menu-item']).length > 0;
|
|
138
|
+
};
|
|
109
139
|
export var getPasteMenuComponents = function getPasteMenuComponents(_ref3) {
|
|
110
140
|
var api = _ref3.api;
|
|
111
141
|
return [{
|
|
@@ -125,17 +155,14 @@ export var getPasteMenuComponents = function getPasteMenuComponents(_ref3) {
|
|
|
125
155
|
return !((_pluginState$showLega = pluginState === null || pluginState === void 0 ? void 0 : pluginState.showLegacyOptions) !== null && _pluginState$showLega !== void 0 ? _pluginState$showLega : false);
|
|
126
156
|
},
|
|
127
157
|
component: function component(props) {
|
|
128
|
-
var
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}));
|
|
135
|
-
});
|
|
136
|
-
var hasVisibleAiActions = getVisibleKeys(aiMenuItems, ['menu-item']).length > 0;
|
|
158
|
+
var hasVisibleAiActions = getHasVisibleAiActions(api);
|
|
159
|
+
if (!hasVisibleAiActions) {
|
|
160
|
+
return /*#__PURE__*/React.createElement(Box, {
|
|
161
|
+
padding: "space.050"
|
|
162
|
+
}, props.children);
|
|
163
|
+
}
|
|
137
164
|
return /*#__PURE__*/React.createElement(ToolbarDropdownItemSection, {
|
|
138
|
-
hasSeparator:
|
|
165
|
+
hasSeparator: true
|
|
139
166
|
}, props.children);
|
|
140
167
|
}
|
|
141
168
|
}, {
|
|
@@ -147,7 +174,10 @@ export var getPasteMenuComponents = function getPasteMenuComponents(_ref3) {
|
|
|
147
174
|
rank: PASTE_MENU_SECTION_RANK[PASTE_NESTED_MENU.key]
|
|
148
175
|
}],
|
|
149
176
|
component: function component(props) {
|
|
150
|
-
|
|
177
|
+
var hasVisibleAiActions = getHasVisibleAiActions(api);
|
|
178
|
+
return /*#__PURE__*/React.createElement(PasteOptionsNestedMenu, {
|
|
179
|
+
hasVisibleAiActions: hasVisibleAiActions
|
|
180
|
+
}, props.children);
|
|
151
181
|
}
|
|
152
182
|
}, {
|
|
153
183
|
type: PASTE_MENU_NESTED_SECTION.type,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* PasteOptionsDropdownButton.tsx generated by @compiled/babel-plugin v0.39.1 */
|
|
2
|
+
import { ax, ix } from "@compiled/react/runtime";
|
|
3
|
+
import React, { useCallback } from 'react';
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
|
|
6
|
+
|
|
7
|
+
import DropdownMenu from '@atlaskit/dropdown-menu';
|
|
8
|
+
import { ToolbarButton, ToolbarTooltip } from '@atlaskit/editor-toolbar';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A compact dropdown button for paste options, styled like floating toolbar buttons.
|
|
12
|
+
* Renders as a ToolbarButton with an icon and dropdown caret that opens a
|
|
13
|
+
* dropdown menu below. Used when AI actions are not visible and the paste
|
|
14
|
+
* options menu is the only content.
|
|
15
|
+
*/
|
|
16
|
+
export var PasteOptionsDropdownButton = function PasteOptionsDropdownButton(_ref) {
|
|
17
|
+
var children = _ref.children,
|
|
18
|
+
elemBefore = _ref.elemBefore,
|
|
19
|
+
elemAfter = _ref.elemAfter,
|
|
20
|
+
label = _ref.label,
|
|
21
|
+
testId = _ref.testId,
|
|
22
|
+
tooltipContent = _ref.tooltipContent;
|
|
23
|
+
var trigger = useCallback(function (triggerProps) {
|
|
24
|
+
var button = /*#__PURE__*/React.createElement(ToolbarButton, {
|
|
25
|
+
ref: triggerProps.triggerRef,
|
|
26
|
+
isSelected: triggerProps.isSelected,
|
|
27
|
+
"aria-expanded": triggerProps['aria-expanded'],
|
|
28
|
+
"aria-haspopup": triggerProps['aria-haspopup'],
|
|
29
|
+
onClick: triggerProps.onClick,
|
|
30
|
+
testId: testId,
|
|
31
|
+
iconBefore: elemBefore,
|
|
32
|
+
label: label
|
|
33
|
+
}, elemAfter);
|
|
34
|
+
if (tooltipContent) {
|
|
35
|
+
return /*#__PURE__*/React.createElement(ToolbarTooltip, {
|
|
36
|
+
content: tooltipContent,
|
|
37
|
+
position: "top"
|
|
38
|
+
}, button);
|
|
39
|
+
}
|
|
40
|
+
return button;
|
|
41
|
+
}, [testId, elemBefore, elemAfter, label, tooltipContent]);
|
|
42
|
+
return /*#__PURE__*/React.createElement(DropdownMenu, {
|
|
43
|
+
placement: "bottom-start",
|
|
44
|
+
trigger: trigger
|
|
45
|
+
}, children);
|
|
46
|
+
};
|
|
@@ -25,10 +25,36 @@ export declare function resolveTableAfterPos(editorView: EditorView, pos: number
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function getVisualEndBottom(editorView: EditorView, pasteEndPos: number, tableAfterPos?: number): number;
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* Finds the DOM element for the nearest block-level ProseMirror ancestor of
|
|
29
|
+
* the given document position. Uses ProseMirror's schema (`node.isBlock`)
|
|
30
|
+
* rather than CSS display properties, so the check is always in sync with the
|
|
31
|
+
* document model.
|
|
32
|
+
*
|
|
33
|
+
* Returns `null` if no block ancestor can be resolved to a DOM element.
|
|
34
|
+
*/
|
|
35
|
+
export declare function findBlockAncestorDOM(editorView: EditorView, pos: number): HTMLElement | null;
|
|
36
|
+
/**
|
|
37
|
+
* Positions the paste menu inline, immediately to the right of the cursor
|
|
38
|
+
* at the paste end position, vertically centered with the line.
|
|
39
|
+
* Used for short pastes without AI actions.
|
|
40
|
+
*/
|
|
41
|
+
export declare function onInlinePositionCalculated(editorView: EditorView, pasteEndPos: number, targetElement: HTMLElement, popupContentRef: React.RefObject<HTMLDivElement | null>): (position: {
|
|
42
|
+
bottom?: number;
|
|
43
|
+
left?: number;
|
|
44
|
+
right?: number;
|
|
45
|
+
top?: number;
|
|
46
|
+
}) => {
|
|
47
|
+
bottom?: number;
|
|
48
|
+
left?: number;
|
|
49
|
+
right?: number;
|
|
50
|
+
top: number;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Adjusts the position of the paste menu so that:
|
|
54
|
+
*
|
|
55
|
+
* **Vertical:** The menu aligns with the top of the pasted content using the
|
|
56
|
+
* exact coordinates at the paste start position, and sticks to the top of the
|
|
57
|
+
* scroll container when the pasted content scrolls above the visible area.
|
|
32
58
|
*
|
|
33
59
|
* The Popup uses alignY="bottom", which positions the popup below the target
|
|
34
60
|
* element's bottom edge. This override:
|
|
@@ -39,6 +65,14 @@ export declare function getVisualEndBottom(editorView: EditorView, pasteEndPos:
|
|
|
39
65
|
* to the scroll container's top edge (sticky-top).
|
|
40
66
|
* 3. Stops sticking once the entire pasted range (pasteEndPos) has scrolled
|
|
41
67
|
* above the visible area.
|
|
68
|
+
*
|
|
69
|
+
* **Horizontal:** When the target element is an inline element (e.g. a mark
|
|
70
|
+
* wrapper like `<strong>`, or an inline node like an emoji), the Popup's
|
|
71
|
+
* `alignX="end"` would place the menu at the right edge of that narrow
|
|
72
|
+
* element. This override resolves the nearest block-level ProseMirror
|
|
73
|
+
* ancestor (using `node.isBlock` from the document schema) and re-anchors
|
|
74
|
+
* the horizontal position to its right edge, so the menu consistently
|
|
75
|
+
* appears at the right side of the content area.
|
|
42
76
|
*/
|
|
43
77
|
export declare function onPositionCalculated(editorView: EditorView, pasteStartPos: number, pasteEndPos: number, targetElement: HTMLElement, scrollableElement?: HTMLElement | false): (position: {
|
|
44
78
|
bottom?: number;
|
|
@@ -2,8 +2,9 @@ import React from 'react';
|
|
|
2
2
|
import type { RegisterComponent } from '@atlaskit/editor-ui-control-model';
|
|
3
3
|
interface PasteActionsMenuContentProps {
|
|
4
4
|
components: RegisterComponent[];
|
|
5
|
+
contentRef?: React.RefObject<HTMLDivElement | null>;
|
|
5
6
|
onMouseDown: (e: React.MouseEvent) => void;
|
|
6
7
|
onMouseEnter: () => void;
|
|
7
8
|
}
|
|
8
|
-
export declare const PasteActionsMenuContent: ({ onMouseDown, onMouseEnter, components, }: PasteActionsMenuContentProps) => React.JSX.Element;
|
|
9
|
+
export declare const PasteActionsMenuContent: ({ onMouseDown, onMouseEnter, components, contentRef, }: PasteActionsMenuContentProps) => React.JSX.Element;
|
|
9
10
|
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jsxRuntime classic
|
|
3
|
+
* @jsx jsx
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
/**
|
|
7
|
+
* A compact dropdown button for paste options, styled like floating toolbar buttons.
|
|
8
|
+
* Renders as a ToolbarButton with an icon and dropdown caret that opens a
|
|
9
|
+
* dropdown menu below. Used when AI actions are not visible and the paste
|
|
10
|
+
* options menu is the only content.
|
|
11
|
+
*/
|
|
12
|
+
export declare const PasteOptionsDropdownButton: ({ children, elemBefore, elemAfter, label, testId, tooltipContent, }: {
|
|
13
|
+
children?: React.ReactNode;
|
|
14
|
+
elemAfter: React.ReactNode;
|
|
15
|
+
elemBefore: React.ReactNode;
|
|
16
|
+
label: string;
|
|
17
|
+
testId?: string;
|
|
18
|
+
tooltipContent?: string;
|
|
19
|
+
}) => JSX.Element;
|
|
@@ -25,10 +25,36 @@ export declare function resolveTableAfterPos(editorView: EditorView, pos: number
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function getVisualEndBottom(editorView: EditorView, pasteEndPos: number, tableAfterPos?: number): number;
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* Finds the DOM element for the nearest block-level ProseMirror ancestor of
|
|
29
|
+
* the given document position. Uses ProseMirror's schema (`node.isBlock`)
|
|
30
|
+
* rather than CSS display properties, so the check is always in sync with the
|
|
31
|
+
* document model.
|
|
32
|
+
*
|
|
33
|
+
* Returns `null` if no block ancestor can be resolved to a DOM element.
|
|
34
|
+
*/
|
|
35
|
+
export declare function findBlockAncestorDOM(editorView: EditorView, pos: number): HTMLElement | null;
|
|
36
|
+
/**
|
|
37
|
+
* Positions the paste menu inline, immediately to the right of the cursor
|
|
38
|
+
* at the paste end position, vertically centered with the line.
|
|
39
|
+
* Used for short pastes without AI actions.
|
|
40
|
+
*/
|
|
41
|
+
export declare function onInlinePositionCalculated(editorView: EditorView, pasteEndPos: number, targetElement: HTMLElement, popupContentRef: React.RefObject<HTMLDivElement | null>): (position: {
|
|
42
|
+
bottom?: number;
|
|
43
|
+
left?: number;
|
|
44
|
+
right?: number;
|
|
45
|
+
top?: number;
|
|
46
|
+
}) => {
|
|
47
|
+
bottom?: number;
|
|
48
|
+
left?: number;
|
|
49
|
+
right?: number;
|
|
50
|
+
top: number;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Adjusts the position of the paste menu so that:
|
|
54
|
+
*
|
|
55
|
+
* **Vertical:** The menu aligns with the top of the pasted content using the
|
|
56
|
+
* exact coordinates at the paste start position, and sticks to the top of the
|
|
57
|
+
* scroll container when the pasted content scrolls above the visible area.
|
|
32
58
|
*
|
|
33
59
|
* The Popup uses alignY="bottom", which positions the popup below the target
|
|
34
60
|
* element's bottom edge. This override:
|
|
@@ -39,6 +65,14 @@ export declare function getVisualEndBottom(editorView: EditorView, pasteEndPos:
|
|
|
39
65
|
* to the scroll container's top edge (sticky-top).
|
|
40
66
|
* 3. Stops sticking once the entire pasted range (pasteEndPos) has scrolled
|
|
41
67
|
* above the visible area.
|
|
68
|
+
*
|
|
69
|
+
* **Horizontal:** When the target element is an inline element (e.g. a mark
|
|
70
|
+
* wrapper like `<strong>`, or an inline node like an emoji), the Popup's
|
|
71
|
+
* `alignX="end"` would place the menu at the right edge of that narrow
|
|
72
|
+
* element. This override resolves the nearest block-level ProseMirror
|
|
73
|
+
* ancestor (using `node.isBlock` from the document schema) and re-anchors
|
|
74
|
+
* the horizontal position to its right edge, so the menu consistently
|
|
75
|
+
* appears at the right side of the content area.
|
|
42
76
|
*/
|
|
43
77
|
export declare function onPositionCalculated(editorView: EditorView, pasteStartPos: number, pasteEndPos: number, targetElement: HTMLElement, scrollableElement?: HTMLElement | false): (position: {
|
|
44
78
|
bottom?: number;
|
|
@@ -2,8 +2,9 @@ import React from 'react';
|
|
|
2
2
|
import type { RegisterComponent } from '@atlaskit/editor-ui-control-model';
|
|
3
3
|
interface PasteActionsMenuContentProps {
|
|
4
4
|
components: RegisterComponent[];
|
|
5
|
+
contentRef?: React.RefObject<HTMLDivElement | null>;
|
|
5
6
|
onMouseDown: (e: React.MouseEvent) => void;
|
|
6
7
|
onMouseEnter: () => void;
|
|
7
8
|
}
|
|
8
|
-
export declare const PasteActionsMenuContent: ({ onMouseDown, onMouseEnter, components, }: PasteActionsMenuContentProps) => React.JSX.Element;
|
|
9
|
+
export declare const PasteActionsMenuContent: ({ onMouseDown, onMouseEnter, components, contentRef, }: PasteActionsMenuContentProps) => React.JSX.Element;
|
|
9
10
|
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jsxRuntime classic
|
|
3
|
+
* @jsx jsx
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
/**
|
|
7
|
+
* A compact dropdown button for paste options, styled like floating toolbar buttons.
|
|
8
|
+
* Renders as a ToolbarButton with an icon and dropdown caret that opens a
|
|
9
|
+
* dropdown menu below. Used when AI actions are not visible and the paste
|
|
10
|
+
* options menu is the only content.
|
|
11
|
+
*/
|
|
12
|
+
export declare const PasteOptionsDropdownButton: ({ children, elemBefore, elemAfter, label, testId, tooltipContent, }: {
|
|
13
|
+
children?: React.ReactNode;
|
|
14
|
+
elemAfter: React.ReactNode;
|
|
15
|
+
elemBefore: React.ReactNode;
|
|
16
|
+
label: string;
|
|
17
|
+
testId?: string;
|
|
18
|
+
tooltipContent?: string;
|
|
19
|
+
}) => JSX.Element;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-paste-options-toolbar",
|
|
3
|
-
"version": "9.1.
|
|
3
|
+
"version": "9.1.7",
|
|
4
4
|
"description": "Paste options toolbar for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"atlaskit:src": "src/index.ts",
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@atlaskit/css": "^0.19.0",
|
|
33
|
+
"@atlaskit/dropdown-menu": "^16.8.0",
|
|
33
34
|
"@atlaskit/editor-markdown-transformer": "^5.20.0",
|
|
34
35
|
"@atlaskit/editor-plugin-analytics": "^8.0.0",
|
|
35
36
|
"@atlaskit/editor-plugin-paste": "^9.0.0",
|
|
@@ -38,17 +39,17 @@
|
|
|
38
39
|
"@atlaskit/editor-shared-styles": "^3.10.0",
|
|
39
40
|
"@atlaskit/editor-toolbar": "^0.20.0",
|
|
40
41
|
"@atlaskit/editor-ui-control-model": "^1.1.0",
|
|
41
|
-
"@atlaskit/icon": "^33.
|
|
42
|
+
"@atlaskit/icon": "^33.1.0",
|
|
42
43
|
"@atlaskit/platform-feature-flags": "^1.1.0",
|
|
43
|
-
"@atlaskit/primitives": "^18.
|
|
44
|
-
"@atlaskit/tokens": "^11.
|
|
44
|
+
"@atlaskit/primitives": "^18.1.0",
|
|
45
|
+
"@atlaskit/tokens": "^11.3.0",
|
|
45
46
|
"@babel/runtime": "^7.0.0",
|
|
46
47
|
"@compiled/react": "^0.20.0",
|
|
47
48
|
"@emotion/react": "^11.7.1",
|
|
48
49
|
"react-intl-next": "npm:react-intl@^5.18.1"
|
|
49
50
|
},
|
|
50
51
|
"peerDependencies": {
|
|
51
|
-
"@atlaskit/editor-common": "^112.
|
|
52
|
+
"@atlaskit/editor-common": "^112.11.0",
|
|
52
53
|
"react": "^18.2.0",
|
|
53
54
|
"react-dom": "^18.2.0"
|
|
54
55
|
},
|