@atlaskit/editor-plugin-paste 11.0.1 → 11.0.3
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 +24 -0
- package/dist/cjs/pm-plugins/main.js +21 -33
- package/dist/cjs/pm-plugins/util/handlers.js +74 -7
- package/dist/es2019/pm-plugins/main.js +20 -36
- package/dist/es2019/pm-plugins/util/handlers.js +68 -3
- package/dist/esm/pm-plugins/main.js +22 -34
- package/dist/esm/pm-plugins/util/handlers.js +74 -7
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-paste
|
|
2
2
|
|
|
3
|
+
## 11.0.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`402738b592e0b`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/402738b592e0b) -
|
|
8
|
+
Fix invalid flexible list structures caused by delete, paste, and typing operations.
|
|
9
|
+
|
|
10
|
+
Under platform_editor_flexible_list_schema, operations that remove content spanning list or task
|
|
11
|
+
list items could leave nodes with a nested list as their first child instead of a required
|
|
12
|
+
paragraph/item. Normalisation now runs efficiently on all relevant transactions in
|
|
13
|
+
appendTransaction.
|
|
14
|
+
|
|
15
|
+
- Updated dependencies
|
|
16
|
+
|
|
17
|
+
## 11.0.2
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- [`90779068bff5a`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/90779068bff5a) -
|
|
22
|
+
Fix: preserve fontSize block mark when pasting small text copied from inside container nodes (e.g.
|
|
23
|
+
panel, expand). ProseMirror wraps the pasted content back in the container context, increasing
|
|
24
|
+
openStart/openEnd. handleParagraphBlockMarks now unwraps these container nodes so the fontSize
|
|
25
|
+
mark is preserved during paste.
|
|
26
|
+
|
|
3
27
|
## 11.0.1
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
|
@@ -45,30 +45,6 @@ var _tinyMCE = require("./util/tinyMCE");
|
|
|
45
45
|
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
|
|
46
46
|
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
47
47
|
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
|
|
48
|
-
function isListIntoListPaste(tr, state) {
|
|
49
|
-
var _state$schema$nodes = state.schema.nodes,
|
|
50
|
-
listItem = _state$schema$nodes.listItem,
|
|
51
|
-
bulletList = _state$schema$nodes.bulletList,
|
|
52
|
-
orderedList = _state$schema$nodes.orderedList;
|
|
53
|
-
var _state$selection = state.selection,
|
|
54
|
-
$from = _state$selection.$from,
|
|
55
|
-
$to = _state$selection.$to;
|
|
56
|
-
var selectionInList = !!(0, _utils2.findParentNodeOfTypeClosestToPos)($from, [listItem]) || !!(0, _utils2.findParentNodeOfTypeClosestToPos)($to, [listItem]);
|
|
57
|
-
if (!selectionInList) {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
return tr.steps.some(function (step) {
|
|
61
|
-
var _slice$content;
|
|
62
|
-
var slice = (0, _utils.extractSliceFromStep)(step);
|
|
63
|
-
var listExists = false;
|
|
64
|
-
slice === null || slice === void 0 || (_slice$content = slice.content) === null || _slice$content === void 0 || _slice$content.forEach(function (node) {
|
|
65
|
-
if (node.type === bulletList || node.type === orderedList) {
|
|
66
|
-
listExists = true;
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
return listExists;
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
48
|
var isInsideBlockQuote = exports.isInsideBlockQuote = function isInsideBlockQuote(state) {
|
|
73
49
|
var blockquote = state.schema.nodes.blockquote;
|
|
74
50
|
return (0, _utils2.hasParentNodeOfType)(blockquote)(state.selection);
|
|
@@ -307,10 +283,10 @@ function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pl
|
|
|
307
283
|
// to split those repairing transactions in prosemirror-history when they're being added to the
|
|
308
284
|
// "done" stack
|
|
309
285
|
var isPastingTable = tr.steps.some(function (step) {
|
|
310
|
-
var _slice$
|
|
286
|
+
var _slice$content;
|
|
311
287
|
var slice = (0, _utils.extractSliceFromStep)(step);
|
|
312
288
|
var tableExists = false;
|
|
313
|
-
slice === null || slice === void 0 || (_slice$
|
|
289
|
+
slice === null || slice === void 0 || (_slice$content = slice.content) === null || _slice$content === void 0 || _slice$content.forEach(function (node) {
|
|
314
290
|
if (node.type === state.schema.nodes.table) {
|
|
315
291
|
tableExists = true;
|
|
316
292
|
}
|
|
@@ -318,14 +294,26 @@ function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pl
|
|
|
318
294
|
return tableExists;
|
|
319
295
|
});
|
|
320
296
|
|
|
321
|
-
// Don't
|
|
322
|
-
// appendTransaction
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
|
|
326
|
-
|
|
297
|
+
// Don't flag as a paste event (add closeHistory) when the paste affects a list and
|
|
298
|
+
// the list plugin's appendTransaction will normalise the structure, and we want the
|
|
299
|
+
// paste + normalisation to be a single undo step.
|
|
300
|
+
// Pasting into an existing list — selection is inside a list node.
|
|
301
|
+
var isPastingIntoList = false;
|
|
302
|
+
// Pasting list content from an external source — slice top-level contains a list node.
|
|
303
|
+
var isPastingListContent = false;
|
|
304
|
+
if ((0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_flexible_list_schema', 'isEnabled', true)) {
|
|
305
|
+
var listNodeTypes = [state.schema.nodes.bulletList, state.schema.nodes.orderedList, state.schema.nodes.taskList].filter(function (n) {
|
|
306
|
+
return Boolean(n);
|
|
307
|
+
});
|
|
308
|
+
isPastingIntoList = (0, _utils2.hasParentNodeOfType)(listNodeTypes)(state.selection);
|
|
309
|
+
for (var i = 0; i < slice.content.childCount; i++) {
|
|
310
|
+
if (listNodeTypes.includes(slice.content.child(i).type)) {
|
|
311
|
+
isPastingListContent = true;
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
327
315
|
}
|
|
328
|
-
if (!isPastingTextInsidePlaceholderText && !isPastingTable && !isPastingOverLayoutColumns && !
|
|
316
|
+
if (!isPastingTextInsidePlaceholderText && !isPastingTable && !isPastingOverLayoutColumns && !isPastingIntoList && !isPastingListContent && pluginInjectionApi !== null && pluginInjectionApi !== void 0 && pluginInjectionApi.betterTypeHistory) {
|
|
329
317
|
var _pluginInjectionApi$b;
|
|
330
318
|
tr = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$b = pluginInjectionApi.betterTypeHistory) === null || _pluginInjectionApi$b === void 0 ? void 0 : _pluginInjectionApi$b.actions.flagPasteEvent(tr);
|
|
331
319
|
}
|
|
@@ -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
|
|
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
|
|
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) {
|
|
@@ -13,7 +13,7 @@ import { removeBreakoutFromRendererSyncBlockHTML, transformSingleColumnLayout, t
|
|
|
13
13
|
import { containsAnyAnnotations, extractSliceFromStep, linkifyContent, mapChildren } from '@atlaskit/editor-common/utils';
|
|
14
14
|
import { MarkdownTransformer } from '@atlaskit/editor-markdown-transformer';
|
|
15
15
|
import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
|
|
16
|
-
import { contains,
|
|
16
|
+
import { contains, hasParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
|
|
17
17
|
import { handlePaste as handlePasteTable } from '@atlaskit/editor-tables/utils';
|
|
18
18
|
import { insm } from '@atlaskit/insm';
|
|
19
19
|
import { extractClientIdsFromHtml } from '@atlaskit/media-common';
|
|
@@ -32,32 +32,6 @@ import { handleVSCodeBlock } from './util/edge-cases/handleVSCodeBlock';
|
|
|
32
32
|
import { handleMacroAutoConvert, handleMention, handleParagraphBlockMarks, handlePasteExpand, handleTableContentPasteInBodiedExtension } from './util/handlers';
|
|
33
33
|
import { handleSyncBlocksPaste } from './util/sync-block';
|
|
34
34
|
import { htmlHasIncompleteTable, isPastedFromTinyMCEConfluence, tryRebuildCompleteTableHtml } from './util/tinyMCE';
|
|
35
|
-
function isListIntoListPaste(tr, state) {
|
|
36
|
-
const {
|
|
37
|
-
listItem,
|
|
38
|
-
bulletList,
|
|
39
|
-
orderedList
|
|
40
|
-
} = state.schema.nodes;
|
|
41
|
-
const {
|
|
42
|
-
$from,
|
|
43
|
-
$to
|
|
44
|
-
} = state.selection;
|
|
45
|
-
const selectionInList = !!findParentNodeOfTypeClosestToPos($from, [listItem]) || !!findParentNodeOfTypeClosestToPos($to, [listItem]);
|
|
46
|
-
if (!selectionInList) {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
return tr.steps.some(step => {
|
|
50
|
-
var _slice$content;
|
|
51
|
-
const slice = extractSliceFromStep(step);
|
|
52
|
-
let listExists = false;
|
|
53
|
-
slice === null || slice === void 0 ? void 0 : (_slice$content = slice.content) === null || _slice$content === void 0 ? void 0 : _slice$content.forEach(node => {
|
|
54
|
-
if (node.type === bulletList || node.type === orderedList) {
|
|
55
|
-
listExists = true;
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
return listExists;
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
35
|
export const isInsideBlockQuote = state => {
|
|
62
36
|
const {
|
|
63
37
|
blockquote
|
|
@@ -271,10 +245,10 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
|
|
|
271
245
|
// to split those repairing transactions in prosemirror-history when they're being added to the
|
|
272
246
|
// "done" stack
|
|
273
247
|
const isPastingTable = tr.steps.some(step => {
|
|
274
|
-
var _slice$
|
|
248
|
+
var _slice$content;
|
|
275
249
|
const slice = extractSliceFromStep(step);
|
|
276
250
|
let tableExists = false;
|
|
277
|
-
slice === null || slice === void 0 ? void 0 : (_slice$
|
|
251
|
+
slice === null || slice === void 0 ? void 0 : (_slice$content = slice.content) === null || _slice$content === void 0 ? void 0 : _slice$content.forEach(node => {
|
|
278
252
|
if (node.type === state.schema.nodes.table) {
|
|
279
253
|
tableExists = true;
|
|
280
254
|
}
|
|
@@ -282,14 +256,24 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
|
|
|
282
256
|
return tableExists;
|
|
283
257
|
});
|
|
284
258
|
|
|
285
|
-
// Don't
|
|
286
|
-
// appendTransaction
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
|
|
259
|
+
// Don't flag as a paste event (add closeHistory) when the paste affects a list and
|
|
260
|
+
// the list plugin's appendTransaction will normalise the structure, and we want the
|
|
261
|
+
// paste + normalisation to be a single undo step.
|
|
262
|
+
// Pasting into an existing list — selection is inside a list node.
|
|
263
|
+
let isPastingIntoList = false;
|
|
264
|
+
// Pasting list content from an external source — slice top-level contains a list node.
|
|
265
|
+
let isPastingListContent = false;
|
|
266
|
+
if (expValEqualsNoExposure('platform_editor_flexible_list_schema', 'isEnabled', true)) {
|
|
267
|
+
const listNodeTypes = [state.schema.nodes.bulletList, state.schema.nodes.orderedList, state.schema.nodes.taskList].filter(n => Boolean(n));
|
|
268
|
+
isPastingIntoList = hasParentNodeOfType(listNodeTypes)(state.selection);
|
|
269
|
+
for (let i = 0; i < slice.content.childCount; i++) {
|
|
270
|
+
if (listNodeTypes.includes(slice.content.child(i).type)) {
|
|
271
|
+
isPastingListContent = true;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
291
275
|
}
|
|
292
|
-
if (!isPastingTextInsidePlaceholderText && !isPastingTable && !isPastingOverLayoutColumns && !
|
|
276
|
+
if (!isPastingTextInsidePlaceholderText && !isPastingTable && !isPastingOverLayoutColumns && !isPastingIntoList && !isPastingListContent && pluginInjectionApi !== null && pluginInjectionApi !== void 0 && pluginInjectionApi.betterTypeHistory) {
|
|
293
277
|
var _pluginInjectionApi$b;
|
|
294
278
|
tr = pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$b = pluginInjectionApi.betterTypeHistory) === null || _pluginInjectionApi$b === void 0 ? void 0 : _pluginInjectionApi$b.actions.flagPasteEvent(tr);
|
|
295
279
|
}
|
|
@@ -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
|
|
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,
|
|
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)));
|
|
@@ -18,7 +18,7 @@ import { removeBreakoutFromRendererSyncBlockHTML, transformSingleColumnLayout, t
|
|
|
18
18
|
import { containsAnyAnnotations, extractSliceFromStep, linkifyContent, mapChildren } from '@atlaskit/editor-common/utils';
|
|
19
19
|
import { MarkdownTransformer } from '@atlaskit/editor-markdown-transformer';
|
|
20
20
|
import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
|
|
21
|
-
import { contains,
|
|
21
|
+
import { contains, hasParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
|
|
22
22
|
import { handlePaste as handlePasteTable } from '@atlaskit/editor-tables/utils';
|
|
23
23
|
import { insm } from '@atlaskit/insm';
|
|
24
24
|
import { extractClientIdsFromHtml } from '@atlaskit/media-common';
|
|
@@ -37,30 +37,6 @@ import { handleVSCodeBlock } from './util/edge-cases/handleVSCodeBlock';
|
|
|
37
37
|
import { handleMacroAutoConvert, handleMention, handleParagraphBlockMarks, handlePasteExpand, handleTableContentPasteInBodiedExtension } from './util/handlers';
|
|
38
38
|
import { handleSyncBlocksPaste } from './util/sync-block';
|
|
39
39
|
import { htmlHasIncompleteTable, isPastedFromTinyMCEConfluence, tryRebuildCompleteTableHtml } from './util/tinyMCE';
|
|
40
|
-
function isListIntoListPaste(tr, state) {
|
|
41
|
-
var _state$schema$nodes = state.schema.nodes,
|
|
42
|
-
listItem = _state$schema$nodes.listItem,
|
|
43
|
-
bulletList = _state$schema$nodes.bulletList,
|
|
44
|
-
orderedList = _state$schema$nodes.orderedList;
|
|
45
|
-
var _state$selection = state.selection,
|
|
46
|
-
$from = _state$selection.$from,
|
|
47
|
-
$to = _state$selection.$to;
|
|
48
|
-
var selectionInList = !!findParentNodeOfTypeClosestToPos($from, [listItem]) || !!findParentNodeOfTypeClosestToPos($to, [listItem]);
|
|
49
|
-
if (!selectionInList) {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
return tr.steps.some(function (step) {
|
|
53
|
-
var _slice$content;
|
|
54
|
-
var slice = extractSliceFromStep(step);
|
|
55
|
-
var listExists = false;
|
|
56
|
-
slice === null || slice === void 0 || (_slice$content = slice.content) === null || _slice$content === void 0 || _slice$content.forEach(function (node) {
|
|
57
|
-
if (node.type === bulletList || node.type === orderedList) {
|
|
58
|
-
listExists = true;
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
return listExists;
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
40
|
export var isInsideBlockQuote = function isInsideBlockQuote(state) {
|
|
65
41
|
var blockquote = state.schema.nodes.blockquote;
|
|
66
42
|
return hasParentNodeOfType(blockquote)(state.selection);
|
|
@@ -299,10 +275,10 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
|
|
|
299
275
|
// to split those repairing transactions in prosemirror-history when they're being added to the
|
|
300
276
|
// "done" stack
|
|
301
277
|
var isPastingTable = tr.steps.some(function (step) {
|
|
302
|
-
var _slice$
|
|
278
|
+
var _slice$content;
|
|
303
279
|
var slice = extractSliceFromStep(step);
|
|
304
280
|
var tableExists = false;
|
|
305
|
-
slice === null || slice === void 0 || (_slice$
|
|
281
|
+
slice === null || slice === void 0 || (_slice$content = slice.content) === null || _slice$content === void 0 || _slice$content.forEach(function (node) {
|
|
306
282
|
if (node.type === state.schema.nodes.table) {
|
|
307
283
|
tableExists = true;
|
|
308
284
|
}
|
|
@@ -310,14 +286,26 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
|
|
|
310
286
|
return tableExists;
|
|
311
287
|
});
|
|
312
288
|
|
|
313
|
-
// Don't
|
|
314
|
-
// appendTransaction
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
|
|
289
|
+
// Don't flag as a paste event (add closeHistory) when the paste affects a list and
|
|
290
|
+
// the list plugin's appendTransaction will normalise the structure, and we want the
|
|
291
|
+
// paste + normalisation to be a single undo step.
|
|
292
|
+
// Pasting into an existing list — selection is inside a list node.
|
|
293
|
+
var isPastingIntoList = false;
|
|
294
|
+
// Pasting list content from an external source — slice top-level contains a list node.
|
|
295
|
+
var isPastingListContent = false;
|
|
296
|
+
if (expValEqualsNoExposure('platform_editor_flexible_list_schema', 'isEnabled', true)) {
|
|
297
|
+
var listNodeTypes = [state.schema.nodes.bulletList, state.schema.nodes.orderedList, state.schema.nodes.taskList].filter(function (n) {
|
|
298
|
+
return Boolean(n);
|
|
299
|
+
});
|
|
300
|
+
isPastingIntoList = hasParentNodeOfType(listNodeTypes)(state.selection);
|
|
301
|
+
for (var i = 0; i < slice.content.childCount; i++) {
|
|
302
|
+
if (listNodeTypes.includes(slice.content.child(i).type)) {
|
|
303
|
+
isPastingListContent = true;
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
319
307
|
}
|
|
320
|
-
if (!isPastingTextInsidePlaceholderText && !isPastingTable && !isPastingOverLayoutColumns && !
|
|
308
|
+
if (!isPastingTextInsidePlaceholderText && !isPastingTable && !isPastingOverLayoutColumns && !isPastingIntoList && !isPastingListContent && pluginInjectionApi !== null && pluginInjectionApi !== void 0 && pluginInjectionApi.betterTypeHistory) {
|
|
321
309
|
var _pluginInjectionApi$b;
|
|
322
310
|
tr = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$b = pluginInjectionApi.betterTypeHistory) === null || _pluginInjectionApi$b === void 0 ? void 0 : _pluginInjectionApi$b.actions.flagPasteEvent(tr);
|
|
323
311
|
}
|
|
@@ -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
|
|
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
|
|
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.
|
|
3
|
+
"version": "11.0.3",
|
|
4
4
|
"description": "Paste plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"uuid": "^3.1.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@atlaskit/editor-common": "^114.
|
|
58
|
+
"@atlaskit/editor-common": "^114.3.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"
|