@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.
- package/CHANGELOG.md +19 -0
- package/dist/cjs/insertBlockPlugin.js +17 -0
- package/dist/cjs/pm-plugins/experiences/toolbar-action-experiences.js +183 -0
- package/dist/cjs/pm-plugins/experiences/toolbar-experience-utils.js +409 -0
- package/dist/cjs/ui/ElementBrowser/InsertMenu.js +18 -5
- package/dist/cjs/ui/toolbar-components/EmojiButton.js +3 -1
- package/dist/cjs/ui/toolbar-components/ImageButton.js +2 -1
- package/dist/cjs/ui/toolbar-components/InsertButton.js +2 -1
- package/dist/cjs/ui/toolbar-components/LayoutButton.js +2 -1
- package/dist/cjs/ui/toolbar-components/MediaButton.js +3 -1
- package/dist/cjs/ui/toolbar-components/MentionButton.js +3 -1
- package/dist/cjs/ui/toolbar-components/TableButton.js +1 -1
- package/dist/cjs/ui/toolbar-components/TableSizePicker.js +3 -1
- package/dist/cjs/ui/toolbar-components/TaskListButton.js +2 -1
- package/dist/es2019/insertBlockPlugin.js +15 -0
- package/dist/es2019/pm-plugins/experiences/toolbar-action-experiences.js +173 -0
- package/dist/es2019/pm-plugins/experiences/toolbar-experience-utils.js +279 -0
- package/dist/es2019/ui/ElementBrowser/InsertMenu.js +15 -4
- package/dist/es2019/ui/toolbar-components/EmojiButton.js +3 -1
- package/dist/es2019/ui/toolbar-components/ImageButton.js +3 -2
- package/dist/es2019/ui/toolbar-components/InsertButton.js +3 -2
- package/dist/es2019/ui/toolbar-components/LayoutButton.js +3 -2
- package/dist/es2019/ui/toolbar-components/MediaButton.js +3 -1
- package/dist/es2019/ui/toolbar-components/MentionButton.js +3 -1
- package/dist/es2019/ui/toolbar-components/TableButton.js +2 -2
- package/dist/es2019/ui/toolbar-components/TableSizePicker.js +3 -1
- package/dist/es2019/ui/toolbar-components/TaskListButton.js +3 -2
- package/dist/esm/insertBlockPlugin.js +17 -0
- package/dist/esm/pm-plugins/experiences/toolbar-action-experiences.js +177 -0
- package/dist/esm/pm-plugins/experiences/toolbar-experience-utils.js +403 -0
- package/dist/esm/ui/ElementBrowser/InsertMenu.js +17 -4
- package/dist/esm/ui/toolbar-components/EmojiButton.js +3 -1
- package/dist/esm/ui/toolbar-components/ImageButton.js +3 -2
- package/dist/esm/ui/toolbar-components/InsertButton.js +3 -2
- package/dist/esm/ui/toolbar-components/LayoutButton.js +3 -2
- package/dist/esm/ui/toolbar-components/MediaButton.js +3 -1
- package/dist/esm/ui/toolbar-components/MentionButton.js +3 -1
- package/dist/esm/ui/toolbar-components/TableButton.js +2 -2
- package/dist/esm/ui/toolbar-components/TableSizePicker.js +3 -1
- package/dist/esm/ui/toolbar-components/TaskListButton.js +3 -2
- package/dist/types/pm-plugins/experiences/toolbar-action-experiences.d.ts +10 -0
- package/dist/types/pm-plugins/experiences/toolbar-experience-utils.d.ts +57 -0
- package/dist/types/ui/ElementBrowser/InsertMenu.d.ts +5 -2
- package/dist/types-ts4.5/pm-plugins/experiences/toolbar-action-experiences.d.ts +10 -0
- package/dist/types-ts4.5/pm-plugins/experiences/toolbar-experience-utils.d.ts +57 -0
- package/dist/types-ts4.5/ui/ElementBrowser/InsertMenu.d.ts +5 -2
- 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.
|
|
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
|
|
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
|
|
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
|
|
174
|
-
result =
|
|
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
|
};
|
|
@@ -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
|
};
|
|
@@ -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
|
+
};
|