@atlaskit/editor-plugin-block-menu 1.0.6 → 1.0.8

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,21 @@
1
1
  # @atlaskit/editor-plugin-block-menu
2
2
 
3
+ ## 1.0.8
4
+
5
+ ### Patch Changes
6
+
7
+ - [`b3e1332c170a6`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/b3e1332c170a6) -
8
+ Container to container transform with unsupportted content
9
+ - Updated dependencies
10
+
11
+ ## 1.0.7
12
+
13
+ ### Patch Changes
14
+
15
+ - [`34871606f04ba`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/34871606f04ba) -
16
+ [ux] Updates unwrapAndConvertToList to support codeBlock to list case and cases where contnent is
17
+ not textblock.
18
+
3
19
  ## 1.0.6
4
20
 
5
21
  ### Patch Changes
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.unwrapAndConvertToList = exports.unwrapAndConvertToBlockType = exports.transformToContainer = exports.transformContainerNode = void 0;
7
+ exports.unwrapAndConvertToList = exports.unwrapAndConvertToBlockType = exports.transformToContainer = exports.transformContainerNode = exports.transformBetweenContainerTypes = void 0;
8
8
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
9
  var _model = require("@atlaskit/editor-prosemirror/model");
10
10
  var _utils = require("./utils");
@@ -84,8 +84,13 @@ var transformContainerNode = exports.transformContainerNode = function transform
84
84
 
85
85
  // Transform between container types
86
86
  if ((0, _utils.isContainerNodeType)(targetNodeType)) {
87
- tr.setNodeMarkup(sourcePos, targetNodeType, targetAttrs);
88
- return tr;
87
+ return transformBetweenContainerTypes({
88
+ tr: tr,
89
+ sourceNode: sourceNode,
90
+ sourcePos: sourcePos,
91
+ targetNodeType: targetNodeType,
92
+ targetAttrs: targetAttrs
93
+ });
89
94
  }
90
95
  return null;
91
96
  };
@@ -171,18 +176,16 @@ var unwrapAndConvertToList = exports.unwrapAndConvertToList = function unwrapAnd
171
176
  sourcePos = _ref3.sourcePos,
172
177
  targetNodeType = _ref3.targetNodeType,
173
178
  targetAttrs = _ref3.targetAttrs;
174
- if (sourcePos === null) {
175
- return tr;
176
- }
177
179
  var schema = tr.doc.type.schema;
178
180
  var _schema$nodes2 = schema.nodes,
179
181
  listItem = _schema$nodes2.listItem,
180
182
  paragraph = _schema$nodes2.paragraph,
181
183
  taskList = _schema$nodes2.taskList,
182
- taskItem = _schema$nodes2.taskItem;
184
+ taskItem = _schema$nodes2.taskItem,
185
+ heading = _schema$nodes2.heading;
183
186
  var isTargetTaskList = targetNodeType === taskList;
184
- var createListItemFromInline = function createListItemFromInline(inlineFrag) {
185
- return isTargetTaskList ? taskItem.create(null, inlineFrag) : listItem.create(null, paragraph.create(null, inlineFrag));
187
+ var createListItemFromInline = function createListItemFromInline(content) {
188
+ return isTargetTaskList ? taskItem.create(null, content) : listItem.create(null, paragraph.create(null, content));
186
189
  };
187
190
  var getInlineContent = function getInlineContent(textblock) {
188
191
  var inlineContent = [];
@@ -191,30 +194,134 @@ var unwrapAndConvertToList = exports.unwrapAndConvertToList = function unwrapAnd
191
194
  });
192
195
  return inlineContent;
193
196
  };
194
- var items = [];
195
-
197
+ var resultContent = [];
198
+ var currentListItems = [];
199
+ var targetListItemType = isTargetTaskList ? taskItem : listItem;
196
200
  // Expand's title should become the first item of the list
197
201
  if (sourceNode.type.name === 'expand') {
198
202
  var _sourceNode$attrs2;
199
203
  var title = (_sourceNode$attrs2 = sourceNode.attrs) === null || _sourceNode$attrs2 === void 0 ? void 0 : _sourceNode$attrs2.title;
200
204
  if (title) {
201
205
  var titleContent = schema.text(title);
202
- items.push(isTargetTaskList ? taskItem.create(null, titleContent) : listItem.create(null, paragraph.create(null, titleContent)));
206
+ currentListItems.push(createListItemFromInline(titleContent));
203
207
  }
204
208
  }
205
- for (var i = 0; i < sourceNode.childCount; i++) {
206
- var node = sourceNode.child(i);
207
-
208
- // Abort early if unsupported content (e.g. table) encounted inside of the container
209
- if (!node.isTextblock) {
210
- return tr;
209
+ var createListAndAddToContent = function createListAndAddToContent() {
210
+ if (currentListItems.length) {
211
+ var currentList = targetNodeType.create(targetAttrs || null, _model.Fragment.from(currentListItems));
212
+ currentListItems = [];
213
+ resultContent.push(currentList);
211
214
  }
212
- var inline = _model.Fragment.from(getInlineContent(node));
213
- items.push(createListItemFromInline(inline));
215
+ };
216
+ if (sourceNode.type.name === 'codeBlock') {
217
+ var codeText = sourceNode.textContent;
218
+ if (codeText) {
219
+ var lines = codeText.split('\n');
220
+ // Remove empty lines
221
+ var nonEmptyLines = lines.filter(function (line) {
222
+ return line.trim().length > 0;
223
+ });
224
+ nonEmptyLines.forEach(function (line) {
225
+ var lineText = schema.text(line);
226
+ currentListItems.push(createListItemFromInline(lineText));
227
+ });
228
+ }
229
+ } else {
230
+ sourceNode.forEach(function (child) {
231
+ if (targetListItemType.validContent(_model.Fragment.from(child))) {
232
+ currentListItems.push(targetListItemType.create(null, child));
233
+ } else if (heading === child.type || isTargetTaskList && paragraph === child.type) {
234
+ var inline = _model.Fragment.from(getInlineContent(child));
235
+ currentListItems.push(createListItemFromInline(inline));
236
+ } else {
237
+ // Create list and add list first
238
+ createListAndAddToContent();
239
+ // Then add content that can't be converted into listItem
240
+ resultContent.push(child);
241
+ }
242
+ });
214
243
  }
215
- if (!items.length) {
216
- return tr;
244
+ if (!resultContent.length && !currentListItems.length) {
245
+ return null;
246
+ }
247
+ createListAndAddToContent();
248
+ return tr.replaceWith(sourcePos, sourcePos + sourceNode.nodeSize, _model.Fragment.from(resultContent));
249
+ };
250
+ var transformBetweenContainerTypes = exports.transformBetweenContainerTypes = function transformBetweenContainerTypes(context) {
251
+ var tr = context.tr,
252
+ sourceNode = context.sourceNode,
253
+ sourcePos = context.sourcePos,
254
+ targetNodeType = context.targetNodeType,
255
+ targetAttrs = context.targetAttrs;
256
+
257
+ // Get content validation for target container type
258
+ var isContentSupported = (0, _utils.getContentSupportChecker)(targetNodeType);
259
+
260
+ // Process content and collect splits
261
+ var contentSplits = splitContentAroundUnsupportedBlocks(sourceNode, isContentSupported, targetNodeType, targetAttrs, tr.doc.type.schema);
262
+
263
+ // Replace the original node with the first split
264
+ var insertPos = sourcePos;
265
+ contentSplits.forEach(function (splitNode, index) {
266
+ if (index === 0) {
267
+ // Replace the original node with the first split
268
+ tr.replaceWith(sourcePos, sourcePos + sourceNode.nodeSize, splitNode);
269
+ insertPos = sourcePos + splitNode.nodeSize;
270
+ } else {
271
+ // Insert additional splits after
272
+ tr.insert(insertPos, splitNode);
273
+ insertPos += splitNode.nodeSize;
274
+ }
275
+ });
276
+ return tr;
277
+ };
278
+
279
+ /**
280
+ * Split content around unsupported block nodes, creating separate containers
281
+ * for content before and after each unsupported block
282
+ */
283
+ var splitContentAroundUnsupportedBlocks = function splitContentAroundUnsupportedBlocks(sourceNode, isContentSupported, targetNodeType, targetAttrs, schema) {
284
+ var _sourceNode$attrs3;
285
+ var splits = [];
286
+ var children = sourceNode.content.content;
287
+ var currentContainerContent = [];
288
+
289
+ // Handle expand title - add as first paragraph if source is expand with title
290
+ if (sourceNode.type.name === 'expand' && (_sourceNode$attrs3 = sourceNode.attrs) !== null && _sourceNode$attrs3 !== void 0 && _sourceNode$attrs3.title) {
291
+ var titleParagraph = schema.nodes.paragraph.create({}, schema.text(sourceNode.attrs.title));
292
+ currentContainerContent.push(titleParagraph);
217
293
  }
218
- var list = targetNodeType.create(targetAttrs || null, _model.Fragment.from(items));
219
- return tr.replaceWith(sourcePos, sourcePos + sourceNode.nodeSize, list);
294
+ var flushCurrentContainer = function flushCurrentContainer() {
295
+ if (currentContainerContent.length > 0) {
296
+ var containerNode = targetNodeType.create(targetAttrs, _model.Fragment.fromArray(currentContainerContent));
297
+ splits.push(containerNode);
298
+ currentContainerContent = [];
299
+ }
300
+ };
301
+ children.forEach(function (childNode) {
302
+ if (isContentSupported(childNode)) {
303
+ // Supported content - add to current container
304
+ currentContainerContent.push(childNode);
305
+ } else if ((0, _utils.isBlockNodeForExtraction)(childNode)) {
306
+ // Unsupported block node - flush current container, add block, continue
307
+ flushCurrentContainer();
308
+ splits.push(childNode);
309
+ } else if (childNode.type.name === targetNodeType.name) {
310
+ // Same type of container merge contents
311
+ childNode.content.forEach(function (child) {
312
+ currentContainerContent.push(child);
313
+ });
314
+ } else {
315
+ // Unsupported inline content - convert to paragraph and add to container
316
+ var inlineContent = (0, _utils.convertNodeToInlineContent)(childNode, schema);
317
+ if (inlineContent.length > 0) {
318
+ var paragraph = schema.nodes.paragraph.create({}, _model.Fragment.fromArray(inlineContent));
319
+ currentContainerContent.push(paragraph);
320
+ }
321
+ }
322
+ });
323
+
324
+ // Flush any remaining container content
325
+ flushCurrentContainer();
326
+ return splits;
220
327
  };
@@ -47,6 +47,11 @@ function transformNodeToTargetType(tr, sourceNode, sourcePos, targetType) {
47
47
  if ((0, _utils.isLayoutNodeType)(targetNodeType)) {
48
48
  return (0, _layoutTransforms.convertToLayout)(transformationContext);
49
49
  }
50
+
51
+ // special case codeblock to listType
52
+ if (sourceNode.type.name === 'codeBlock' && (0, _utils.isListNodeType)(targetNodeType)) {
53
+ return (0, _containerTransforms.unwrapAndConvertToList)(transformationContext);
54
+ }
50
55
  if ((0, _utils.isBlockNode)(sourceNode)) {
51
56
  return (0, _blockTransforms.transformBlockNode)(transformationContext);
52
57
  }
@@ -3,7 +3,8 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.isTaskList = exports.isListNodeType = exports.isListNode = exports.isLayoutNodeType = exports.isContainerNodeType = exports.isContainerNode = exports.isBulletOrOrderedList = exports.isBlockNodeType = exports.isBlockNode = exports.getTargetNodeInfo = exports.getSupportedListTypesSet = exports.getSupportedListTypes = void 0;
6
+ exports.isTaskList = exports.isListNodeType = exports.isListNode = exports.isLayoutNodeType = exports.isContainerNodeType = exports.isContainerNode = exports.isBulletOrOrderedList = exports.isBlockNodeType = exports.isBlockNodeForExtraction = exports.isBlockNode = exports.getTargetNodeInfo = exports.getSupportedListTypesSet = exports.getSupportedListTypes = exports.getContentSupportChecker = exports.convertNodeToInlineContent = void 0;
7
+ var _model = require("@atlaskit/editor-prosemirror/model");
7
8
  var getTargetNodeInfo = exports.getTargetNodeInfo = function getTargetNodeInfo(targetType, nodes) {
8
9
  switch (targetType) {
9
10
  case 'heading1':
@@ -127,4 +128,50 @@ var getSupportedListTypesSet = exports.getSupportedListTypesSet = function getSu
127
128
  };
128
129
  var isLayoutNodeType = exports.isLayoutNodeType = function isLayoutNodeType(nodeType) {
129
130
  return nodeType.name === 'layoutSection';
131
+ };
132
+
133
+ /**
134
+ * Check if a node should be extracted as a standalone block node
135
+ * rather than converted to inline content
136
+ */
137
+ var isBlockNodeForExtraction = exports.isBlockNodeForExtraction = function isBlockNodeForExtraction(node) {
138
+ var blockNodesForExtraction = ['table', 'mediaSingle', 'extension', 'bodiedExtension', 'blockCard', 'embedCard'];
139
+ return blockNodesForExtraction.includes(node.type.name);
140
+ };
141
+
142
+ /**
143
+ * Get a function that checks if content is supported in the target container type
144
+ */
145
+ var getContentSupportChecker = exports.getContentSupportChecker = function getContentSupportChecker(targetNodeType) {
146
+ return function (node) {
147
+ // Check if the target container type can contain this node
148
+ try {
149
+ return targetNodeType.validContent(_model.Fragment.from(node));
150
+ } catch (_unused) {
151
+ return false;
152
+ }
153
+ };
154
+ };
155
+
156
+ /**
157
+ * Convert a node to inline content that can be placed in a paragraph
158
+ */
159
+ var convertNodeToInlineContent = exports.convertNodeToInlineContent = function convertNodeToInlineContent(node, schema) {
160
+ // Extract text and inline nodes from any complex node
161
+ var inlineNodes = [];
162
+ node.descendants(function (childNode) {
163
+ if (childNode.isText) {
164
+ inlineNodes.push(childNode);
165
+ } else if (childNode.isInline) {
166
+ inlineNodes.push(childNode);
167
+ }
168
+ return true; // Continue traversing
169
+ });
170
+
171
+ // If no inline content was found but the node has text content,
172
+ // create a text node with the full text content
173
+ if (inlineNodes.length === 0 && node.textContent) {
174
+ return [schema.text(node.textContent)];
175
+ }
176
+ return inlineNodes;
130
177
  };
@@ -1,5 +1,5 @@
1
1
  import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
2
- import { isBlockNodeType, isListNodeType, isContainerNodeType } from './utils';
2
+ import { isBlockNodeType, isListNodeType, isContainerNodeType, isBlockNodeForExtraction, convertNodeToInlineContent, getContentSupportChecker } from './utils';
3
3
  const convertInvalidNodeToValidNodeType = (sourceContent, sourceNodeType, validNodeType, withMarks) => {
4
4
  const validTransformedContent = [];
5
5
  // Headings are not valid inside headings so convert heading nodes to paragraphs
@@ -78,8 +78,13 @@ export const transformContainerNode = ({
78
78
 
79
79
  // Transform between container types
80
80
  if (isContainerNodeType(targetNodeType)) {
81
- tr.setNodeMarkup(sourcePos, targetNodeType, targetAttrs);
82
- return tr;
81
+ return transformBetweenContainerTypes({
82
+ tr,
83
+ sourceNode,
84
+ sourcePos,
85
+ targetNodeType,
86
+ targetAttrs
87
+ });
83
88
  }
84
89
  return null;
85
90
  };
@@ -167,9 +172,6 @@ export const unwrapAndConvertToList = ({
167
172
  targetNodeType,
168
173
  targetAttrs
169
174
  }) => {
170
- if (sourcePos === null) {
171
- return tr;
172
- }
173
175
  const {
174
176
  schema
175
177
  } = tr.doc.type;
@@ -177,11 +179,12 @@ export const unwrapAndConvertToList = ({
177
179
  listItem,
178
180
  paragraph,
179
181
  taskList,
180
- taskItem
182
+ taskItem,
183
+ heading
181
184
  } = schema.nodes;
182
185
  const isTargetTaskList = targetNodeType === taskList;
183
- const createListItemFromInline = inlineFrag => {
184
- return isTargetTaskList ? taskItem.create(null, inlineFrag) : listItem.create(null, paragraph.create(null, inlineFrag));
186
+ const createListItemFromInline = content => {
187
+ return isTargetTaskList ? taskItem.create(null, content) : listItem.create(null, paragraph.create(null, content));
185
188
  };
186
189
  const getInlineContent = textblock => {
187
190
  const inlineContent = [];
@@ -190,30 +193,134 @@ export const unwrapAndConvertToList = ({
190
193
  });
191
194
  return inlineContent;
192
195
  };
193
- const items = [];
194
-
196
+ const resultContent = [];
197
+ let currentListItems = [];
198
+ const targetListItemType = isTargetTaskList ? taskItem : listItem;
195
199
  // Expand's title should become the first item of the list
196
200
  if (sourceNode.type.name === 'expand') {
197
201
  var _sourceNode$attrs2;
198
202
  const title = (_sourceNode$attrs2 = sourceNode.attrs) === null || _sourceNode$attrs2 === void 0 ? void 0 : _sourceNode$attrs2.title;
199
203
  if (title) {
200
204
  const titleContent = schema.text(title);
201
- items.push(isTargetTaskList ? taskItem.create(null, titleContent) : listItem.create(null, paragraph.create(null, titleContent)));
205
+ currentListItems.push(createListItemFromInline(titleContent));
202
206
  }
203
207
  }
204
- for (let i = 0; i < sourceNode.childCount; i++) {
205
- const node = sourceNode.child(i);
206
-
207
- // Abort early if unsupported content (e.g. table) encounted inside of the container
208
- if (!node.isTextblock) {
209
- return tr;
208
+ const createListAndAddToContent = () => {
209
+ if (currentListItems.length) {
210
+ const currentList = targetNodeType.create(targetAttrs || null, Fragment.from(currentListItems));
211
+ currentListItems = [];
212
+ resultContent.push(currentList);
210
213
  }
211
- const inline = Fragment.from(getInlineContent(node));
212
- items.push(createListItemFromInline(inline));
214
+ };
215
+ if (sourceNode.type.name === 'codeBlock') {
216
+ const codeText = sourceNode.textContent;
217
+ if (codeText) {
218
+ const lines = codeText.split('\n');
219
+ // Remove empty lines
220
+ const nonEmptyLines = lines.filter(line => line.trim().length > 0);
221
+ nonEmptyLines.forEach(line => {
222
+ const lineText = schema.text(line);
223
+ currentListItems.push(createListItemFromInline(lineText));
224
+ });
225
+ }
226
+ } else {
227
+ sourceNode.forEach(child => {
228
+ if (targetListItemType.validContent(Fragment.from(child))) {
229
+ currentListItems.push(targetListItemType.create(null, child));
230
+ } else if (heading === child.type || isTargetTaskList && paragraph === child.type) {
231
+ const inline = Fragment.from(getInlineContent(child));
232
+ currentListItems.push(createListItemFromInline(inline));
233
+ } else {
234
+ // Create list and add list first
235
+ createListAndAddToContent();
236
+ // Then add content that can't be converted into listItem
237
+ resultContent.push(child);
238
+ }
239
+ });
240
+ }
241
+ if (!resultContent.length && !currentListItems.length) {
242
+ return null;
213
243
  }
214
- if (!items.length) {
215
- return tr;
244
+ createListAndAddToContent();
245
+ return tr.replaceWith(sourcePos, sourcePos + sourceNode.nodeSize, Fragment.from(resultContent));
246
+ };
247
+ export const transformBetweenContainerTypes = context => {
248
+ const {
249
+ tr,
250
+ sourceNode,
251
+ sourcePos,
252
+ targetNodeType,
253
+ targetAttrs
254
+ } = context;
255
+
256
+ // Get content validation for target container type
257
+ const isContentSupported = getContentSupportChecker(targetNodeType);
258
+
259
+ // Process content and collect splits
260
+ const contentSplits = splitContentAroundUnsupportedBlocks(sourceNode, isContentSupported, targetNodeType, targetAttrs, tr.doc.type.schema);
261
+
262
+ // Replace the original node with the first split
263
+ let insertPos = sourcePos;
264
+ contentSplits.forEach((splitNode, index) => {
265
+ if (index === 0) {
266
+ // Replace the original node with the first split
267
+ tr.replaceWith(sourcePos, sourcePos + sourceNode.nodeSize, splitNode);
268
+ insertPos = sourcePos + splitNode.nodeSize;
269
+ } else {
270
+ // Insert additional splits after
271
+ tr.insert(insertPos, splitNode);
272
+ insertPos += splitNode.nodeSize;
273
+ }
274
+ });
275
+ return tr;
276
+ };
277
+
278
+ /**
279
+ * Split content around unsupported block nodes, creating separate containers
280
+ * for content before and after each unsupported block
281
+ */
282
+ const splitContentAroundUnsupportedBlocks = (sourceNode, isContentSupported, targetNodeType, targetAttrs, schema) => {
283
+ var _sourceNode$attrs3;
284
+ const splits = [];
285
+ const children = sourceNode.content.content;
286
+ let currentContainerContent = [];
287
+
288
+ // Handle expand title - add as first paragraph if source is expand with title
289
+ if (sourceNode.type.name === 'expand' && (_sourceNode$attrs3 = sourceNode.attrs) !== null && _sourceNode$attrs3 !== void 0 && _sourceNode$attrs3.title) {
290
+ const titleParagraph = schema.nodes.paragraph.create({}, schema.text(sourceNode.attrs.title));
291
+ currentContainerContent.push(titleParagraph);
216
292
  }
217
- const list = targetNodeType.create(targetAttrs || null, Fragment.from(items));
218
- return tr.replaceWith(sourcePos, sourcePos + sourceNode.nodeSize, list);
293
+ const flushCurrentContainer = () => {
294
+ if (currentContainerContent.length > 0) {
295
+ const containerNode = targetNodeType.create(targetAttrs, Fragment.fromArray(currentContainerContent));
296
+ splits.push(containerNode);
297
+ currentContainerContent = [];
298
+ }
299
+ };
300
+ children.forEach(childNode => {
301
+ if (isContentSupported(childNode)) {
302
+ // Supported content - add to current container
303
+ currentContainerContent.push(childNode);
304
+ } else if (isBlockNodeForExtraction(childNode)) {
305
+ // Unsupported block node - flush current container, add block, continue
306
+ flushCurrentContainer();
307
+ splits.push(childNode);
308
+ } else if (childNode.type.name === targetNodeType.name) {
309
+ // Same type of container merge contents
310
+ childNode.content.forEach(child => {
311
+ currentContainerContent.push(child);
312
+ });
313
+ } else {
314
+ // Unsupported inline content - convert to paragraph and add to container
315
+ const inlineContent = convertNodeToInlineContent(childNode, schema);
316
+ if (inlineContent.length > 0) {
317
+ const paragraph = schema.nodes.paragraph.create({}, Fragment.fromArray(inlineContent));
318
+ currentContainerContent.push(paragraph);
319
+ }
320
+ }
321
+ });
322
+
323
+ // Flush any remaining container content
324
+ flushCurrentContainer();
325
+ return splits;
219
326
  };
@@ -1,8 +1,8 @@
1
1
  import { transformBlockNode } from './block-transforms';
2
- import { transformContainerNode } from './container-transforms';
2
+ import { transformContainerNode, unwrapAndConvertToList } from './container-transforms';
3
3
  import { convertToLayout } from './layout-transforms';
4
4
  import { transformListNode } from './list-transforms';
5
- import { getTargetNodeInfo, isBlockNode, isListNode, isContainerNode, isLayoutNodeType } from './utils';
5
+ import { getTargetNodeInfo, isBlockNode, isListNode, isListNodeType, isContainerNode, isLayoutNodeType } from './utils';
6
6
  export function transformNodeToTargetType(tr, sourceNode, sourcePos, targetType) {
7
7
  const {
8
8
  nodes
@@ -45,6 +45,11 @@ export function transformNodeToTargetType(tr, sourceNode, sourcePos, targetType)
45
45
  if (isLayoutNodeType(targetNodeType)) {
46
46
  return convertToLayout(transformationContext);
47
47
  }
48
+
49
+ // special case codeblock to listType
50
+ if (sourceNode.type.name === 'codeBlock' && isListNodeType(targetNodeType)) {
51
+ return unwrapAndConvertToList(transformationContext);
52
+ }
48
53
  if (isBlockNode(sourceNode)) {
49
54
  return transformBlockNode(transformationContext);
50
55
  }
@@ -1,3 +1,4 @@
1
+ import { Fragment } from '@atlaskit/editor-prosemirror/model';
1
2
  export const getTargetNodeInfo = (targetType, nodes) => {
2
3
  switch (targetType) {
3
4
  case 'heading1':
@@ -121,4 +122,50 @@ export const getSupportedListTypesSet = nodes => {
121
122
  };
122
123
  export const isLayoutNodeType = nodeType => {
123
124
  return nodeType.name === 'layoutSection';
125
+ };
126
+
127
+ /**
128
+ * Check if a node should be extracted as a standalone block node
129
+ * rather than converted to inline content
130
+ */
131
+ export const isBlockNodeForExtraction = node => {
132
+ const blockNodesForExtraction = ['table', 'mediaSingle', 'extension', 'bodiedExtension', 'blockCard', 'embedCard'];
133
+ return blockNodesForExtraction.includes(node.type.name);
134
+ };
135
+
136
+ /**
137
+ * Get a function that checks if content is supported in the target container type
138
+ */
139
+ export const getContentSupportChecker = targetNodeType => {
140
+ return node => {
141
+ // Check if the target container type can contain this node
142
+ try {
143
+ return targetNodeType.validContent(Fragment.from(node));
144
+ } catch {
145
+ return false;
146
+ }
147
+ };
148
+ };
149
+
150
+ /**
151
+ * Convert a node to inline content that can be placed in a paragraph
152
+ */
153
+ export const convertNodeToInlineContent = (node, schema) => {
154
+ // Extract text and inline nodes from any complex node
155
+ const inlineNodes = [];
156
+ node.descendants(childNode => {
157
+ if (childNode.isText) {
158
+ inlineNodes.push(childNode);
159
+ } else if (childNode.isInline) {
160
+ inlineNodes.push(childNode);
161
+ }
162
+ return true; // Continue traversing
163
+ });
164
+
165
+ // If no inline content was found but the node has text content,
166
+ // create a text node with the full text content
167
+ if (inlineNodes.length === 0 && node.textContent) {
168
+ return [schema.text(node.textContent)];
169
+ }
170
+ return inlineNodes;
124
171
  };
@@ -1,6 +1,6 @@
1
1
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
2
  import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
3
- import { isBlockNodeType, isListNodeType, isContainerNodeType } from './utils';
3
+ import { isBlockNodeType, isListNodeType, isContainerNodeType, isBlockNodeForExtraction, convertNodeToInlineContent, getContentSupportChecker } from './utils';
4
4
  var convertInvalidNodeToValidNodeType = function convertInvalidNodeToValidNodeType(sourceContent, sourceNodeType, validNodeType, withMarks) {
5
5
  var validTransformedContent = [];
6
6
  // Headings are not valid inside headings so convert heading nodes to paragraphs
@@ -77,8 +77,13 @@ export var transformContainerNode = function transformContainerNode(_ref2) {
77
77
 
78
78
  // Transform between container types
79
79
  if (isContainerNodeType(targetNodeType)) {
80
- tr.setNodeMarkup(sourcePos, targetNodeType, targetAttrs);
81
- return tr;
80
+ return transformBetweenContainerTypes({
81
+ tr: tr,
82
+ sourceNode: sourceNode,
83
+ sourcePos: sourcePos,
84
+ targetNodeType: targetNodeType,
85
+ targetAttrs: targetAttrs
86
+ });
82
87
  }
83
88
  return null;
84
89
  };
@@ -164,18 +169,16 @@ export var unwrapAndConvertToList = function unwrapAndConvertToList(_ref3) {
164
169
  sourcePos = _ref3.sourcePos,
165
170
  targetNodeType = _ref3.targetNodeType,
166
171
  targetAttrs = _ref3.targetAttrs;
167
- if (sourcePos === null) {
168
- return tr;
169
- }
170
172
  var schema = tr.doc.type.schema;
171
173
  var _schema$nodes2 = schema.nodes,
172
174
  listItem = _schema$nodes2.listItem,
173
175
  paragraph = _schema$nodes2.paragraph,
174
176
  taskList = _schema$nodes2.taskList,
175
- taskItem = _schema$nodes2.taskItem;
177
+ taskItem = _schema$nodes2.taskItem,
178
+ heading = _schema$nodes2.heading;
176
179
  var isTargetTaskList = targetNodeType === taskList;
177
- var createListItemFromInline = function createListItemFromInline(inlineFrag) {
178
- return isTargetTaskList ? taskItem.create(null, inlineFrag) : listItem.create(null, paragraph.create(null, inlineFrag));
180
+ var createListItemFromInline = function createListItemFromInline(content) {
181
+ return isTargetTaskList ? taskItem.create(null, content) : listItem.create(null, paragraph.create(null, content));
179
182
  };
180
183
  var getInlineContent = function getInlineContent(textblock) {
181
184
  var inlineContent = [];
@@ -184,30 +187,134 @@ export var unwrapAndConvertToList = function unwrapAndConvertToList(_ref3) {
184
187
  });
185
188
  return inlineContent;
186
189
  };
187
- var items = [];
188
-
190
+ var resultContent = [];
191
+ var currentListItems = [];
192
+ var targetListItemType = isTargetTaskList ? taskItem : listItem;
189
193
  // Expand's title should become the first item of the list
190
194
  if (sourceNode.type.name === 'expand') {
191
195
  var _sourceNode$attrs2;
192
196
  var title = (_sourceNode$attrs2 = sourceNode.attrs) === null || _sourceNode$attrs2 === void 0 ? void 0 : _sourceNode$attrs2.title;
193
197
  if (title) {
194
198
  var titleContent = schema.text(title);
195
- items.push(isTargetTaskList ? taskItem.create(null, titleContent) : listItem.create(null, paragraph.create(null, titleContent)));
199
+ currentListItems.push(createListItemFromInline(titleContent));
196
200
  }
197
201
  }
198
- for (var i = 0; i < sourceNode.childCount; i++) {
199
- var node = sourceNode.child(i);
200
-
201
- // Abort early if unsupported content (e.g. table) encounted inside of the container
202
- if (!node.isTextblock) {
203
- return tr;
202
+ var createListAndAddToContent = function createListAndAddToContent() {
203
+ if (currentListItems.length) {
204
+ var currentList = targetNodeType.create(targetAttrs || null, Fragment.from(currentListItems));
205
+ currentListItems = [];
206
+ resultContent.push(currentList);
204
207
  }
205
- var inline = Fragment.from(getInlineContent(node));
206
- items.push(createListItemFromInline(inline));
208
+ };
209
+ if (sourceNode.type.name === 'codeBlock') {
210
+ var codeText = sourceNode.textContent;
211
+ if (codeText) {
212
+ var lines = codeText.split('\n');
213
+ // Remove empty lines
214
+ var nonEmptyLines = lines.filter(function (line) {
215
+ return line.trim().length > 0;
216
+ });
217
+ nonEmptyLines.forEach(function (line) {
218
+ var lineText = schema.text(line);
219
+ currentListItems.push(createListItemFromInline(lineText));
220
+ });
221
+ }
222
+ } else {
223
+ sourceNode.forEach(function (child) {
224
+ if (targetListItemType.validContent(Fragment.from(child))) {
225
+ currentListItems.push(targetListItemType.create(null, child));
226
+ } else if (heading === child.type || isTargetTaskList && paragraph === child.type) {
227
+ var inline = Fragment.from(getInlineContent(child));
228
+ currentListItems.push(createListItemFromInline(inline));
229
+ } else {
230
+ // Create list and add list first
231
+ createListAndAddToContent();
232
+ // Then add content that can't be converted into listItem
233
+ resultContent.push(child);
234
+ }
235
+ });
207
236
  }
208
- if (!items.length) {
209
- return tr;
237
+ if (!resultContent.length && !currentListItems.length) {
238
+ return null;
239
+ }
240
+ createListAndAddToContent();
241
+ return tr.replaceWith(sourcePos, sourcePos + sourceNode.nodeSize, Fragment.from(resultContent));
242
+ };
243
+ export var transformBetweenContainerTypes = function transformBetweenContainerTypes(context) {
244
+ var tr = context.tr,
245
+ sourceNode = context.sourceNode,
246
+ sourcePos = context.sourcePos,
247
+ targetNodeType = context.targetNodeType,
248
+ targetAttrs = context.targetAttrs;
249
+
250
+ // Get content validation for target container type
251
+ var isContentSupported = getContentSupportChecker(targetNodeType);
252
+
253
+ // Process content and collect splits
254
+ var contentSplits = splitContentAroundUnsupportedBlocks(sourceNode, isContentSupported, targetNodeType, targetAttrs, tr.doc.type.schema);
255
+
256
+ // Replace the original node with the first split
257
+ var insertPos = sourcePos;
258
+ contentSplits.forEach(function (splitNode, index) {
259
+ if (index === 0) {
260
+ // Replace the original node with the first split
261
+ tr.replaceWith(sourcePos, sourcePos + sourceNode.nodeSize, splitNode);
262
+ insertPos = sourcePos + splitNode.nodeSize;
263
+ } else {
264
+ // Insert additional splits after
265
+ tr.insert(insertPos, splitNode);
266
+ insertPos += splitNode.nodeSize;
267
+ }
268
+ });
269
+ return tr;
270
+ };
271
+
272
+ /**
273
+ * Split content around unsupported block nodes, creating separate containers
274
+ * for content before and after each unsupported block
275
+ */
276
+ var splitContentAroundUnsupportedBlocks = function splitContentAroundUnsupportedBlocks(sourceNode, isContentSupported, targetNodeType, targetAttrs, schema) {
277
+ var _sourceNode$attrs3;
278
+ var splits = [];
279
+ var children = sourceNode.content.content;
280
+ var currentContainerContent = [];
281
+
282
+ // Handle expand title - add as first paragraph if source is expand with title
283
+ if (sourceNode.type.name === 'expand' && (_sourceNode$attrs3 = sourceNode.attrs) !== null && _sourceNode$attrs3 !== void 0 && _sourceNode$attrs3.title) {
284
+ var titleParagraph = schema.nodes.paragraph.create({}, schema.text(sourceNode.attrs.title));
285
+ currentContainerContent.push(titleParagraph);
210
286
  }
211
- var list = targetNodeType.create(targetAttrs || null, Fragment.from(items));
212
- return tr.replaceWith(sourcePos, sourcePos + sourceNode.nodeSize, list);
287
+ var flushCurrentContainer = function flushCurrentContainer() {
288
+ if (currentContainerContent.length > 0) {
289
+ var containerNode = targetNodeType.create(targetAttrs, Fragment.fromArray(currentContainerContent));
290
+ splits.push(containerNode);
291
+ currentContainerContent = [];
292
+ }
293
+ };
294
+ children.forEach(function (childNode) {
295
+ if (isContentSupported(childNode)) {
296
+ // Supported content - add to current container
297
+ currentContainerContent.push(childNode);
298
+ } else if (isBlockNodeForExtraction(childNode)) {
299
+ // Unsupported block node - flush current container, add block, continue
300
+ flushCurrentContainer();
301
+ splits.push(childNode);
302
+ } else if (childNode.type.name === targetNodeType.name) {
303
+ // Same type of container merge contents
304
+ childNode.content.forEach(function (child) {
305
+ currentContainerContent.push(child);
306
+ });
307
+ } else {
308
+ // Unsupported inline content - convert to paragraph and add to container
309
+ var inlineContent = convertNodeToInlineContent(childNode, schema);
310
+ if (inlineContent.length > 0) {
311
+ var paragraph = schema.nodes.paragraph.create({}, Fragment.fromArray(inlineContent));
312
+ currentContainerContent.push(paragraph);
313
+ }
314
+ }
315
+ });
316
+
317
+ // Flush any remaining container content
318
+ flushCurrentContainer();
319
+ return splits;
213
320
  };
@@ -1,8 +1,8 @@
1
1
  import { transformBlockNode } from './block-transforms';
2
- import { transformContainerNode } from './container-transforms';
2
+ import { transformContainerNode, unwrapAndConvertToList } from './container-transforms';
3
3
  import { convertToLayout } from './layout-transforms';
4
4
  import { transformListNode } from './list-transforms';
5
- import { getTargetNodeInfo, isBlockNode, isListNode, isContainerNode, isLayoutNodeType } from './utils';
5
+ import { getTargetNodeInfo, isBlockNode, isListNode, isListNodeType, isContainerNode, isLayoutNodeType } from './utils';
6
6
  export function transformNodeToTargetType(tr, sourceNode, sourcePos, targetType) {
7
7
  var nodes = tr.doc.type.schema.nodes;
8
8
  var targetNodeInfo = getTargetNodeInfo(targetType, nodes);
@@ -41,6 +41,11 @@ export function transformNodeToTargetType(tr, sourceNode, sourcePos, targetType)
41
41
  if (isLayoutNodeType(targetNodeType)) {
42
42
  return convertToLayout(transformationContext);
43
43
  }
44
+
45
+ // special case codeblock to listType
46
+ if (sourceNode.type.name === 'codeBlock' && isListNodeType(targetNodeType)) {
47
+ return unwrapAndConvertToList(transformationContext);
48
+ }
44
49
  if (isBlockNode(sourceNode)) {
45
50
  return transformBlockNode(transformationContext);
46
51
  }
@@ -1,3 +1,4 @@
1
+ import { Fragment } from '@atlaskit/editor-prosemirror/model';
1
2
  export var getTargetNodeInfo = function getTargetNodeInfo(targetType, nodes) {
2
3
  switch (targetType) {
3
4
  case 'heading1':
@@ -121,4 +122,50 @@ export var getSupportedListTypesSet = function getSupportedListTypesSet(nodes) {
121
122
  };
122
123
  export var isLayoutNodeType = function isLayoutNodeType(nodeType) {
123
124
  return nodeType.name === 'layoutSection';
125
+ };
126
+
127
+ /**
128
+ * Check if a node should be extracted as a standalone block node
129
+ * rather than converted to inline content
130
+ */
131
+ export var isBlockNodeForExtraction = function isBlockNodeForExtraction(node) {
132
+ var blockNodesForExtraction = ['table', 'mediaSingle', 'extension', 'bodiedExtension', 'blockCard', 'embedCard'];
133
+ return blockNodesForExtraction.includes(node.type.name);
134
+ };
135
+
136
+ /**
137
+ * Get a function that checks if content is supported in the target container type
138
+ */
139
+ export var getContentSupportChecker = function getContentSupportChecker(targetNodeType) {
140
+ return function (node) {
141
+ // Check if the target container type can contain this node
142
+ try {
143
+ return targetNodeType.validContent(Fragment.from(node));
144
+ } catch (_unused) {
145
+ return false;
146
+ }
147
+ };
148
+ };
149
+
150
+ /**
151
+ * Convert a node to inline content that can be placed in a paragraph
152
+ */
153
+ export var convertNodeToInlineContent = function convertNodeToInlineContent(node, schema) {
154
+ // Extract text and inline nodes from any complex node
155
+ var inlineNodes = [];
156
+ node.descendants(function (childNode) {
157
+ if (childNode.isText) {
158
+ inlineNodes.push(childNode);
159
+ } else if (childNode.isInline) {
160
+ inlineNodes.push(childNode);
161
+ }
162
+ return true; // Continue traversing
163
+ });
164
+
165
+ // If no inline content was found but the node has text content,
166
+ // create a text node with the full text content
167
+ if (inlineNodes.length === 0 && node.textContent) {
168
+ return [schema.text(node.textContent)];
169
+ }
170
+ return inlineNodes;
124
171
  };
@@ -14,4 +14,5 @@ export declare const unwrapAndConvertToBlockType: (context: TransformContext) =>
14
14
  /**
15
15
  * Unwrap container node and convert content to list
16
16
  */
17
- export declare const unwrapAndConvertToList: ({ tr, sourceNode, sourcePos, targetNodeType, targetAttrs, }: TransformContext) => import("prosemirror-state").Transaction;
17
+ export declare const unwrapAndConvertToList: ({ tr, sourceNode, sourcePos, targetNodeType, targetAttrs, }: TransformContext) => import("prosemirror-state").Transaction | null;
18
+ export declare const transformBetweenContainerTypes: (context: TransformContext) => import("prosemirror-state").Transaction;
@@ -1,4 +1,4 @@
1
- import type { Node as PMNode, NodeType } from '@atlaskit/editor-prosemirror/model';
1
+ import type { Node as PMNode, NodeType, Schema } from '@atlaskit/editor-prosemirror/model';
2
2
  import type { FormatNodeTargetType } from './types';
3
3
  export declare const getTargetNodeInfo: (targetType: FormatNodeTargetType, nodes: Record<string, NodeType>) => {
4
4
  attrs?: Record<string, unknown>;
@@ -15,3 +15,16 @@ export declare const isTaskList: (nodeType: NodeType) => boolean;
15
15
  export declare const getSupportedListTypes: (nodes: Record<string, NodeType>) => NodeType[];
16
16
  export declare const getSupportedListTypesSet: (nodes: Record<string, NodeType>) => Set<NodeType>;
17
17
  export declare const isLayoutNodeType: (nodeType: NodeType) => boolean;
18
+ /**
19
+ * Check if a node should be extracted as a standalone block node
20
+ * rather than converted to inline content
21
+ */
22
+ export declare const isBlockNodeForExtraction: (node: PMNode) => boolean;
23
+ /**
24
+ * Get a function that checks if content is supported in the target container type
25
+ */
26
+ export declare const getContentSupportChecker: (targetNodeType: NodeType) => ((node: PMNode) => boolean);
27
+ /**
28
+ * Convert a node to inline content that can be placed in a paragraph
29
+ */
30
+ export declare const convertNodeToInlineContent: (node: PMNode, schema: Schema) => PMNode[];
@@ -14,4 +14,5 @@ export declare const unwrapAndConvertToBlockType: (context: TransformContext) =>
14
14
  /**
15
15
  * Unwrap container node and convert content to list
16
16
  */
17
- export declare const unwrapAndConvertToList: ({ tr, sourceNode, sourcePos, targetNodeType, targetAttrs, }: TransformContext) => import("prosemirror-state").Transaction;
17
+ export declare const unwrapAndConvertToList: ({ tr, sourceNode, sourcePos, targetNodeType, targetAttrs, }: TransformContext) => import("prosemirror-state").Transaction | null;
18
+ export declare const transformBetweenContainerTypes: (context: TransformContext) => import("prosemirror-state").Transaction;
@@ -1,4 +1,4 @@
1
- import type { Node as PMNode, NodeType } from '@atlaskit/editor-prosemirror/model';
1
+ import type { Node as PMNode, NodeType, Schema } from '@atlaskit/editor-prosemirror/model';
2
2
  import type { FormatNodeTargetType } from './types';
3
3
  export declare const getTargetNodeInfo: (targetType: FormatNodeTargetType, nodes: Record<string, NodeType>) => {
4
4
  attrs?: Record<string, unknown>;
@@ -15,3 +15,16 @@ export declare const isTaskList: (nodeType: NodeType) => boolean;
15
15
  export declare const getSupportedListTypes: (nodes: Record<string, NodeType>) => NodeType[];
16
16
  export declare const getSupportedListTypesSet: (nodes: Record<string, NodeType>) => Set<NodeType>;
17
17
  export declare const isLayoutNodeType: (nodeType: NodeType) => boolean;
18
+ /**
19
+ * Check if a node should be extracted as a standalone block node
20
+ * rather than converted to inline content
21
+ */
22
+ export declare const isBlockNodeForExtraction: (node: PMNode) => boolean;
23
+ /**
24
+ * Get a function that checks if content is supported in the target container type
25
+ */
26
+ export declare const getContentSupportChecker: (targetNodeType: NodeType) => ((node: PMNode) => boolean);
27
+ /**
28
+ * Convert a node to inline content that can be placed in a paragraph
29
+ */
30
+ export declare const convertNodeToInlineContent: (node: PMNode, schema: Schema) => PMNode[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-block-menu",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "BlockMenu plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -28,7 +28,7 @@
28
28
  "sideEffects": false,
29
29
  "atlaskit:src": "src/index.ts",
30
30
  "dependencies": {
31
- "@atlaskit/css": "^0.13.0",
31
+ "@atlaskit/css": "^0.14.0",
32
32
  "@atlaskit/dropdown-menu": "^16.3.0",
33
33
  "@atlaskit/editor-plugin-block-controls": "^5.0.0",
34
34
  "@atlaskit/editor-plugin-decorations": "^4.0.0",
@@ -41,8 +41,8 @@
41
41
  "@atlaskit/icon": "^28.1.0",
42
42
  "@atlaskit/icon-lab": "^5.7.0",
43
43
  "@atlaskit/platform-feature-flags": "^1.1.0",
44
- "@atlaskit/primitives": "^14.13.0",
45
- "@atlaskit/tokens": "^6.2.0",
44
+ "@atlaskit/primitives": "^14.14.0",
45
+ "@atlaskit/tokens": "^6.3.0",
46
46
  "@babel/runtime": "^7.0.0"
47
47
  },
48
48
  "peerDependencies": {