@atlaskit/editor-plugin-paste 12.0.0 → 12.1.0
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 +11 -0
- package/dist/cjs/pm-plugins/clipboard-parser.js +60 -0
- package/dist/cjs/pm-plugins/main.js +14 -0
- package/dist/cjs/pm-plugins/util/handlers.js +60 -0
- package/dist/es2019/pm-plugins/clipboard-parser.js +54 -0
- package/dist/es2019/pm-plugins/main.js +15 -1
- package/dist/es2019/pm-plugins/util/handlers.js +58 -0
- package/dist/esm/pm-plugins/clipboard-parser.js +54 -0
- package/dist/esm/pm-plugins/main.js +15 -1
- package/dist/esm/pm-plugins/util/handlers.js +58 -0
- package/dist/types/pm-plugins/clipboard-parser.d.ts +7 -0
- package/dist/types/pm-plugins/util/handlers.d.ts +2 -0
- package/dist/types-ts4.5/pm-plugins/clipboard-parser.d.ts +7 -0
- package/dist/types-ts4.5/pm-plugins/util/handlers.d.ts +2 -0
- package/package.json +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-paste
|
|
2
2
|
|
|
3
|
+
## 12.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`f7fc6c3bcc4e3`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/f7fc6c3bcc4e3) -
|
|
8
|
+
[ux] EDITOR-7242 add paste logic to support table in panel nesting
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- Updated dependencies
|
|
13
|
+
|
|
3
14
|
## 12.0.0
|
|
4
15
|
|
|
5
16
|
### 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.
|
|
3
|
+
"version": "12.1.0",
|
|
4
4
|
"description": "Paste plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"sideEffects": false,
|
|
28
28
|
"atlaskit:src": "src/index.ts",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@atlaskit/adf-schema": "^52.
|
|
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",
|
|
@@ -42,13 +43,13 @@
|
|
|
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.
|
|
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.
|
|
52
|
+
"@atlaskit/tmp-editor-statsig": "^88.3.0",
|
|
52
53
|
"@atlaskit/tokens": "^13.1.0",
|
|
53
54
|
"@babel/runtime": "^7.0.0",
|
|
54
55
|
"lodash": "^4.17.21",
|