@atlaskit/editor-plugin-block-menu 12.0.3 → 12.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 (29) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/afm-cc/tsconfig.json +3 -0
  3. package/afm-products/tsconfig.json +3 -0
  4. package/dist/cjs/blockMenuPlugin.js +13 -2
  5. package/dist/cjs/editor-actions/isTransformToTargetDisabled.js +13 -1
  6. package/dist/cjs/editor-actions/transformSource.js +23 -0
  7. package/dist/cjs/editor-actions/transformSourceRegistry.js +54 -0
  8. package/dist/cjs/editor-commands/transformNode.js +133 -25
  9. package/dist/cjs/ui/utils/checkIsFormatMenuHidden.js +3 -1
  10. package/dist/es2019/blockMenuPlugin.js +13 -4
  11. package/dist/es2019/editor-actions/isTransformToTargetDisabled.js +13 -1
  12. package/dist/es2019/editor-actions/transformSource.js +15 -0
  13. package/dist/es2019/editor-actions/transformSourceRegistry.js +49 -0
  14. package/dist/es2019/editor-commands/transformNode.js +122 -12
  15. package/dist/es2019/ui/block-menu-components.js +3 -5
  16. package/dist/es2019/ui/utils/checkIsFormatMenuHidden.js +3 -1
  17. package/dist/esm/blockMenuPlugin.js +13 -2
  18. package/dist/esm/editor-actions/isTransformToTargetDisabled.js +13 -1
  19. package/dist/esm/editor-actions/transformSource.js +17 -0
  20. package/dist/esm/editor-actions/transformSourceRegistry.js +48 -0
  21. package/dist/esm/editor-commands/transformNode.js +131 -24
  22. package/dist/esm/ui/utils/checkIsFormatMenuHidden.js +3 -1
  23. package/dist/types/blockMenuPluginType.d.ts +2 -0
  24. package/dist/types/editor-actions/isTransformToTargetDisabled.d.ts +3 -1
  25. package/dist/types/editor-actions/transformSource.d.ts +4 -0
  26. package/dist/types/editor-actions/transformSourceRegistry.d.ts +13 -0
  27. package/dist/types/editor-commands/transformNode.d.ts +2 -1
  28. package/package.json +5 -4
  29. package/tsconfig.json +5 -2
@@ -3,13 +3,16 @@ import { expandedState } from '@atlaskit/editor-common/expand';
3
3
  import { logException } from '@atlaskit/editor-common/monitoring';
4
4
  import { startMeasure, stopMeasure } from '@atlaskit/editor-common/performance-measures';
5
5
  import { expandSelectionToBlockRange, getSourceNodesFromSelectionRange } from '@atlaskit/editor-common/selection';
6
- import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
6
+ import { Fragment, Node } from '@atlaskit/editor-prosemirror/model';
7
+ import { NodeSelection, Selection } from '@atlaskit/editor-prosemirror/state';
7
8
  import { Mapping, StepMap } from '@atlaskit/editor-prosemirror/transform';
8
9
  import { CellSelection } from '@atlaskit/editor-tables';
10
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
11
+ import { getSingleTransformSourceNode, isExtensionTransformSource } from '../editor-actions/transformSource';
9
12
  import { isNestedNode } from '../ui/utils/isNestedNode';
10
13
  import { convertNodesToTargetType } from './transform-node-utils/transform';
11
14
  import { isListNode } from './transform-node-utils/utils';
12
- export const transformNode = api => (targetType, metadata) => ({
15
+ export const transformNode = (api, transformRegistry) => (targetType, metadata) => ({
13
16
  tr
14
17
  }) => {
15
18
  var _api$blockControls, _api$blockControls$sh;
@@ -17,6 +20,113 @@ export const transformNode = api => (targetType, metadata) => ({
17
20
  if (!preservedSelection) {
18
21
  return tr;
19
22
  }
23
+ if (isExperimentEnabled('platform_editor_block_menu_transform_extensions')) {
24
+ const {
25
+ $from,
26
+ $to,
27
+ range
28
+ } = expandSelectionToBlockRange(preservedSelection);
29
+ const sourceNode = getSingleTransformSourceNode(preservedSelection, range);
30
+ if (isExtensionTransformSource(sourceNode)) {
31
+ var _metadata$targetTypeN;
32
+ const context = {
33
+ source: sourceNode.toJSON(),
34
+ targetTypeName: (_metadata$targetTypeN = metadata === null || metadata === void 0 ? void 0 : metadata.targetTypeName) !== null && _metadata$targetTypeN !== void 0 ? _metadata$targetTypeN : targetType.name
35
+ };
36
+ const resolution = transformRegistry === null || transformRegistry === void 0 ? void 0 : transformRegistry.resolve(context);
37
+ if (!resolution || resolution.status === 'unsupported') {
38
+ return tr;
39
+ }
40
+ const measureId = `transformNode_${targetType.name}_${Date.now()}`;
41
+ startMeasure(measureId);
42
+ try {
43
+ var _api$blockControls2, _api$blockControls3;
44
+ const transformResult = resolution.transform.transform(context);
45
+ if (!transformResult) {
46
+ stopMeasure(measureId);
47
+ return tr;
48
+ }
49
+ if (!Array.isArray(transformResult.output) || transformResult.output.length === 0) {
50
+ throw new Error('Block menu transform returned no output');
51
+ }
52
+ const resultNodes = transformResult.output.map(nodeAdf => {
53
+ const node = Node.fromJSON(tr.doc.type.schema, nodeAdf);
54
+ node.check();
55
+ return node;
56
+ });
57
+ if (!range || !range.parent.canReplace(range.startIndex, range.endIndex, Fragment.from(resultNodes))) {
58
+ throw new Error('Block menu transform output is invalid at the selected position');
59
+ }
60
+ const sliceStart = $from.pos;
61
+ tr.replaceWith(sliceStart, $to.pos, resultNodes);
62
+ const insertedNode = tr.doc.nodeAt(sliceStart);
63
+ const nextSelection = insertedNode && NodeSelection.isSelectable(insertedNode) ? NodeSelection.create(tr.doc, sliceStart) : Selection.near(tr.doc.resolve(sliceStart));
64
+ tr.setSelection(nextSelection);
65
+ api === null || api === void 0 ? void 0 : (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 ? void 0 : _api$blockControls2.commands.stopPreservingSelection()({
66
+ tr
67
+ });
68
+ api === null || api === void 0 ? void 0 : (_api$blockControls3 = api.blockControls) === null || _api$blockControls3 === void 0 ? void 0 : _api$blockControls3.commands.toggleBlockMenu({
69
+ closeMenu: true
70
+ })({
71
+ tr
72
+ });
73
+ const {
74
+ expand,
75
+ nestedExpand
76
+ } = tr.doc.type.schema.nodes;
77
+ resultNodes.forEach(node => {
78
+ if (node.type === expand || node.type === nestedExpand) {
79
+ expandedState.set(node, true);
80
+ }
81
+ });
82
+ stopMeasure(measureId, (duration, startTime) => {
83
+ var _api$analytics, _api$analytics$action;
84
+ 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.attachAnalyticsEvent({
85
+ action: ACTION.TRANSFORMED,
86
+ actionSubject: ACTION_SUBJECT.ELEMENT,
87
+ attributes: {
88
+ duration,
89
+ isEmptyLine: false,
90
+ isNested: isNestedNode(preservedSelection, ''),
91
+ isSuggested: Boolean(metadata === null || metadata === void 0 ? void 0 : metadata.isSuggested),
92
+ sourceNodesCount: 1,
93
+ sourceNodesCountByType: {
94
+ [sourceNode.type.name]: 1
95
+ },
96
+ sourceNodeType: sourceNode.type.name,
97
+ startTime,
98
+ targetNodeType: targetType.name,
99
+ outputNodesCount: resultNodes.length,
100
+ inputMethod: INPUT_METHOD.BLOCK_MENU
101
+ },
102
+ eventType: EVENT_TYPE.TRACK
103
+ })(tr);
104
+ });
105
+ } catch (error) {
106
+ var _api$analytics2, _api$analytics2$actio;
107
+ stopMeasure(measureId);
108
+ logException(error, {
109
+ location: 'editor-plugin-block-menu'
110
+ });
111
+ api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : (_api$analytics2$actio = _api$analytics2.actions) === null || _api$analytics2$actio === void 0 ? void 0 : _api$analytics2$actio.attachAnalyticsEvent({
112
+ action: ACTION.ERRORED,
113
+ actionSubject: ACTION_SUBJECT.ELEMENT,
114
+ actionSubjectId: ACTION_SUBJECT_ID.TRANSFORM,
115
+ eventType: EVENT_TYPE.OPERATIONAL,
116
+ attributes: {
117
+ docSize: tr.doc.nodeSize,
118
+ error: error.message,
119
+ errorStack: error.stack,
120
+ from: sourceNode.type.name,
121
+ inputMethod: INPUT_METHOD.BLOCK_MENU,
122
+ selection: preservedSelection.toJSON(),
123
+ to: targetType.name
124
+ }
125
+ })(tr);
126
+ }
127
+ return tr;
128
+ }
129
+ }
20
130
  const measureId = `transformNode_${targetType.name}_${Date.now()}`;
21
131
  startMeasure(measureId);
22
132
  const {
@@ -41,7 +151,7 @@ export const transformNode = api => (targetType, metadata) => ({
41
151
  const isEmptyLine = sourceNodes.length === 1 && (sourceNodes[0].type === nodes.paragraph || sourceNodes[0].type === nodes.heading) && (sourceNodes[0].content.size === 0 || sourceNodes[0].textContent.trim() === '');
42
152
  const isSuggested = Boolean(metadata === null || metadata === void 0 ? void 0 : metadata.isSuggested);
43
153
  try {
44
- var _api$blockControls4;
154
+ var _api$blockControls6;
45
155
  const resultNodes = convertNodesToTargetType({
46
156
  sourceNodes,
47
157
  targetNodeType: targetType,
@@ -62,7 +172,7 @@ export const transformNode = api => (targetType, metadata) => ({
62
172
  }
63
173
  });
64
174
  if (preservedSelection instanceof NodeSelection && preservedSelection.node.type === nodes.mediaSingle) {
65
- var _api$blockControls2;
175
+ var _api$blockControls4;
66
176
  // when node is media single, use tr.replaceWith freeze editor, if modify position, tr.replaceWith creates duplicats
67
177
  const deleteFrom = $from.pos;
68
178
  const deleteTo = $to.pos;
@@ -77,7 +187,7 @@ export const transformNode = api => (targetType, metadata) => ({
77
187
  // and stop preserving it
78
188
  const oldSize = sourceNodes.reduce((sum, node) => sum + node.nodeSize, 0);
79
189
  const newSize = content.reduce((sum, node) => sum + node.nodeSize, 0);
80
- api === null || api === void 0 ? void 0 : (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 ? void 0 : _api$blockControls2.commands.mapPreservedSelection(new Mapping([new StepMap([0, oldSize, newSize])]))({
190
+ api === null || api === void 0 ? void 0 : (_api$blockControls4 = api.blockControls) === null || _api$blockControls4 === void 0 ? void 0 : _api$blockControls4.commands.mapPreservedSelection(new Mapping([new StepMap([0, oldSize, newSize])]))({
81
191
  tr
82
192
  });
83
193
  } else {
@@ -87,22 +197,22 @@ export const transformNode = api => (targetType, metadata) => ({
87
197
  const insertedNode = tr.doc.nodeAt($from.pos);
88
198
  const isSelectable = insertedNode && NodeSelection.isSelectable(insertedNode);
89
199
  if (isSelectable) {
90
- var _api$blockControls3;
200
+ var _api$blockControls5;
91
201
  const nodeSelection = NodeSelection.create(tr.doc, $from.pos);
92
202
  tr.setSelection(nodeSelection);
93
- api === null || api === void 0 ? void 0 : (_api$blockControls3 = api.blockControls) === null || _api$blockControls3 === void 0 ? void 0 : _api$blockControls3.commands.startPreservingSelection()({
203
+ api === null || api === void 0 ? void 0 : (_api$blockControls5 = api.blockControls) === null || _api$blockControls5 === void 0 ? void 0 : _api$blockControls5.commands.startPreservingSelection()({
94
204
  tr
95
205
  });
96
206
  }
97
207
  }
98
- api === null || api === void 0 ? void 0 : (_api$blockControls4 = api.blockControls) === null || _api$blockControls4 === void 0 ? void 0 : _api$blockControls4.commands.toggleBlockMenu({
208
+ api === null || api === void 0 ? void 0 : (_api$blockControls6 = api.blockControls) === null || _api$blockControls6 === void 0 ? void 0 : _api$blockControls6.commands.toggleBlockMenu({
99
209
  closeMenu: true
100
210
  })({
101
211
  tr
102
212
  });
103
213
  stopMeasure(measureId, (duration, startTime) => {
104
- var _api$analytics, _api$analytics$action;
105
- 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.attachAnalyticsEvent({
214
+ var _api$analytics3, _api$analytics3$actio;
215
+ api === null || api === void 0 ? void 0 : (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 ? void 0 : (_api$analytics3$actio = _api$analytics3.actions) === null || _api$analytics3$actio === void 0 ? void 0 : _api$analytics3$actio.attachAnalyticsEvent({
106
216
  action: ACTION.TRANSFORMED,
107
217
  actionSubject: ACTION_SUBJECT.ELEMENT,
108
218
  attributes: {
@@ -122,12 +232,12 @@ export const transformNode = api => (targetType, metadata) => ({
122
232
  })(tr);
123
233
  });
124
234
  } catch (error) {
125
- var _api$analytics2, _api$analytics2$actio;
235
+ var _api$analytics4, _api$analytics4$actio;
126
236
  stopMeasure(measureId);
127
237
  logException(error, {
128
238
  location: 'editor-plugin-block-menu'
129
239
  });
130
- api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : (_api$analytics2$actio = _api$analytics2.actions) === null || _api$analytics2$actio === void 0 ? void 0 : _api$analytics2$actio.attachAnalyticsEvent({
240
+ api === null || api === void 0 ? void 0 : (_api$analytics4 = api.analytics) === null || _api$analytics4 === void 0 ? void 0 : (_api$analytics4$actio = _api$analytics4.actions) === null || _api$analytics4$actio === void 0 ? void 0 : _api$analytics4$actio.attachAnalyticsEvent({
131
241
  action: ACTION.ERRORED,
132
242
  actionSubject: ACTION_SUBJECT.ELEMENT,
133
243
  actionSubjectId: ACTION_SUBJECT_ID.TRANSFORM,
@@ -173,11 +173,9 @@ const getTurnIntoMenuComponents = api => {
173
173
  rank: MAIN_BLOCK_MENU_SECTION_RANK[TRANSFORM_MENU_SECTION.key],
174
174
  component: ({
175
175
  children
176
- }) => {
177
- return /*#__PURE__*/React.createElement(FormatMenuSection, {
178
- api: api
179
- }, children);
180
- }
176
+ }) => /*#__PURE__*/React.createElement(FormatMenuSection, {
177
+ api: api
178
+ }, children)
181
179
  }];
182
180
  };
183
181
  export const getBlockMenuComponents = ({
@@ -1,5 +1,6 @@
1
1
  import memoizeOne from 'memoize-one';
2
2
  import { findSelectedNodeOfType } from '@atlaskit/editor-prosemirror/utils';
3
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
3
4
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
4
5
  const getDisabledNodeTypes = memoizeOne(nodes => [nodes.rule, ...(expValEquals('confluence_native_tabs_experiment', 'isEnabled', true) ? [nodes.multiBodiedExtension] : [])]);
5
6
  const getIsFormatMenuHidden = (selection, schema) => {
@@ -7,7 +8,8 @@ const getIsFormatMenuHidden = (selection, schema) => {
7
8
  if (!nodes) {
8
9
  return false;
9
10
  }
10
- const disabledNode = findSelectedNodeOfType(getDisabledNodeTypes(nodes))(selection);
11
+ const disabledNodeTypes = isExperimentEnabled('platform_editor_block_menu_transform_extensions') ? [nodes.rule] : getDisabledNodeTypes(nodes);
12
+ const disabledNode = findSelectedNodeOfType(disabledNodeTypes)(selection);
11
13
  return !!disabledNode;
12
14
  };
13
15
  export const checkIsFormatMenuHidden = api => {
@@ -1,8 +1,10 @@
1
1
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
2
  import React from 'react';
3
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
3
4
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
4
5
  import { createBlockMenuRegistry } from './editor-actions';
5
6
  import { isTransformToTargetDisabled } from './editor-actions/isTransformToTargetDisabled';
7
+ import { createBlockMenuTransformSourceRegistry } from './editor-actions/transformSourceRegistry';
6
8
  import { transformNode as _transformNode } from './editor-commands/transformNode';
7
9
  import { getBlockMenuExperiencesPlugin } from './pm-plugins/experiences/block-menu-experiences';
8
10
  import { keymapPlugin } from './pm-plugins/keymap';
@@ -15,6 +17,7 @@ export var blockMenuPlugin = function blockMenuPlugin(_ref) {
15
17
  var api = _ref.api,
16
18
  config = _ref.config;
17
19
  var registry = createBlockMenuRegistry();
20
+ var transformSourceRegistry = isExperimentEnabled('platform_editor_block_menu_transform_extensions') ? createBlockMenuTransformSourceRegistry() : undefined;
18
21
  registry.register(getBlockMenuComponents({
19
22
  api: api,
20
23
  config: config
@@ -64,13 +67,21 @@ export var blockMenuPlugin = function blockMenuPlugin(_ref) {
64
67
  return isTransformToTargetDisabled({
65
68
  selection: currentSelection,
66
69
  targetNodeTypeName: optionNodeTypeName,
67
- targetNodeTypeAttrs: optionNodeTypeAttrs
70
+ targetNodeTypeAttrs: optionNodeTypeAttrs,
71
+ transformRegistry: transformSourceRegistry
68
72
  });
73
+ },
74
+ registerBlockMenuTransforms: function registerBlockMenuTransforms(transforms) {
75
+ if (isExperimentEnabled('platform_editor_block_menu_transform_extensions')) {
76
+ var _transformSourceRegis;
77
+ return (_transformSourceRegis = transformSourceRegistry === null || transformSourceRegistry === void 0 ? void 0 : transformSourceRegistry.register(transforms)) !== null && _transformSourceRegis !== void 0 ? _transformSourceRegis : function () {};
78
+ }
79
+ return function () {};
69
80
  }
70
81
  },
71
82
  commands: {
72
83
  transformNode: function transformNode(targetType, metadata) {
73
- return _transformNode(api)(targetType, metadata);
84
+ return _transformNode(api, transformSourceRegistry)(targetType, metadata);
74
85
  }
75
86
  },
76
87
  getSharedState: function getSharedState(editorState) {
@@ -1,8 +1,10 @@
1
1
  import { expandSelectionToBlockRange } from '@atlaskit/editor-common/selection';
2
2
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
3
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
3
4
  import { isTransformDisabledBasedOnStepsConfig } from '../editor-commands/transform-node-utils/transform';
4
5
  import { toNodeTypeValue } from '../editor-commands/transform-node-utils/types';
5
6
  import { getBlockNodesInRange, getTargetNodeTypeNameInContext } from '../editor-commands/transform-node-utils/utils';
7
+ import { getSingleTransformSourceNode, isExtensionTransformSource } from './transformSource';
6
8
  export var canParentContainNodeType = function canParentContainNodeType(schema, selectedNodeTypeName, parentNode, nodeTypeName, nodeTypeAttrs) {
7
9
  var adjustedNodeTypeName = getTargetNodeTypeNameInContext(nodeTypeName, true, parentNode, schema);
8
10
  if (!adjustedNodeTypeName) {
@@ -46,12 +48,22 @@ var isTransformEnabledForNode = function isTransformEnabledForNode(node, targetN
46
48
  export var isTransformToTargetDisabled = function isTransformToTargetDisabled(_ref) {
47
49
  var selection = _ref.selection,
48
50
  targetNodeTypeName = _ref.targetNodeTypeName,
49
- targetNodeTypeAttrs = _ref.targetNodeTypeAttrs;
51
+ targetNodeTypeAttrs = _ref.targetNodeTypeAttrs,
52
+ transformRegistry = _ref.transformRegistry;
50
53
  var _expandSelectionToBlo = expandSelectionToBlockRange(selection),
51
54
  range = _expandSelectionToBlo.range;
52
55
  if (!range) {
53
56
  return false;
54
57
  }
58
+ if (isExperimentEnabled('platform_editor_block_menu_transform_extensions')) {
59
+ var sourceNode = getSingleTransformSourceNode(selection, range);
60
+ if (isExtensionTransformSource(sourceNode)) {
61
+ return (transformRegistry === null || transformRegistry === void 0 ? void 0 : transformRegistry.resolve({
62
+ source: sourceNode.toJSON(),
63
+ targetTypeName: targetNodeTypeName
64
+ }).status) !== 'supported';
65
+ }
66
+ }
55
67
  var selectedNodes = getBlockNodesInRange(range);
56
68
  var parent = range.parent;
57
69
  var isNested = range.depth >= 1;
@@ -0,0 +1,17 @@
1
+ import { getBlockNodesInRange, getSelectedNode } from '../editor-commands/transform-node-utils/utils';
2
+ var EXTENSION_NODE_TYPES = new Set(['extension', 'bodiedExtension', 'multiBodiedExtension']);
3
+ export var getSingleTransformSourceNode = function getSingleTransformSourceNode(selection, range) {
4
+ var _getSelectedNode;
5
+ var selectedNode = (_getSelectedNode = getSelectedNode(selection)) === null || _getSelectedNode === void 0 ? void 0 : _getSelectedNode.node;
6
+ if (selectedNode) {
7
+ return selectedNode;
8
+ }
9
+ if (!range) {
10
+ return undefined;
11
+ }
12
+ var blockNodes = getBlockNodesInRange(range);
13
+ return blockNodes.length === 1 ? blockNodes[0] : undefined;
14
+ };
15
+ export var isExtensionTransformSource = function isExtensionTransformSource(node) {
16
+ return Boolean(node && EXTENSION_NODE_TYPES.has(node.type.name));
17
+ };
@@ -0,0 +1,48 @@
1
+ import { logException } from '@atlaskit/editor-common/monitoring';
2
+ export var createBlockMenuTransformSourceRegistry = function createBlockMenuTransformSourceRegistry() {
3
+ var transforms = new Map();
4
+ return {
5
+ register: function register(registeredTransforms) {
6
+ var registered = [];
7
+ registeredTransforms.forEach(function (transform) {
8
+ if (transforms.has(transform.key)) {
9
+ void logException(new Error("Duplicate Block Menu transform key: ".concat(transform.key)), {
10
+ location: 'editor-plugin-block-menu'
11
+ });
12
+ return;
13
+ }
14
+ transforms.set(transform.key, transform);
15
+ registered.push(transform);
16
+ });
17
+ if (registered.length === 0) {
18
+ return function () {};
19
+ }
20
+ return function () {
21
+ registered.forEach(function (transform) {
22
+ if (transforms.get(transform.key) === transform) {
23
+ transforms.delete(transform.key);
24
+ }
25
+ });
26
+ };
27
+ },
28
+ resolve: function resolve(context) {
29
+ var transform = Array.from(transforms.values()).find(function (_ref) {
30
+ var isSupported = _ref.isSupported;
31
+ try {
32
+ return isSupported(context);
33
+ } catch (error) {
34
+ void logException(error instanceof Error ? error : new Error('Block Menu transform support check failed'), {
35
+ location: 'editor-plugin-block-menu'
36
+ });
37
+ return false;
38
+ }
39
+ });
40
+ return transform ? {
41
+ status: 'supported',
42
+ transform: transform
43
+ } : {
44
+ status: 'unsupported'
45
+ };
46
+ }
47
+ };
48
+ };
@@ -1,15 +1,19 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
1
2
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
2
3
  import { expandedState } from '@atlaskit/editor-common/expand';
3
4
  import { logException } from '@atlaskit/editor-common/monitoring';
4
5
  import { startMeasure, stopMeasure } from '@atlaskit/editor-common/performance-measures';
5
6
  import { expandSelectionToBlockRange, getSourceNodesFromSelectionRange } from '@atlaskit/editor-common/selection';
6
- import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
7
+ import { Fragment, Node } from '@atlaskit/editor-prosemirror/model';
8
+ import { NodeSelection, Selection } from '@atlaskit/editor-prosemirror/state';
7
9
  import { Mapping, StepMap } from '@atlaskit/editor-prosemirror/transform';
8
10
  import { CellSelection } from '@atlaskit/editor-tables';
11
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
12
+ import { getSingleTransformSourceNode, isExtensionTransformSource } from '../editor-actions/transformSource';
9
13
  import { isNestedNode } from '../ui/utils/isNestedNode';
10
14
  import { convertNodesToTargetType } from './transform-node-utils/transform';
11
15
  import { isListNode } from './transform-node-utils/utils';
12
- export var transformNode = function transformNode(api) {
16
+ export var transformNode = function transformNode(api, transformRegistry) {
13
17
  return function (targetType, metadata) {
14
18
  return function (_ref) {
15
19
  var _api$blockControls;
@@ -18,12 +22,115 @@ export var transformNode = function transformNode(api) {
18
22
  if (!preservedSelection) {
19
23
  return tr;
20
24
  }
25
+ if (isExperimentEnabled('platform_editor_block_menu_transform_extensions')) {
26
+ var _expandSelectionToBlo = expandSelectionToBlockRange(preservedSelection),
27
+ _$from = _expandSelectionToBlo.$from,
28
+ _$to = _expandSelectionToBlo.$to,
29
+ range = _expandSelectionToBlo.range;
30
+ var sourceNode = getSingleTransformSourceNode(preservedSelection, range);
31
+ if (isExtensionTransformSource(sourceNode)) {
32
+ var _metadata$targetTypeN;
33
+ var context = {
34
+ source: sourceNode.toJSON(),
35
+ targetTypeName: (_metadata$targetTypeN = metadata === null || metadata === void 0 ? void 0 : metadata.targetTypeName) !== null && _metadata$targetTypeN !== void 0 ? _metadata$targetTypeN : targetType.name
36
+ };
37
+ var resolution = transformRegistry === null || transformRegistry === void 0 ? void 0 : transformRegistry.resolve(context);
38
+ if (!resolution || resolution.status === 'unsupported') {
39
+ return tr;
40
+ }
41
+ var _measureId = "transformNode_".concat(targetType.name, "_").concat(Date.now());
42
+ startMeasure(_measureId);
43
+ try {
44
+ var _api$blockControls2, _api$blockControls3;
45
+ var transformResult = resolution.transform.transform(context);
46
+ if (!transformResult) {
47
+ stopMeasure(_measureId);
48
+ return tr;
49
+ }
50
+ if (!Array.isArray(transformResult.output) || transformResult.output.length === 0) {
51
+ throw new Error('Block menu transform returned no output');
52
+ }
53
+ var resultNodes = transformResult.output.map(function (nodeAdf) {
54
+ var node = Node.fromJSON(tr.doc.type.schema, nodeAdf);
55
+ node.check();
56
+ return node;
57
+ });
58
+ if (!range || !range.parent.canReplace(range.startIndex, range.endIndex, Fragment.from(resultNodes))) {
59
+ throw new Error('Block menu transform output is invalid at the selected position');
60
+ }
61
+ var sliceStart = _$from.pos;
62
+ tr.replaceWith(sliceStart, _$to.pos, resultNodes);
63
+ var insertedNode = tr.doc.nodeAt(sliceStart);
64
+ var nextSelection = insertedNode && NodeSelection.isSelectable(insertedNode) ? NodeSelection.create(tr.doc, sliceStart) : Selection.near(tr.doc.resolve(sliceStart));
65
+ tr.setSelection(nextSelection);
66
+ api === null || api === void 0 || (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || _api$blockControls2.commands.stopPreservingSelection()({
67
+ tr: tr
68
+ });
69
+ api === null || api === void 0 || (_api$blockControls3 = api.blockControls) === null || _api$blockControls3 === void 0 || _api$blockControls3.commands.toggleBlockMenu({
70
+ closeMenu: true
71
+ })({
72
+ tr: tr
73
+ });
74
+ var _tr$doc$type$schema$n = tr.doc.type.schema.nodes,
75
+ expand = _tr$doc$type$schema$n.expand,
76
+ nestedExpand = _tr$doc$type$schema$n.nestedExpand;
77
+ resultNodes.forEach(function (node) {
78
+ if (node.type === expand || node.type === nestedExpand) {
79
+ expandedState.set(node, true);
80
+ }
81
+ });
82
+ stopMeasure(_measureId, function (duration, startTime) {
83
+ var _api$analytics;
84
+ api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || (_api$analytics = _api$analytics.actions) === null || _api$analytics === void 0 || _api$analytics.attachAnalyticsEvent({
85
+ action: ACTION.TRANSFORMED,
86
+ actionSubject: ACTION_SUBJECT.ELEMENT,
87
+ attributes: {
88
+ duration: duration,
89
+ isEmptyLine: false,
90
+ isNested: isNestedNode(preservedSelection, ''),
91
+ isSuggested: Boolean(metadata === null || metadata === void 0 ? void 0 : metadata.isSuggested),
92
+ sourceNodesCount: 1,
93
+ sourceNodesCountByType: _defineProperty({}, sourceNode.type.name, 1),
94
+ sourceNodeType: sourceNode.type.name,
95
+ startTime: startTime,
96
+ targetNodeType: targetType.name,
97
+ outputNodesCount: resultNodes.length,
98
+ inputMethod: INPUT_METHOD.BLOCK_MENU
99
+ },
100
+ eventType: EVENT_TYPE.TRACK
101
+ })(tr);
102
+ });
103
+ } catch (error) {
104
+ var _api$analytics2;
105
+ stopMeasure(_measureId);
106
+ logException(error, {
107
+ location: 'editor-plugin-block-menu'
108
+ });
109
+ api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || (_api$analytics2 = _api$analytics2.actions) === null || _api$analytics2 === void 0 || _api$analytics2.attachAnalyticsEvent({
110
+ action: ACTION.ERRORED,
111
+ actionSubject: ACTION_SUBJECT.ELEMENT,
112
+ actionSubjectId: ACTION_SUBJECT_ID.TRANSFORM,
113
+ eventType: EVENT_TYPE.OPERATIONAL,
114
+ attributes: {
115
+ docSize: tr.doc.nodeSize,
116
+ error: error.message,
117
+ errorStack: error.stack,
118
+ from: sourceNode.type.name,
119
+ inputMethod: INPUT_METHOD.BLOCK_MENU,
120
+ selection: preservedSelection.toJSON(),
121
+ to: targetType.name
122
+ }
123
+ })(tr);
124
+ }
125
+ return tr;
126
+ }
127
+ }
21
128
  var measureId = "transformNode_".concat(targetType.name, "_").concat(Date.now());
22
129
  startMeasure(measureId);
23
130
  var nodes = tr.doc.type.schema.nodes;
24
- var _expandSelectionToBlo = expandSelectionToBlockRange(preservedSelection),
25
- $from = _expandSelectionToBlo.$from,
26
- $to = _expandSelectionToBlo.$to;
131
+ var _expandSelectionToBlo2 = expandSelectionToBlockRange(preservedSelection),
132
+ $from = _expandSelectionToBlo2.$from,
133
+ $to = _expandSelectionToBlo2.$to;
27
134
  var selectedParent = $from.parent;
28
135
  var isParentLayout = selectedParent.type === nodes.layoutColumn;
29
136
  var isNested = isNestedNode(preservedSelection, '') && !isParentLayout;
@@ -39,8 +146,8 @@ export var transformNode = function transformNode(api) {
39
146
  var isEmptyLine = sourceNodes.length === 1 && (sourceNodes[0].type === nodes.paragraph || sourceNodes[0].type === nodes.heading) && (sourceNodes[0].content.size === 0 || sourceNodes[0].textContent.trim() === '');
40
147
  var isSuggested = Boolean(metadata === null || metadata === void 0 ? void 0 : metadata.isSuggested);
41
148
  try {
42
- var _api$blockControls4;
43
- var resultNodes = convertNodesToTargetType({
149
+ var _api$blockControls6;
150
+ var _resultNodes = convertNodesToTargetType({
44
151
  sourceNodes: sourceNodes,
45
152
  targetNodeType: targetType,
46
153
  schema: tr.doc.type.schema,
@@ -48,17 +155,17 @@ export var transformNode = function transformNode(api) {
48
155
  targetAttrs: metadata === null || metadata === void 0 ? void 0 : metadata.targetAttrs,
49
156
  parentNode: selectedParent
50
157
  });
51
- var content = resultNodes.length > 0 ? resultNodes : sourceNodes;
52
- var sliceStart = isList ? $from.pos - 1 : $from.pos;
53
- var expand = nodes.expand,
54
- nestedExpand = nodes.nestedExpand;
158
+ var content = _resultNodes.length > 0 ? _resultNodes : sourceNodes;
159
+ var _sliceStart = isList ? $from.pos - 1 : $from.pos;
160
+ var _expand = nodes.expand,
161
+ _nestedExpand = nodes.nestedExpand;
55
162
  content.forEach(function (node) {
56
- if (node.type === expand || node.type === nestedExpand) {
163
+ if (node.type === _expand || node.type === _nestedExpand) {
57
164
  expandedState.set(node, true);
58
165
  }
59
166
  });
60
167
  if (preservedSelection instanceof NodeSelection && preservedSelection.node.type === nodes.mediaSingle) {
61
- var _api$blockControls2;
168
+ var _api$blockControls4;
62
169
  // when node is media single, use tr.replaceWith freeze editor, if modify position, tr.replaceWith creates duplicats
63
170
  var deleteFrom = $from.pos;
64
171
  var deleteTo = $to.pos;
@@ -77,32 +184,32 @@ export var transformNode = function transformNode(api) {
77
184
  var newSize = content.reduce(function (sum, node) {
78
185
  return sum + node.nodeSize;
79
186
  }, 0);
80
- api === null || api === void 0 || (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || _api$blockControls2.commands.mapPreservedSelection(new Mapping([new StepMap([0, oldSize, newSize])]))({
187
+ api === null || api === void 0 || (_api$blockControls4 = api.blockControls) === null || _api$blockControls4 === void 0 || _api$blockControls4.commands.mapPreservedSelection(new Mapping([new StepMap([0, oldSize, newSize])]))({
81
188
  tr: tr
82
189
  });
83
190
  } else {
84
- tr.replaceWith(sliceStart, $to.pos, content);
191
+ tr.replaceWith(_sliceStart, $to.pos, content);
85
192
  }
86
193
  if (preservedSelection instanceof CellSelection) {
87
- var insertedNode = tr.doc.nodeAt($from.pos);
88
- var isSelectable = insertedNode && NodeSelection.isSelectable(insertedNode);
194
+ var _insertedNode = tr.doc.nodeAt($from.pos);
195
+ var isSelectable = _insertedNode && NodeSelection.isSelectable(_insertedNode);
89
196
  if (isSelectable) {
90
- var _api$blockControls3;
197
+ var _api$blockControls5;
91
198
  var nodeSelection = NodeSelection.create(tr.doc, $from.pos);
92
199
  tr.setSelection(nodeSelection);
93
- api === null || api === void 0 || (_api$blockControls3 = api.blockControls) === null || _api$blockControls3 === void 0 || _api$blockControls3.commands.startPreservingSelection()({
200
+ api === null || api === void 0 || (_api$blockControls5 = api.blockControls) === null || _api$blockControls5 === void 0 || _api$blockControls5.commands.startPreservingSelection()({
94
201
  tr: tr
95
202
  });
96
203
  }
97
204
  }
98
- api === null || api === void 0 || (_api$blockControls4 = api.blockControls) === null || _api$blockControls4 === void 0 || _api$blockControls4.commands.toggleBlockMenu({
205
+ api === null || api === void 0 || (_api$blockControls6 = api.blockControls) === null || _api$blockControls6 === void 0 || _api$blockControls6.commands.toggleBlockMenu({
99
206
  closeMenu: true
100
207
  })({
101
208
  tr: tr
102
209
  });
103
210
  stopMeasure(measureId, function (duration, startTime) {
104
- var _api$analytics;
105
- api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || (_api$analytics = _api$analytics.actions) === null || _api$analytics === void 0 || _api$analytics.attachAnalyticsEvent({
211
+ var _api$analytics3;
212
+ api === null || api === void 0 || (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 || (_api$analytics3 = _api$analytics3.actions) === null || _api$analytics3 === void 0 || _api$analytics3.attachAnalyticsEvent({
106
213
  action: ACTION.TRANSFORMED,
107
214
  actionSubject: ACTION_SUBJECT.ELEMENT,
108
215
  attributes: {
@@ -122,12 +229,12 @@ export var transformNode = function transformNode(api) {
122
229
  })(tr);
123
230
  });
124
231
  } catch (error) {
125
- var _api$analytics2;
232
+ var _api$analytics4;
126
233
  stopMeasure(measureId);
127
234
  logException(error, {
128
235
  location: 'editor-plugin-block-menu'
129
236
  });
130
- api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || (_api$analytics2 = _api$analytics2.actions) === null || _api$analytics2 === void 0 || _api$analytics2.attachAnalyticsEvent({
237
+ api === null || api === void 0 || (_api$analytics4 = api.analytics) === null || _api$analytics4 === void 0 || (_api$analytics4 = _api$analytics4.actions) === null || _api$analytics4 === void 0 || _api$analytics4.attachAnalyticsEvent({
131
238
  action: ACTION.ERRORED,
132
239
  actionSubject: ACTION_SUBJECT.ELEMENT,
133
240
  actionSubjectId: ACTION_SUBJECT_ID.TRANSFORM,
@@ -1,6 +1,7 @@
1
1
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
2
  import memoizeOne from 'memoize-one';
3
3
  import { findSelectedNodeOfType } from '@atlaskit/editor-prosemirror/utils';
4
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
4
5
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
5
6
  var getDisabledNodeTypes = memoizeOne(function (nodes) {
6
7
  return [nodes.rule].concat(_toConsumableArray(expValEquals('confluence_native_tabs_experiment', 'isEnabled', true) ? [nodes.multiBodiedExtension] : []));
@@ -10,7 +11,8 @@ var getIsFormatMenuHidden = function getIsFormatMenuHidden(selection, schema) {
10
11
  if (!nodes) {
11
12
  return false;
12
13
  }
13
- var disabledNode = findSelectedNodeOfType(getDisabledNodeTypes(nodes))(selection);
14
+ var disabledNodeTypes = isExperimentEnabled('platform_editor_block_menu_transform_extensions') ? [nodes.rule] : getDisabledNodeTypes(nodes);
15
+ var disabledNode = findSelectedNodeOfType(disabledNodeTypes)(selection);
14
16
  return !!disabledNode;
15
17
  };
16
18
  export var checkIsFormatMenuHidden = function checkIsFormatMenuHidden(api) {
@@ -1,3 +1,4 @@
1
+ import type { BlockTransformExtension } from '@atlaskit/editor-common/block-menu/block-transform-extension';
1
2
  import type { EditorCommand, NextEditorPlugin, OptionalPlugin } from '@atlaskit/editor-common/types';
2
3
  import type { AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
3
4
  import type { BlockControlsPlugin } from '@atlaskit/editor-plugin-block-controls';
@@ -15,6 +16,7 @@ export type BlockMenuPlugin = NextEditorPlugin<'blockMenu', {
15
16
  getBlockMenuComponents: () => Array<RegisterBlockMenuComponent>;
16
17
  isTransformOptionDisabled: (optionNodeTypeName: string, optionNodeTypeAttrs?: Record<string, unknown>) => boolean;
17
18
  registerBlockMenuComponents: (blockMenuComponents: Array<RegisterBlockMenuComponent>) => void;
19
+ registerBlockMenuTransforms: (transforms: readonly BlockTransformExtension[]) => () => void;
18
20
  };
19
21
  commands: {
20
22
  transformNode: TransformNodeCommand;