@atlaskit/editor-plugin-synced-block 5.1.0 → 5.1.1
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 +8 -0
- package/dist/cjs/editor-commands/index.js +7 -1
- package/dist/cjs/pm-plugins/experience-tracking/create-reference-experience.js +113 -0
- package/dist/cjs/pm-plugins/experience-tracking/create-source-experience.js +185 -0
- package/dist/cjs/pm-plugins/experience-tracking/get-experience-tracking-plugins.js +31 -0
- package/dist/cjs/syncedBlockPlugin.js +21 -3
- package/dist/cjs/ui/CreateSyncedBlockButton.js +1 -0
- package/dist/cjs/ui/CreateSyncedBlockDropdownItem.js +1 -0
- package/dist/es2019/editor-commands/index.js +7 -1
- package/dist/es2019/pm-plugins/experience-tracking/create-reference-experience.js +105 -0
- package/dist/es2019/pm-plugins/experience-tracking/create-source-experience.js +179 -0
- package/dist/es2019/pm-plugins/experience-tracking/get-experience-tracking-plugins.js +22 -0
- package/dist/es2019/syncedBlockPlugin.js +21 -3
- package/dist/es2019/ui/CreateSyncedBlockButton.js +1 -0
- package/dist/es2019/ui/CreateSyncedBlockDropdownItem.js +1 -0
- package/dist/esm/editor-commands/index.js +7 -1
- package/dist/esm/pm-plugins/experience-tracking/create-reference-experience.js +106 -0
- package/dist/esm/pm-plugins/experience-tracking/create-source-experience.js +178 -0
- package/dist/esm/pm-plugins/experience-tracking/get-experience-tracking-plugins.js +25 -0
- package/dist/esm/syncedBlockPlugin.js +21 -3
- package/dist/esm/ui/CreateSyncedBlockButton.js +1 -0
- package/dist/esm/ui/CreateSyncedBlockDropdownItem.js +1 -0
- package/dist/types/pm-plugins/experience-tracking/create-reference-experience.d.ts +17 -0
- package/dist/types/pm-plugins/experience-tracking/create-source-experience.d.ts +21 -0
- package/dist/types/pm-plugins/experience-tracking/get-experience-tracking-plugins.d.ts +16 -0
- package/dist/types-ts4.5/pm-plugins/experience-tracking/create-reference-experience.d.ts +17 -0
- package/dist/types-ts4.5/pm-plugins/experience-tracking/create-source-experience.d.ts +21 -0
- package/dist/types-ts4.5/pm-plugins/experience-tracking/get-experience-tracking-plugins.d.ts +16 -0
- package/package.json +6 -5
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { bind } from 'bind-event-listener';
|
|
2
|
+
import { ACTION_SUBJECT, ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
|
|
3
|
+
import { Experience, ExperienceCheckDomMutation, ExperienceCheckTimeout, getPopupContainerFromEditorView, popupWithNestedElement } from '@atlaskit/editor-common/experiences';
|
|
4
|
+
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
5
|
+
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
6
|
+
const pluginKey = new PluginKey('createSourceSyncBlockExperience');
|
|
7
|
+
const ABORT_REASON = {
|
|
8
|
+
EDITOR_DESTROYED: 'editor-destroyed'
|
|
9
|
+
};
|
|
10
|
+
const START_METHOD = {
|
|
11
|
+
BLOCK_MENU: 'block-menu',
|
|
12
|
+
PINNED_TOOLBAR: 'pinned-toolbar',
|
|
13
|
+
QUICK_INSERT: 'quick-insert'
|
|
14
|
+
};
|
|
15
|
+
const SYNCED_BLOCK_CREATE_BUTTON_IDS = ['create-synced-block-toolbar-btn', 'create-synced-block-block-menu-btn', 'create-synced-block-quick-insert-btn'];
|
|
16
|
+
const syncedBlockCreateButtonIds = new Set(SYNCED_BLOCK_CREATE_BUTTON_IDS);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* This experience tracks when a source sync block is inserted.
|
|
20
|
+
*
|
|
21
|
+
* Start: When user inserts a sync block via block menu, quick insert or pinned toolbar
|
|
22
|
+
* Success: When the sync block is added to the DOM within 2000ms of start
|
|
23
|
+
* Failure: When 500ms passes without the source sync block being added to the DOM
|
|
24
|
+
*/
|
|
25
|
+
export const getCreateSourceExperiencePlugin = ({
|
|
26
|
+
refs,
|
|
27
|
+
dispatchAnalyticsEvent,
|
|
28
|
+
syncBlockStore
|
|
29
|
+
}) => {
|
|
30
|
+
let popupsTargetEl;
|
|
31
|
+
let editorViewEl;
|
|
32
|
+
const getPopupsTarget = () => {
|
|
33
|
+
if (!popupsTargetEl) {
|
|
34
|
+
popupsTargetEl = refs.popupsMountPoint || refs.wrapperElement || getPopupContainerFromEditorView(editorViewEl);
|
|
35
|
+
}
|
|
36
|
+
return popupsTargetEl;
|
|
37
|
+
};
|
|
38
|
+
const experience = getCreateSourceExperience({
|
|
39
|
+
refs,
|
|
40
|
+
dispatchAnalyticsEvent,
|
|
41
|
+
syncBlockStore
|
|
42
|
+
});
|
|
43
|
+
syncBlockStore.sourceManager.setCreateExperience(experience);
|
|
44
|
+
const unbindClickListener = bind(document, {
|
|
45
|
+
type: 'click',
|
|
46
|
+
listener: event => {
|
|
47
|
+
const target = event.target;
|
|
48
|
+
if (!target) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const button = target.closest('button[data-testid]');
|
|
52
|
+
if (!button || !(button instanceof HTMLButtonElement)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const testId = button.dataset.testid;
|
|
56
|
+
if (!isSyncedBlockCreateButtonId(testId)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
handleButtonClick(testId, experience);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const unbindKeydownListener = bind(document, {
|
|
63
|
+
type: 'keydown',
|
|
64
|
+
listener: event => {
|
|
65
|
+
if (isEnterKey(event.key)) {
|
|
66
|
+
const typeaheadPopup = popupWithNestedElement(getPopupsTarget(), '.fabric-editor-typeahead');
|
|
67
|
+
if (!typeaheadPopup || !(typeaheadPopup instanceof HTMLElement)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const firstItem = typeaheadPopup.querySelector('[role="option"]');
|
|
71
|
+
if (!firstItem || !(firstItem instanceof HTMLElement)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const testId = firstItem.dataset.testid;
|
|
75
|
+
if (testId === 'create-synced-block-quick-insert-btn') {
|
|
76
|
+
experience.start({
|
|
77
|
+
method: START_METHOD.QUICK_INSERT
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
options: {
|
|
83
|
+
capture: true
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return new SafePlugin({
|
|
87
|
+
key: pluginKey,
|
|
88
|
+
view: editorView => {
|
|
89
|
+
editorViewEl = editorView.dom;
|
|
90
|
+
return {
|
|
91
|
+
destroy: () => {
|
|
92
|
+
experience.abort({
|
|
93
|
+
reason: ABORT_REASON.EDITOR_DESTROYED
|
|
94
|
+
});
|
|
95
|
+
unbindClickListener();
|
|
96
|
+
unbindKeydownListener();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
const getCreateSourceExperience = ({
|
|
103
|
+
refs,
|
|
104
|
+
dispatchAnalyticsEvent
|
|
105
|
+
}) => {
|
|
106
|
+
return new Experience(ACTION_SUBJECT.SYNCED_BLOCK, {
|
|
107
|
+
actionSubjectId: ACTION_SUBJECT_ID.SYNCED_BLOCK_CREATE,
|
|
108
|
+
dispatchAnalyticsEvent,
|
|
109
|
+
checks: [new ExperienceCheckTimeout({
|
|
110
|
+
durationMs: 2000
|
|
111
|
+
}), new ExperienceCheckDomMutation({
|
|
112
|
+
onDomMutation: ({
|
|
113
|
+
mutations
|
|
114
|
+
}) => {
|
|
115
|
+
if (mutations.some(isSourceSyncBlockAddedInMutation)) {
|
|
116
|
+
return {
|
|
117
|
+
status: 'success'
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return undefined;
|
|
121
|
+
},
|
|
122
|
+
observeConfig: () => {
|
|
123
|
+
var _refs$containerElemen;
|
|
124
|
+
const proseMirrorElement = (_refs$containerElemen = refs.containerElement) === null || _refs$containerElemen === void 0 ? void 0 : _refs$containerElemen.querySelector('.ProseMirror');
|
|
125
|
+
if (!proseMirrorElement || !(proseMirrorElement instanceof HTMLElement)) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
target: proseMirrorElement,
|
|
130
|
+
options: {
|
|
131
|
+
childList: true
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
})]
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
const isSyncedBlockCreateButtonId = value => {
|
|
139
|
+
return !!value && syncedBlockCreateButtonIds.has(value);
|
|
140
|
+
};
|
|
141
|
+
const handleButtonClick = (testId, experience) => {
|
|
142
|
+
switch (testId) {
|
|
143
|
+
case 'create-synced-block-toolbar-btn':
|
|
144
|
+
experience.start({
|
|
145
|
+
method: START_METHOD.PINNED_TOOLBAR
|
|
146
|
+
});
|
|
147
|
+
break;
|
|
148
|
+
case 'create-synced-block-block-menu-btn':
|
|
149
|
+
experience.start({
|
|
150
|
+
method: START_METHOD.BLOCK_MENU
|
|
151
|
+
});
|
|
152
|
+
break;
|
|
153
|
+
case 'create-synced-block-quick-insert-btn':
|
|
154
|
+
experience.start({
|
|
155
|
+
method: START_METHOD.QUICK_INSERT
|
|
156
|
+
});
|
|
157
|
+
break;
|
|
158
|
+
default:
|
|
159
|
+
{
|
|
160
|
+
// Exhaustiveness check: if a new SyncedBlockToolbarButtonId is added
|
|
161
|
+
// but not handled above, TypeScript will error here.
|
|
162
|
+
const _exhaustiveCheck = testId;
|
|
163
|
+
return _exhaustiveCheck;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
const isEnterKey = key => {
|
|
168
|
+
return key === 'Enter';
|
|
169
|
+
};
|
|
170
|
+
const isSourceSyncBlockAddedInMutation = ({
|
|
171
|
+
type,
|
|
172
|
+
addedNodes
|
|
173
|
+
}) => type === 'childList' && [...addedNodes].some(isSourceSyncBlockNode);
|
|
174
|
+
const isSourceSyncBlockNode = node => {
|
|
175
|
+
if (!(node instanceof HTMLElement)) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
return !!node.querySelector('[data-prosemirror-node-name="bodiedSyncBlock"]');
|
|
179
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getCreateReferenceExperiencePlugin } from "./create-reference-experience";
|
|
2
|
+
import { getCreateSourceExperiencePlugin } from "./create-source-experience";
|
|
3
|
+
export const getExperienceTrackingPlugins = ({
|
|
4
|
+
refs,
|
|
5
|
+
dispatchAnalyticsEvent,
|
|
6
|
+
syncBlockStore
|
|
7
|
+
}) => {
|
|
8
|
+
return [{
|
|
9
|
+
name: 'createReferenceSyncedBlockExperiencePlugin',
|
|
10
|
+
plugin: () => getCreateReferenceExperiencePlugin({
|
|
11
|
+
refs,
|
|
12
|
+
dispatchAnalyticsEvent
|
|
13
|
+
})
|
|
14
|
+
}, {
|
|
15
|
+
name: 'createSourceSyncedBlockExperiencePlugin',
|
|
16
|
+
plugin: () => getCreateSourceExperiencePlugin({
|
|
17
|
+
refs,
|
|
18
|
+
dispatchAnalyticsEvent,
|
|
19
|
+
syncBlockStore
|
|
20
|
+
})
|
|
21
|
+
}];
|
|
22
|
+
};
|
|
@@ -4,8 +4,10 @@ import { blockTypeMessages } from '@atlaskit/editor-common/messages';
|
|
|
4
4
|
import { IconSyncBlock } from '@atlaskit/editor-common/quick-insert';
|
|
5
5
|
import { SyncBlockStoreManager } from '@atlaskit/editor-synced-block-provider';
|
|
6
6
|
import Lozenge from '@atlaskit/lozenge';
|
|
7
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
7
8
|
import { flushBodiedSyncBlocks, flushSyncBlocks } from './editor-actions';
|
|
8
9
|
import { copySyncedBlockReferenceToClipboardEditorCommand, createSyncedBlock } from './editor-commands';
|
|
10
|
+
import { getExperienceTrackingPlugins } from './pm-plugins/experience-tracking/get-experience-tracking-plugins';
|
|
9
11
|
import { createPlugin, syncedBlockPluginKey } from './pm-plugins/main';
|
|
10
12
|
import { getBlockMenuComponents } from './ui/block-menu-components';
|
|
11
13
|
import { DeleteConfirmationModal } from './ui/DeleteConfirmationModal';
|
|
@@ -18,6 +20,7 @@ export const syncedBlockPlugin = ({
|
|
|
18
20
|
api
|
|
19
21
|
}) => {
|
|
20
22
|
var _api$analytics, _api$analytics$action, _api$blockMenu, _config$enableSourceC, _api$toolbar, _config$enableSourceC2;
|
|
23
|
+
const refs = {};
|
|
21
24
|
const syncBlockStore = new SyncBlockStoreManager(config === null || config === void 0 ? void 0 : config.syncBlockDataProvider);
|
|
22
25
|
syncBlockStore.setFireAnalyticsEvent(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);
|
|
23
26
|
api === null || api === void 0 ? void 0 : (_api$blockMenu = api.blockMenu) === null || _api$blockMenu === void 0 ? void 0 : _api$blockMenu.actions.registerBlockMenuComponents(getBlockMenuComponents(api, (_config$enableSourceC = config === null || config === void 0 ? void 0 : config.enableSourceCreation) !== null && _config$enableSourceC !== void 0 ? _config$enableSourceC : false));
|
|
@@ -37,7 +40,14 @@ export const syncedBlockPlugin = ({
|
|
|
37
40
|
return [{
|
|
38
41
|
name: 'syncedBlockPlugin',
|
|
39
42
|
plugin: params => createPlugin(config, params, syncBlockStore, api)
|
|
40
|
-
}
|
|
43
|
+
}, ...(fg('platform_synced_block_dogfooding') ? getExperienceTrackingPlugins({
|
|
44
|
+
refs,
|
|
45
|
+
dispatchAnalyticsEvent: payload => {
|
|
46
|
+
var _api$analytics2;
|
|
47
|
+
return api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions.fireAnalyticsEvent(payload);
|
|
48
|
+
},
|
|
49
|
+
syncBlockStore
|
|
50
|
+
}) : [])];
|
|
41
51
|
},
|
|
42
52
|
commands: {
|
|
43
53
|
copySyncedBlockReferenceToClipboard: () => copySyncedBlockReferenceToClipboardEditorCommand(syncBlockStore, api),
|
|
@@ -88,12 +98,20 @@ export const syncedBlockPlugin = ({
|
|
|
88
98
|
syncBlockStore,
|
|
89
99
|
typeAheadInsert: insert
|
|
90
100
|
});
|
|
91
|
-
}
|
|
101
|
+
},
|
|
102
|
+
testId: fg('platform_synced_block_dogfooding') ? 'create-synced-block-quick-insert-btn' : undefined
|
|
92
103
|
}];
|
|
93
104
|
},
|
|
94
105
|
floatingToolbar: (state, intl) => getToolbarConfig(state, intl, api, syncBlockStore)
|
|
95
106
|
},
|
|
96
|
-
contentComponent: (
|
|
107
|
+
contentComponent: ({
|
|
108
|
+
containerElement,
|
|
109
|
+
wrapperElement,
|
|
110
|
+
popupsMountPoint
|
|
111
|
+
}) => {
|
|
112
|
+
refs.containerElement = containerElement || undefined;
|
|
113
|
+
refs.popupsMountPoint = popupsMountPoint || undefined;
|
|
114
|
+
refs.wrapperElement = wrapperElement || undefined;
|
|
97
115
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SyncBlockRefresher, {
|
|
98
116
|
syncBlockStoreManager: syncBlockStore,
|
|
99
117
|
api: api
|
|
@@ -43,6 +43,7 @@ const CreateSyncedBlockDropdownItem = ({
|
|
|
43
43
|
}),
|
|
44
44
|
onClick: onClick,
|
|
45
45
|
isDisabled: isOffline,
|
|
46
|
+
testId: "create-synced-block-block-menu-btn",
|
|
46
47
|
elemAfter: /*#__PURE__*/React.createElement(Lozenge, {
|
|
47
48
|
appearance: "new"
|
|
48
49
|
}, formatMessage(blockMenuMessages.newLozenge))
|
|
@@ -2,6 +2,7 @@ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit
|
|
|
2
2
|
import { copyDomNode, toDOM } from '@atlaskit/editor-common/copy-button';
|
|
3
3
|
import { TextSelection } from '@atlaskit/editor-prosemirror/state';
|
|
4
4
|
import { findSelectedNodeOfType, removeParentNodeOfType, removeSelectedNode, safeInsert } from '@atlaskit/editor-prosemirror/utils';
|
|
5
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
5
6
|
import { syncedBlockPluginKey } from '../pm-plugins/main';
|
|
6
7
|
import { canBeConvertedToSyncBlock, findSyncBlock, findSyncBlockOrBodiedSyncBlock, isBodiedSyncBlockNode } from '../pm-plugins/utils/utils';
|
|
7
8
|
import { FLAG_ID } from '../types';
|
|
@@ -33,7 +34,12 @@ export var createSyncedBlock = function createSyncedBlock(_ref) {
|
|
|
33
34
|
} else {
|
|
34
35
|
var conversionInfo = canBeConvertedToSyncBlock(tr.selection);
|
|
35
36
|
if (!conversionInfo) {
|
|
36
|
-
|
|
37
|
+
if (fg('platform_synced_block_dogfooding')) {
|
|
38
|
+
var _syncBlockStore$sourc;
|
|
39
|
+
(_syncBlockStore$sourc = syncBlockStore.sourceManager.createExperience) === null || _syncBlockStore$sourc === void 0 || _syncBlockStore$sourc.failure({
|
|
40
|
+
reason: 'Selection is not allowed to be converted to sync block'
|
|
41
|
+
});
|
|
42
|
+
}
|
|
37
43
|
return false;
|
|
38
44
|
}
|
|
39
45
|
var _attrs = syncBlockStore.sourceManager.generateBodiedSyncBlockAttrs();
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
2
|
+
import { ACTION_SUBJECT, ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
|
|
3
|
+
import { Experience, ExperienceCheckDomMutation, ExperienceCheckTimeout } from '@atlaskit/editor-common/experiences';
|
|
4
|
+
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
5
|
+
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
6
|
+
var isPastedFromFabricEditor = function isPastedFromFabricEditor(html) {
|
|
7
|
+
return !!html && html.indexOf('data-pm-slice="') >= 0;
|
|
8
|
+
};
|
|
9
|
+
var pluginKey = new PluginKey('createReferenceSyncBlockExperience');
|
|
10
|
+
var START_METHOD = {
|
|
11
|
+
PASTE: 'paste'
|
|
12
|
+
};
|
|
13
|
+
var ABORT_REASON = {
|
|
14
|
+
EDITOR_DESTROYED: 'editor-destroyed'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* This experience tracks when a reference sync block is inserted.
|
|
19
|
+
*
|
|
20
|
+
* Start: When user pastes a sync block from editor and createSyncedBlock is called
|
|
21
|
+
* Success: When the sync block is added to the DOM within 500ms of start
|
|
22
|
+
* Failure: When 500ms passes without the reference sync block being added to the DOM
|
|
23
|
+
*/
|
|
24
|
+
export var getCreateReferenceExperiencePlugin = function getCreateReferenceExperiencePlugin(_ref) {
|
|
25
|
+
var refs = _ref.refs,
|
|
26
|
+
dispatchAnalyticsEvent = _ref.dispatchAnalyticsEvent;
|
|
27
|
+
var experience = getCreateReferenceExperience({
|
|
28
|
+
refs: refs,
|
|
29
|
+
dispatchAnalyticsEvent: dispatchAnalyticsEvent
|
|
30
|
+
});
|
|
31
|
+
return new SafePlugin({
|
|
32
|
+
key: pluginKey,
|
|
33
|
+
view: function view() {
|
|
34
|
+
return {
|
|
35
|
+
destroy: function destroy() {
|
|
36
|
+
experience.abort({
|
|
37
|
+
reason: ABORT_REASON.EDITOR_DESTROYED
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
props: {
|
|
43
|
+
handlePaste: function handlePaste(_view, rawEvent, slice) {
|
|
44
|
+
var _event$clipboardData;
|
|
45
|
+
var event = rawEvent;
|
|
46
|
+
var html = (_event$clipboardData = event.clipboardData) === null || _event$clipboardData === void 0 ? void 0 : _event$clipboardData.getData('text/html');
|
|
47
|
+
|
|
48
|
+
// do not start on paste from renderer, because this flattens the content and does not create a reference block
|
|
49
|
+
if (isPastedFromFabricEditor(html)) {
|
|
50
|
+
slice.content.forEach(function (node) {
|
|
51
|
+
if (node.type.name === 'syncBlock' || node.type.name === 'bodiedSyncBlock') {
|
|
52
|
+
experience.start({
|
|
53
|
+
method: START_METHOD.PASTE
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
var getCreateReferenceExperience = function getCreateReferenceExperience(_ref2) {
|
|
63
|
+
var refs = _ref2.refs,
|
|
64
|
+
dispatchAnalyticsEvent = _ref2.dispatchAnalyticsEvent;
|
|
65
|
+
return new Experience(ACTION_SUBJECT.SYNCED_BLOCK, {
|
|
66
|
+
actionSubjectId: ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_CREATE,
|
|
67
|
+
dispatchAnalyticsEvent: dispatchAnalyticsEvent,
|
|
68
|
+
checks: [new ExperienceCheckTimeout({
|
|
69
|
+
durationMs: 500
|
|
70
|
+
}), new ExperienceCheckDomMutation({
|
|
71
|
+
onDomMutation: function onDomMutation(_ref3) {
|
|
72
|
+
var mutations = _ref3.mutations;
|
|
73
|
+
if (mutations.some(isReferenceSyncBlockAddedInMutation)) {
|
|
74
|
+
return {
|
|
75
|
+
status: 'success'
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
},
|
|
80
|
+
observeConfig: function observeConfig() {
|
|
81
|
+
var _refs$containerElemen;
|
|
82
|
+
var proseMirrorElement = (_refs$containerElemen = refs.containerElement) === null || _refs$containerElemen === void 0 ? void 0 : _refs$containerElemen.querySelector('.ProseMirror');
|
|
83
|
+
if (!proseMirrorElement || !(proseMirrorElement instanceof HTMLElement)) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
target: proseMirrorElement,
|
|
88
|
+
options: {
|
|
89
|
+
childList: true
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
})]
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
var isReferenceSyncBlockAddedInMutation = function isReferenceSyncBlockAddedInMutation(_ref4) {
|
|
97
|
+
var type = _ref4.type,
|
|
98
|
+
addedNodes = _ref4.addedNodes;
|
|
99
|
+
return type === 'childList' && _toConsumableArray(addedNodes).some(isReferenceSyncBlockNode);
|
|
100
|
+
};
|
|
101
|
+
var isReferenceSyncBlockNode = function isReferenceSyncBlockNode(node) {
|
|
102
|
+
if (!(node instanceof HTMLElement)) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
return !!node.querySelector('[data-prosemirror-node-name="syncBlock"]');
|
|
106
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
2
|
+
import { bind } from 'bind-event-listener';
|
|
3
|
+
import { ACTION_SUBJECT, ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
|
|
4
|
+
import { Experience, ExperienceCheckDomMutation, ExperienceCheckTimeout, getPopupContainerFromEditorView, popupWithNestedElement } from '@atlaskit/editor-common/experiences';
|
|
5
|
+
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
6
|
+
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
7
|
+
var pluginKey = new PluginKey('createSourceSyncBlockExperience');
|
|
8
|
+
var ABORT_REASON = {
|
|
9
|
+
EDITOR_DESTROYED: 'editor-destroyed'
|
|
10
|
+
};
|
|
11
|
+
var START_METHOD = {
|
|
12
|
+
BLOCK_MENU: 'block-menu',
|
|
13
|
+
PINNED_TOOLBAR: 'pinned-toolbar',
|
|
14
|
+
QUICK_INSERT: 'quick-insert'
|
|
15
|
+
};
|
|
16
|
+
var SYNCED_BLOCK_CREATE_BUTTON_IDS = ['create-synced-block-toolbar-btn', 'create-synced-block-block-menu-btn', 'create-synced-block-quick-insert-btn'];
|
|
17
|
+
var syncedBlockCreateButtonIds = new Set(SYNCED_BLOCK_CREATE_BUTTON_IDS);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* This experience tracks when a source sync block is inserted.
|
|
21
|
+
*
|
|
22
|
+
* Start: When user inserts a sync block via block menu, quick insert or pinned toolbar
|
|
23
|
+
* Success: When the sync block is added to the DOM within 2000ms of start
|
|
24
|
+
* Failure: When 500ms passes without the source sync block being added to the DOM
|
|
25
|
+
*/
|
|
26
|
+
export var getCreateSourceExperiencePlugin = function getCreateSourceExperiencePlugin(_ref) {
|
|
27
|
+
var refs = _ref.refs,
|
|
28
|
+
dispatchAnalyticsEvent = _ref.dispatchAnalyticsEvent,
|
|
29
|
+
syncBlockStore = _ref.syncBlockStore;
|
|
30
|
+
var popupsTargetEl;
|
|
31
|
+
var editorViewEl;
|
|
32
|
+
var getPopupsTarget = function getPopupsTarget() {
|
|
33
|
+
if (!popupsTargetEl) {
|
|
34
|
+
popupsTargetEl = refs.popupsMountPoint || refs.wrapperElement || getPopupContainerFromEditorView(editorViewEl);
|
|
35
|
+
}
|
|
36
|
+
return popupsTargetEl;
|
|
37
|
+
};
|
|
38
|
+
var experience = getCreateSourceExperience({
|
|
39
|
+
refs: refs,
|
|
40
|
+
dispatchAnalyticsEvent: dispatchAnalyticsEvent,
|
|
41
|
+
syncBlockStore: syncBlockStore
|
|
42
|
+
});
|
|
43
|
+
syncBlockStore.sourceManager.setCreateExperience(experience);
|
|
44
|
+
var unbindClickListener = bind(document, {
|
|
45
|
+
type: 'click',
|
|
46
|
+
listener: function listener(event) {
|
|
47
|
+
var target = event.target;
|
|
48
|
+
if (!target) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
var button = target.closest('button[data-testid]');
|
|
52
|
+
if (!button || !(button instanceof HTMLButtonElement)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
var testId = button.dataset.testid;
|
|
56
|
+
if (!isSyncedBlockCreateButtonId(testId)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
handleButtonClick(testId, experience);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
var unbindKeydownListener = bind(document, {
|
|
63
|
+
type: 'keydown',
|
|
64
|
+
listener: function listener(event) {
|
|
65
|
+
if (isEnterKey(event.key)) {
|
|
66
|
+
var typeaheadPopup = popupWithNestedElement(getPopupsTarget(), '.fabric-editor-typeahead');
|
|
67
|
+
if (!typeaheadPopup || !(typeaheadPopup instanceof HTMLElement)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
var firstItem = typeaheadPopup.querySelector('[role="option"]');
|
|
71
|
+
if (!firstItem || !(firstItem instanceof HTMLElement)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
var testId = firstItem.dataset.testid;
|
|
75
|
+
if (testId === 'create-synced-block-quick-insert-btn') {
|
|
76
|
+
experience.start({
|
|
77
|
+
method: START_METHOD.QUICK_INSERT
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
options: {
|
|
83
|
+
capture: true
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return new SafePlugin({
|
|
87
|
+
key: pluginKey,
|
|
88
|
+
view: function view(editorView) {
|
|
89
|
+
editorViewEl = editorView.dom;
|
|
90
|
+
return {
|
|
91
|
+
destroy: function destroy() {
|
|
92
|
+
experience.abort({
|
|
93
|
+
reason: ABORT_REASON.EDITOR_DESTROYED
|
|
94
|
+
});
|
|
95
|
+
unbindClickListener();
|
|
96
|
+
unbindKeydownListener();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
var getCreateSourceExperience = function getCreateSourceExperience(_ref2) {
|
|
103
|
+
var refs = _ref2.refs,
|
|
104
|
+
dispatchAnalyticsEvent = _ref2.dispatchAnalyticsEvent;
|
|
105
|
+
return new Experience(ACTION_SUBJECT.SYNCED_BLOCK, {
|
|
106
|
+
actionSubjectId: ACTION_SUBJECT_ID.SYNCED_BLOCK_CREATE,
|
|
107
|
+
dispatchAnalyticsEvent: dispatchAnalyticsEvent,
|
|
108
|
+
checks: [new ExperienceCheckTimeout({
|
|
109
|
+
durationMs: 2000
|
|
110
|
+
}), new ExperienceCheckDomMutation({
|
|
111
|
+
onDomMutation: function onDomMutation(_ref3) {
|
|
112
|
+
var mutations = _ref3.mutations;
|
|
113
|
+
if (mutations.some(isSourceSyncBlockAddedInMutation)) {
|
|
114
|
+
return {
|
|
115
|
+
status: 'success'
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
119
|
+
},
|
|
120
|
+
observeConfig: function observeConfig() {
|
|
121
|
+
var _refs$containerElemen;
|
|
122
|
+
var proseMirrorElement = (_refs$containerElemen = refs.containerElement) === null || _refs$containerElemen === void 0 ? void 0 : _refs$containerElemen.querySelector('.ProseMirror');
|
|
123
|
+
if (!proseMirrorElement || !(proseMirrorElement instanceof HTMLElement)) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
target: proseMirrorElement,
|
|
128
|
+
options: {
|
|
129
|
+
childList: true
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
})]
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
var isSyncedBlockCreateButtonId = function isSyncedBlockCreateButtonId(value) {
|
|
137
|
+
return !!value && syncedBlockCreateButtonIds.has(value);
|
|
138
|
+
};
|
|
139
|
+
var handleButtonClick = function handleButtonClick(testId, experience) {
|
|
140
|
+
switch (testId) {
|
|
141
|
+
case 'create-synced-block-toolbar-btn':
|
|
142
|
+
experience.start({
|
|
143
|
+
method: START_METHOD.PINNED_TOOLBAR
|
|
144
|
+
});
|
|
145
|
+
break;
|
|
146
|
+
case 'create-synced-block-block-menu-btn':
|
|
147
|
+
experience.start({
|
|
148
|
+
method: START_METHOD.BLOCK_MENU
|
|
149
|
+
});
|
|
150
|
+
break;
|
|
151
|
+
case 'create-synced-block-quick-insert-btn':
|
|
152
|
+
experience.start({
|
|
153
|
+
method: START_METHOD.QUICK_INSERT
|
|
154
|
+
});
|
|
155
|
+
break;
|
|
156
|
+
default:
|
|
157
|
+
{
|
|
158
|
+
// Exhaustiveness check: if a new SyncedBlockToolbarButtonId is added
|
|
159
|
+
// but not handled above, TypeScript will error here.
|
|
160
|
+
var _exhaustiveCheck = testId;
|
|
161
|
+
return _exhaustiveCheck;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
var isEnterKey = function isEnterKey(key) {
|
|
166
|
+
return key === 'Enter';
|
|
167
|
+
};
|
|
168
|
+
var isSourceSyncBlockAddedInMutation = function isSourceSyncBlockAddedInMutation(_ref4) {
|
|
169
|
+
var type = _ref4.type,
|
|
170
|
+
addedNodes = _ref4.addedNodes;
|
|
171
|
+
return type === 'childList' && _toConsumableArray(addedNodes).some(isSourceSyncBlockNode);
|
|
172
|
+
};
|
|
173
|
+
var isSourceSyncBlockNode = function isSourceSyncBlockNode(node) {
|
|
174
|
+
if (!(node instanceof HTMLElement)) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
return !!node.querySelector('[data-prosemirror-node-name="bodiedSyncBlock"]');
|
|
178
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getCreateReferenceExperiencePlugin } from "./create-reference-experience";
|
|
2
|
+
import { getCreateSourceExperiencePlugin } from "./create-source-experience";
|
|
3
|
+
export var getExperienceTrackingPlugins = function getExperienceTrackingPlugins(_ref) {
|
|
4
|
+
var refs = _ref.refs,
|
|
5
|
+
dispatchAnalyticsEvent = _ref.dispatchAnalyticsEvent,
|
|
6
|
+
syncBlockStore = _ref.syncBlockStore;
|
|
7
|
+
return [{
|
|
8
|
+
name: 'createReferenceSyncedBlockExperiencePlugin',
|
|
9
|
+
plugin: function plugin() {
|
|
10
|
+
return getCreateReferenceExperiencePlugin({
|
|
11
|
+
refs: refs,
|
|
12
|
+
dispatchAnalyticsEvent: dispatchAnalyticsEvent
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}, {
|
|
16
|
+
name: 'createSourceSyncedBlockExperiencePlugin',
|
|
17
|
+
plugin: function plugin() {
|
|
18
|
+
return getCreateSourceExperiencePlugin({
|
|
19
|
+
refs: refs,
|
|
20
|
+
dispatchAnalyticsEvent: dispatchAnalyticsEvent,
|
|
21
|
+
syncBlockStore: syncBlockStore
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}];
|
|
25
|
+
};
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
1
2
|
import React from 'react';
|
|
2
3
|
import { bodiedSyncBlock, syncBlock } from '@atlaskit/adf-schema';
|
|
3
4
|
import { blockTypeMessages } from '@atlaskit/editor-common/messages';
|
|
4
5
|
import { IconSyncBlock } from '@atlaskit/editor-common/quick-insert';
|
|
5
6
|
import { SyncBlockStoreManager } from '@atlaskit/editor-synced-block-provider';
|
|
6
7
|
import Lozenge from '@atlaskit/lozenge';
|
|
8
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
7
9
|
import { flushBodiedSyncBlocks as _flushBodiedSyncBlocks, flushSyncBlocks } from './editor-actions';
|
|
8
10
|
import { copySyncedBlockReferenceToClipboardEditorCommand, createSyncedBlock } from './editor-commands';
|
|
11
|
+
import { getExperienceTrackingPlugins } from './pm-plugins/experience-tracking/get-experience-tracking-plugins';
|
|
9
12
|
import { createPlugin, syncedBlockPluginKey } from './pm-plugins/main';
|
|
10
13
|
import { getBlockMenuComponents } from './ui/block-menu-components';
|
|
11
14
|
import { DeleteConfirmationModal } from './ui/DeleteConfirmationModal';
|
|
@@ -17,6 +20,7 @@ export var syncedBlockPlugin = function syncedBlockPlugin(_ref) {
|
|
|
17
20
|
var _api$analytics, _api$blockMenu, _config$enableSourceC, _api$toolbar, _config$enableSourceC2;
|
|
18
21
|
var config = _ref.config,
|
|
19
22
|
api = _ref.api;
|
|
23
|
+
var refs = {};
|
|
20
24
|
var syncBlockStore = new SyncBlockStoreManager(config === null || config === void 0 ? void 0 : config.syncBlockDataProvider);
|
|
21
25
|
syncBlockStore.setFireAnalyticsEvent(api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || (_api$analytics = _api$analytics.actions) === null || _api$analytics === void 0 ? void 0 : _api$analytics.fireAnalyticsEvent);
|
|
22
26
|
api === null || api === void 0 || (_api$blockMenu = api.blockMenu) === null || _api$blockMenu === void 0 || _api$blockMenu.actions.registerBlockMenuComponents(getBlockMenuComponents(api, (_config$enableSourceC = config === null || config === void 0 ? void 0 : config.enableSourceCreation) !== null && _config$enableSourceC !== void 0 ? _config$enableSourceC : false));
|
|
@@ -38,7 +42,14 @@ export var syncedBlockPlugin = function syncedBlockPlugin(_ref) {
|
|
|
38
42
|
plugin: function plugin(params) {
|
|
39
43
|
return createPlugin(config, params, syncBlockStore, api);
|
|
40
44
|
}
|
|
41
|
-
}]
|
|
45
|
+
}].concat(_toConsumableArray(fg('platform_synced_block_dogfooding') ? getExperienceTrackingPlugins({
|
|
46
|
+
refs: refs,
|
|
47
|
+
dispatchAnalyticsEvent: function dispatchAnalyticsEvent(payload) {
|
|
48
|
+
var _api$analytics2;
|
|
49
|
+
return api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions.fireAnalyticsEvent(payload);
|
|
50
|
+
},
|
|
51
|
+
syncBlockStore: syncBlockStore
|
|
52
|
+
}) : []));
|
|
42
53
|
},
|
|
43
54
|
commands: {
|
|
44
55
|
copySyncedBlockReferenceToClipboard: function copySyncedBlockReferenceToClipboard() {
|
|
@@ -93,14 +104,21 @@ export var syncedBlockPlugin = function syncedBlockPlugin(_ref) {
|
|
|
93
104
|
syncBlockStore: syncBlockStore,
|
|
94
105
|
typeAheadInsert: insert
|
|
95
106
|
});
|
|
96
|
-
}
|
|
107
|
+
},
|
|
108
|
+
testId: fg('platform_synced_block_dogfooding') ? 'create-synced-block-quick-insert-btn' : undefined
|
|
97
109
|
}];
|
|
98
110
|
},
|
|
99
111
|
floatingToolbar: function floatingToolbar(state, intl) {
|
|
100
112
|
return getToolbarConfig(state, intl, api, syncBlockStore);
|
|
101
113
|
}
|
|
102
114
|
},
|
|
103
|
-
contentComponent: function contentComponent() {
|
|
115
|
+
contentComponent: function contentComponent(_ref4) {
|
|
116
|
+
var containerElement = _ref4.containerElement,
|
|
117
|
+
wrapperElement = _ref4.wrapperElement,
|
|
118
|
+
popupsMountPoint = _ref4.popupsMountPoint;
|
|
119
|
+
refs.containerElement = containerElement || undefined;
|
|
120
|
+
refs.popupsMountPoint = popupsMountPoint || undefined;
|
|
121
|
+
refs.wrapperElement = wrapperElement || undefined;
|
|
104
122
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SyncBlockRefresher, {
|
|
105
123
|
syncBlockStoreManager: syncBlockStore,
|
|
106
124
|
api: api
|