@atlaskit/editor-plugin-synced-block 5.0.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/afm-cc/tsconfig.json +3 -0
  3. package/afm-jira/tsconfig.json +3 -0
  4. package/afm-products/tsconfig.json +3 -0
  5. package/dist/cjs/editor-commands/index.js +7 -1
  6. package/dist/cjs/nodeviews/syncedBlock.js +7 -3
  7. package/dist/cjs/pm-plugins/experience-tracking/create-reference-experience.js +113 -0
  8. package/dist/cjs/pm-plugins/experience-tracking/create-source-experience.js +185 -0
  9. package/dist/cjs/pm-plugins/experience-tracking/get-experience-tracking-plugins.js +31 -0
  10. package/dist/cjs/pm-plugins/main.js +18 -1
  11. package/dist/cjs/syncedBlockPlugin.js +21 -3
  12. package/dist/cjs/ui/CreateSyncedBlockButton.js +1 -0
  13. package/dist/cjs/ui/CreateSyncedBlockDropdownItem.js +1 -0
  14. package/dist/es2019/editor-commands/index.js +7 -1
  15. package/dist/es2019/nodeviews/syncedBlock.js +6 -3
  16. package/dist/es2019/pm-plugins/experience-tracking/create-reference-experience.js +105 -0
  17. package/dist/es2019/pm-plugins/experience-tracking/create-source-experience.js +179 -0
  18. package/dist/es2019/pm-plugins/experience-tracking/get-experience-tracking-plugins.js +22 -0
  19. package/dist/es2019/pm-plugins/main.js +18 -1
  20. package/dist/es2019/syncedBlockPlugin.js +21 -3
  21. package/dist/es2019/ui/CreateSyncedBlockButton.js +1 -0
  22. package/dist/es2019/ui/CreateSyncedBlockDropdownItem.js +1 -0
  23. package/dist/esm/editor-commands/index.js +7 -1
  24. package/dist/esm/nodeviews/syncedBlock.js +6 -2
  25. package/dist/esm/pm-plugins/experience-tracking/create-reference-experience.js +106 -0
  26. package/dist/esm/pm-plugins/experience-tracking/create-source-experience.js +178 -0
  27. package/dist/esm/pm-plugins/experience-tracking/get-experience-tracking-plugins.js +25 -0
  28. package/dist/esm/pm-plugins/main.js +18 -1
  29. package/dist/esm/syncedBlockPlugin.js +21 -3
  30. package/dist/esm/ui/CreateSyncedBlockButton.js +1 -0
  31. package/dist/esm/ui/CreateSyncedBlockDropdownItem.js +1 -0
  32. package/dist/types/nodeviews/syncedBlock.d.ts +13 -0
  33. package/dist/types/pm-plugins/experience-tracking/create-reference-experience.d.ts +17 -0
  34. package/dist/types/pm-plugins/experience-tracking/create-source-experience.d.ts +21 -0
  35. package/dist/types/pm-plugins/experience-tracking/get-experience-tracking-plugins.d.ts +16 -0
  36. package/dist/types-ts4.5/nodeviews/syncedBlock.d.ts +13 -0
  37. package/dist/types-ts4.5/pm-plugins/experience-tracking/create-reference-experience.d.ts +17 -0
  38. package/dist/types-ts4.5/pm-plugins/experience-tracking/create-source-experience.d.ts +21 -0
  39. package/dist/types-ts4.5/pm-plugins/experience-tracking/get-experience-tracking-plugins.d.ts +16 -0
  40. package/package.json +12 -5
@@ -0,0 +1,105 @@
1
+ import { ACTION_SUBJECT, ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
2
+ import { Experience, ExperienceCheckDomMutation, ExperienceCheckTimeout } from '@atlaskit/editor-common/experiences';
3
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
4
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
5
+ const isPastedFromFabricEditor = html => !!html && html.indexOf('data-pm-slice="') >= 0;
6
+ const pluginKey = new PluginKey('createReferenceSyncBlockExperience');
7
+ const START_METHOD = {
8
+ PASTE: 'paste'
9
+ };
10
+ const ABORT_REASON = {
11
+ EDITOR_DESTROYED: 'editor-destroyed'
12
+ };
13
+
14
+ /**
15
+ * This experience tracks when a reference sync block is inserted.
16
+ *
17
+ * Start: When user pastes a sync block from editor and createSyncedBlock is called
18
+ * Success: When the sync block is added to the DOM within 500ms of start
19
+ * Failure: When 500ms passes without the reference sync block being added to the DOM
20
+ */
21
+ export const getCreateReferenceExperiencePlugin = ({
22
+ refs,
23
+ dispatchAnalyticsEvent
24
+ }) => {
25
+ const experience = getCreateReferenceExperience({
26
+ refs,
27
+ dispatchAnalyticsEvent
28
+ });
29
+ return new SafePlugin({
30
+ key: pluginKey,
31
+ view: () => {
32
+ return {
33
+ destroy: () => {
34
+ experience.abort({
35
+ reason: ABORT_REASON.EDITOR_DESTROYED
36
+ });
37
+ }
38
+ };
39
+ },
40
+ props: {
41
+ handlePaste: (_view, rawEvent, slice) => {
42
+ var _event$clipboardData;
43
+ const event = rawEvent;
44
+ const html = (_event$clipboardData = event.clipboardData) === null || _event$clipboardData === void 0 ? void 0 : _event$clipboardData.getData('text/html');
45
+
46
+ // do not start on paste from renderer, because this flattens the content and does not create a reference block
47
+ if (isPastedFromFabricEditor(html)) {
48
+ slice.content.forEach(node => {
49
+ if (node.type.name === 'syncBlock' || node.type.name === 'bodiedSyncBlock') {
50
+ experience.start({
51
+ method: START_METHOD.PASTE
52
+ });
53
+ }
54
+ });
55
+ }
56
+ }
57
+ }
58
+ });
59
+ };
60
+ const getCreateReferenceExperience = ({
61
+ refs,
62
+ dispatchAnalyticsEvent
63
+ }) => {
64
+ return new Experience(ACTION_SUBJECT.SYNCED_BLOCK, {
65
+ actionSubjectId: ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_CREATE,
66
+ dispatchAnalyticsEvent,
67
+ checks: [new ExperienceCheckTimeout({
68
+ durationMs: 500
69
+ }), new ExperienceCheckDomMutation({
70
+ onDomMutation: ({
71
+ mutations
72
+ }) => {
73
+ if (mutations.some(isReferenceSyncBlockAddedInMutation)) {
74
+ return {
75
+ status: 'success'
76
+ };
77
+ }
78
+ return undefined;
79
+ },
80
+ observeConfig: () => {
81
+ var _refs$containerElemen;
82
+ const 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
+ const isReferenceSyncBlockAddedInMutation = ({
97
+ type,
98
+ addedNodes
99
+ }) => type === 'childList' && [...addedNodes].some(isReferenceSyncBlockNode);
100
+ const isReferenceSyncBlockNode = node => {
101
+ if (!(node instanceof HTMLElement)) {
102
+ return false;
103
+ }
104
+ return !!node.querySelector('[data-prosemirror-node-name="syncBlock"]');
105
+ };
@@ -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
+ };
@@ -6,8 +6,10 @@ import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity';
6
6
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
7
7
  import { DecorationSet, Decoration } from '@atlaskit/editor-prosemirror/view';
8
8
  import { convertPMNodesToSyncBlockNodes, rebaseTransaction } from '@atlaskit/editor-synced-block-provider';
9
+ import { fg } from '@atlaskit/platform-feature-flags';
9
10
  import { lazyBodiedSyncBlockView } from '../nodeviews/bodiedLazySyncedBlock';
10
11
  import { lazySyncBlockView } from '../nodeviews/lazySyncedBlock';
12
+ import { SyncBlock as SyncBlockView } from '../nodeviews/syncedBlock';
11
13
  import { FLAG_ID } from '../types';
12
14
  import { handleBodiedSyncBlockRemoval } from './utils/handle-bodied-sync-block-removal';
13
15
  import { shouldIgnoreDomEvent } from './utils/ignore-dom-event';
@@ -72,7 +74,22 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
72
74
  },
73
75
  props: {
74
76
  nodeViews: {
75
- syncBlock: lazySyncBlockView({
77
+ syncBlock: fg('platform_synced_block_dogfooding') ? (node, view, getPos, _decorations) => {
78
+ // To support SSR, pass `syncBlockStore` here
79
+ // and do not use lazy loading.
80
+ // We cannot start rendering and then load `syncBlockStore` asynchronously,
81
+ // because obtaining it is asynchronous (sharedPluginState.currentState() is delayed).
82
+ return new SyncBlockView({
83
+ api,
84
+ options,
85
+ node,
86
+ view,
87
+ getPos,
88
+ portalProviderAPI: pmPluginFactoryParams.portalProviderAPI,
89
+ eventDispatcher: pmPluginFactoryParams.eventDispatcher,
90
+ syncBlockStore: syncBlockStore
91
+ }).init();
92
+ } : lazySyncBlockView({
76
93
  options,
77
94
  pmPluginFactoryParams,
78
95
  api
@@ -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
@@ -45,6 +45,7 @@ export const CreateSyncedBlockButton = ({
45
45
  label: ""
46
46
  }),
47
47
  isDisabled: isDisabled,
48
+ testId: "create-synced-block-toolbar-btn",
48
49
  onClick: onClick
49
50
  }));
50
51
  };
@@ -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
- // TODO: EDITOR-1665 - Raise an error analytics event
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();
@@ -11,14 +11,16 @@ import React from 'react';
11
11
  import ReactNodeView from '@atlaskit/editor-common/react-node-view';
12
12
  import { SyncBlockSharedCssClassName } from '@atlaskit/editor-common/sync-block';
13
13
  import { useFetchSyncBlockData as _useFetchSyncBlockData, useFetchSyncBlockTitle as _useFetchSyncBlockTitle } from '@atlaskit/editor-synced-block-provider';
14
+ import { fg } from '@atlaskit/platform-feature-flags';
14
15
  import { SyncBlockRendererWrapper } from '../ui/SyncBlockRendererWrapper';
15
- var SyncBlock = /*#__PURE__*/function (_ReactNodeView) {
16
+ export var SyncBlock = /*#__PURE__*/function (_ReactNodeView) {
16
17
  function SyncBlock(props) {
17
18
  var _this;
18
19
  _classCallCheck(this, SyncBlock);
19
20
  _this = _callSuper(this, SyncBlock, [props.node, props.view, props.getPos, props.portalProviderAPI, props.eventDispatcher, props]);
20
21
  _this.options = props.options;
21
22
  _this.api = props.api;
23
+ _this.syncBlockStore = props.syncBlockStore;
22
24
  return _this;
23
25
  }
24
26
  _inherits(SyncBlock, _ReactNodeView);
@@ -33,6 +35,7 @@ var SyncBlock = /*#__PURE__*/function (_ReactNodeView) {
33
35
  key: "render",
34
36
  value: function render() {
35
37
  var _this$options,
38
+ _this$api$syncedBlock,
36
39
  _this$api,
37
40
  _this$options2,
38
41
  _this2 = this;
@@ -45,7 +48,8 @@ var SyncBlock = /*#__PURE__*/function (_ReactNodeView) {
45
48
  if (!resourceId || !localId) {
46
49
  return null;
47
50
  }
48
- var syncBlockStore = (_this$api = this.api) === null || _this$api === void 0 || (_this$api = _this$api.syncedBlock) === null || _this$api === void 0 || (_this$api = _this$api.sharedState.currentState()) === null || _this$api === void 0 ? void 0 : _this$api.syncBlockStore;
51
+ var initialSyncBlockStore = fg('platform_synced_block_dogfooding') ? this.syncBlockStore : undefined;
52
+ var syncBlockStore = (_this$api$syncedBlock = (_this$api = this.api) === null || _this$api === void 0 || (_this$api = _this$api.syncedBlock) === null || _this$api === void 0 || (_this$api = _this$api.sharedState.currentState()) === null || _this$api === void 0 ? void 0 : _this$api.syncBlockStore) !== null && _this$api$syncedBlock !== void 0 ? _this$api$syncedBlock : initialSyncBlockStore;
49
53
  if (!syncBlockStore) {
50
54
  return null;
51
55
  }
@@ -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
+ };