@atlaskit/editor-plugin-paste 11.0.0 → 11.0.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,21 @@
1
1
  # @atlaskit/editor-plugin-paste
2
2
 
3
+ ## 11.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`90779068bff5a`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/90779068bff5a) -
8
+ Fix: preserve fontSize block mark when pasting small text copied from inside container nodes (e.g.
9
+ panel, expand). ProseMirror wraps the pasted content back in the container context, increasing
10
+ openStart/openEnd. handleParagraphBlockMarks now unwraps these container nodes so the fontSize
11
+ mark is preserved during paste.
12
+
13
+ ## 11.0.1
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies
18
+
3
19
  ## 11.0.0
4
20
 
5
21
  ### Major Changes
@@ -912,6 +912,70 @@ function getTopLevelMarkTypesInSlice(slice) {
912
912
  });
913
913
  return markTypes;
914
914
  }
915
+
916
+ /**
917
+ * Peels container wrapper nodes (e.g. panel, expand) added by ProseMirror's addContext()
918
+ * so that fontSize-marked paragraphs become top-level, preserving the mark on paste.
919
+ */
920
+ function unwrapContainerNodesWithBlockMarks(slice, schema, fontSize) {
921
+ var content = slice.content;
922
+ var levelsUnwrapped = 0;
923
+ while (content.childCount === 1 && content.firstChild && !content.firstChild.isTextblock && slice.openStart - levelsUnwrapped > 1) {
924
+ var hasBlockMarkedParagraph = false;
925
+ for (var i = 0; i < content.firstChild.childCount; i++) {
926
+ var child = content.firstChild.child(i);
927
+ if (child.type === schema.nodes.paragraph && child.marks.some(function (m) {
928
+ return m.type === fontSize;
929
+ })) {
930
+ hasBlockMarkedParagraph = true;
931
+ break;
932
+ }
933
+ }
934
+ if (!hasBlockMarkedParagraph) {
935
+ break;
936
+ }
937
+ content = content.firstChild.content;
938
+ levelsUnwrapped++;
939
+ }
940
+ if (levelsUnwrapped === 0) {
941
+ return slice;
942
+ }
943
+ return new _model.Slice(content, slice.openStart - levelsUnwrapped, Math.max(0, slice.openEnd - levelsUnwrapped));
944
+ }
945
+
946
+ /**
947
+ * Returns the fontSize attrs to apply at the paste destination, or false if none.
948
+ * Checks list and task destinations in priority order.
949
+ */
950
+ function getDestinationFontSizeAttrs(destinationListNode, isInSmallTaskContext, $from, currentNode, fontSize) {
951
+ if (destinationListNode) {
952
+ return (0, _lists.getFirstParagraphBlockMarkAttrs)(destinationListNode, fontSize);
953
+ }
954
+ if (isInSmallTaskContext) {
955
+ return (0, _lists.getBlockMarkAttrs)($from.parent, fontSize) || (0, _lists.getFirstParagraphBlockMarkAttrs)(currentNode, fontSize);
956
+ }
957
+ return false;
958
+ }
959
+
960
+ /**
961
+ * Resolves which marks to apply to a paragraph node after filtering forbidden marks.
962
+ * When a destination block mark is provided, replaces any existing fontSize mark with it.
963
+ * When normalizing for the target context, removes the fontSize mark entirely.
964
+ * Otherwise returns the filtered marks unchanged.
965
+ */
966
+ function resolveParagraphMarks(marks, destinationBlockMarkAttrs, shouldNormalize, fontSize) {
967
+ if (destinationBlockMarkAttrs) {
968
+ return marks.filter(function (m) {
969
+ return m.type !== fontSize;
970
+ }).concat(fontSize.create(destinationBlockMarkAttrs));
971
+ }
972
+ if (shouldNormalize) {
973
+ return marks.filter(function (m) {
974
+ return m.type !== fontSize;
975
+ });
976
+ }
977
+ return marks;
978
+ }
915
979
  function handleParagraphBlockMarks(state, slice) {
916
980
  var _findParentNodeOfType2;
917
981
  if (slice.content.size === 0) {
@@ -928,12 +992,19 @@ function handleParagraphBlockMarks(state, slice) {
928
992
  paragraph = _schema$nodes3.paragraph,
929
993
  heading = _schema$nodes3.heading;
930
994
  var fontSize = schema.marks.fontSize;
995
+ var isSmallFontSizeEnabled = (0, _expValEquals.expValEquals)('platform_editor_small_font_size', 'isEnabled', true) && !!fontSize;
996
+
997
+ // When copying from inside a container (e.g. panel, expand), ProseMirror wraps the
998
+ // content back in the container via addContext(), increasing openStart/openEnd. Unwrap
999
+ // so the paragraph (with its fontSize mark) becomes top-level.
1000
+ if (isSmallFontSizeEnabled) {
1001
+ slice = unwrapContainerNodesWithBlockMarks(slice, schema, fontSize);
1002
+ }
931
1003
  var destinationListNode = (_findParentNodeOfType2 = (0, _utils2.findParentNodeOfType)([bulletList, orderedList])(selection)) === null || _findParentNodeOfType2 === void 0 ? void 0 : _findParentNodeOfType2.node;
932
1004
  var currentNode = typeof $from.node === 'function' ? $from.node() : undefined;
933
1005
  var isInNormalTaskContext = (currentNode === null || currentNode === void 0 ? void 0 : currentNode.type) === taskItem || $from.parent.type === taskItem;
934
1006
  var isInSmallTaskContext = !!blockTaskItem && ((currentNode === null || currentNode === void 0 ? void 0 : currentNode.type) === blockTaskItem || $from.parent.type === blockTaskItem || $from.parent.type === paragraph && $from.depth > 0 && $from.node($from.depth - 1).type === blockTaskItem);
935
- var isSmallFontSizeEnabled = (0, _expValEquals.expValEquals)('platform_editor_small_font_size', 'isEnabled', true) && !!fontSize;
936
- var destinationBlockMarkAttrs = isSmallFontSizeEnabled ? destinationListNode ? (0, _lists.getFirstParagraphBlockMarkAttrs)(destinationListNode, fontSize) : isInSmallTaskContext ? (0, _lists.getBlockMarkAttrs)($from.parent, fontSize) || (0, _lists.getFirstParagraphBlockMarkAttrs)(currentNode, fontSize) : false : false;
1007
+ var destinationBlockMarkAttrs = isSmallFontSizeEnabled ? getDestinationFontSizeAttrs(destinationListNode, isInSmallTaskContext, $from, currentNode, fontSize) : false;
937
1008
 
938
1009
  // If no paragraph in the slice contains marks, there's no need for special handling
939
1010
  // unless we're pasting into a small-text list and need to add the destination block mark.
@@ -975,11 +1046,7 @@ function handleParagraphBlockMarks(state, slice) {
975
1046
  var paragraphMarks = node.marks.filter(function (mark) {
976
1047
  return !forbiddenMarkTypes.includes(mark.type);
977
1048
  });
978
- return paragraph.createChecked(undefined, node.content, destinationBlockMarkAttrs ? paragraphMarks.filter(function (mark) {
979
- return mark.type !== fontSize;
980
- }).concat(fontSize.create(destinationBlockMarkAttrs)) : shouldNormalizeFontSizeForTarget ? paragraphMarks.filter(function (mark) {
981
- return mark.type !== fontSize;
982
- }) : paragraphMarks);
1049
+ return paragraph.createChecked(undefined, node.content, resolveParagraphMarks(paragraphMarks, destinationBlockMarkAttrs, shouldNormalizeFontSizeForTarget, fontSize));
983
1050
  } else if (node.type === heading) {
984
1051
  // Preserve heading attributes to keep formatting
985
1052
  return heading.createChecked(node.attrs, node.content, node.marks.filter(function (mark) {
@@ -898,6 +898,64 @@ function getTopLevelMarkTypesInSlice(slice) {
898
898
  });
899
899
  return markTypes;
900
900
  }
901
+
902
+ /**
903
+ * Peels container wrapper nodes (e.g. panel, expand) added by ProseMirror's addContext()
904
+ * so that fontSize-marked paragraphs become top-level, preserving the mark on paste.
905
+ */
906
+ function unwrapContainerNodesWithBlockMarks(slice, schema, fontSize) {
907
+ let content = slice.content;
908
+ let levelsUnwrapped = 0;
909
+ while (content.childCount === 1 && content.firstChild && !content.firstChild.isTextblock && slice.openStart - levelsUnwrapped > 1) {
910
+ let hasBlockMarkedParagraph = false;
911
+ for (let i = 0; i < content.firstChild.childCount; i++) {
912
+ const child = content.firstChild.child(i);
913
+ if (child.type === schema.nodes.paragraph && child.marks.some(m => m.type === fontSize)) {
914
+ hasBlockMarkedParagraph = true;
915
+ break;
916
+ }
917
+ }
918
+ if (!hasBlockMarkedParagraph) {
919
+ break;
920
+ }
921
+ content = content.firstChild.content;
922
+ levelsUnwrapped++;
923
+ }
924
+ if (levelsUnwrapped === 0) {
925
+ return slice;
926
+ }
927
+ return new Slice(content, slice.openStart - levelsUnwrapped, Math.max(0, slice.openEnd - levelsUnwrapped));
928
+ }
929
+
930
+ /**
931
+ * Returns the fontSize attrs to apply at the paste destination, or false if none.
932
+ * Checks list and task destinations in priority order.
933
+ */
934
+ function getDestinationFontSizeAttrs(destinationListNode, isInSmallTaskContext, $from, currentNode, fontSize) {
935
+ if (destinationListNode) {
936
+ return getFirstParagraphBlockMarkAttrs(destinationListNode, fontSize);
937
+ }
938
+ if (isInSmallTaskContext) {
939
+ return getBlockMarkAttrs($from.parent, fontSize) || getFirstParagraphBlockMarkAttrs(currentNode, fontSize);
940
+ }
941
+ return false;
942
+ }
943
+
944
+ /**
945
+ * Resolves which marks to apply to a paragraph node after filtering forbidden marks.
946
+ * When a destination block mark is provided, replaces any existing fontSize mark with it.
947
+ * When normalizing for the target context, removes the fontSize mark entirely.
948
+ * Otherwise returns the filtered marks unchanged.
949
+ */
950
+ function resolveParagraphMarks(marks, destinationBlockMarkAttrs, shouldNormalize, fontSize) {
951
+ if (destinationBlockMarkAttrs) {
952
+ return marks.filter(m => m.type !== fontSize).concat(fontSize.create(destinationBlockMarkAttrs));
953
+ }
954
+ if (shouldNormalize) {
955
+ return marks.filter(m => m.type !== fontSize);
956
+ }
957
+ return marks;
958
+ }
901
959
  export function handleParagraphBlockMarks(state, slice) {
902
960
  var _findParentNodeOfType2;
903
961
  if (slice.content.size === 0) {
@@ -921,12 +979,19 @@ export function handleParagraphBlockMarks(state, slice) {
921
979
  const {
922
980
  fontSize
923
981
  } = schema.marks;
982
+ const isSmallFontSizeEnabled = expValEquals('platform_editor_small_font_size', 'isEnabled', true) && !!fontSize;
983
+
984
+ // When copying from inside a container (e.g. panel, expand), ProseMirror wraps the
985
+ // content back in the container via addContext(), increasing openStart/openEnd. Unwrap
986
+ // so the paragraph (with its fontSize mark) becomes top-level.
987
+ if (isSmallFontSizeEnabled) {
988
+ slice = unwrapContainerNodesWithBlockMarks(slice, schema, fontSize);
989
+ }
924
990
  const destinationListNode = (_findParentNodeOfType2 = findParentNodeOfType([bulletList, orderedList])(selection)) === null || _findParentNodeOfType2 === void 0 ? void 0 : _findParentNodeOfType2.node;
925
991
  const currentNode = typeof $from.node === 'function' ? $from.node() : undefined;
926
992
  const isInNormalTaskContext = (currentNode === null || currentNode === void 0 ? void 0 : currentNode.type) === taskItem || $from.parent.type === taskItem;
927
993
  const isInSmallTaskContext = !!blockTaskItem && ((currentNode === null || currentNode === void 0 ? void 0 : currentNode.type) === blockTaskItem || $from.parent.type === blockTaskItem || $from.parent.type === paragraph && $from.depth > 0 && $from.node($from.depth - 1).type === blockTaskItem);
928
- const isSmallFontSizeEnabled = expValEquals('platform_editor_small_font_size', 'isEnabled', true) && !!fontSize;
929
- const destinationBlockMarkAttrs = isSmallFontSizeEnabled ? destinationListNode ? getFirstParagraphBlockMarkAttrs(destinationListNode, fontSize) : isInSmallTaskContext ? getBlockMarkAttrs($from.parent, fontSize) || getFirstParagraphBlockMarkAttrs(currentNode, fontSize) : false : false;
994
+ const destinationBlockMarkAttrs = isSmallFontSizeEnabled ? getDestinationFontSizeAttrs(destinationListNode, isInSmallTaskContext, $from, currentNode, fontSize) : false;
930
995
 
931
996
  // If no paragraph in the slice contains marks, there's no need for special handling
932
997
  // unless we're pasting into a small-text list and need to add the destination block mark.
@@ -957,7 +1022,7 @@ export function handleParagraphBlockMarks(state, slice) {
957
1022
  const normalizedContent = mapSlice(slice, node => {
958
1023
  if (node.type === paragraph) {
959
1024
  const paragraphMarks = node.marks.filter(mark => !forbiddenMarkTypes.includes(mark.type));
960
- return paragraph.createChecked(undefined, node.content, destinationBlockMarkAttrs ? paragraphMarks.filter(mark => mark.type !== fontSize).concat(fontSize.create(destinationBlockMarkAttrs)) : shouldNormalizeFontSizeForTarget ? paragraphMarks.filter(mark => mark.type !== fontSize) : paragraphMarks);
1025
+ return paragraph.createChecked(undefined, node.content, resolveParagraphMarks(paragraphMarks, destinationBlockMarkAttrs, shouldNormalizeFontSizeForTarget, fontSize));
961
1026
  } else if (node.type === heading) {
962
1027
  // Preserve heading attributes to keep formatting
963
1028
  return heading.createChecked(node.attrs, node.content, node.marks.filter(mark => !forbiddenMarkTypes.includes(mark.type)));
@@ -885,6 +885,70 @@ function getTopLevelMarkTypesInSlice(slice) {
885
885
  });
886
886
  return markTypes;
887
887
  }
888
+
889
+ /**
890
+ * Peels container wrapper nodes (e.g. panel, expand) added by ProseMirror's addContext()
891
+ * so that fontSize-marked paragraphs become top-level, preserving the mark on paste.
892
+ */
893
+ function unwrapContainerNodesWithBlockMarks(slice, schema, fontSize) {
894
+ var content = slice.content;
895
+ var levelsUnwrapped = 0;
896
+ while (content.childCount === 1 && content.firstChild && !content.firstChild.isTextblock && slice.openStart - levelsUnwrapped > 1) {
897
+ var hasBlockMarkedParagraph = false;
898
+ for (var i = 0; i < content.firstChild.childCount; i++) {
899
+ var child = content.firstChild.child(i);
900
+ if (child.type === schema.nodes.paragraph && child.marks.some(function (m) {
901
+ return m.type === fontSize;
902
+ })) {
903
+ hasBlockMarkedParagraph = true;
904
+ break;
905
+ }
906
+ }
907
+ if (!hasBlockMarkedParagraph) {
908
+ break;
909
+ }
910
+ content = content.firstChild.content;
911
+ levelsUnwrapped++;
912
+ }
913
+ if (levelsUnwrapped === 0) {
914
+ return slice;
915
+ }
916
+ return new Slice(content, slice.openStart - levelsUnwrapped, Math.max(0, slice.openEnd - levelsUnwrapped));
917
+ }
918
+
919
+ /**
920
+ * Returns the fontSize attrs to apply at the paste destination, or false if none.
921
+ * Checks list and task destinations in priority order.
922
+ */
923
+ function getDestinationFontSizeAttrs(destinationListNode, isInSmallTaskContext, $from, currentNode, fontSize) {
924
+ if (destinationListNode) {
925
+ return getFirstParagraphBlockMarkAttrs(destinationListNode, fontSize);
926
+ }
927
+ if (isInSmallTaskContext) {
928
+ return getBlockMarkAttrs($from.parent, fontSize) || getFirstParagraphBlockMarkAttrs(currentNode, fontSize);
929
+ }
930
+ return false;
931
+ }
932
+
933
+ /**
934
+ * Resolves which marks to apply to a paragraph node after filtering forbidden marks.
935
+ * When a destination block mark is provided, replaces any existing fontSize mark with it.
936
+ * When normalizing for the target context, removes the fontSize mark entirely.
937
+ * Otherwise returns the filtered marks unchanged.
938
+ */
939
+ function resolveParagraphMarks(marks, destinationBlockMarkAttrs, shouldNormalize, fontSize) {
940
+ if (destinationBlockMarkAttrs) {
941
+ return marks.filter(function (m) {
942
+ return m.type !== fontSize;
943
+ }).concat(fontSize.create(destinationBlockMarkAttrs));
944
+ }
945
+ if (shouldNormalize) {
946
+ return marks.filter(function (m) {
947
+ return m.type !== fontSize;
948
+ });
949
+ }
950
+ return marks;
951
+ }
888
952
  export function handleParagraphBlockMarks(state, slice) {
889
953
  var _findParentNodeOfType2;
890
954
  if (slice.content.size === 0) {
@@ -901,12 +965,19 @@ export function handleParagraphBlockMarks(state, slice) {
901
965
  paragraph = _schema$nodes3.paragraph,
902
966
  heading = _schema$nodes3.heading;
903
967
  var fontSize = schema.marks.fontSize;
968
+ var isSmallFontSizeEnabled = expValEquals('platform_editor_small_font_size', 'isEnabled', true) && !!fontSize;
969
+
970
+ // When copying from inside a container (e.g. panel, expand), ProseMirror wraps the
971
+ // content back in the container via addContext(), increasing openStart/openEnd. Unwrap
972
+ // so the paragraph (with its fontSize mark) becomes top-level.
973
+ if (isSmallFontSizeEnabled) {
974
+ slice = unwrapContainerNodesWithBlockMarks(slice, schema, fontSize);
975
+ }
904
976
  var destinationListNode = (_findParentNodeOfType2 = findParentNodeOfType([bulletList, orderedList])(selection)) === null || _findParentNodeOfType2 === void 0 ? void 0 : _findParentNodeOfType2.node;
905
977
  var currentNode = typeof $from.node === 'function' ? $from.node() : undefined;
906
978
  var isInNormalTaskContext = (currentNode === null || currentNode === void 0 ? void 0 : currentNode.type) === taskItem || $from.parent.type === taskItem;
907
979
  var isInSmallTaskContext = !!blockTaskItem && ((currentNode === null || currentNode === void 0 ? void 0 : currentNode.type) === blockTaskItem || $from.parent.type === blockTaskItem || $from.parent.type === paragraph && $from.depth > 0 && $from.node($from.depth - 1).type === blockTaskItem);
908
- var isSmallFontSizeEnabled = expValEquals('platform_editor_small_font_size', 'isEnabled', true) && !!fontSize;
909
- var destinationBlockMarkAttrs = isSmallFontSizeEnabled ? destinationListNode ? getFirstParagraphBlockMarkAttrs(destinationListNode, fontSize) : isInSmallTaskContext ? getBlockMarkAttrs($from.parent, fontSize) || getFirstParagraphBlockMarkAttrs(currentNode, fontSize) : false : false;
980
+ var destinationBlockMarkAttrs = isSmallFontSizeEnabled ? getDestinationFontSizeAttrs(destinationListNode, isInSmallTaskContext, $from, currentNode, fontSize) : false;
910
981
 
911
982
  // If no paragraph in the slice contains marks, there's no need for special handling
912
983
  // unless we're pasting into a small-text list and need to add the destination block mark.
@@ -948,11 +1019,7 @@ export function handleParagraphBlockMarks(state, slice) {
948
1019
  var paragraphMarks = node.marks.filter(function (mark) {
949
1020
  return !forbiddenMarkTypes.includes(mark.type);
950
1021
  });
951
- return paragraph.createChecked(undefined, node.content, destinationBlockMarkAttrs ? paragraphMarks.filter(function (mark) {
952
- return mark.type !== fontSize;
953
- }).concat(fontSize.create(destinationBlockMarkAttrs)) : shouldNormalizeFontSizeForTarget ? paragraphMarks.filter(function (mark) {
954
- return mark.type !== fontSize;
955
- }) : paragraphMarks);
1022
+ return paragraph.createChecked(undefined, node.content, resolveParagraphMarks(paragraphMarks, destinationBlockMarkAttrs, shouldNormalizeFontSizeForTarget, fontSize));
956
1023
  } else if (node.type === heading) {
957
1024
  // Preserve heading attributes to keep formatting
958
1025
  return heading.createChecked(node.attrs, node.content, node.marks.filter(function (mark) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-paste",
3
- "version": "11.0.0",
3
+ "version": "11.0.2",
4
4
  "description": "Paste plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -37,7 +37,7 @@
37
37
  "@atlaskit/editor-plugin-expand": "^11.0.0",
38
38
  "@atlaskit/editor-plugin-feature-flags": "^9.0.0",
39
39
  "@atlaskit/editor-plugin-list": "^12.0.0",
40
- "@atlaskit/editor-plugin-media": "^12.0.0",
40
+ "@atlaskit/editor-plugin-media": "^12.1.0",
41
41
  "@atlaskit/editor-plugin-mentions": "^12.0.0",
42
42
  "@atlaskit/editor-prosemirror": "^7.3.0",
43
43
  "@atlaskit/editor-tables": "^2.9.0",
@@ -48,14 +48,14 @@
48
48
  "@atlaskit/media-common": "^13.0.0",
49
49
  "@atlaskit/platform-feature-flags": "^1.1.0",
50
50
  "@atlaskit/prosemirror-history": "^0.2.0",
51
- "@atlaskit/tmp-editor-statsig": "^62.4.0",
51
+ "@atlaskit/tmp-editor-statsig": "^63.0.0",
52
52
  "@atlaskit/tokens": "^13.0.0",
53
53
  "@babel/runtime": "^7.0.0",
54
54
  "lodash": "^4.17.21",
55
55
  "uuid": "^3.1.0"
56
56
  },
57
57
  "peerDependencies": {
58
- "@atlaskit/editor-common": "^114.0.0",
58
+ "@atlaskit/editor-common": "^114.2.0",
59
59
  "react": "^18.2.0",
60
60
  "react-dom": "^18.2.0",
61
61
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"