@atlaskit/editor-plugin-synced-block 4.2.1 → 4.2.2

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # @atlaskit/editor-plugin-synced-block
2
2
 
3
+ ## 4.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`3432c4a4a074c`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/3432c4a4a074c) -
8
+ [ux] EDITOR-2525 update block menu convert to sync block to support all fishfooding node types
9
+ - Updated dependencies
10
+
3
11
  ## 4.2.1
4
12
 
5
13
  ### Patch Changes
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.removeSyncedBlock = exports.editSyncedBlockSource = exports.createSyncedBlock = exports.copySyncedBlockReferenceToClipboard = void 0;
7
7
  var _analytics = require("@atlaskit/editor-common/analytics");
8
8
  var _copyButton = require("@atlaskit/editor-common/copy-button");
9
+ var _state = require("@atlaskit/editor-prosemirror/state");
9
10
  var _utils = require("@atlaskit/editor-prosemirror/utils");
10
11
  var _utils2 = require("../pm-plugins/utils/utils");
11
12
  var createSyncedBlock = exports.createSyncedBlock = function createSyncedBlock(_ref) {
@@ -48,7 +49,10 @@ var createSyncedBlock = exports.createSyncedBlock = function createSyncedBlock(_
48
49
  // Save the new node with empty content to backend
49
50
  // This is so that the node can be copied and referenced without the source being saved/published
50
51
  syncBlockStore.createBodiedSyncBlockNode(_attrs);
51
- tr.replaceWith(conversionInfo.from - 1, conversionInfo.to, _newBodiedSyncBlockNode).scrollIntoView();
52
+ tr.replaceWith(conversionInfo.from > 0 ? conversionInfo.from - 1 : 0, conversionInfo.to, _newBodiedSyncBlockNode).scrollIntoView();
53
+
54
+ // set selection to the end of the previous selection + 1 for the position taken up by the start of the new synced block
55
+ tr.setSelection(_state.TextSelection.create(tr.doc, conversionInfo.to + 1));
52
56
  }
53
57
 
54
58
  // This transaction will be intercepted in filterTransaction and dispatched when saving to backend succeeds
@@ -5,8 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.isBodiedSyncBlockNode = exports.findSyncBlockOrBodiedSyncBlock = exports.findSyncBlock = exports.findBodiedSyncBlock = exports.canBeConvertedToSyncBlock = void 0;
7
7
  var _model = require("@atlaskit/editor-prosemirror/model");
8
+ var _state = require("@atlaskit/editor-prosemirror/state");
8
9
  var _utils = require("@atlaskit/editor-prosemirror/utils");
9
- var _editorSyncedBlockProvider = require("@atlaskit/editor-synced-block-provider");
10
10
  var _editorTables = require("@atlaskit/editor-tables");
11
11
  var findSyncBlock = exports.findSyncBlock = function findSyncBlock(state, selection) {
12
12
  var syncBlock = state.schema.nodes.syncBlock;
@@ -22,11 +22,22 @@ var findSyncBlockOrBodiedSyncBlock = exports.findSyncBlockOrBodiedSyncBlock = fu
22
22
  var isBodiedSyncBlockNode = exports.isBodiedSyncBlockNode = function isBodiedSyncBlockNode(node, bodiedSyncBlock) {
23
23
  return node.type === bodiedSyncBlock;
24
24
  };
25
+ var UNSUPPORTED_NODE_TYPES = new Set(['inlineExtension', 'extension', 'bodiedExtension', 'syncBlock', 'bodiedSyncBlock']);
26
+
27
+ /**
28
+ * Checks whether the selection can be converted to sync block
29
+ *
30
+ * @param selection - the current editor selection to validate for sync block conversion
31
+ * @returns A fragment containing the content to include in the synced block,
32
+ * stripping out unsupported marks (breakout on codeblock/expand/layout), as well as from and to positions,
33
+ * or false if conversion is not possible
34
+ */
25
35
  var canBeConvertedToSyncBlock = exports.canBeConvertedToSyncBlock = function canBeConvertedToSyncBlock(selection) {
36
+ var schema = selection.$from.doc.type.schema;
37
+ var nodes = schema.nodes;
26
38
  var from = selection.from;
27
39
  var to = selection.to;
28
- var depth = selection.$from.depth;
29
- var contentToInclude;
40
+ var contentToInclude = selection.content().content;
30
41
  if (selection instanceof _editorTables.CellSelection) {
31
42
  var table = (0, _editorTables.findTable)(selection);
32
43
  if (!table) {
@@ -35,35 +46,41 @@ var canBeConvertedToSyncBlock = exports.canBeConvertedToSyncBlock = function can
35
46
  contentToInclude = _model.Fragment.from([table.node]);
36
47
  from = table.pos;
37
48
  to = table.pos + table.node.nodeSize;
38
- depth = selection.$from.doc.resolve(table.pos).depth;
39
- } else {
40
- contentToInclude = _model.Fragment.from(selection.content().content);
41
- }
42
-
43
- // sync blocks can't be nested
44
- if (depth > 1) {
45
- return false;
49
+ } else if (selection instanceof _state.TextSelection) {
50
+ var trueParent = (0, _utils.findParentNodeOfType)([nodes.bulletList, nodes.orderedList, nodes.taskList, nodes.blockquote])(selection);
51
+ if (trueParent) {
52
+ contentToInclude = _model.Fragment.from([trueParent.node]);
53
+ from = trueParent.pos;
54
+ to = trueParent.pos + trueParent.node.nodeSize;
55
+ }
46
56
  }
47
- var syncBlockSchema = (0, _editorSyncedBlockProvider.getDefaultSyncBlockSchema)();
48
57
  var canBeConverted = true;
49
58
  selection.$from.doc.nodesBetween(from, to, function (node) {
50
- if (!(node.type.name in syncBlockSchema.nodes)) {
59
+ if (UNSUPPORTED_NODE_TYPES.has(node.type.name)) {
51
60
  canBeConverted = false;
52
61
  return false;
53
62
  }
54
- node.marks.forEach(function (mark) {
55
- if (!(mark.type.name in syncBlockSchema.marks)) {
56
- canBeConverted = false;
57
- return false;
58
- }
59
- });
60
63
  });
61
64
  if (!canBeConverted) {
62
65
  return false;
63
66
  }
67
+ contentToInclude = removeBreakoutMarks(contentToInclude);
64
68
  return {
65
69
  contentToInclude: contentToInclude,
66
70
  from: from,
67
71
  to: to
68
72
  };
73
+ };
74
+ var removeBreakoutMarks = function removeBreakoutMarks(content) {
75
+ var nodes = [];
76
+
77
+ // we only need to recurse at the top level, because breakout has to be on a top level
78
+ content.forEach(function (node) {
79
+ var filteredMarks = node.marks.filter(function (mark) {
80
+ return mark.type.name !== 'breakout';
81
+ });
82
+ var newNode = node.type.create(node.attrs, node.content, filteredMarks);
83
+ nodes.push(newNode);
84
+ });
85
+ return _model.Fragment.from(nodes);
69
86
  };
@@ -1,25 +1,38 @@
1
1
  "use strict";
2
2
 
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _typeof = require("@babel/runtime/helpers/typeof");
4
5
  Object.defineProperty(exports, "__esModule", {
5
6
  value: true
6
7
  });
7
8
  exports.CreateSyncedBlockDropdownItem = void 0;
8
- var _react = _interopRequireDefault(require("react"));
9
+ var _react = _interopRequireWildcard(require("react"));
9
10
  var _reactIntlNext = require("react-intl-next");
11
+ var _hooks = require("@atlaskit/editor-common/hooks");
10
12
  var _messages = require("@atlaskit/editor-common/messages");
11
13
  var _editorToolbar = require("@atlaskit/editor-toolbar");
12
14
  var _lozenge = _interopRequireDefault(require("@atlaskit/lozenge"));
13
15
  var _compiled = require("@atlaskit/primitives/compiled");
14
16
  var _utils = require("../pm-plugins/utils/utils");
17
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
15
18
  var CreateSyncedBlockDropdownItem = exports.CreateSyncedBlockDropdownItem = function CreateSyncedBlockDropdownItem(_ref) {
16
- var _api$selection;
17
19
  var api = _ref.api;
18
20
  var _useIntl = (0, _reactIntlNext.useIntl)(),
19
21
  formatMessage = _useIntl.formatMessage;
20
- var selection = api === null || api === void 0 || (_api$selection = api.selection) === null || _api$selection === void 0 || (_api$selection = _api$selection.sharedState) === null || _api$selection === void 0 || (_api$selection = _api$selection.currentState()) === null || _api$selection === void 0 ? void 0 : _api$selection.selection;
21
- var canCreateSyncBlock = selection && (0, _utils.canBeConvertedToSyncBlock)(selection);
22
- if (!canCreateSyncBlock) {
22
+ var _useSharedPluginState = (0, _hooks.useSharedPluginStateWithSelector)(api, ['selection', 'blockControls'], function (states) {
23
+ var _states$selectionStat, _states$blockControls;
24
+ return {
25
+ selection: (_states$selectionStat = states.selectionState) === null || _states$selectionStat === void 0 ? void 0 : _states$selectionStat.selection,
26
+ activeNode: (_states$blockControls = states.blockControlsState) === null || _states$blockControls === void 0 ? void 0 : _states$blockControls.activeNode
27
+ };
28
+ }),
29
+ selection = _useSharedPluginState.selection,
30
+ activeNode = _useSharedPluginState.activeNode;
31
+ var isNested = activeNode && activeNode.rootPos !== activeNode.pos;
32
+ var canBeConverted = (0, _react.useMemo)(function () {
33
+ return selection && (0, _utils.canBeConvertedToSyncBlock)(selection);
34
+ }, [selection]);
35
+ if (isNested || !canBeConverted) {
23
36
  return null;
24
37
  }
25
38
  var onClick = function onClick() {
@@ -37,7 +50,7 @@ var CreateSyncedBlockDropdownItem = exports.CreateSyncedBlockDropdownItem = func
37
50
  }, /*#__PURE__*/_react.default.createElement(_compiled.Flex, {
38
51
  alignItems: "center",
39
52
  gap: "space.050"
40
- }, /*#__PURE__*/_react.default.createElement(_compiled.Text, null, selection !== null && selection !== void 0 && selection.empty ? formatMessage(_messages.blockMenuMessages.createSyncedBlock) : formatMessage(_messages.blockMenuMessages.convertToSyncedBlock)), /*#__PURE__*/_react.default.createElement(_lozenge.default, {
53
+ }, /*#__PURE__*/_react.default.createElement(_compiled.Text, null, formatMessage(_messages.blockMenuMessages.createSyncedBlock)), /*#__PURE__*/_react.default.createElement(_lozenge.default, {
41
54
  appearance: "new"
42
55
  }, formatMessage(_messages.blockMenuMessages.newLozenge))));
43
56
  };
@@ -1,5 +1,6 @@
1
1
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
2
2
  import { copyDomNode, toDOM } from '@atlaskit/editor-common/copy-button';
3
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
3
4
  import { findSelectedNodeOfType, removeParentNodeOfType, removeSelectedNode } from '@atlaskit/editor-prosemirror/utils';
4
5
  import { canBeConvertedToSyncBlock, findSyncBlock, findSyncBlockOrBodiedSyncBlock, isBodiedSyncBlockNode } from '../pm-plugins/utils/utils';
5
6
  export const createSyncedBlock = ({
@@ -48,7 +49,10 @@ export const createSyncedBlock = ({
48
49
  // Save the new node with empty content to backend
49
50
  // This is so that the node can be copied and referenced without the source being saved/published
50
51
  syncBlockStore.createBodiedSyncBlockNode(attrs);
51
- tr.replaceWith(conversionInfo.from - 1, conversionInfo.to, newBodiedSyncBlockNode).scrollIntoView();
52
+ tr.replaceWith(conversionInfo.from > 0 ? conversionInfo.from - 1 : 0, conversionInfo.to, newBodiedSyncBlockNode).scrollIntoView();
53
+
54
+ // set selection to the end of the previous selection + 1 for the position taken up by the start of the new synced block
55
+ tr.setSelection(TextSelection.create(tr.doc, conversionInfo.to + 1));
52
56
  }
53
57
 
54
58
  // This transaction will be intercepted in filterTransaction and dispatched when saving to backend succeeds
@@ -1,6 +1,6 @@
1
1
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
2
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
2
3
  import { findParentNodeOfType, findSelectedNodeOfType } from '@atlaskit/editor-prosemirror/utils';
3
- import { getDefaultSyncBlockSchema } from '@atlaskit/editor-synced-block-provider';
4
4
  import { CellSelection, findTable } from '@atlaskit/editor-tables';
5
5
  export const findSyncBlock = (state, selection) => {
6
6
  const {
@@ -18,11 +18,24 @@ export const findSyncBlockOrBodiedSyncBlock = (state, selection) => {
18
18
  return findSyncBlock(state, selection) || findBodiedSyncBlock(state, selection);
19
19
  };
20
20
  export const isBodiedSyncBlockNode = (node, bodiedSyncBlock) => node.type === bodiedSyncBlock;
21
+ const UNSUPPORTED_NODE_TYPES = new Set(['inlineExtension', 'extension', 'bodiedExtension', 'syncBlock', 'bodiedSyncBlock']);
22
+
23
+ /**
24
+ * Checks whether the selection can be converted to sync block
25
+ *
26
+ * @param selection - the current editor selection to validate for sync block conversion
27
+ * @returns A fragment containing the content to include in the synced block,
28
+ * stripping out unsupported marks (breakout on codeblock/expand/layout), as well as from and to positions,
29
+ * or false if conversion is not possible
30
+ */
21
31
  export const canBeConvertedToSyncBlock = selection => {
32
+ const schema = selection.$from.doc.type.schema;
33
+ const {
34
+ nodes
35
+ } = schema;
22
36
  let from = selection.from;
23
37
  let to = selection.to;
24
- let depth = selection.$from.depth;
25
- let contentToInclude;
38
+ let contentToInclude = selection.content().content;
26
39
  if (selection instanceof CellSelection) {
27
40
  const table = findTable(selection);
28
41
  if (!table) {
@@ -31,35 +44,39 @@ export const canBeConvertedToSyncBlock = selection => {
31
44
  contentToInclude = Fragment.from([table.node]);
32
45
  from = table.pos;
33
46
  to = table.pos + table.node.nodeSize;
34
- depth = selection.$from.doc.resolve(table.pos).depth;
35
- } else {
36
- contentToInclude = Fragment.from(selection.content().content);
37
- }
38
-
39
- // sync blocks can't be nested
40
- if (depth > 1) {
41
- return false;
47
+ } else if (selection instanceof TextSelection) {
48
+ const trueParent = findParentNodeOfType([nodes.bulletList, nodes.orderedList, nodes.taskList, nodes.blockquote])(selection);
49
+ if (trueParent) {
50
+ contentToInclude = Fragment.from([trueParent.node]);
51
+ from = trueParent.pos;
52
+ to = trueParent.pos + trueParent.node.nodeSize;
53
+ }
42
54
  }
43
- const syncBlockSchema = getDefaultSyncBlockSchema();
44
55
  let canBeConverted = true;
45
56
  selection.$from.doc.nodesBetween(from, to, node => {
46
- if (!(node.type.name in syncBlockSchema.nodes)) {
57
+ if (UNSUPPORTED_NODE_TYPES.has(node.type.name)) {
47
58
  canBeConverted = false;
48
59
  return false;
49
60
  }
50
- node.marks.forEach(mark => {
51
- if (!(mark.type.name in syncBlockSchema.marks)) {
52
- canBeConverted = false;
53
- return false;
54
- }
55
- });
56
61
  });
57
62
  if (!canBeConverted) {
58
63
  return false;
59
64
  }
65
+ contentToInclude = removeBreakoutMarks(contentToInclude);
60
66
  return {
61
67
  contentToInclude,
62
68
  from,
63
69
  to
64
70
  };
71
+ };
72
+ const removeBreakoutMarks = content => {
73
+ const nodes = [];
74
+
75
+ // we only need to recurse at the top level, because breakout has to be on a top level
76
+ content.forEach(node => {
77
+ const filteredMarks = node.marks.filter(mark => mark.type.name !== 'breakout');
78
+ const newNode = node.type.create(node.attrs, node.content, filteredMarks);
79
+ nodes.push(newNode);
80
+ });
81
+ return Fragment.from(nodes);
65
82
  };
@@ -1,5 +1,6 @@
1
- import React from 'react';
1
+ import React, { useMemo } from 'react';
2
2
  import { useIntl } from 'react-intl-next';
3
+ import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
3
4
  import { blockMenuMessages } from '@atlaskit/editor-common/messages';
4
5
  import { SyncBlocksIcon, ToolbarDropdownItem } from '@atlaskit/editor-toolbar';
5
6
  import Lozenge from '@atlaskit/lozenge';
@@ -8,13 +9,22 @@ import { canBeConvertedToSyncBlock } from '../pm-plugins/utils/utils';
8
9
  export const CreateSyncedBlockDropdownItem = ({
9
10
  api
10
11
  }) => {
11
- var _api$selection, _api$selection$shared, _api$selection$shared2;
12
12
  const {
13
13
  formatMessage
14
14
  } = useIntl();
15
- const selection = api === null || api === void 0 ? void 0 : (_api$selection = api.selection) === null || _api$selection === void 0 ? void 0 : (_api$selection$shared = _api$selection.sharedState) === null || _api$selection$shared === void 0 ? void 0 : (_api$selection$shared2 = _api$selection$shared.currentState()) === null || _api$selection$shared2 === void 0 ? void 0 : _api$selection$shared2.selection;
16
- const canCreateSyncBlock = selection && canBeConvertedToSyncBlock(selection);
17
- if (!canCreateSyncBlock) {
15
+ const {
16
+ selection,
17
+ activeNode
18
+ } = useSharedPluginStateWithSelector(api, ['selection', 'blockControls'], states => {
19
+ var _states$selectionStat, _states$blockControls;
20
+ return {
21
+ selection: (_states$selectionStat = states.selectionState) === null || _states$selectionStat === void 0 ? void 0 : _states$selectionStat.selection,
22
+ activeNode: (_states$blockControls = states.blockControlsState) === null || _states$blockControls === void 0 ? void 0 : _states$blockControls.activeNode
23
+ };
24
+ });
25
+ const isNested = activeNode && activeNode.rootPos !== activeNode.pos;
26
+ const canBeConverted = useMemo(() => selection && canBeConvertedToSyncBlock(selection), [selection]);
27
+ if (isNested || !canBeConverted) {
18
28
  return null;
19
29
  }
20
30
  const onClick = () => {
@@ -32,7 +42,7 @@ export const CreateSyncedBlockDropdownItem = ({
32
42
  }, /*#__PURE__*/React.createElement(Flex, {
33
43
  alignItems: "center",
34
44
  gap: "space.050"
35
- }, /*#__PURE__*/React.createElement(Text, null, selection !== null && selection !== void 0 && selection.empty ? formatMessage(blockMenuMessages.createSyncedBlock) : formatMessage(blockMenuMessages.convertToSyncedBlock)), /*#__PURE__*/React.createElement(Lozenge, {
45
+ }, /*#__PURE__*/React.createElement(Text, null, formatMessage(blockMenuMessages.createSyncedBlock)), /*#__PURE__*/React.createElement(Lozenge, {
36
46
  appearance: "new"
37
47
  }, formatMessage(blockMenuMessages.newLozenge))));
38
48
  };
@@ -1,5 +1,6 @@
1
1
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
2
2
  import { copyDomNode, toDOM } from '@atlaskit/editor-common/copy-button';
3
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
3
4
  import { findSelectedNodeOfType, removeParentNodeOfType, removeSelectedNode } from '@atlaskit/editor-prosemirror/utils';
4
5
  import { canBeConvertedToSyncBlock, findSyncBlock, findSyncBlockOrBodiedSyncBlock, isBodiedSyncBlockNode } from '../pm-plugins/utils/utils';
5
6
  export var createSyncedBlock = function createSyncedBlock(_ref) {
@@ -42,7 +43,10 @@ export var createSyncedBlock = function createSyncedBlock(_ref) {
42
43
  // Save the new node with empty content to backend
43
44
  // This is so that the node can be copied and referenced without the source being saved/published
44
45
  syncBlockStore.createBodiedSyncBlockNode(_attrs);
45
- tr.replaceWith(conversionInfo.from - 1, conversionInfo.to, _newBodiedSyncBlockNode).scrollIntoView();
46
+ tr.replaceWith(conversionInfo.from > 0 ? conversionInfo.from - 1 : 0, conversionInfo.to, _newBodiedSyncBlockNode).scrollIntoView();
47
+
48
+ // set selection to the end of the previous selection + 1 for the position taken up by the start of the new synced block
49
+ tr.setSelection(TextSelection.create(tr.doc, conversionInfo.to + 1));
46
50
  }
47
51
 
48
52
  // This transaction will be intercepted in filterTransaction and dispatched when saving to backend succeeds
@@ -1,6 +1,6 @@
1
1
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
2
+ import { TextSelection } from '@atlaskit/editor-prosemirror/state';
2
3
  import { findParentNodeOfType, findSelectedNodeOfType } from '@atlaskit/editor-prosemirror/utils';
3
- import { getDefaultSyncBlockSchema } from '@atlaskit/editor-synced-block-provider';
4
4
  import { CellSelection, findTable } from '@atlaskit/editor-tables';
5
5
  export var findSyncBlock = function findSyncBlock(state, selection) {
6
6
  var syncBlock = state.schema.nodes.syncBlock;
@@ -16,11 +16,22 @@ export var findSyncBlockOrBodiedSyncBlock = function findSyncBlockOrBodiedSyncBl
16
16
  export var isBodiedSyncBlockNode = function isBodiedSyncBlockNode(node, bodiedSyncBlock) {
17
17
  return node.type === bodiedSyncBlock;
18
18
  };
19
+ var UNSUPPORTED_NODE_TYPES = new Set(['inlineExtension', 'extension', 'bodiedExtension', 'syncBlock', 'bodiedSyncBlock']);
20
+
21
+ /**
22
+ * Checks whether the selection can be converted to sync block
23
+ *
24
+ * @param selection - the current editor selection to validate for sync block conversion
25
+ * @returns A fragment containing the content to include in the synced block,
26
+ * stripping out unsupported marks (breakout on codeblock/expand/layout), as well as from and to positions,
27
+ * or false if conversion is not possible
28
+ */
19
29
  export var canBeConvertedToSyncBlock = function canBeConvertedToSyncBlock(selection) {
30
+ var schema = selection.$from.doc.type.schema;
31
+ var nodes = schema.nodes;
20
32
  var from = selection.from;
21
33
  var to = selection.to;
22
- var depth = selection.$from.depth;
23
- var contentToInclude;
34
+ var contentToInclude = selection.content().content;
24
35
  if (selection instanceof CellSelection) {
25
36
  var table = findTable(selection);
26
37
  if (!table) {
@@ -29,35 +40,41 @@ export var canBeConvertedToSyncBlock = function canBeConvertedToSyncBlock(select
29
40
  contentToInclude = Fragment.from([table.node]);
30
41
  from = table.pos;
31
42
  to = table.pos + table.node.nodeSize;
32
- depth = selection.$from.doc.resolve(table.pos).depth;
33
- } else {
34
- contentToInclude = Fragment.from(selection.content().content);
35
- }
36
-
37
- // sync blocks can't be nested
38
- if (depth > 1) {
39
- return false;
43
+ } else if (selection instanceof TextSelection) {
44
+ var trueParent = findParentNodeOfType([nodes.bulletList, nodes.orderedList, nodes.taskList, nodes.blockquote])(selection);
45
+ if (trueParent) {
46
+ contentToInclude = Fragment.from([trueParent.node]);
47
+ from = trueParent.pos;
48
+ to = trueParent.pos + trueParent.node.nodeSize;
49
+ }
40
50
  }
41
- var syncBlockSchema = getDefaultSyncBlockSchema();
42
51
  var canBeConverted = true;
43
52
  selection.$from.doc.nodesBetween(from, to, function (node) {
44
- if (!(node.type.name in syncBlockSchema.nodes)) {
53
+ if (UNSUPPORTED_NODE_TYPES.has(node.type.name)) {
45
54
  canBeConverted = false;
46
55
  return false;
47
56
  }
48
- node.marks.forEach(function (mark) {
49
- if (!(mark.type.name in syncBlockSchema.marks)) {
50
- canBeConverted = false;
51
- return false;
52
- }
53
- });
54
57
  });
55
58
  if (!canBeConverted) {
56
59
  return false;
57
60
  }
61
+ contentToInclude = removeBreakoutMarks(contentToInclude);
58
62
  return {
59
63
  contentToInclude: contentToInclude,
60
64
  from: from,
61
65
  to: to
62
66
  };
67
+ };
68
+ var removeBreakoutMarks = function removeBreakoutMarks(content) {
69
+ var nodes = [];
70
+
71
+ // we only need to recurse at the top level, because breakout has to be on a top level
72
+ content.forEach(function (node) {
73
+ var filteredMarks = node.marks.filter(function (mark) {
74
+ return mark.type.name !== 'breakout';
75
+ });
76
+ var newNode = node.type.create(node.attrs, node.content, filteredMarks);
77
+ nodes.push(newNode);
78
+ });
79
+ return Fragment.from(nodes);
63
80
  };
@@ -1,18 +1,29 @@
1
- import React from 'react';
1
+ import React, { useMemo } from 'react';
2
2
  import { useIntl } from 'react-intl-next';
3
+ import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
3
4
  import { blockMenuMessages } from '@atlaskit/editor-common/messages';
4
5
  import { SyncBlocksIcon, ToolbarDropdownItem } from '@atlaskit/editor-toolbar';
5
6
  import Lozenge from '@atlaskit/lozenge';
6
7
  import { Flex, Text } from '@atlaskit/primitives/compiled';
7
8
  import { canBeConvertedToSyncBlock } from '../pm-plugins/utils/utils';
8
9
  export var CreateSyncedBlockDropdownItem = function CreateSyncedBlockDropdownItem(_ref) {
9
- var _api$selection;
10
10
  var api = _ref.api;
11
11
  var _useIntl = useIntl(),
12
12
  formatMessage = _useIntl.formatMessage;
13
- var selection = api === null || api === void 0 || (_api$selection = api.selection) === null || _api$selection === void 0 || (_api$selection = _api$selection.sharedState) === null || _api$selection === void 0 || (_api$selection = _api$selection.currentState()) === null || _api$selection === void 0 ? void 0 : _api$selection.selection;
14
- var canCreateSyncBlock = selection && canBeConvertedToSyncBlock(selection);
15
- if (!canCreateSyncBlock) {
13
+ var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['selection', 'blockControls'], function (states) {
14
+ var _states$selectionStat, _states$blockControls;
15
+ return {
16
+ selection: (_states$selectionStat = states.selectionState) === null || _states$selectionStat === void 0 ? void 0 : _states$selectionStat.selection,
17
+ activeNode: (_states$blockControls = states.blockControlsState) === null || _states$blockControls === void 0 ? void 0 : _states$blockControls.activeNode
18
+ };
19
+ }),
20
+ selection = _useSharedPluginState.selection,
21
+ activeNode = _useSharedPluginState.activeNode;
22
+ var isNested = activeNode && activeNode.rootPos !== activeNode.pos;
23
+ var canBeConverted = useMemo(function () {
24
+ return selection && canBeConvertedToSyncBlock(selection);
25
+ }, [selection]);
26
+ if (isNested || !canBeConverted) {
16
27
  return null;
17
28
  }
18
29
  var onClick = function onClick() {
@@ -30,7 +41,7 @@ export var CreateSyncedBlockDropdownItem = function CreateSyncedBlockDropdownIte
30
41
  }, /*#__PURE__*/React.createElement(Flex, {
31
42
  alignItems: "center",
32
43
  gap: "space.050"
33
- }, /*#__PURE__*/React.createElement(Text, null, selection !== null && selection !== void 0 && selection.empty ? formatMessage(blockMenuMessages.createSyncedBlock) : formatMessage(blockMenuMessages.convertToSyncedBlock)), /*#__PURE__*/React.createElement(Lozenge, {
44
+ }, /*#__PURE__*/React.createElement(Text, null, formatMessage(blockMenuMessages.createSyncedBlock)), /*#__PURE__*/React.createElement(Lozenge, {
34
45
  appearance: "new"
35
46
  }, formatMessage(blockMenuMessages.newLozenge))));
36
47
  };
@@ -1,6 +1,6 @@
1
1
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
2
2
  import type { NodeType, Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
- import type { EditorState, Selection } from '@atlaskit/editor-prosemirror/state';
3
+ import { type EditorState, type Selection } from '@atlaskit/editor-prosemirror/state';
4
4
  import type { ContentNodeWithPos } from '@atlaskit/editor-prosemirror/utils';
5
5
  export declare const findSyncBlock: (state: EditorState, selection?: Selection | null) => ContentNodeWithPos | undefined;
6
6
  export declare const findBodiedSyncBlock: (state: EditorState, selection?: Selection | null) => ContentNodeWithPos | undefined;
@@ -11,4 +11,12 @@ export interface SyncBlockConversionInfo {
11
11
  from: number;
12
12
  to: number;
13
13
  }
14
+ /**
15
+ * Checks whether the selection can be converted to sync block
16
+ *
17
+ * @param selection - the current editor selection to validate for sync block conversion
18
+ * @returns A fragment containing the content to include in the synced block,
19
+ * stripping out unsupported marks (breakout on codeblock/expand/layout), as well as from and to positions,
20
+ * or false if conversion is not possible
21
+ */
14
22
  export declare const canBeConvertedToSyncBlock: (selection: Selection) => SyncBlockConversionInfo | false;
@@ -1,6 +1,6 @@
1
1
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
2
2
  import type { NodeType, Node as PMNode } from '@atlaskit/editor-prosemirror/model';
3
- import type { EditorState, Selection } from '@atlaskit/editor-prosemirror/state';
3
+ import { type EditorState, type Selection } from '@atlaskit/editor-prosemirror/state';
4
4
  import type { ContentNodeWithPos } from '@atlaskit/editor-prosemirror/utils';
5
5
  export declare const findSyncBlock: (state: EditorState, selection?: Selection | null) => ContentNodeWithPos | undefined;
6
6
  export declare const findBodiedSyncBlock: (state: EditorState, selection?: Selection | null) => ContentNodeWithPos | undefined;
@@ -11,4 +11,12 @@ export interface SyncBlockConversionInfo {
11
11
  from: number;
12
12
  to: number;
13
13
  }
14
+ /**
15
+ * Checks whether the selection can be converted to sync block
16
+ *
17
+ * @param selection - the current editor selection to validate for sync block conversion
18
+ * @returns A fragment containing the content to include in the synced block,
19
+ * stripping out unsupported marks (breakout on codeblock/expand/layout), as well as from and to positions,
20
+ * or false if conversion is not possible
21
+ */
14
22
  export declare const canBeConvertedToSyncBlock: (selection: Selection) => SyncBlockConversionInfo | false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-synced-block",
3
- "version": "4.2.1",
3
+ "version": "4.2.2",
4
4
  "description": "SyncedBlock plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -42,7 +42,7 @@
42
42
  "@atlaskit/editor-tables": "^2.9.0",
43
43
  "@atlaskit/editor-toolbar": "^0.17.0",
44
44
  "@atlaskit/icon": "28.5.4",
45
- "@atlaskit/icon-lab": "^5.11.0",
45
+ "@atlaskit/icon-lab": "^5.12.0",
46
46
  "@atlaskit/logo": "^19.9.0",
47
47
  "@atlaskit/lozenge": "^13.1.0",
48
48
  "@atlaskit/modal-dialog": "^14.6.0",