@atlaskit/editor-plugin-paste-options-toolbar 9.1.7 → 9.1.9
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 +15 -0
- package/dist/cjs/pasteOptionsToolbarPlugin.js +12 -5
- package/dist/cjs/ui/on-paste-actions-menu/PasteActionsMenu.js +26 -26
- package/dist/cjs/ui/on-paste-actions-menu/exposure.js +66 -0
- package/dist/es2019/pasteOptionsToolbarPlugin.js +12 -5
- package/dist/es2019/ui/on-paste-actions-menu/PasteActionsMenu.js +24 -24
- package/dist/es2019/ui/on-paste-actions-menu/exposure.js +58 -0
- package/dist/esm/pasteOptionsToolbarPlugin.js +12 -5
- package/dist/esm/ui/on-paste-actions-menu/PasteActionsMenu.js +26 -26
- package/dist/esm/ui/on-paste-actions-menu/exposure.js +60 -0
- package/dist/types/ui/on-paste-actions-menu/exposure.d.ts +2 -0
- package/dist/types-ts4.5/ui/on-paste-actions-menu/exposure.d.ts +2 -0
- package/package.json +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-paste-options-toolbar
|
|
2
2
|
|
|
3
|
+
## 9.1.9
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`194f00cfcef60`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/194f00cfcef60) -
|
|
8
|
+
EDITOR-6197 Fire manual experiment exposure for paste actions menu experiment
|
|
9
|
+
|
|
10
|
+
## 9.1.8
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- [`fb9bbd5719238`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/fb9bbd5719238) -
|
|
15
|
+
EDITOR-6193 Correcting `visibleAiActions` attribute of paste actions menu which was not being
|
|
16
|
+
populated.
|
|
17
|
+
|
|
3
18
|
## 9.1.7
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
|
@@ -7,9 +7,12 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
7
7
|
exports.pasteOptionsToolbarPlugin = void 0;
|
|
8
8
|
var _react = _interopRequireWildcard(require("react"));
|
|
9
9
|
var _hooks = require("@atlaskit/editor-common/hooks");
|
|
10
|
+
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
11
|
+
var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
|
|
10
12
|
var _commands = require("./editor-commands/commands");
|
|
11
13
|
var _main = require("./pm-plugins/main");
|
|
12
14
|
var _types = require("./types/types");
|
|
15
|
+
var _exposure = require("./ui/on-paste-actions-menu/exposure");
|
|
13
16
|
var _PasteActionsMenu = require("./ui/on-paste-actions-menu/PasteActionsMenu");
|
|
14
17
|
var _PasteMenuComponents = require("./ui/on-paste-actions-menu/PasteMenuComponents");
|
|
15
18
|
var _toolbar = require("./ui/toolbar");
|
|
@@ -19,7 +22,7 @@ var pasteOptionsToolbarPlugin = exports.pasteOptionsToolbarPlugin = function pas
|
|
|
19
22
|
var config = _ref.config,
|
|
20
23
|
api = _ref.api;
|
|
21
24
|
var editorAnalyticsAPI = api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
|
|
22
|
-
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu) {
|
|
25
|
+
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_paste_actions_menu', 'isEnabled', true)) {
|
|
23
26
|
var _api$uiControlRegistr;
|
|
24
27
|
api === null || api === void 0 || (_api$uiControlRegistr = api.uiControlRegistry) === null || _api$uiControlRegistr === void 0 || _api$uiControlRegistr.actions.register((0, _PasteMenuComponents.getPasteMenuComponents)({
|
|
25
28
|
api: api
|
|
@@ -33,7 +36,7 @@ var pasteOptionsToolbarPlugin = exports.pasteOptionsToolbarPlugin = function pas
|
|
|
33
36
|
plugin: function plugin(_ref2) {
|
|
34
37
|
var dispatch = _ref2.dispatch;
|
|
35
38
|
return (0, _main.createPlugin)(dispatch, {
|
|
36
|
-
useNewPasteMenu: config === null || config === void 0 ? void 0 : config.usePopupBasedPasteActionsMenu
|
|
39
|
+
useNewPasteMenu: (config === null || config === void 0 ? void 0 : config.usePopupBasedPasteActionsMenu) && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_paste_actions_menu', 'isEnabled', true)
|
|
37
40
|
});
|
|
38
41
|
}
|
|
39
42
|
}];
|
|
@@ -66,7 +69,7 @@ var pasteOptionsToolbarPlugin = exports.pasteOptionsToolbarPlugin = function pas
|
|
|
66
69
|
},
|
|
67
70
|
pluginsOptions: {
|
|
68
71
|
floatingToolbar: function floatingToolbar(state, intl) {
|
|
69
|
-
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu) {
|
|
72
|
+
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_paste_actions_menu', 'isEnabled', true)) {
|
|
70
73
|
return;
|
|
71
74
|
}
|
|
72
75
|
var pastePluginState = _types.pasteOptionsPluginKey.getState(state);
|
|
@@ -81,7 +84,7 @@ var pasteOptionsToolbarPlugin = exports.pasteOptionsToolbarPlugin = function pas
|
|
|
81
84
|
popupsMountPoint = _ref3.popupsMountPoint,
|
|
82
85
|
popupsBoundariesElement = _ref3.popupsBoundariesElement,
|
|
83
86
|
popupsScrollableElement = _ref3.popupsScrollableElement;
|
|
84
|
-
if (!(config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu) || !editorView) {
|
|
87
|
+
if (!(config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_paste_actions_menu', 'isEnabled', true)) || !editorView) {
|
|
85
88
|
return null;
|
|
86
89
|
}
|
|
87
90
|
return /*#__PURE__*/_react.default.createElement(_PasteActionsMenu.PasteActionsMenu, {
|
|
@@ -102,7 +105,11 @@ var pasteOptionsToolbarPlugin = exports.pasteOptionsToolbarPlugin = function pas
|
|
|
102
105
|
}),
|
|
103
106
|
lastContentPasted = _useSharedPluginState.lastContentPasted;
|
|
104
107
|
(0, _react.useEffect)(function () {
|
|
105
|
-
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu) {
|
|
108
|
+
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu && (0, _platformFeatureFlags.fg)('platform_editor_paste_actions_menu_exposure')) {
|
|
109
|
+
var _lastContentPasted$te, _lastContentPasted$te2;
|
|
110
|
+
(0, _exposure.firePasteActionsMenuExperimentExposure)((_lastContentPasted$te = lastContentPasted === null || lastContentPasted === void 0 || (_lastContentPasted$te2 = lastContentPasted.text) === null || _lastContentPasted$te2 === void 0 ? void 0 : _lastContentPasted$te2.length) !== null && _lastContentPasted$te !== void 0 ? _lastContentPasted$te : 0, editorView.state, lastContentPasted === null || lastContentPasted === void 0 ? void 0 : lastContentPasted.pasteStartPos, lastContentPasted === null || lastContentPasted === void 0 ? void 0 : lastContentPasted.pasteEndPos, lastContentPasted === null || lastContentPasted === void 0 ? void 0 : lastContentPasted.text);
|
|
111
|
+
}
|
|
112
|
+
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu && (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_paste_actions_menu', 'isEnabled', true)) {
|
|
106
113
|
return;
|
|
107
114
|
}
|
|
108
115
|
if (!lastContentPasted) {
|
|
@@ -225,7 +225,7 @@ function onPositionCalculated(editorView, pasteStartPos, pasteEndPos, targetElem
|
|
|
225
225
|
};
|
|
226
226
|
}
|
|
227
227
|
var PasteActionsMenu = exports.PasteActionsMenu = function PasteActionsMenu(_ref) {
|
|
228
|
-
var _api$analytics, _api$uiControlRegistr, _api$uiControlRegistr2
|
|
228
|
+
var _api$analytics, _api$uiControlRegistr, _api$uiControlRegistr2;
|
|
229
229
|
var api = _ref.api,
|
|
230
230
|
editorView = _ref.editorView,
|
|
231
231
|
mountTo = _ref.mountTo,
|
|
@@ -279,22 +279,6 @@ var PasteActionsMenu = exports.PasteActionsMenu = function PasteActionsMenu(_ref
|
|
|
279
279
|
isToolbarShown = _useSharedPluginState2.showToolbar,
|
|
280
280
|
pasteStartPos = _useSharedPluginState2.pasteStartPos,
|
|
281
281
|
pasteEndPos = _useSharedPluginState2.pasteEndPos;
|
|
282
|
-
var aiSurfaceComponents = (_api$uiControlRegistr = api === null || api === void 0 || (_api$uiControlRegistr2 = api.uiControlRegistry) === null || _api$uiControlRegistr2 === void 0 ? void 0 : _api$uiControlRegistr2.actions.getComponents('ai-paste-menu')) !== null && _api$uiControlRegistr !== void 0 ? _api$uiControlRegistr : [];
|
|
283
|
-
var visibleAiActionKeys = (0, _hasVisibleButton.getVisibleKeys)(aiSurfaceComponents, ['button', 'menu-item']);
|
|
284
|
-
(0, _react.useEffect)(function () {
|
|
285
|
-
if (!prevShowToolbarRef.current && isToolbarShown) {
|
|
286
|
-
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.fireAnalyticsEvent({
|
|
287
|
-
action: _analytics.ACTION.OPENED,
|
|
288
|
-
actionSubject: _analytics.ACTION_SUBJECT.PASTE_ACTIONS_MENU,
|
|
289
|
-
eventType: _analytics.EVENT_TYPE.UI,
|
|
290
|
-
attributes: {
|
|
291
|
-
visibleAiActions: visibleAiActionKeys
|
|
292
|
-
}
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
prevShowToolbarRef.current = isToolbarShown;
|
|
296
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
297
|
-
}, [isToolbarShown, editorAnalyticsAPI]);
|
|
298
282
|
var preventEditorFocusLoss = (0, _react.useCallback)(function (e) {
|
|
299
283
|
e.preventDefault();
|
|
300
284
|
}, []);
|
|
@@ -326,22 +310,38 @@ var PasteActionsMenu = exports.PasteActionsMenu = function PasteActionsMenu(_ref
|
|
|
326
310
|
// onPositionCalculated with fresh viewport coordinates.
|
|
327
311
|
var overflowScrollParent = isToolbarShown ? (0, _ui.findOverflowScrollParent)(editorView.dom) : false;
|
|
328
312
|
var effectiveScrollableElement = overflowScrollParent || scrollableElement;
|
|
329
|
-
var pasteMenuComponents = (_api$
|
|
313
|
+
var pasteMenuComponents = (_api$uiControlRegistr = api === null || api === void 0 || (_api$uiControlRegistr2 = api.uiControlRegistry) === null || _api$uiControlRegistr2 === void 0 ? void 0 : _api$uiControlRegistr2.actions.getComponents(_toolbar.PASTE_MENU.key)) !== null && _api$uiControlRegistr !== void 0 ? _api$uiControlRegistr : [];
|
|
330
314
|
var anyComponentVisible = (0, _hasVisibleButton.hasVisibleButton)(pasteMenuComponents);
|
|
331
315
|
|
|
332
|
-
// Two positioning modes:
|
|
333
|
-
// 1. Inline: no AI actions visible — menu appears to the right of the cursor,
|
|
334
|
-
// vertically centered with the text line.
|
|
335
|
-
// 2. Block-anchored: AI actions are visible — menu appears at the right edge
|
|
336
|
-
// of the content block, aligned with paste start.
|
|
337
|
-
var hasVisibleAiActions = (0, _hasVisibleButton.getVisibleKeys)(
|
|
338
316
|
// eslint-disable-next-line @atlassian/perf-linting/no-expensive-computations-in-render -- pasteMenuComponents changes by reference each render; filter is small (< 10 items)
|
|
339
|
-
pasteMenuComponents.filter(function (c) {
|
|
317
|
+
var aiMenuItems = pasteMenuComponents.filter(function (c) {
|
|
340
318
|
var _c$parents;
|
|
341
319
|
return c.type === 'menu-item' && ((_c$parents = c.parents) === null || _c$parents === void 0 ? void 0 : _c$parents.some(function (p) {
|
|
342
320
|
return p.key === _toolbar.AI_PASTE_MENU_SECTION.key;
|
|
343
321
|
}));
|
|
344
|
-
})
|
|
322
|
+
});
|
|
323
|
+
var visibleAiActionKeys = (0, _hasVisibleButton.getVisibleKeys)(aiMenuItems, ['menu-item']);
|
|
324
|
+
|
|
325
|
+
// Two positioning modes:
|
|
326
|
+
// 1. Inline: no AI actions visible — menu appears to the right of the cursor,
|
|
327
|
+
// vertically centered with the text line.
|
|
328
|
+
// 2. Block-anchored: AI actions are visible — menu appears at the right edge
|
|
329
|
+
// of the content block, aligned with paste start.
|
|
330
|
+
var hasVisibleAiActions = visibleAiActionKeys.length > 0;
|
|
331
|
+
(0, _react.useEffect)(function () {
|
|
332
|
+
if (!prevShowToolbarRef.current && isToolbarShown) {
|
|
333
|
+
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.fireAnalyticsEvent({
|
|
334
|
+
action: _analytics.ACTION.OPENED,
|
|
335
|
+
actionSubject: _analytics.ACTION_SUBJECT.PASTE_ACTIONS_MENU,
|
|
336
|
+
eventType: _analytics.EVENT_TYPE.UI,
|
|
337
|
+
attributes: {
|
|
338
|
+
visibleAiActions: visibleAiActionKeys
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
prevShowToolbarRef.current = isToolbarShown;
|
|
343
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
344
|
+
}, [isToolbarShown, editorAnalyticsAPI]);
|
|
345
345
|
var useInlinePosition = !hasVisibleAiActions;
|
|
346
346
|
if (!isToolbarShown) {
|
|
347
347
|
return null;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.firePasteActionsMenuExperimentExposure = void 0;
|
|
7
|
+
var _expVal = require("@atlaskit/tmp-editor-statsig/expVal");
|
|
8
|
+
var isNotProse = function isNotProse(text) {
|
|
9
|
+
var trimmed = text.trim();
|
|
10
|
+
if (!trimmed) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Check each character: if we find whitespace it's prose-like,
|
|
15
|
+
// if we find a non-ASCII character it's likely CJK/Thai/etc.
|
|
16
|
+
for (var i = 0; i < trimmed.length; i++) {
|
|
17
|
+
var code = trimmed.charCodeAt(i);
|
|
18
|
+
// Whitespace (space, tab, newline, etc.) → prose-like
|
|
19
|
+
if (code === 0x20 || code === 0x09 || code === 0x0a || code === 0x0d) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
// Non-ASCII character → likely a non-Latin script (CJK, Thai, etc.)
|
|
23
|
+
if (code > 0x7f) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// No whitespace and all ASCII → URL, token, path, etc.
|
|
29
|
+
return true;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Manual exposure event for `platform_editor_paste_actions_menu`. Due to the fact that as part of this experiment
|
|
33
|
+
// the paste menu was completely redesigned, it was very difficult to ensure that an exposure event fires accurately
|
|
34
|
+
// for both control and test cohorts without executing code paths for both menus.
|
|
35
|
+
// This manual exposure event executes all criteria for showing AI buttons and fires the exposure manually in a code path that
|
|
36
|
+
// is guaranteed to execute on both control and test.
|
|
37
|
+
var firePasteActionsMenuExperimentExposure = exports.firePasteActionsMenuExperimentExposure = function firePasteActionsMenuExperimentExposure(contentLength, state, pasteStartPos, pasteEndPos, pastedText) {
|
|
38
|
+
if (contentLength < 100 || !pasteStartPos || !pasteEndPos || !pastedText) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (isNotProse(pastedText)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
var $pos = state.doc.resolve(pasteStartPos);
|
|
46
|
+
var pasteAncestorNodeNames = [];
|
|
47
|
+
for (var depth = $pos.depth; depth > 0; depth--) {
|
|
48
|
+
// Only include an ancestor if the entire pasted range is contained within it.
|
|
49
|
+
// This prevents nodes like 'heading' from being flagged as ancestors when the
|
|
50
|
+
// pasted content starts in a heading but extends beyond it (e.g. heading + paragraph).
|
|
51
|
+
if (pasteEndPos <= $pos.end(depth)) {
|
|
52
|
+
pasteAncestorNodeNames.push($pos.node(depth).type.name);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
var isInExcludedNode = pasteAncestorNodeNames.some(function (name) {
|
|
56
|
+
return ['codeBlock', 'heading'].includes(name);
|
|
57
|
+
});
|
|
58
|
+
if (!isInExcludedNode) {
|
|
59
|
+
(0, _expVal.expVal)('platform_editor_paste_actions_menu', 'isEnabled', false);
|
|
60
|
+
}
|
|
61
|
+
} catch (_unused) {
|
|
62
|
+
// pasteStartPos may be out of bounds if the document changed between
|
|
63
|
+
// when the paste was recorded and when this effect fires.
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
2
|
import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
|
|
3
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
4
|
+
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
|
|
3
5
|
import { hideToolbar, showToolbar } from './editor-commands/commands';
|
|
4
6
|
import { createPlugin } from './pm-plugins/main';
|
|
5
7
|
import { pasteOptionsPluginKey, ToolbarDropdownOption } from './types/types';
|
|
8
|
+
import { firePasteActionsMenuExperimentExposure } from './ui/on-paste-actions-menu/exposure';
|
|
6
9
|
import { PasteActionsMenu } from './ui/on-paste-actions-menu/PasteActionsMenu';
|
|
7
10
|
import { getPasteMenuComponents } from './ui/on-paste-actions-menu/PasteMenuComponents';
|
|
8
11
|
import { buildToolbar, isToolbarVisible } from './ui/toolbar';
|
|
@@ -12,7 +15,7 @@ export const pasteOptionsToolbarPlugin = ({
|
|
|
12
15
|
}) => {
|
|
13
16
|
var _api$analytics;
|
|
14
17
|
const editorAnalyticsAPI = api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
|
|
15
|
-
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu) {
|
|
18
|
+
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu && expValEqualsNoExposure('platform_editor_paste_actions_menu', 'isEnabled', true)) {
|
|
16
19
|
var _api$uiControlRegistr;
|
|
17
20
|
api === null || api === void 0 ? void 0 : (_api$uiControlRegistr = api.uiControlRegistry) === null || _api$uiControlRegistr === void 0 ? void 0 : _api$uiControlRegistr.actions.register(getPasteMenuComponents({
|
|
18
21
|
api
|
|
@@ -26,7 +29,7 @@ export const pasteOptionsToolbarPlugin = ({
|
|
|
26
29
|
plugin: ({
|
|
27
30
|
dispatch
|
|
28
31
|
}) => createPlugin(dispatch, {
|
|
29
|
-
useNewPasteMenu: config === null || config === void 0 ? void 0 : config.usePopupBasedPasteActionsMenu
|
|
32
|
+
useNewPasteMenu: (config === null || config === void 0 ? void 0 : config.usePopupBasedPasteActionsMenu) && expValEqualsNoExposure('platform_editor_paste_actions_menu', 'isEnabled', true)
|
|
30
33
|
})
|
|
31
34
|
}];
|
|
32
35
|
},
|
|
@@ -58,7 +61,7 @@ export const pasteOptionsToolbarPlugin = ({
|
|
|
58
61
|
},
|
|
59
62
|
pluginsOptions: {
|
|
60
63
|
floatingToolbar(state, intl) {
|
|
61
|
-
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu) {
|
|
64
|
+
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu && expValEqualsNoExposure('platform_editor_paste_actions_menu', 'isEnabled', true)) {
|
|
62
65
|
return;
|
|
63
66
|
}
|
|
64
67
|
const pastePluginState = pasteOptionsPluginKey.getState(state);
|
|
@@ -74,7 +77,7 @@ export const pasteOptionsToolbarPlugin = ({
|
|
|
74
77
|
popupsBoundariesElement,
|
|
75
78
|
popupsScrollableElement
|
|
76
79
|
}) {
|
|
77
|
-
if (!(config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu) || !editorView) {
|
|
80
|
+
if (!(config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu && expValEqualsNoExposure('platform_editor_paste_actions_menu', 'isEnabled', true)) || !editorView) {
|
|
78
81
|
return null;
|
|
79
82
|
}
|
|
80
83
|
return /*#__PURE__*/React.createElement(PasteActionsMenu, {
|
|
@@ -97,7 +100,11 @@ export const pasteOptionsToolbarPlugin = ({
|
|
|
97
100
|
};
|
|
98
101
|
});
|
|
99
102
|
useEffect(() => {
|
|
100
|
-
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu) {
|
|
103
|
+
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu && fg('platform_editor_paste_actions_menu_exposure')) {
|
|
104
|
+
var _lastContentPasted$te, _lastContentPasted$te2;
|
|
105
|
+
firePasteActionsMenuExperimentExposure((_lastContentPasted$te = lastContentPasted === null || lastContentPasted === void 0 ? void 0 : (_lastContentPasted$te2 = lastContentPasted.text) === null || _lastContentPasted$te2 === void 0 ? void 0 : _lastContentPasted$te2.length) !== null && _lastContentPasted$te !== void 0 ? _lastContentPasted$te : 0, editorView.state, lastContentPasted === null || lastContentPasted === void 0 ? void 0 : lastContentPasted.pasteStartPos, lastContentPasted === null || lastContentPasted === void 0 ? void 0 : lastContentPasted.pasteEndPos, lastContentPasted === null || lastContentPasted === void 0 ? void 0 : lastContentPasted.text);
|
|
106
|
+
}
|
|
107
|
+
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu && expValEqualsNoExposure('platform_editor_paste_actions_menu', 'isEnabled', true)) {
|
|
101
108
|
return;
|
|
102
109
|
}
|
|
103
110
|
if (!lastContentPasted) {
|
|
@@ -215,7 +215,7 @@ export const PasteActionsMenu = ({
|
|
|
215
215
|
boundariesElement,
|
|
216
216
|
scrollableElement
|
|
217
217
|
}) => {
|
|
218
|
-
var _api$analytics, _api$uiControlRegistr, _api$uiControlRegistr2
|
|
218
|
+
var _api$analytics, _api$uiControlRegistr, _api$uiControlRegistr2;
|
|
219
219
|
const editorAnalyticsAPI = api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
|
|
220
220
|
const {
|
|
221
221
|
lastContentPasted
|
|
@@ -266,22 +266,6 @@ export const PasteActionsMenu = ({
|
|
|
266
266
|
pasteEndPos: (_pluginState$pasteEnd = pluginState === null || pluginState === void 0 ? void 0 : pluginState.pasteEndPos) !== null && _pluginState$pasteEnd !== void 0 ? _pluginState$pasteEnd : 0
|
|
267
267
|
};
|
|
268
268
|
});
|
|
269
|
-
const aiSurfaceComponents = (_api$uiControlRegistr = api === null || api === void 0 ? void 0 : (_api$uiControlRegistr2 = api.uiControlRegistry) === null || _api$uiControlRegistr2 === void 0 ? void 0 : _api$uiControlRegistr2.actions.getComponents('ai-paste-menu')) !== null && _api$uiControlRegistr !== void 0 ? _api$uiControlRegistr : [];
|
|
270
|
-
const visibleAiActionKeys = getVisibleKeys(aiSurfaceComponents, ['button', 'menu-item']);
|
|
271
|
-
useEffect(() => {
|
|
272
|
-
if (!prevShowToolbarRef.current && isToolbarShown) {
|
|
273
|
-
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.fireAnalyticsEvent({
|
|
274
|
-
action: ACTION.OPENED,
|
|
275
|
-
actionSubject: ACTION_SUBJECT.PASTE_ACTIONS_MENU,
|
|
276
|
-
eventType: EVENT_TYPE.UI,
|
|
277
|
-
attributes: {
|
|
278
|
-
visibleAiActions: visibleAiActionKeys
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
prevShowToolbarRef.current = isToolbarShown;
|
|
283
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
284
|
-
}, [isToolbarShown, editorAnalyticsAPI]);
|
|
285
269
|
const preventEditorFocusLoss = useCallback(e => {
|
|
286
270
|
e.preventDefault();
|
|
287
271
|
}, []);
|
|
@@ -313,20 +297,36 @@ export const PasteActionsMenu = ({
|
|
|
313
297
|
// onPositionCalculated with fresh viewport coordinates.
|
|
314
298
|
const overflowScrollParent = isToolbarShown ? findOverflowScrollParent(editorView.dom) : false;
|
|
315
299
|
const effectiveScrollableElement = overflowScrollParent || scrollableElement;
|
|
316
|
-
const pasteMenuComponents = (_api$
|
|
300
|
+
const pasteMenuComponents = (_api$uiControlRegistr = api === null || api === void 0 ? 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 : [];
|
|
317
301
|
const anyComponentVisible = hasVisibleButton(pasteMenuComponents);
|
|
318
302
|
|
|
303
|
+
// eslint-disable-next-line @atlassian/perf-linting/no-expensive-computations-in-render -- pasteMenuComponents changes by reference each render; filter is small (< 10 items)
|
|
304
|
+
const aiMenuItems = pasteMenuComponents.filter(c => {
|
|
305
|
+
var _c$parents;
|
|
306
|
+
return c.type === 'menu-item' && ((_c$parents = c.parents) === null || _c$parents === void 0 ? void 0 : _c$parents.some(p => p.key === AI_PASTE_MENU_SECTION.key));
|
|
307
|
+
});
|
|
308
|
+
const visibleAiActionKeys = getVisibleKeys(aiMenuItems, ['menu-item']);
|
|
309
|
+
|
|
319
310
|
// Two positioning modes:
|
|
320
311
|
// 1. Inline: no AI actions visible — menu appears to the right of the cursor,
|
|
321
312
|
// vertically centered with the text line.
|
|
322
313
|
// 2. Block-anchored: AI actions are visible — menu appears at the right edge
|
|
323
314
|
// of the content block, aligned with paste start.
|
|
324
|
-
const hasVisibleAiActions =
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
315
|
+
const hasVisibleAiActions = visibleAiActionKeys.length > 0;
|
|
316
|
+
useEffect(() => {
|
|
317
|
+
if (!prevShowToolbarRef.current && isToolbarShown) {
|
|
318
|
+
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.fireAnalyticsEvent({
|
|
319
|
+
action: ACTION.OPENED,
|
|
320
|
+
actionSubject: ACTION_SUBJECT.PASTE_ACTIONS_MENU,
|
|
321
|
+
eventType: EVENT_TYPE.UI,
|
|
322
|
+
attributes: {
|
|
323
|
+
visibleAiActions: visibleAiActionKeys
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
prevShowToolbarRef.current = isToolbarShown;
|
|
328
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
329
|
+
}, [isToolbarShown, editorAnalyticsAPI]);
|
|
330
330
|
const useInlinePosition = !hasVisibleAiActions;
|
|
331
331
|
if (!isToolbarShown) {
|
|
332
332
|
return null;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { expVal } from '@atlaskit/tmp-editor-statsig/expVal';
|
|
2
|
+
const isNotProse = text => {
|
|
3
|
+
const trimmed = text.trim();
|
|
4
|
+
if (!trimmed) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Check each character: if we find whitespace it's prose-like,
|
|
9
|
+
// if we find a non-ASCII character it's likely CJK/Thai/etc.
|
|
10
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
11
|
+
const code = trimmed.charCodeAt(i);
|
|
12
|
+
// Whitespace (space, tab, newline, etc.) → prose-like
|
|
13
|
+
if (code === 0x20 || code === 0x09 || code === 0x0a || code === 0x0d) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
// Non-ASCII character → likely a non-Latin script (CJK, Thai, etc.)
|
|
17
|
+
if (code > 0x7f) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// No whitespace and all ASCII → URL, token, path, etc.
|
|
23
|
+
return true;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Manual exposure event for `platform_editor_paste_actions_menu`. Due to the fact that as part of this experiment
|
|
27
|
+
// the paste menu was completely redesigned, it was very difficult to ensure that an exposure event fires accurately
|
|
28
|
+
// for both control and test cohorts without executing code paths for both menus.
|
|
29
|
+
// This manual exposure event executes all criteria for showing AI buttons and fires the exposure manually in a code path that
|
|
30
|
+
// is guaranteed to execute on both control and test.
|
|
31
|
+
export const firePasteActionsMenuExperimentExposure = (contentLength, state, pasteStartPos, pasteEndPos, pastedText) => {
|
|
32
|
+
if (contentLength < 100 || !pasteStartPos || !pasteEndPos || !pastedText) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (isNotProse(pastedText)) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const $pos = state.doc.resolve(pasteStartPos);
|
|
40
|
+
const pasteAncestorNodeNames = [];
|
|
41
|
+
for (let depth = $pos.depth; depth > 0; depth--) {
|
|
42
|
+
// Only include an ancestor if the entire pasted range is contained within it.
|
|
43
|
+
// This prevents nodes like 'heading' from being flagged as ancestors when the
|
|
44
|
+
// pasted content starts in a heading but extends beyond it (e.g. heading + paragraph).
|
|
45
|
+
if (pasteEndPos <= $pos.end(depth)) {
|
|
46
|
+
pasteAncestorNodeNames.push($pos.node(depth).type.name);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const isInExcludedNode = pasteAncestorNodeNames.some(name => ['codeBlock', 'heading'].includes(name));
|
|
50
|
+
if (!isInExcludedNode) {
|
|
51
|
+
expVal('platform_editor_paste_actions_menu', 'isEnabled', false);
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// pasteStartPos may be out of bounds if the document changed between
|
|
55
|
+
// when the paste was recorded and when this effect fires.
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
2
|
import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
|
|
3
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
4
|
+
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
|
|
3
5
|
import { hideToolbar, showToolbar } from './editor-commands/commands';
|
|
4
6
|
import { createPlugin } from './pm-plugins/main';
|
|
5
7
|
import { pasteOptionsPluginKey, ToolbarDropdownOption } from './types/types';
|
|
8
|
+
import { firePasteActionsMenuExperimentExposure } from './ui/on-paste-actions-menu/exposure';
|
|
6
9
|
import { PasteActionsMenu } from './ui/on-paste-actions-menu/PasteActionsMenu';
|
|
7
10
|
import { getPasteMenuComponents } from './ui/on-paste-actions-menu/PasteMenuComponents';
|
|
8
11
|
import { buildToolbar, isToolbarVisible } from './ui/toolbar';
|
|
@@ -11,7 +14,7 @@ export var pasteOptionsToolbarPlugin = function pasteOptionsToolbarPlugin(_ref)
|
|
|
11
14
|
var config = _ref.config,
|
|
12
15
|
api = _ref.api;
|
|
13
16
|
var editorAnalyticsAPI = api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
|
|
14
|
-
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu) {
|
|
17
|
+
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu && expValEqualsNoExposure('platform_editor_paste_actions_menu', 'isEnabled', true)) {
|
|
15
18
|
var _api$uiControlRegistr;
|
|
16
19
|
api === null || api === void 0 || (_api$uiControlRegistr = api.uiControlRegistry) === null || _api$uiControlRegistr === void 0 || _api$uiControlRegistr.actions.register(getPasteMenuComponents({
|
|
17
20
|
api: api
|
|
@@ -25,7 +28,7 @@ export var pasteOptionsToolbarPlugin = function pasteOptionsToolbarPlugin(_ref)
|
|
|
25
28
|
plugin: function plugin(_ref2) {
|
|
26
29
|
var dispatch = _ref2.dispatch;
|
|
27
30
|
return createPlugin(dispatch, {
|
|
28
|
-
useNewPasteMenu: config === null || config === void 0 ? void 0 : config.usePopupBasedPasteActionsMenu
|
|
31
|
+
useNewPasteMenu: (config === null || config === void 0 ? void 0 : config.usePopupBasedPasteActionsMenu) && expValEqualsNoExposure('platform_editor_paste_actions_menu', 'isEnabled', true)
|
|
29
32
|
});
|
|
30
33
|
}
|
|
31
34
|
}];
|
|
@@ -58,7 +61,7 @@ export var pasteOptionsToolbarPlugin = function pasteOptionsToolbarPlugin(_ref)
|
|
|
58
61
|
},
|
|
59
62
|
pluginsOptions: {
|
|
60
63
|
floatingToolbar: function floatingToolbar(state, intl) {
|
|
61
|
-
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu) {
|
|
64
|
+
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu && expValEqualsNoExposure('platform_editor_paste_actions_menu', 'isEnabled', true)) {
|
|
62
65
|
return;
|
|
63
66
|
}
|
|
64
67
|
var pastePluginState = pasteOptionsPluginKey.getState(state);
|
|
@@ -73,7 +76,7 @@ export var pasteOptionsToolbarPlugin = function pasteOptionsToolbarPlugin(_ref)
|
|
|
73
76
|
popupsMountPoint = _ref3.popupsMountPoint,
|
|
74
77
|
popupsBoundariesElement = _ref3.popupsBoundariesElement,
|
|
75
78
|
popupsScrollableElement = _ref3.popupsScrollableElement;
|
|
76
|
-
if (!(config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu) || !editorView) {
|
|
79
|
+
if (!(config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu && expValEqualsNoExposure('platform_editor_paste_actions_menu', 'isEnabled', true)) || !editorView) {
|
|
77
80
|
return null;
|
|
78
81
|
}
|
|
79
82
|
return /*#__PURE__*/React.createElement(PasteActionsMenu, {
|
|
@@ -94,7 +97,11 @@ export var pasteOptionsToolbarPlugin = function pasteOptionsToolbarPlugin(_ref)
|
|
|
94
97
|
}),
|
|
95
98
|
lastContentPasted = _useSharedPluginState.lastContentPasted;
|
|
96
99
|
useEffect(function () {
|
|
97
|
-
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu) {
|
|
100
|
+
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu && fg('platform_editor_paste_actions_menu_exposure')) {
|
|
101
|
+
var _lastContentPasted$te, _lastContentPasted$te2;
|
|
102
|
+
firePasteActionsMenuExperimentExposure((_lastContentPasted$te = lastContentPasted === null || lastContentPasted === void 0 || (_lastContentPasted$te2 = lastContentPasted.text) === null || _lastContentPasted$te2 === void 0 ? void 0 : _lastContentPasted$te2.length) !== null && _lastContentPasted$te !== void 0 ? _lastContentPasted$te : 0, editorView.state, lastContentPasted === null || lastContentPasted === void 0 ? void 0 : lastContentPasted.pasteStartPos, lastContentPasted === null || lastContentPasted === void 0 ? void 0 : lastContentPasted.pasteEndPos, lastContentPasted === null || lastContentPasted === void 0 ? void 0 : lastContentPasted.text);
|
|
103
|
+
}
|
|
104
|
+
if (config !== null && config !== void 0 && config.usePopupBasedPasteActionsMenu && expValEqualsNoExposure('platform_editor_paste_actions_menu', 'isEnabled', true)) {
|
|
98
105
|
return;
|
|
99
106
|
}
|
|
100
107
|
if (!lastContentPasted) {
|
|
@@ -210,7 +210,7 @@ export function onPositionCalculated(editorView, pasteStartPos, pasteEndPos, tar
|
|
|
210
210
|
};
|
|
211
211
|
}
|
|
212
212
|
export var PasteActionsMenu = function PasteActionsMenu(_ref) {
|
|
213
|
-
var _api$analytics, _api$uiControlRegistr, _api$uiControlRegistr2
|
|
213
|
+
var _api$analytics, _api$uiControlRegistr, _api$uiControlRegistr2;
|
|
214
214
|
var api = _ref.api,
|
|
215
215
|
editorView = _ref.editorView,
|
|
216
216
|
mountTo = _ref.mountTo,
|
|
@@ -264,22 +264,6 @@ export var PasteActionsMenu = function PasteActionsMenu(_ref) {
|
|
|
264
264
|
isToolbarShown = _useSharedPluginState2.showToolbar,
|
|
265
265
|
pasteStartPos = _useSharedPluginState2.pasteStartPos,
|
|
266
266
|
pasteEndPos = _useSharedPluginState2.pasteEndPos;
|
|
267
|
-
var aiSurfaceComponents = (_api$uiControlRegistr = api === null || api === void 0 || (_api$uiControlRegistr2 = api.uiControlRegistry) === null || _api$uiControlRegistr2 === void 0 ? void 0 : _api$uiControlRegistr2.actions.getComponents('ai-paste-menu')) !== null && _api$uiControlRegistr !== void 0 ? _api$uiControlRegistr : [];
|
|
268
|
-
var visibleAiActionKeys = getVisibleKeys(aiSurfaceComponents, ['button', 'menu-item']);
|
|
269
|
-
useEffect(function () {
|
|
270
|
-
if (!prevShowToolbarRef.current && isToolbarShown) {
|
|
271
|
-
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.fireAnalyticsEvent({
|
|
272
|
-
action: ACTION.OPENED,
|
|
273
|
-
actionSubject: ACTION_SUBJECT.PASTE_ACTIONS_MENU,
|
|
274
|
-
eventType: EVENT_TYPE.UI,
|
|
275
|
-
attributes: {
|
|
276
|
-
visibleAiActions: visibleAiActionKeys
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
prevShowToolbarRef.current = isToolbarShown;
|
|
281
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
282
|
-
}, [isToolbarShown, editorAnalyticsAPI]);
|
|
283
267
|
var preventEditorFocusLoss = useCallback(function (e) {
|
|
284
268
|
e.preventDefault();
|
|
285
269
|
}, []);
|
|
@@ -311,22 +295,38 @@ export var PasteActionsMenu = function PasteActionsMenu(_ref) {
|
|
|
311
295
|
// onPositionCalculated with fresh viewport coordinates.
|
|
312
296
|
var overflowScrollParent = isToolbarShown ? findOverflowScrollParent(editorView.dom) : false;
|
|
313
297
|
var effectiveScrollableElement = overflowScrollParent || scrollableElement;
|
|
314
|
-
var pasteMenuComponents = (_api$
|
|
298
|
+
var pasteMenuComponents = (_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 : [];
|
|
315
299
|
var anyComponentVisible = hasVisibleButton(pasteMenuComponents);
|
|
316
300
|
|
|
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
301
|
// 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) {
|
|
302
|
+
var aiMenuItems = pasteMenuComponents.filter(function (c) {
|
|
325
303
|
var _c$parents;
|
|
326
304
|
return c.type === 'menu-item' && ((_c$parents = c.parents) === null || _c$parents === void 0 ? void 0 : _c$parents.some(function (p) {
|
|
327
305
|
return p.key === AI_PASTE_MENU_SECTION.key;
|
|
328
306
|
}));
|
|
329
|
-
})
|
|
307
|
+
});
|
|
308
|
+
var visibleAiActionKeys = getVisibleKeys(aiMenuItems, ['menu-item']);
|
|
309
|
+
|
|
310
|
+
// Two positioning modes:
|
|
311
|
+
// 1. Inline: no AI actions visible — menu appears to the right of the cursor,
|
|
312
|
+
// vertically centered with the text line.
|
|
313
|
+
// 2. Block-anchored: AI actions are visible — menu appears at the right edge
|
|
314
|
+
// of the content block, aligned with paste start.
|
|
315
|
+
var hasVisibleAiActions = visibleAiActionKeys.length > 0;
|
|
316
|
+
useEffect(function () {
|
|
317
|
+
if (!prevShowToolbarRef.current && isToolbarShown) {
|
|
318
|
+
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.fireAnalyticsEvent({
|
|
319
|
+
action: ACTION.OPENED,
|
|
320
|
+
actionSubject: ACTION_SUBJECT.PASTE_ACTIONS_MENU,
|
|
321
|
+
eventType: EVENT_TYPE.UI,
|
|
322
|
+
attributes: {
|
|
323
|
+
visibleAiActions: visibleAiActionKeys
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
prevShowToolbarRef.current = isToolbarShown;
|
|
328
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
329
|
+
}, [isToolbarShown, editorAnalyticsAPI]);
|
|
330
330
|
var useInlinePosition = !hasVisibleAiActions;
|
|
331
331
|
if (!isToolbarShown) {
|
|
332
332
|
return null;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { expVal } from '@atlaskit/tmp-editor-statsig/expVal';
|
|
2
|
+
var isNotProse = function isNotProse(text) {
|
|
3
|
+
var trimmed = text.trim();
|
|
4
|
+
if (!trimmed) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Check each character: if we find whitespace it's prose-like,
|
|
9
|
+
// if we find a non-ASCII character it's likely CJK/Thai/etc.
|
|
10
|
+
for (var i = 0; i < trimmed.length; i++) {
|
|
11
|
+
var code = trimmed.charCodeAt(i);
|
|
12
|
+
// Whitespace (space, tab, newline, etc.) → prose-like
|
|
13
|
+
if (code === 0x20 || code === 0x09 || code === 0x0a || code === 0x0d) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
// Non-ASCII character → likely a non-Latin script (CJK, Thai, etc.)
|
|
17
|
+
if (code > 0x7f) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// No whitespace and all ASCII → URL, token, path, etc.
|
|
23
|
+
return true;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Manual exposure event for `platform_editor_paste_actions_menu`. Due to the fact that as part of this experiment
|
|
27
|
+
// the paste menu was completely redesigned, it was very difficult to ensure that an exposure event fires accurately
|
|
28
|
+
// for both control and test cohorts without executing code paths for both menus.
|
|
29
|
+
// This manual exposure event executes all criteria for showing AI buttons and fires the exposure manually in a code path that
|
|
30
|
+
// is guaranteed to execute on both control and test.
|
|
31
|
+
export var firePasteActionsMenuExperimentExposure = function firePasteActionsMenuExperimentExposure(contentLength, state, pasteStartPos, pasteEndPos, pastedText) {
|
|
32
|
+
if (contentLength < 100 || !pasteStartPos || !pasteEndPos || !pastedText) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (isNotProse(pastedText)) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
var $pos = state.doc.resolve(pasteStartPos);
|
|
40
|
+
var pasteAncestorNodeNames = [];
|
|
41
|
+
for (var depth = $pos.depth; depth > 0; depth--) {
|
|
42
|
+
// Only include an ancestor if the entire pasted range is contained within it.
|
|
43
|
+
// This prevents nodes like 'heading' from being flagged as ancestors when the
|
|
44
|
+
// pasted content starts in a heading but extends beyond it (e.g. heading + paragraph).
|
|
45
|
+
if (pasteEndPos <= $pos.end(depth)) {
|
|
46
|
+
pasteAncestorNodeNames.push($pos.node(depth).type.name);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
var isInExcludedNode = pasteAncestorNodeNames.some(function (name) {
|
|
50
|
+
return ['codeBlock', 'heading'].includes(name);
|
|
51
|
+
});
|
|
52
|
+
if (!isInExcludedNode) {
|
|
53
|
+
expVal('platform_editor_paste_actions_menu', 'isEnabled', false);
|
|
54
|
+
}
|
|
55
|
+
} catch (_unused) {
|
|
56
|
+
// pasteStartPos may be out of bounds if the document changed between
|
|
57
|
+
// when the paste was recorded and when this effect fires.
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
};
|
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.9",
|
|
4
4
|
"description": "Paste options toolbar for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@atlaskit/dropdown-menu": "^16.8.0",
|
|
34
34
|
"@atlaskit/editor-markdown-transformer": "^5.20.0",
|
|
35
35
|
"@atlaskit/editor-plugin-analytics": "^8.0.0",
|
|
36
|
-
"@atlaskit/editor-plugin-paste": "^9.
|
|
36
|
+
"@atlaskit/editor-plugin-paste": "^9.1.0",
|
|
37
37
|
"@atlaskit/editor-plugin-ui-control-registry": "^2.0.0",
|
|
38
38
|
"@atlaskit/editor-prosemirror": "^7.3.0",
|
|
39
39
|
"@atlaskit/editor-shared-styles": "^3.10.0",
|
|
@@ -42,14 +42,15 @@
|
|
|
42
42
|
"@atlaskit/icon": "^33.1.0",
|
|
43
43
|
"@atlaskit/platform-feature-flags": "^1.1.0",
|
|
44
44
|
"@atlaskit/primitives": "^18.1.0",
|
|
45
|
-
"@atlaskit/
|
|
45
|
+
"@atlaskit/tmp-editor-statsig": "^50.0.0",
|
|
46
|
+
"@atlaskit/tokens": "^11.4.0",
|
|
46
47
|
"@babel/runtime": "^7.0.0",
|
|
47
48
|
"@compiled/react": "^0.20.0",
|
|
48
49
|
"@emotion/react": "^11.7.1",
|
|
49
50
|
"react-intl-next": "npm:react-intl@^5.18.1"
|
|
50
51
|
},
|
|
51
52
|
"peerDependencies": {
|
|
52
|
-
"@atlaskit/editor-common": "^112.
|
|
53
|
+
"@atlaskit/editor-common": "^112.13.0",
|
|
53
54
|
"react": "^18.2.0",
|
|
54
55
|
"react-dom": "^18.2.0"
|
|
55
56
|
},
|
|
@@ -60,6 +61,9 @@
|
|
|
60
61
|
"platform-feature-flags": {
|
|
61
62
|
"platform_editor_paste_actions_keypress_fix": {
|
|
62
63
|
"type": "boolean"
|
|
64
|
+
},
|
|
65
|
+
"platform_editor_paste_actions_menu_exposure": {
|
|
66
|
+
"type": "boolean"
|
|
63
67
|
}
|
|
64
68
|
},
|
|
65
69
|
"techstack": {
|