@atlaskit/editor-plugin-insert-block 8.4.4 → 8.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cjs/insertBlockPlugin.js +17 -0
  3. package/dist/cjs/pm-plugins/experiences/toolbar-action-experiences.js +183 -0
  4. package/dist/cjs/pm-plugins/experiences/toolbar-experience-utils.js +409 -0
  5. package/dist/cjs/ui/ElementBrowser/InsertMenu.js +18 -5
  6. package/dist/cjs/ui/toolbar-components/EmojiButton.js +3 -1
  7. package/dist/cjs/ui/toolbar-components/ImageButton.js +2 -1
  8. package/dist/cjs/ui/toolbar-components/InsertButton.js +2 -1
  9. package/dist/cjs/ui/toolbar-components/LayoutButton.js +2 -1
  10. package/dist/cjs/ui/toolbar-components/MediaButton.js +3 -1
  11. package/dist/cjs/ui/toolbar-components/MentionButton.js +3 -1
  12. package/dist/cjs/ui/toolbar-components/TableButton.js +1 -1
  13. package/dist/cjs/ui/toolbar-components/TableSizePicker.js +3 -1
  14. package/dist/cjs/ui/toolbar-components/TaskListButton.js +2 -1
  15. package/dist/es2019/insertBlockPlugin.js +15 -0
  16. package/dist/es2019/pm-plugins/experiences/toolbar-action-experiences.js +173 -0
  17. package/dist/es2019/pm-plugins/experiences/toolbar-experience-utils.js +279 -0
  18. package/dist/es2019/ui/ElementBrowser/InsertMenu.js +15 -4
  19. package/dist/es2019/ui/toolbar-components/EmojiButton.js +3 -1
  20. package/dist/es2019/ui/toolbar-components/ImageButton.js +3 -2
  21. package/dist/es2019/ui/toolbar-components/InsertButton.js +3 -2
  22. package/dist/es2019/ui/toolbar-components/LayoutButton.js +3 -2
  23. package/dist/es2019/ui/toolbar-components/MediaButton.js +3 -1
  24. package/dist/es2019/ui/toolbar-components/MentionButton.js +3 -1
  25. package/dist/es2019/ui/toolbar-components/TableButton.js +2 -2
  26. package/dist/es2019/ui/toolbar-components/TableSizePicker.js +3 -1
  27. package/dist/es2019/ui/toolbar-components/TaskListButton.js +3 -2
  28. package/dist/esm/insertBlockPlugin.js +17 -0
  29. package/dist/esm/pm-plugins/experiences/toolbar-action-experiences.js +177 -0
  30. package/dist/esm/pm-plugins/experiences/toolbar-experience-utils.js +403 -0
  31. package/dist/esm/ui/ElementBrowser/InsertMenu.js +17 -4
  32. package/dist/esm/ui/toolbar-components/EmojiButton.js +3 -1
  33. package/dist/esm/ui/toolbar-components/ImageButton.js +3 -2
  34. package/dist/esm/ui/toolbar-components/InsertButton.js +3 -2
  35. package/dist/esm/ui/toolbar-components/LayoutButton.js +3 -2
  36. package/dist/esm/ui/toolbar-components/MediaButton.js +3 -1
  37. package/dist/esm/ui/toolbar-components/MentionButton.js +3 -1
  38. package/dist/esm/ui/toolbar-components/TableButton.js +2 -2
  39. package/dist/esm/ui/toolbar-components/TableSizePicker.js +3 -1
  40. package/dist/esm/ui/toolbar-components/TaskListButton.js +3 -2
  41. package/dist/types/pm-plugins/experiences/toolbar-action-experiences.d.ts +10 -0
  42. package/dist/types/pm-plugins/experiences/toolbar-experience-utils.d.ts +57 -0
  43. package/dist/types/ui/ElementBrowser/InsertMenu.d.ts +5 -2
  44. package/dist/types-ts4.5/pm-plugins/experiences/toolbar-action-experiences.d.ts +10 -0
  45. package/dist/types-ts4.5/pm-plugins/experiences/toolbar-experience-utils.d.ts +57 -0
  46. package/dist/types-ts4.5/ui/ElementBrowser/InsertMenu.d.ts +5 -2
  47. package/package.json +6 -2
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.filterForPinWhiteboards = exports.default = exports.DEFAULT_HEIGHT = void 0;
7
+ exports.sortPrioritizedElements = exports.default = exports.DEFAULT_HEIGHT = void 0;
8
8
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
9
9
  var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
10
10
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
@@ -21,6 +21,7 @@ var _quickInsert = require("@atlaskit/editor-common/quick-insert");
21
21
  var _uiReact = require("@atlaskit/editor-common/ui-react");
22
22
  var _editorPluginConnectivity = require("@atlaskit/editor-plugin-connectivity");
23
23
  var _colors = require("@atlaskit/theme/colors");
24
+ var _expVal = require("@atlaskit/tmp-editor-statsig/expVal");
24
25
  var _excluded = ["children"];
25
26
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
26
27
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /**
@@ -30,12 +31,24 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
30
31
  var DEFAULT_HEIGHT = exports.DEFAULT_HEIGHT = 560;
31
32
 
32
33
  /**
33
- * Exported helper to allow testing of InsertMenu whiteboard pinning logic. NOTE: this is
34
+ * Exported helper to allow testing of InsertMenu pinning logic. NOTE: this is
34
35
  *not* the ideal way to approach this, quickinsert plugin provides a `getSuggestions`
35
36
  method that can be used to get suggestions -> once all experiments are cleaned up,
36
37
  they should be unified through `pluginInjectionApi?.quickInsert?.actions.getSuggestions`
38
+
39
+ `cc_fd_db_top_editor_toolbar` experiment adds new logic to sort elements by `priority`
40
+ this newer implementation matches how the "quick insert menu" sorts elements
37
41
  */
38
- var filterForPinWhiteboards = exports.filterForPinWhiteboards = function filterForPinWhiteboards(featuredItems, formatMessage) {
42
+ var sortPrioritizedElements = exports.sortPrioritizedElements = function sortPrioritizedElements(featuredItems, formatMessage) {
43
+ if (['new-description', 'orig-description'].includes((0, _expVal.expVal)('cc_fd_db_top_editor_toolbar', 'cohort', 'control'))) {
44
+ // Sort by priority (lower first) on the concatenated list so items
45
+ // with "priority" are at the top (e.g. Whiteboard before Database)
46
+ return featuredItems.slice(0).sort(function (a, b) {
47
+ return (a.priority || Number.POSITIVE_INFINITY) - (b.priority || Number.POSITIVE_INFINITY);
48
+ });
49
+ }
50
+
51
+ // old logic sort whiteboards to top
39
52
  var DIAGRAM_KEY = 'whiteboard-extension:create-diagram';
40
53
  var isDiagram = function isDiagram(item) {
41
54
  return item.key === DIAGRAM_KEY;
@@ -170,8 +183,8 @@ var InsertMenu = function InsertMenu(_ref) {
170
183
  }) : item;
171
184
  })) !== null && _pluginInjectionApi$q4 !== void 0 ? _pluginInjectionApi$q4 : [];
172
185
  var unfilteredResult = quickInsertDropdownItems.concat(featuredQuickInsertSuggestions);
173
- // need to filter on the concatenated list so whiteboards are at the top
174
- result = filterForPinWhiteboards(unfilteredResult, formatMessage);
186
+ // need to sort on the concatenated list so desired elements are at the top
187
+ result = sortPrioritizedElements(unfilteredResult, formatMessage);
175
188
  }
176
189
  setItemCount(result.length);
177
190
  return result;
@@ -10,6 +10,7 @@ var _reactIntlNext = require("react-intl-next");
10
10
  var _hooks = require("@atlaskit/editor-common/hooks");
11
11
  var _keymaps = require("@atlaskit/editor-common/keymaps");
12
12
  var _messages = require("@atlaskit/editor-common/messages");
13
+ var _toolbar = require("@atlaskit/editor-common/toolbar");
13
14
  var _editorToolbar = require("@atlaskit/editor-toolbar");
14
15
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
15
16
  var _useEmojiPickerPopup = require("./hooks/useEmojiPickerPopup");
@@ -75,6 +76,7 @@ var EmojiButton = exports.EmojiButton = function EmojiButton(_ref) {
75
76
  return emojiPickerPopup.toggle();
76
77
  },
77
78
  isSelected: emojiPickerPopup.isOpen,
78
- isDisabled: !isTypeAheadAllowed || !emojiProvider
79
+ isDisabled: !isTypeAheadAllowed || !emojiProvider,
80
+ testId: _toolbar.TOOLBAR_BUTTON_TEST_ID.EMOJI
79
81
  })));
80
82
  };
@@ -44,6 +44,7 @@ var ImageButton = exports.ImageButton = function ImageButton(_ref) {
44
44
  size: "small"
45
45
  }),
46
46
  onClick: onClick,
47
- isDisabled: !imageUploadEnabled || isOffline
47
+ isDisabled: !imageUploadEnabled || isOffline,
48
+ testId: _toolbar.TOOLBAR_BUTTON_TEST_ID.IMAGE
48
49
  }));
49
50
  };
@@ -249,6 +249,7 @@ var InsertButton = exports.InsertButton = function InsertButton(_ref) {
249
249
  ref: insertButtonRef,
250
250
  onClick: onClick,
251
251
  isSelected: insertMenuOpen,
252
- isDisabled: !isTypeAheadAllowed || isDisabled
252
+ isDisabled: !isTypeAheadAllowed || isDisabled,
253
+ testId: _toolbar.TOOLBAR_BUTTON_TEST_ID.INSERT
253
254
  })));
254
255
  };
@@ -35,6 +35,7 @@ var LayoutButton = exports.LayoutButton = function LayoutButton(_ref) {
35
35
  label: formatMessage(_messages.toolbarInsertBlockMessages.columns),
36
36
  size: "small"
37
37
  }),
38
- onClick: onClick
38
+ onClick: onClick,
39
+ testId: _toolbar.TOOLBAR_BUTTON_TEST_ID.LAYOUT
39
40
  }));
40
41
  };
@@ -10,6 +10,7 @@ var _reactIntlNext = require("react-intl-next");
10
10
  var _analytics = require("@atlaskit/editor-common/analytics");
11
11
  var _hooks = require("@atlaskit/editor-common/hooks");
12
12
  var _messages = require("@atlaskit/editor-common/messages");
13
+ var _toolbar = require("@atlaskit/editor-common/toolbar");
13
14
  var _editorPluginConnectivity = require("@atlaskit/editor-plugin-connectivity");
14
15
  var _editorToolbar = require("@atlaskit/editor-toolbar");
15
16
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
@@ -67,6 +68,7 @@ var MediaButton = exports.MediaButton = function MediaButton(_ref) {
67
68
  }),
68
69
  onClick: onClick,
69
70
  ref: mediaButtonRef,
70
- isDisabled: isOffline || !allowsUploads
71
+ isDisabled: isOffline || !allowsUploads,
72
+ testId: _toolbar.TOOLBAR_BUTTON_TEST_ID.MEDIA
71
73
  }));
72
74
  };
@@ -11,6 +11,7 @@ var _analytics = require("@atlaskit/editor-common/analytics");
11
11
  var _hooks = require("@atlaskit/editor-common/hooks");
12
12
  var _keymaps = require("@atlaskit/editor-common/keymaps");
13
13
  var _messages = require("@atlaskit/editor-common/messages");
14
+ var _toolbar = require("@atlaskit/editor-common/toolbar");
14
15
  var _editorToolbar = require("@atlaskit/editor-toolbar");
15
16
  var MentionButton = exports.MentionButton = function MentionButton(_ref) {
16
17
  var api = _ref.api;
@@ -46,6 +47,7 @@ var MentionButton = exports.MentionButton = function MentionButton(_ref) {
46
47
  }),
47
48
  onClick: onClick,
48
49
  ariaKeyshortcuts: "Shift+2 Space",
49
- isDisabled: !canInsertMention || !mentionProvider || !isTypeAheadAllowed
50
+ isDisabled: !canInsertMention || !mentionProvider || !isTypeAheadAllowed,
51
+ testId: _toolbar.TOOLBAR_BUTTON_TEST_ID.MENTION
50
52
  }));
51
53
  };
@@ -52,6 +52,6 @@ var TableButton = exports.TableButton = function TableButton(_ref) {
52
52
  }),
53
53
  onClick: onClick,
54
54
  ariaKeyshortcuts: (0, _keymaps.getAriaKeyshortcuts)(_keymaps.toggleTable),
55
- testId: "Table"
55
+ testId: _toolbar.TOOLBAR_BUTTON_TEST_ID.TABLE
56
56
  }));
57
57
  };
@@ -8,6 +8,7 @@ exports.TableSizePicker = void 0;
8
8
  var _react = _interopRequireWildcard(require("react"));
9
9
  var _reactIntlNext = require("react-intl-next");
10
10
  var _messages = require("@atlaskit/editor-common/messages");
11
+ var _toolbar = require("@atlaskit/editor-common/toolbar");
11
12
  var _editorToolbar = require("@atlaskit/editor-toolbar");
12
13
  var _useTableSelectorPopup = require("./hooks/useTableSelectorPopup");
13
14
  var _TableSelectorPopupWrapper = require("./popups/TableSelectorPopupWrapper");
@@ -54,6 +55,7 @@ var TableSizePicker = exports.TableSizePicker = function TableSizePicker(_ref) {
54
55
  }),
55
56
  onClick: onClick,
56
57
  isSelected: tableSelectorPopup.isOpen,
57
- ref: tableSizePickerRef
58
+ ref: tableSizePickerRef,
59
+ testId: _toolbar.TOOLBAR_BUTTON_TEST_ID.TABLE_SELECTOR
58
60
  })));
59
61
  };
@@ -40,6 +40,7 @@ var TaskListButton = exports.TaskListButton = function TaskListButton(_ref) {
40
40
  size: "small"
41
41
  }),
42
42
  onClick: onClick,
43
- ariaKeyshortcuts: "[ ] Space"
43
+ ariaKeyshortcuts: "[ ] Space",
44
+ testId: _toolbar.TOOLBAR_BUTTON_TEST_ID.TASK_LIST
44
45
  }));
45
46
  };
@@ -9,6 +9,7 @@ import { BLOCK_QUOTE, CODE_BLOCK, PANEL } from '@atlaskit/editor-plugin-block-ty
9
9
  import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity';
10
10
  import { fg } from '@atlaskit/platform-feature-flags';
11
11
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
12
+ import { getToolbarActionExperiencesPlugin } from './pm-plugins/experiences/toolbar-action-experiences';
12
13
  import { toggleInsertBlockPmKey, toggleInsertBlockPmPlugin } from './pm-plugins/toggleInsertBlock';
13
14
  import { getToolbarComponents } from './ui/toolbar-components';
14
15
  // Ignored via go/ees005
@@ -92,6 +93,7 @@ export const insertBlockPlugin = ({
92
93
  api
93
94
  }) => {
94
95
  const isToolbarAIFCEnabled = Boolean(api === null || api === void 0 ? void 0 : api.toolbar);
96
+ const refs = {};
95
97
  const primaryToolbarComponent = ({
96
98
  editorView,
97
99
  editorActions,
@@ -105,6 +107,7 @@ export const insertBlockPlugin = ({
105
107
  isToolbarReducedSpacing,
106
108
  isLastItem
107
109
  }) => {
110
+ refs.popupsMountPoint = popupsMountPoint || undefined;
108
111
  const renderNode = providers => {
109
112
  if (!editorView) {
110
113
  return null;
@@ -210,6 +213,18 @@ export const insertBlockPlugin = ({
210
213
  name: 'toggleInsertBlockPmPlugin',
211
214
  plugin: () => toggleInsertBlockPmPlugin()
212
215
  });
216
+ if (fg('platform_editor_experience_tracking_toolbar_button')) {
217
+ plugins.push({
218
+ name: 'toolbarActionExperiences',
219
+ plugin: () => getToolbarActionExperiencesPlugin({
220
+ refs,
221
+ dispatchAnalyticsEvent: payload => {
222
+ var _api$analytics, _api$analytics$action;
223
+ return api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : (_api$analytics$action = _api$analytics.actions) === null || _api$analytics$action === void 0 ? void 0 : _api$analytics$action.fireAnalyticsEvent(payload);
224
+ }
225
+ })
226
+ });
227
+ }
213
228
  return plugins;
214
229
  },
215
230
  pluginsOptions: {},
@@ -0,0 +1,173 @@
1
+ import { bind } from 'bind-event-listener';
2
+ import { getDocument } from '@atlaskit/browser-apis';
3
+ import { Experience, EXPERIENCE_ID, ExperienceCheckDomMutation, ExperienceCheckTimeout, getPopupContainerFromEditorView } from '@atlaskit/editor-common/experiences';
4
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
5
+ import { TOOLBAR_BUTTON_TEST_ID } from '@atlaskit/editor-common/toolbar';
6
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
7
+ import { ExperienceCheckPopupMutation, getParentDOMAtSelection, handleEditorNodeInsertDomMutation, handleTypeAheadOpenDomMutation, isToolbarButtonClick } from './toolbar-experience-utils';
8
+ const pluginKey = new PluginKey('toolbarActionExperiences');
9
+ const TIMEOUT_DURATION = 1000;
10
+ const PRIMARY_TOOLBAR = 'primaryToolbar';
11
+ const ABORT_REASON = {
12
+ USER_CANCELED: 'userCanceled',
13
+ EDITOR_DESTROYED: 'editorDestroyed'
14
+ };
15
+ export const getToolbarActionExperiencesPlugin = ({
16
+ refs,
17
+ dispatchAnalyticsEvent
18
+ }) => {
19
+ let editorView;
20
+ let popupTargetEl;
21
+ const getPopupsTarget = () => {
22
+ if (!popupTargetEl) {
23
+ var _editorView;
24
+ popupTargetEl = refs.popupsMountPoint || getPopupContainerFromEditorView((_editorView = editorView) === null || _editorView === void 0 ? void 0 : _editorView.dom);
25
+ }
26
+ return popupTargetEl;
27
+ };
28
+ const getEditorDom = () => {
29
+ var _editorView2;
30
+ if (((_editorView2 = editorView) === null || _editorView2 === void 0 ? void 0 : _editorView2.dom) instanceof HTMLElement) {
31
+ return editorView.dom;
32
+ }
33
+ return null;
34
+ };
35
+ const narrowParentObserveConfig = () => {
36
+ var _getParentDOMAtSelect;
37
+ return {
38
+ target: (_getParentDOMAtSelect = getParentDOMAtSelection(editorView)) !== null && _getParentDOMAtSelect !== void 0 ? _getParentDOMAtSelect : getEditorDom(),
39
+ options: {
40
+ childList: true
41
+ }
42
+ };
43
+ };
44
+ const rootObserveConfig = () => ({
45
+ target: getEditorDom(),
46
+ options: {
47
+ childList: true
48
+ }
49
+ });
50
+ const createNodeInsertExperience = action => new Experience(EXPERIENCE_ID.TOOLBAR_ACTION, {
51
+ action,
52
+ actionSubjectId: PRIMARY_TOOLBAR,
53
+ dispatchAnalyticsEvent,
54
+ checks: [new ExperienceCheckTimeout({
55
+ durationMs: TIMEOUT_DURATION
56
+ }), new ExperienceCheckDomMutation({
57
+ onDomMutation: handleEditorNodeInsertDomMutation,
58
+ observeConfig: narrowParentObserveConfig
59
+ }), new ExperienceCheckDomMutation({
60
+ onDomMutation: handleEditorNodeInsertDomMutation,
61
+ observeConfig: rootObserveConfig
62
+ })]
63
+ });
64
+ const createPopupExperience = (action, popupSelector) => new Experience(EXPERIENCE_ID.TOOLBAR_ACTION, {
65
+ action,
66
+ actionSubjectId: PRIMARY_TOOLBAR,
67
+ dispatchAnalyticsEvent,
68
+ checks: [new ExperienceCheckTimeout({
69
+ durationMs: TIMEOUT_DURATION
70
+ }), new ExperienceCheckPopupMutation(popupSelector, getPopupsTarget, getEditorDom)]
71
+ });
72
+ const experienceButtonMappings = [{
73
+ experience: createPopupExperience('emoji', '[data-emoji-picker-container]'),
74
+ buttonTestId: TOOLBAR_BUTTON_TEST_ID.EMOJI
75
+ }, {
76
+ experience: createPopupExperience('media', '[id="local-media-upload-button"], [data-testid="media-picker-file-input"]'),
77
+ buttonTestId: TOOLBAR_BUTTON_TEST_ID.MEDIA
78
+ }, {
79
+ experience: new Experience(EXPERIENCE_ID.TOOLBAR_ACTION, {
80
+ action: 'mention',
81
+ actionSubjectId: PRIMARY_TOOLBAR,
82
+ dispatchAnalyticsEvent,
83
+ checks: [new ExperienceCheckTimeout({
84
+ durationMs: TIMEOUT_DURATION
85
+ }), new ExperienceCheckDomMutation({
86
+ onDomMutation: handleTypeAheadOpenDomMutation,
87
+ observeConfig: narrowParentObserveConfig
88
+ })]
89
+ }),
90
+ buttonTestId: TOOLBAR_BUTTON_TEST_ID.MENTION
91
+ }, {
92
+ experience: createNodeInsertExperience('table'),
93
+ buttonTestId: TOOLBAR_BUTTON_TEST_ID.TABLE
94
+ }, {
95
+ experience: createPopupExperience('tableSelector', '[aria-label*="table size"], [data-testid*="table-selector"]'),
96
+ buttonTestId: TOOLBAR_BUTTON_TEST_ID.TABLE_SELECTOR
97
+ }, {
98
+ experience: createNodeInsertExperience('layout'),
99
+ buttonTestId: TOOLBAR_BUTTON_TEST_ID.LAYOUT
100
+ }, {
101
+ experience: createPopupExperience('image', '[id="local-media-upload-button"], [data-testid="media-picker-file-input"]'),
102
+ buttonTestId: TOOLBAR_BUTTON_TEST_ID.IMAGE
103
+ }, {
104
+ experience: createNodeInsertExperience('action'),
105
+ buttonTestId: TOOLBAR_BUTTON_TEST_ID.TASK_LIST
106
+ }];
107
+ const handleToolbarButtonClick = target => {
108
+ for (const {
109
+ experience,
110
+ buttonTestId
111
+ } of experienceButtonMappings) {
112
+ if (isToolbarButtonClick(target, buttonTestId)) {
113
+ experience.start({
114
+ forceRestart: true
115
+ });
116
+ return;
117
+ }
118
+ }
119
+ };
120
+ const abortAllExperiences = reason => {
121
+ for (const {
122
+ experience
123
+ } of experienceButtonMappings) {
124
+ experience.abort({
125
+ reason
126
+ });
127
+ }
128
+ };
129
+ const doc = getDocument();
130
+ if (!doc) {
131
+ return new SafePlugin({
132
+ key: pluginKey
133
+ });
134
+ }
135
+ const unbindClickListener = bind(doc, {
136
+ type: 'click',
137
+ listener: event => {
138
+ const target = event.target;
139
+ if (target instanceof HTMLElement) {
140
+ handleToolbarButtonClick(target);
141
+ }
142
+ },
143
+ options: {
144
+ capture: true
145
+ }
146
+ });
147
+ const unbindKeydownListener = bind(doc, {
148
+ type: 'keydown',
149
+ listener: event => {
150
+ if (event.key === 'Escape') {
151
+ abortAllExperiences(ABORT_REASON.USER_CANCELED);
152
+ }
153
+ },
154
+ options: {
155
+ capture: true
156
+ }
157
+ });
158
+ return new SafePlugin({
159
+ key: pluginKey,
160
+ view: view => {
161
+ editorView = view;
162
+ return {
163
+ destroy: () => {
164
+ abortAllExperiences(ABORT_REASON.EDITOR_DESTROYED);
165
+ editorView = undefined;
166
+ popupTargetEl = undefined;
167
+ unbindClickListener();
168
+ unbindKeydownListener();
169
+ }
170
+ };
171
+ }
172
+ });
173
+ };
@@ -0,0 +1,279 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import { getDocument } from '@atlaskit/browser-apis';
3
+ import { EXPERIENCE_FAILURE_REASON, popupWithNestedElement } from '@atlaskit/editor-common/experiences';
4
+ /**
5
+ * DOM marker selectors for node types inserted via toolbar actions.
6
+ * Matches outermost wrapper elements set synchronously by ReactNodeView
7
+ * (`{nodeTypeName}View-content-wrap`) or schema `toDOM` attributes.
8
+ */
9
+ export const NODE_INSERT_MARKERS = {
10
+ TABLE: '.tableView-content-wrap',
11
+ LAYOUT: '.layoutSectionView-content-wrap',
12
+ LAYOUT_COLUMN: '.layoutColumnView-content-wrap',
13
+ TASK_LIST: '[data-node-type="actionList"]',
14
+ TASK_ITEM: '.taskItemView-content-wrap'
15
+ };
16
+ const COMBINED_NODE_INSERT_SELECTOR = [NODE_INSERT_MARKERS.TABLE, NODE_INSERT_MARKERS.LAYOUT, NODE_INSERT_MARKERS.LAYOUT_COLUMN, NODE_INSERT_MARKERS.TASK_LIST, NODE_INSERT_MARKERS.TASK_ITEM].join(', ');
17
+ export const isToolbarButtonClick = (target, testId) => {
18
+ const button = target.closest(`button[data-testid="${testId}"]`);
19
+ if (!button) {
20
+ return false;
21
+ }
22
+ return !button.disabled && button.getAttribute('aria-disabled') !== 'true';
23
+ };
24
+
25
+ /**
26
+ * ExperienceCheck that observes popup mount point and all its
27
+ * `[data-editor-popup]` children with `{ childList: true }` (no subtree).
28
+ *
29
+ * Detects when a popup containing the given nested element is added to the
30
+ * DOM — either as a new `[data-editor-popup]` direct child, or as content
31
+ * rendered inside an existing `[data-editor-popup]` wrapper.
32
+ */
33
+ export const TYPEAHEAD_DECORATION_SELECTOR = '[data-type-ahead="typeaheadDecoration"]';
34
+ export const handleTypeAheadOpenDomMutation = ({
35
+ mutations
36
+ }) => {
37
+ for (const mutation of mutations) {
38
+ if (mutation.type !== 'childList') {
39
+ continue;
40
+ }
41
+ for (const node of mutation.addedNodes) {
42
+ if (!(node instanceof HTMLElement)) {
43
+ continue;
44
+ }
45
+ if (node.matches(TYPEAHEAD_DECORATION_SELECTOR) || node.querySelector(TYPEAHEAD_DECORATION_SELECTOR)) {
46
+ return {
47
+ status: 'success'
48
+ };
49
+ }
50
+ }
51
+ }
52
+ return undefined;
53
+ };
54
+ export class ExperienceCheckPopupMutation {
55
+ constructor(nestedElementQuery, getTarget, getEditorDom) {
56
+ _defineProperty(this, "observers", []);
57
+ this.nestedElementQuery = nestedElementQuery;
58
+ this.getTarget = getTarget;
59
+ this.getEditorDom = getEditorDom;
60
+ }
61
+ start(callback) {
62
+ this.stop();
63
+ const target = this.getTarget();
64
+ if (!target) {
65
+ callback({
66
+ status: 'failure',
67
+ reason: EXPERIENCE_FAILURE_REASON.DOM_MUTATION_TARGET_NOT_FOUND
68
+ });
69
+ return;
70
+ }
71
+ const doc = getDocument();
72
+ if (!doc) {
73
+ callback({
74
+ status: 'failure',
75
+ reason: EXPERIENCE_FAILURE_REASON.DOM_MUTATION_TARGET_NOT_FOUND
76
+ });
77
+ return;
78
+ }
79
+ const query = this.nestedElementQuery;
80
+ const onMutation = mutations => {
81
+ for (const mutation of mutations) {
82
+ if (mutation.type !== 'childList') {
83
+ continue;
84
+ }
85
+ for (const node of mutation.addedNodes) {
86
+ if (!(node instanceof HTMLElement)) {
87
+ continue;
88
+ }
89
+ if (popupWithNestedElement(node, query) || node.matches(query) || !!node.querySelector(query)) {
90
+ this.stop();
91
+ callback({
92
+ status: 'success'
93
+ });
94
+ return;
95
+ }
96
+ }
97
+ }
98
+ };
99
+ const observe = el => {
100
+ const observer = new MutationObserver(onMutation);
101
+ observer.observe(el, {
102
+ childList: true
103
+ });
104
+ this.observers.push(observer);
105
+ };
106
+ observe(target);
107
+ for (const wrapper of target.querySelectorAll('[data-editor-popup]')) {
108
+ observe(wrapper);
109
+ }
110
+ const portalContainer = doc.querySelector('.atlaskit-portal-container');
111
+ if (portalContainer instanceof HTMLElement) {
112
+ const observePortal = portal => {
113
+ observe(portal);
114
+ for (const child of portal.children) {
115
+ if (child instanceof HTMLElement) {
116
+ observe(child);
117
+ }
118
+ }
119
+ };
120
+ const containerObserver = new MutationObserver(mutations => {
121
+ for (const mutation of mutations) {
122
+ if (mutation.type !== 'childList') {
123
+ continue;
124
+ }
125
+ for (const node of mutation.addedNodes) {
126
+ if (node instanceof HTMLElement) {
127
+ observePortal(node);
128
+ }
129
+ }
130
+ }
131
+ onMutation(mutations);
132
+ });
133
+ containerObserver.observe(portalContainer, {
134
+ childList: true
135
+ });
136
+ this.observers.push(containerObserver);
137
+ for (const portal of portalContainer.querySelectorAll('.atlaskit-portal')) {
138
+ observePortal(portal);
139
+ }
140
+ }
141
+ const editorDom = this.getEditorDom();
142
+ if (editorDom !== null && editorDom !== void 0 && editorDom.parentElement) {
143
+ observe(editorDom.parentElement);
144
+ }
145
+
146
+ // Two-frame DOM check to handle cases where rendering happens before
147
+ // observers are attached.
148
+ const checkDom = () => {
149
+ if (doc.querySelector(query)) {
150
+ this.stop();
151
+ callback({
152
+ status: 'success'
153
+ });
154
+ return;
155
+ }
156
+ requestAnimationFrame(() => {
157
+ if (doc.querySelector(query)) {
158
+ this.stop();
159
+ callback({
160
+ status: 'success'
161
+ });
162
+ }
163
+ });
164
+ };
165
+ requestAnimationFrame(checkDom);
166
+ }
167
+ stop() {
168
+ for (const observer of this.observers) {
169
+ observer.disconnect();
170
+ }
171
+ this.observers = [];
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Returns the narrow parent DOM element at the current selection, suitable
177
+ * for observing with `{ childList: true }` (no subtree).
178
+ *
179
+ * Uses the resolved position's depth to find the block node at the cursor
180
+ * via `nodeDOM`, then returns its `parentElement` — the container whose
181
+ * direct children change when content is inserted at this position.
182
+ *
183
+ * Falls back to `domAtPos` if `nodeDOM` is unavailable.
184
+ */
185
+ export const getParentDOMAtSelection = editorView => {
186
+ if (!editorView) {
187
+ return null;
188
+ }
189
+ try {
190
+ const {
191
+ selection
192
+ } = editorView.state;
193
+ const $from = selection.$from;
194
+ const parentDepth = Math.max(1, $from.depth);
195
+ const parentPos = $from.before(parentDepth);
196
+ const parentDom = editorView.nodeDOM(parentPos);
197
+ if (parentDom instanceof HTMLElement && parentDom.parentElement) {
198
+ return parentDom.parentElement;
199
+ }
200
+
201
+ // Fallback: use domAtPos
202
+ const {
203
+ node
204
+ } = editorView.domAtPos(selection.from);
205
+ let element = null;
206
+ if (node instanceof HTMLElement) {
207
+ element = node;
208
+ } else if (node instanceof Text) {
209
+ element = node.parentElement;
210
+ }
211
+ if (!element) {
212
+ return null;
213
+ }
214
+ const proseMirrorRoot = editorView.dom;
215
+ if (!(proseMirrorRoot instanceof HTMLElement)) {
216
+ return null;
217
+ }
218
+ if (element === proseMirrorRoot) {
219
+ return proseMirrorRoot;
220
+ }
221
+ if (element.parentElement && proseMirrorRoot.contains(element.parentElement)) {
222
+ return element.parentElement;
223
+ }
224
+ return proseMirrorRoot;
225
+ } catch {
226
+ return null;
227
+ }
228
+ };
229
+
230
+ /**
231
+ * Checks whether a DOM node matches any known node insert marker,
232
+ * either directly or via a nested element (e.g. breakout mark wrapper).
233
+ */
234
+ const matchesNodeInsertMarker = node => {
235
+ if (!(node instanceof HTMLElement)) {
236
+ return false;
237
+ }
238
+ return node.matches(COMBINED_NODE_INSERT_SELECTOR) || !!node.querySelector(COMBINED_NODE_INSERT_SELECTOR);
239
+ };
240
+
241
+ /**
242
+ * Evaluates DOM mutations to detect a node insert action.
243
+ *
244
+ * Uses two strategies:
245
+ * 1. Marker-based: checks `addedNodes` against known node insert selectors.
246
+ * 2. Structure-based: detects element add+remove (block-level replacement).
247
+ */
248
+ export const handleEditorNodeInsertDomMutation = ({
249
+ mutations
250
+ }) => {
251
+ let hasAddedElement = false;
252
+ let hasRemovedElement = false;
253
+ for (const mutation of mutations) {
254
+ if (mutation.type !== 'childList') {
255
+ continue;
256
+ }
257
+ for (const node of mutation.addedNodes) {
258
+ if (matchesNodeInsertMarker(node)) {
259
+ return {
260
+ status: 'success'
261
+ };
262
+ }
263
+ if (node instanceof HTMLElement) {
264
+ hasAddedElement = true;
265
+ }
266
+ }
267
+ for (const node of mutation.removedNodes) {
268
+ if (node instanceof HTMLElement) {
269
+ hasRemovedElement = true;
270
+ }
271
+ }
272
+ }
273
+ if (hasAddedElement && hasRemovedElement) {
274
+ return {
275
+ status: 'success'
276
+ };
277
+ }
278
+ return undefined;
279
+ };