@atlaskit/editor-plugin-paste 12.0.0 → 12.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @atlaskit/editor-plugin-paste
2
2
 
3
+ ## 12.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 12.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [`f7fc6c3bcc4e3`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/f7fc6c3bcc4e3) -
14
+ [ux] EDITOR-7242 add paste logic to support table in panel nesting
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies
19
+
3
20
  ## 12.0.0
4
21
 
5
22
  ### Patch Changes
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.createClipboardParser = createClipboardParser;
8
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
+ var _model = require("@atlaskit/editor-prosemirror/model");
10
+ var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
11
+ /**
12
+ * Builds additional parseDOM rules to inject into the clipboard parser.
13
+ *
14
+ * Adding a rule with a higher priority than the default schema rules, allows
15
+ * specific HTML patterns to be parsed as a different node type than the schema
16
+ * would normally choose.
17
+ */
18
+ function buildAdditionalClipboardParseRules(schema) {
19
+ var _panel$spec$parseDOM;
20
+ var rules = [];
21
+
22
+ // This means that panels with tables inside always stay as panel_c1,
23
+ // but panels without tables retain standard behaviour
24
+ // of choosing between panel_c1 and panel depending on the context.
25
+ // e.g. panel with table pasted in expand gets lifted out, panel with normal text gets pasted inside expand
26
+ var _schema$nodes = schema.nodes,
27
+ panel_c1 = _schema$nodes.panel_c1,
28
+ panel = _schema$nodes.panel;
29
+ if (!panel_c1 || !panel) {
30
+ return rules;
31
+ }
32
+ var panelGetAttrs = (_panel$spec$parseDOM = panel.spec.parseDOM) === null || _panel$spec$parseDOM === void 0 || (_panel$spec$parseDOM = _panel$spec$parseDOM[0]) === null || _panel$spec$parseDOM === void 0 ? void 0 : _panel$spec$parseDOM.getAttrs;
33
+ rules.push({
34
+ tag: 'div[data-panel-type]',
35
+ priority: 60,
36
+ node: panel_c1.name,
37
+ getAttrs: function getAttrs(dom) {
38
+ var hasTable = dom.querySelector('[data-prosemirror-node-name="table"]') !== null;
39
+ if (!hasTable) {
40
+ return false; // fall through to standard panel rule
41
+ }
42
+ return panelGetAttrs ? panelGetAttrs(dom) : null;
43
+ }
44
+ });
45
+ return rules;
46
+ }
47
+
48
+ /**
49
+ * Creates a ProseMirror plugin with a custom `clipboardParser` that extends the
50
+ * schema's default parser with additional rules for handling specific paste scenarios.
51
+ */
52
+ function createClipboardParser(schema) {
53
+ if (!(0, _expValEquals.expValEquals)('platform_editor_nest_table_in_panel', 'isEnabled', true)) {
54
+ return undefined;
55
+ }
56
+ var additionalRules = buildAdditionalClipboardParseRules(schema);
57
+ var baseParser = _model.DOMParser.fromSchema(schema);
58
+ var customParser = new _model.DOMParser(schema, [].concat((0, _toConsumableArray2.default)(additionalRules), (0, _toConsumableArray2.default)(baseParser.rules)));
59
+ return customParser;
60
+ }
@@ -35,6 +35,7 @@ var _actions = require("../editor-actions/actions");
35
35
  var _commands = require("../editor-commands/commands");
36
36
  var _media = require("../pm-plugins/media");
37
37
  var _analytics2 = require("./analytics");
38
+ var _clipboardParser = require("./clipboard-parser");
38
39
  var _createClipboardTextSerializer = require("./create-clipboard-text-serializer");
39
40
  var _pluginFactory = require("./plugin-factory");
40
41
  var _util = require("./util");
@@ -146,6 +147,7 @@ function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pl
146
147
  props: {
147
148
  // For serialising to plain text
148
149
  clipboardTextSerializer: (0, _createClipboardTextSerializer.createClipboardTextSerializer)(getIntl()),
150
+ clipboardParser: (0, _clipboardParser.createClipboardParser)(schema),
149
151
  handleDOMEvents: {
150
152
  // note
151
153
  paste: function paste(view, event) {
@@ -366,6 +368,11 @@ function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pl
366
368
  view.dispatch(tr.setMeta('uiEvent', 'paste'));
367
369
  };
368
370
  slice = (0, _handlers.handleParagraphBlockMarks)(state, slice);
371
+ if ((0, _expValEquals.expValEquals)('platform_editor_nest_table_in_panel', 'isEnabled', true)) {
372
+ var _view$state$selection, _view$state$selection2;
373
+ var destinationParentType = (_view$state$selection = (_view$state$selection2 = view.state.selection.$from.node(-1)) === null || _view$state$selection2 === void 0 ? void 0 : _view$state$selection2.type.name) !== null && _view$state$selection !== void 0 ? _view$state$selection : view.state.doc.type.name;
374
+ slice = (0, _handlers.applyContainerNodeTransformToSlice)(slice, schema, destinationParentType);
375
+ }
369
376
  slice = (0, _handleVSCodeBlock.handleVSCodeBlock)({
370
377
  state: state,
371
378
  slice: slice,
@@ -656,6 +663,13 @@ function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pl
656
663
  if (html.indexOf(_syncBlock.SyncBlockRendererDataAttributeName) >= 0 && (0, _experiments.editorExperiment)('platform_synced_block', true)) {
657
664
  html = (0, _transforms.removeBreakoutFromRendererSyncBlockHTML)(html);
658
665
  }
666
+
667
+ // When platform_editor_nest_table_in_panel is OFF,
668
+ // move tables out of panel divs in the HTML before ProseMirror
669
+ // parses it — otherwise the panel schema drops the table content.
670
+ if ((0, _expValEquals.expValEquals)('platform_editor_table_in_panel_paste_fallback', 'isEnabled', true) && !(0, _expValEquals.expValEquals)('platform_editor_nest_table_in_panel', 'isEnabled', true)) {
671
+ html = (0, _handlers.splitTablesOutOfPanelHtml)(html);
672
+ }
659
673
  mostRecentPasteEvent = null;
660
674
  return html;
661
675
  }
@@ -4,6 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
+ exports.applyContainerNodeTransformToSlice = applyContainerNodeTransformToSlice;
7
8
  exports.checkIfSelectionInNestedList = checkIfSelectionInNestedList;
8
9
  exports.checkTaskListInList = checkTaskListInList;
9
10
  exports.doesSelectionWhichStartsOrEndsInListContainEntireList = void 0;
@@ -27,10 +28,12 @@ exports.handlePastePreservingMarks = handlePastePreservingMarks;
27
28
  exports.handleRichText = handleRichText;
28
29
  exports.handleSelectedTable = void 0;
29
30
  exports.handleTableContentPasteInBodiedExtension = handleTableContentPasteInBodiedExtension;
31
+ exports.splitTablesOutOfPanelHtml = splitTablesOutOfPanelHtml;
30
32
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
31
33
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
32
34
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
33
35
  var _v = _interopRequireDefault(require("uuid/v4"));
36
+ var _transforms = require("@atlaskit/adf-utils/transforms");
34
37
  var _analytics = require("@atlaskit/editor-common/analytics");
35
38
  var _card = require("@atlaskit/editor-common/card");
36
39
  var _coreUtils = require("@atlaskit/editor-common/core-utils");
@@ -1394,4 +1397,61 @@ function handlePasteExpand(slice) {
1394
1397
  }
1395
1398
  return node;
1396
1399
  });
1400
+ }
1401
+
1402
+ /*
1403
+ * Transform container nodes promoting them to _c1 if required as per
1404
+ * transformContainerNodes from adf-utils
1405
+ */
1406
+ function applyContainerNodeTransformToSlice(slice, schema, destinationParentType) {
1407
+ var _sliceJson$content;
1408
+ var sliceJson = slice.toJSON();
1409
+ if (!sliceJson) {
1410
+ return slice;
1411
+ }
1412
+ var wrappedAdf = {
1413
+ type: destinationParentType,
1414
+ content: (_sliceJson$content = sliceJson.content) !== null && _sliceJson$content !== void 0 ? _sliceJson$content : []
1415
+ };
1416
+ try {
1417
+ var _transformContainerNo = (0, _transforms.transformContainerNodes)(wrappedAdf, schema),
1418
+ transformedAdf = _transformContainerNo.transformedAdf,
1419
+ isTransformed = _transformContainerNo.isTransformed;
1420
+ return isTransformed && transformedAdf && transformedAdf.content && transformedAdf.content.length > 0 ? _model.Slice.fromJSON(schema, _objectSpread(_objectSpread({}, sliceJson), {}, {
1421
+ content: transformedAdf.content
1422
+ })) : slice;
1423
+ } catch (_unused3) {
1424
+ return slice;
1425
+ }
1426
+ }
1427
+
1428
+ /*
1429
+ * When platform_editor_nest_table_in_panel is OFF, move table elements out of panel divs in the
1430
+ * clipboard HTML before ProseMirror parses it. This prevents the panel schema from
1431
+ * dropping table content entirely.
1432
+ */
1433
+ function splitTablesOutOfPanelHtml(html) {
1434
+ if (!html.includes('data-panel-type')) {
1435
+ return html;
1436
+ }
1437
+ var doc = new DOMParser().parseFromString(html, 'text/html');
1438
+ var panelDivs = Array.from(doc.querySelectorAll('div[data-panel-type]'));
1439
+ var changed = false;
1440
+ for (var _i = 0, _panelDivs = panelDivs; _i < _panelDivs.length; _i++) {
1441
+ var panelDiv = _panelDivs[_i];
1442
+ var tableWrappers = Array.from(panelDiv.querySelectorAll('[data-prosemirror-node-name="table"]'));
1443
+ if (tableWrappers.length === 0) {
1444
+ continue;
1445
+ }
1446
+ changed = true;
1447
+ var anchor = panelDiv;
1448
+ for (var _i2 = 0, _tableWrappers = tableWrappers; _i2 < _tableWrappers.length; _i2++) {
1449
+ var _tableWrapper$parentN, _anchor$parentNode;
1450
+ var tableWrapper = _tableWrappers[_i2];
1451
+ (_tableWrapper$parentN = tableWrapper.parentNode) === null || _tableWrapper$parentN === void 0 || _tableWrapper$parentN.removeChild(tableWrapper);
1452
+ (_anchor$parentNode = anchor.parentNode) === null || _anchor$parentNode === void 0 || _anchor$parentNode.insertBefore(tableWrapper, anchor.nextSibling);
1453
+ anchor = tableWrapper;
1454
+ }
1455
+ }
1456
+ return changed ? doc.body.innerHTML : html;
1397
1457
  }
@@ -0,0 +1,54 @@
1
+ import { DOMParser as PMDOMParser } from '@atlaskit/editor-prosemirror/model';
2
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
3
+
4
+ /**
5
+ * Builds additional parseDOM rules to inject into the clipboard parser.
6
+ *
7
+ * Adding a rule with a higher priority than the default schema rules, allows
8
+ * specific HTML patterns to be parsed as a different node type than the schema
9
+ * would normally choose.
10
+ */
11
+ function buildAdditionalClipboardParseRules(schema) {
12
+ var _panel$spec$parseDOM, _panel$spec$parseDOM$;
13
+ const rules = [];
14
+
15
+ // This means that panels with tables inside always stay as panel_c1,
16
+ // but panels without tables retain standard behaviour
17
+ // of choosing between panel_c1 and panel depending on the context.
18
+ // e.g. panel with table pasted in expand gets lifted out, panel with normal text gets pasted inside expand
19
+ const {
20
+ panel_c1,
21
+ panel
22
+ } = schema.nodes;
23
+ if (!panel_c1 || !panel) {
24
+ return rules;
25
+ }
26
+ const panelGetAttrs = (_panel$spec$parseDOM = panel.spec.parseDOM) === null || _panel$spec$parseDOM === void 0 ? void 0 : (_panel$spec$parseDOM$ = _panel$spec$parseDOM[0]) === null || _panel$spec$parseDOM$ === void 0 ? void 0 : _panel$spec$parseDOM$.getAttrs;
27
+ rules.push({
28
+ tag: 'div[data-panel-type]',
29
+ priority: 60,
30
+ node: panel_c1.name,
31
+ getAttrs: dom => {
32
+ const hasTable = dom.querySelector('[data-prosemirror-node-name="table"]') !== null;
33
+ if (!hasTable) {
34
+ return false; // fall through to standard panel rule
35
+ }
36
+ return panelGetAttrs ? panelGetAttrs(dom) : null;
37
+ }
38
+ });
39
+ return rules;
40
+ }
41
+
42
+ /**
43
+ * Creates a ProseMirror plugin with a custom `clipboardParser` that extends the
44
+ * schema's default parser with additional rules for handling specific paste scenarios.
45
+ */
46
+ export function createClipboardParser(schema) {
47
+ if (!expValEquals('platform_editor_nest_table_in_panel', 'isEnabled', true)) {
48
+ return undefined;
49
+ }
50
+ const additionalRules = buildAdditionalClipboardParseRules(schema);
51
+ const baseParser = PMDOMParser.fromSchema(schema);
52
+ const customParser = new PMDOMParser(schema, [...additionalRules, ...baseParser.rules]);
53
+ return customParser;
54
+ }
@@ -25,11 +25,12 @@ import { PastePluginActionTypes } from '../editor-actions/actions';
25
25
  import { splitParagraphs, upgradeTextToLists } from '../editor-commands/commands';
26
26
  import { transformSliceForMedia, transformSliceToCorrectMediaWrapper, transformSliceToMediaSingleWithNewExperience, unwrapNestedMediaElements } from '../pm-plugins/media';
27
27
  import { createPasteMeasurePayload, getContentNodeTypes, handleCodeBlockWithAnalytics, handleExpandWithAnalytics, handleMarkdownWithAnalytics, handleMediaSingleWithAnalytics, handleNestedTablePasteWithAnalytics, handlePasteAsPlainTextWithAnalytics, handlePasteIntoCaptionWithAnalytics, handlePasteIntoTaskAndDecisionWithAnalytics, handlePasteLinkOnSelectedTextWithAnalytics, handlePasteNonNestableBlockNodesIntoListWithAnalytics, handlePastePanelOrDecisionIntoListWithAnalytics, handlePastePreservingMarksWithAnalytics, handleRichTextWithAnalytics, handleSelectedTableWithAnalytics, sendPasteAnalyticsEvent } from './analytics';
28
+ import { createClipboardParser } from './clipboard-parser';
28
29
  import { createClipboardTextSerializer } from './create-clipboard-text-serializer';
29
30
  import { createPluginState, pluginKey as stateKey } from './plugin-factory';
30
31
  import { escapeBackslashAndLinksExceptCodeBlock, getPasteSource, htmlContainsSingleFile, htmlHasInvalidLinkTags, isPastedFromExcel, isPastedFromWord, removeDuplicateInvalidLinks, transformUnsupportedBlockCardToInline } from './util';
31
32
  import { handleVSCodeBlock } from './util/edge-cases/handleVSCodeBlock';
32
- import { handleMacroAutoConvert, handleMention, handleParagraphBlockMarks, handlePasteExpand, handleTableContentPasteInBodiedExtension } from './util/handlers';
33
+ import { applyContainerNodeTransformToSlice, splitTablesOutOfPanelHtml, handleMacroAutoConvert, handleMention, handleParagraphBlockMarks, handlePasteExpand, handleTableContentPasteInBodiedExtension } from './util/handlers';
33
34
  import { normalizePastedCodeBlockAttrs } from './util/normalize-pasted-code-block-attrs';
34
35
  import { handleSyncBlocksPaste } from './util/sync-block';
35
36
  import { htmlHasIncompleteTable, isPastedFromTinyMCEConfluence, tryRebuildCompleteTableHtml } from './util/tinyMCE';
@@ -111,6 +112,7 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
111
112
  props: {
112
113
  // For serialising to plain text
113
114
  clipboardTextSerializer: createClipboardTextSerializer(getIntl()),
115
+ clipboardParser: createClipboardParser(schema),
114
116
  handleDOMEvents: {
115
117
  // note
116
118
  paste: (view, event) => {
@@ -325,6 +327,11 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
325
327
  view.dispatch(tr.setMeta('uiEvent', 'paste'));
326
328
  };
327
329
  slice = handleParagraphBlockMarks(state, slice);
330
+ if (expValEquals('platform_editor_nest_table_in_panel', 'isEnabled', true)) {
331
+ var _view$state$selection, _view$state$selection2;
332
+ const destinationParentType = (_view$state$selection = (_view$state$selection2 = view.state.selection.$from.node(-1)) === null || _view$state$selection2 === void 0 ? void 0 : _view$state$selection2.type.name) !== null && _view$state$selection !== void 0 ? _view$state$selection : view.state.doc.type.name;
333
+ slice = applyContainerNodeTransformToSlice(slice, schema, destinationParentType);
334
+ }
328
335
  slice = handleVSCodeBlock({
329
336
  state,
330
337
  slice,
@@ -615,6 +622,13 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
615
622
  if (html.indexOf(SyncBlockRendererDataAttributeName) >= 0 && editorExperiment('platform_synced_block', true)) {
616
623
  html = removeBreakoutFromRendererSyncBlockHTML(html);
617
624
  }
625
+
626
+ // When platform_editor_nest_table_in_panel is OFF,
627
+ // move tables out of panel divs in the HTML before ProseMirror
628
+ // parses it — otherwise the panel schema drops the table content.
629
+ if (expValEquals('platform_editor_table_in_panel_paste_fallback', 'isEnabled', true) && !expValEquals('platform_editor_nest_table_in_panel', 'isEnabled', true)) {
630
+ html = splitTablesOutOfPanelHtml(html);
631
+ }
618
632
  mostRecentPasteEvent = null;
619
633
  return html;
620
634
  }
@@ -1,5 +1,6 @@
1
1
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
2
2
  import uuid from 'uuid/v4';
3
+ import { transformContainerNodes } from '@atlaskit/adf-utils/transforms';
3
4
  import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
4
5
  import { addLinkMetadata } from '@atlaskit/editor-common/card';
5
6
  import { insideTable } from '@atlaskit/editor-common/core-utils';
@@ -1359,4 +1360,61 @@ export function handlePasteExpand(slice) {
1359
1360
  }
1360
1361
  return node;
1361
1362
  });
1363
+ }
1364
+
1365
+ /*
1366
+ * Transform container nodes promoting them to _c1 if required as per
1367
+ * transformContainerNodes from adf-utils
1368
+ */
1369
+ export function applyContainerNodeTransformToSlice(slice, schema, destinationParentType) {
1370
+ var _sliceJson$content;
1371
+ const sliceJson = slice.toJSON();
1372
+ if (!sliceJson) {
1373
+ return slice;
1374
+ }
1375
+ const wrappedAdf = {
1376
+ type: destinationParentType,
1377
+ content: (_sliceJson$content = sliceJson.content) !== null && _sliceJson$content !== void 0 ? _sliceJson$content : []
1378
+ };
1379
+ try {
1380
+ const {
1381
+ transformedAdf,
1382
+ isTransformed
1383
+ } = transformContainerNodes(wrappedAdf, schema);
1384
+ return isTransformed && transformedAdf && transformedAdf.content && transformedAdf.content.length > 0 ? Slice.fromJSON(schema, {
1385
+ ...sliceJson,
1386
+ content: transformedAdf.content
1387
+ }) : slice;
1388
+ } catch {
1389
+ return slice;
1390
+ }
1391
+ }
1392
+
1393
+ /*
1394
+ * When platform_editor_nest_table_in_panel is OFF, move table elements out of panel divs in the
1395
+ * clipboard HTML before ProseMirror parses it. This prevents the panel schema from
1396
+ * dropping table content entirely.
1397
+ */
1398
+ export function splitTablesOutOfPanelHtml(html) {
1399
+ if (!html.includes('data-panel-type')) {
1400
+ return html;
1401
+ }
1402
+ const doc = new DOMParser().parseFromString(html, 'text/html');
1403
+ const panelDivs = Array.from(doc.querySelectorAll('div[data-panel-type]'));
1404
+ let changed = false;
1405
+ for (const panelDiv of panelDivs) {
1406
+ const tableWrappers = Array.from(panelDiv.querySelectorAll('[data-prosemirror-node-name="table"]'));
1407
+ if (tableWrappers.length === 0) {
1408
+ continue;
1409
+ }
1410
+ changed = true;
1411
+ let anchor = panelDiv;
1412
+ for (const tableWrapper of tableWrappers) {
1413
+ var _tableWrapper$parentN, _anchor$parentNode;
1414
+ (_tableWrapper$parentN = tableWrapper.parentNode) === null || _tableWrapper$parentN === void 0 ? void 0 : _tableWrapper$parentN.removeChild(tableWrapper);
1415
+ (_anchor$parentNode = anchor.parentNode) === null || _anchor$parentNode === void 0 ? void 0 : _anchor$parentNode.insertBefore(tableWrapper, anchor.nextSibling);
1416
+ anchor = tableWrapper;
1417
+ }
1418
+ }
1419
+ return changed ? doc.body.innerHTML : html;
1362
1420
  }
@@ -0,0 +1,54 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
+ import { DOMParser as PMDOMParser } from '@atlaskit/editor-prosemirror/model';
3
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
4
+
5
+ /**
6
+ * Builds additional parseDOM rules to inject into the clipboard parser.
7
+ *
8
+ * Adding a rule with a higher priority than the default schema rules, allows
9
+ * specific HTML patterns to be parsed as a different node type than the schema
10
+ * would normally choose.
11
+ */
12
+ function buildAdditionalClipboardParseRules(schema) {
13
+ var _panel$spec$parseDOM;
14
+ var rules = [];
15
+
16
+ // This means that panels with tables inside always stay as panel_c1,
17
+ // but panels without tables retain standard behaviour
18
+ // of choosing between panel_c1 and panel depending on the context.
19
+ // e.g. panel with table pasted in expand gets lifted out, panel with normal text gets pasted inside expand
20
+ var _schema$nodes = schema.nodes,
21
+ panel_c1 = _schema$nodes.panel_c1,
22
+ panel = _schema$nodes.panel;
23
+ if (!panel_c1 || !panel) {
24
+ return rules;
25
+ }
26
+ var panelGetAttrs = (_panel$spec$parseDOM = panel.spec.parseDOM) === null || _panel$spec$parseDOM === void 0 || (_panel$spec$parseDOM = _panel$spec$parseDOM[0]) === null || _panel$spec$parseDOM === void 0 ? void 0 : _panel$spec$parseDOM.getAttrs;
27
+ rules.push({
28
+ tag: 'div[data-panel-type]',
29
+ priority: 60,
30
+ node: panel_c1.name,
31
+ getAttrs: function getAttrs(dom) {
32
+ var hasTable = dom.querySelector('[data-prosemirror-node-name="table"]') !== null;
33
+ if (!hasTable) {
34
+ return false; // fall through to standard panel rule
35
+ }
36
+ return panelGetAttrs ? panelGetAttrs(dom) : null;
37
+ }
38
+ });
39
+ return rules;
40
+ }
41
+
42
+ /**
43
+ * Creates a ProseMirror plugin with a custom `clipboardParser` that extends the
44
+ * schema's default parser with additional rules for handling specific paste scenarios.
45
+ */
46
+ export function createClipboardParser(schema) {
47
+ if (!expValEquals('platform_editor_nest_table_in_panel', 'isEnabled', true)) {
48
+ return undefined;
49
+ }
50
+ var additionalRules = buildAdditionalClipboardParseRules(schema);
51
+ var baseParser = PMDOMParser.fromSchema(schema);
52
+ var customParser = new PMDOMParser(schema, [].concat(_toConsumableArray(additionalRules), _toConsumableArray(baseParser.rules)));
53
+ return customParser;
54
+ }
@@ -30,11 +30,12 @@ import { PastePluginActionTypes } from '../editor-actions/actions';
30
30
  import { splitParagraphs, upgradeTextToLists } from '../editor-commands/commands';
31
31
  import { transformSliceForMedia, transformSliceToCorrectMediaWrapper, transformSliceToMediaSingleWithNewExperience, unwrapNestedMediaElements } from '../pm-plugins/media';
32
32
  import { createPasteMeasurePayload, getContentNodeTypes, handleCodeBlockWithAnalytics, handleExpandWithAnalytics, handleMarkdownWithAnalytics, handleMediaSingleWithAnalytics, handleNestedTablePasteWithAnalytics, handlePasteAsPlainTextWithAnalytics, handlePasteIntoCaptionWithAnalytics, handlePasteIntoTaskAndDecisionWithAnalytics, handlePasteLinkOnSelectedTextWithAnalytics, handlePasteNonNestableBlockNodesIntoListWithAnalytics, handlePastePanelOrDecisionIntoListWithAnalytics, handlePastePreservingMarksWithAnalytics, handleRichTextWithAnalytics, handleSelectedTableWithAnalytics, sendPasteAnalyticsEvent } from './analytics';
33
+ import { createClipboardParser } from './clipboard-parser';
33
34
  import { createClipboardTextSerializer } from './create-clipboard-text-serializer';
34
35
  import { createPluginState, pluginKey as stateKey } from './plugin-factory';
35
36
  import { escapeBackslashAndLinksExceptCodeBlock, getPasteSource, htmlContainsSingleFile, htmlHasInvalidLinkTags, isPastedFromExcel, isPastedFromWord, removeDuplicateInvalidLinks, transformUnsupportedBlockCardToInline } from './util';
36
37
  import { handleVSCodeBlock } from './util/edge-cases/handleVSCodeBlock';
37
- import { handleMacroAutoConvert, handleMention, handleParagraphBlockMarks, handlePasteExpand, handleTableContentPasteInBodiedExtension } from './util/handlers';
38
+ import { applyContainerNodeTransformToSlice, splitTablesOutOfPanelHtml, handleMacroAutoConvert, handleMention, handleParagraphBlockMarks, handlePasteExpand, handleTableContentPasteInBodiedExtension } from './util/handlers';
38
39
  import { normalizePastedCodeBlockAttrs } from './util/normalize-pasted-code-block-attrs';
39
40
  import { handleSyncBlocksPaste } from './util/sync-block';
40
41
  import { htmlHasIncompleteTable, isPastedFromTinyMCEConfluence, tryRebuildCompleteTableHtml } from './util/tinyMCE';
@@ -138,6 +139,7 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
138
139
  props: {
139
140
  // For serialising to plain text
140
141
  clipboardTextSerializer: createClipboardTextSerializer(getIntl()),
142
+ clipboardParser: createClipboardParser(schema),
141
143
  handleDOMEvents: {
142
144
  // note
143
145
  paste: function paste(view, event) {
@@ -358,6 +360,11 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
358
360
  view.dispatch(tr.setMeta('uiEvent', 'paste'));
359
361
  };
360
362
  slice = handleParagraphBlockMarks(state, slice);
363
+ if (expValEquals('platform_editor_nest_table_in_panel', 'isEnabled', true)) {
364
+ var _view$state$selection, _view$state$selection2;
365
+ var destinationParentType = (_view$state$selection = (_view$state$selection2 = view.state.selection.$from.node(-1)) === null || _view$state$selection2 === void 0 ? void 0 : _view$state$selection2.type.name) !== null && _view$state$selection !== void 0 ? _view$state$selection : view.state.doc.type.name;
366
+ slice = applyContainerNodeTransformToSlice(slice, schema, destinationParentType);
367
+ }
361
368
  slice = handleVSCodeBlock({
362
369
  state: state,
363
370
  slice: slice,
@@ -648,6 +655,13 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
648
655
  if (html.indexOf(SyncBlockRendererDataAttributeName) >= 0 && editorExperiment('platform_synced_block', true)) {
649
656
  html = removeBreakoutFromRendererSyncBlockHTML(html);
650
657
  }
658
+
659
+ // When platform_editor_nest_table_in_panel is OFF,
660
+ // move tables out of panel divs in the HTML before ProseMirror
661
+ // parses it — otherwise the panel schema drops the table content.
662
+ if (expValEquals('platform_editor_table_in_panel_paste_fallback', 'isEnabled', true) && !expValEquals('platform_editor_nest_table_in_panel', 'isEnabled', true)) {
663
+ html = splitTablesOutOfPanelHtml(html);
664
+ }
651
665
  mostRecentPasteEvent = null;
652
666
  return html;
653
667
  }
@@ -8,6 +8,7 @@ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbol
8
8
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
9
9
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
10
10
  import uuid from 'uuid/v4';
11
+ import { transformContainerNodes } from '@atlaskit/adf-utils/transforms';
11
12
  import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
12
13
  import { addLinkMetadata } from '@atlaskit/editor-common/card';
13
14
  import { insideTable } from '@atlaskit/editor-common/core-utils';
@@ -1367,4 +1368,61 @@ export function handlePasteExpand(slice) {
1367
1368
  }
1368
1369
  return node;
1369
1370
  });
1371
+ }
1372
+
1373
+ /*
1374
+ * Transform container nodes promoting them to _c1 if required as per
1375
+ * transformContainerNodes from adf-utils
1376
+ */
1377
+ export function applyContainerNodeTransformToSlice(slice, schema, destinationParentType) {
1378
+ var _sliceJson$content;
1379
+ var sliceJson = slice.toJSON();
1380
+ if (!sliceJson) {
1381
+ return slice;
1382
+ }
1383
+ var wrappedAdf = {
1384
+ type: destinationParentType,
1385
+ content: (_sliceJson$content = sliceJson.content) !== null && _sliceJson$content !== void 0 ? _sliceJson$content : []
1386
+ };
1387
+ try {
1388
+ var _transformContainerNo = transformContainerNodes(wrappedAdf, schema),
1389
+ transformedAdf = _transformContainerNo.transformedAdf,
1390
+ isTransformed = _transformContainerNo.isTransformed;
1391
+ return isTransformed && transformedAdf && transformedAdf.content && transformedAdf.content.length > 0 ? Slice.fromJSON(schema, _objectSpread(_objectSpread({}, sliceJson), {}, {
1392
+ content: transformedAdf.content
1393
+ })) : slice;
1394
+ } catch (_unused3) {
1395
+ return slice;
1396
+ }
1397
+ }
1398
+
1399
+ /*
1400
+ * When platform_editor_nest_table_in_panel is OFF, move table elements out of panel divs in the
1401
+ * clipboard HTML before ProseMirror parses it. This prevents the panel schema from
1402
+ * dropping table content entirely.
1403
+ */
1404
+ export function splitTablesOutOfPanelHtml(html) {
1405
+ if (!html.includes('data-panel-type')) {
1406
+ return html;
1407
+ }
1408
+ var doc = new DOMParser().parseFromString(html, 'text/html');
1409
+ var panelDivs = Array.from(doc.querySelectorAll('div[data-panel-type]'));
1410
+ var changed = false;
1411
+ for (var _i = 0, _panelDivs = panelDivs; _i < _panelDivs.length; _i++) {
1412
+ var panelDiv = _panelDivs[_i];
1413
+ var tableWrappers = Array.from(panelDiv.querySelectorAll('[data-prosemirror-node-name="table"]'));
1414
+ if (tableWrappers.length === 0) {
1415
+ continue;
1416
+ }
1417
+ changed = true;
1418
+ var anchor = panelDiv;
1419
+ for (var _i2 = 0, _tableWrappers = tableWrappers; _i2 < _tableWrappers.length; _i2++) {
1420
+ var _tableWrapper$parentN, _anchor$parentNode;
1421
+ var tableWrapper = _tableWrappers[_i2];
1422
+ (_tableWrapper$parentN = tableWrapper.parentNode) === null || _tableWrapper$parentN === void 0 || _tableWrapper$parentN.removeChild(tableWrapper);
1423
+ (_anchor$parentNode = anchor.parentNode) === null || _anchor$parentNode === void 0 || _anchor$parentNode.insertBefore(tableWrapper, anchor.nextSibling);
1424
+ anchor = tableWrapper;
1425
+ }
1426
+ }
1427
+ return changed ? doc.body.innerHTML : html;
1370
1428
  }
@@ -0,0 +1,7 @@
1
+ import { DOMParser as PMDOMParser } from '@atlaskit/editor-prosemirror/model';
2
+ import type { Schema } from '@atlaskit/editor-prosemirror/model';
3
+ /**
4
+ * Creates a ProseMirror plugin with a custom `clipboardParser` that extends the
5
+ * schema's default parser with additional rules for handling specific paste scenarios.
6
+ */
7
+ export declare function createClipboardParser(schema: Schema): PMDOMParser | undefined;
@@ -58,3 +58,5 @@ export declare const handleSelectedTable: (editorAnalyticsAPI: EditorAnalyticsAP
58
58
  export declare function checkTaskListInList(state: EditorState, slice: Slice): boolean;
59
59
  export declare function checkIfSelectionInNestedList(state: EditorState): boolean;
60
60
  export declare function handlePasteExpand(slice: Slice): Slice;
61
+ export declare function applyContainerNodeTransformToSlice(slice: Slice, schema: Schema, destinationParentType: string): Slice;
62
+ export declare function splitTablesOutOfPanelHtml(html: string): string;
@@ -0,0 +1,7 @@
1
+ import { DOMParser as PMDOMParser } from '@atlaskit/editor-prosemirror/model';
2
+ import type { Schema } from '@atlaskit/editor-prosemirror/model';
3
+ /**
4
+ * Creates a ProseMirror plugin with a custom `clipboardParser` that extends the
5
+ * schema's default parser with additional rules for handling specific paste scenarios.
6
+ */
7
+ export declare function createClipboardParser(schema: Schema): PMDOMParser | undefined;
@@ -58,3 +58,5 @@ export declare const handleSelectedTable: (editorAnalyticsAPI: EditorAnalyticsAP
58
58
  export declare function checkTaskListInList(state: EditorState, slice: Slice): boolean;
59
59
  export declare function checkIfSelectionInNestedList(state: EditorState): boolean;
60
60
  export declare function handlePasteExpand(slice: Slice): Slice;
61
+ export declare function applyContainerNodeTransformToSlice(slice: Slice, schema: Schema, destinationParentType: string): Slice;
62
+ export declare function splitTablesOutOfPanelHtml(html: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-paste",
3
- "version": "12.0.0",
3
+ "version": "12.1.1",
4
4
  "description": "Paste plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -27,35 +27,36 @@
27
27
  "sideEffects": false,
28
28
  "atlaskit:src": "src/index.ts",
29
29
  "dependencies": {
30
- "@atlaskit/adf-schema": "^52.15.0",
30
+ "@atlaskit/adf-schema": "^52.16.0",
31
+ "@atlaskit/adf-utils": "^19.32.0",
31
32
  "@atlaskit/code": "^17.5.0",
32
33
  "@atlaskit/editor-markdown-transformer": "^5.21.0",
33
34
  "@atlaskit/editor-plugin-analytics": "^11.0.0",
34
35
  "@atlaskit/editor-plugin-annotation": "^11.0.0",
35
36
  "@atlaskit/editor-plugin-better-type-history": "^11.0.0",
36
- "@atlaskit/editor-plugin-card": "^17.0.0",
37
+ "@atlaskit/editor-plugin-card": "^17.1.0",
37
38
  "@atlaskit/editor-plugin-expand": "^12.0.0",
38
39
  "@atlaskit/editor-plugin-feature-flags": "^10.0.0",
39
40
  "@atlaskit/editor-plugin-list": "^13.0.0",
40
- "@atlaskit/editor-plugin-media": "^13.0.0",
41
- "@atlaskit/editor-plugin-mentions": "^13.0.0",
41
+ "@atlaskit/editor-plugin-media": "^13.1.0",
42
+ "@atlaskit/editor-plugin-mentions": "^13.1.0",
42
43
  "@atlaskit/editor-prosemirror": "^7.3.0",
43
44
  "@atlaskit/editor-tables": "^2.10.0",
44
45
  "@atlaskit/flag": "^17.12.0",
45
- "@atlaskit/icon": "^35.3.0",
46
+ "@atlaskit/icon": "^35.4.0",
46
47
  "@atlaskit/insm": "^0.4.0",
47
48
  "@atlaskit/media-client": "^36.3.0",
48
49
  "@atlaskit/media-common": "^13.3.0",
49
50
  "@atlaskit/platform-feature-flags": "^1.1.0",
50
51
  "@atlaskit/prosemirror-history": "^0.2.0",
51
- "@atlaskit/tmp-editor-statsig": "^88.0.0",
52
+ "@atlaskit/tmp-editor-statsig": "^89.0.0",
52
53
  "@atlaskit/tokens": "^13.1.0",
53
54
  "@babel/runtime": "^7.0.0",
54
55
  "lodash": "^4.17.21",
55
56
  "uuid": "^3.1.0"
56
57
  },
57
58
  "peerDependencies": {
58
- "@atlaskit/editor-common": "^115.0.0",
59
+ "@atlaskit/editor-common": "^115.2.0",
59
60
  "react": "^18.2.0",
60
61
  "react-dom": "^18.2.0",
61
62
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"