@atlaskit/editor-plugin-paste 1.0.13 → 1.0.15
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 +13 -0
- package/dist/cjs/commands.js +139 -5
- package/dist/cjs/pm-plugins/main.js +9 -2
- package/dist/es2019/commands.js +121 -1
- package/dist/es2019/pm-plugins/main.js +9 -2
- package/dist/esm/commands.js +138 -4
- package/dist/esm/pm-plugins/main.js +9 -2
- package/dist/types/commands.d.ts +3 -1
- package/dist/types-ts4.5/commands.d.ts +3 -1
- package/package.json +11 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-paste
|
|
2
2
|
|
|
3
|
+
## 1.0.15
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#89978](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/89978) [`6e7143622425`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/6e7143622425) - fix paste markdown table into a table issue
|
|
8
|
+
|
|
9
|
+
## 1.0.14
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#87898](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/87898) [`6d4009f72e36`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/6d4009f72e36) - [ux] [ED-22591] Fix pasting logic for lines with number and dot (but is not a list item) to retain formatting and correct list conversion.
|
|
14
|
+
- Updated dependencies
|
|
15
|
+
|
|
3
16
|
## 1.0.13
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
package/dist/cjs/commands.js
CHANGED
|
@@ -4,13 +4,14 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
|
-
exports.upgradeTextToLists = exports.stopTrackingPastedMacroPositions = exports.startTrackingPastedMacroPositions = exports.splitParagraphs = exports.splitIntoParagraphs = void 0;
|
|
7
|
+
exports.upgradeTextToLists = exports.stopTrackingPastedMacroPositions = exports.startTrackingPastedMacroPositions = exports.splitParagraphs = exports.splitIntoParagraphs = exports._extractListFromParagraphV2 = exports._contentSplitByHardBreaks = void 0;
|
|
8
8
|
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
9
9
|
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
10
10
|
var _utils = require("@atlaskit/editor-common/utils");
|
|
11
11
|
var _commands = require("@atlaskit/editor-prosemirror/commands");
|
|
12
12
|
var _model = require("@atlaskit/editor-prosemirror/model");
|
|
13
13
|
var _state = require("@atlaskit/editor-prosemirror/state");
|
|
14
|
+
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
14
15
|
var _actions = require("./actions");
|
|
15
16
|
var _pluginFactory = require("./pm-plugins/plugin-factory");
|
|
16
17
|
/**
|
|
@@ -66,6 +67,135 @@ var getListType = function getListType(node, schema) {
|
|
|
66
67
|
return match ? [listType.node, match[0].length] : lastMatch;
|
|
67
68
|
}, null);
|
|
68
69
|
};
|
|
70
|
+
|
|
71
|
+
// Splits array of nodes by hardBreak. E.g.:
|
|
72
|
+
// [text "1. ", em "hello", date, hardbreak, text "2. ",
|
|
73
|
+
// subsup "world", hardbreak, text "smile"]
|
|
74
|
+
// => [
|
|
75
|
+
// [ text "1. ", em "hello", date ],
|
|
76
|
+
// [hardbreak, text "2. ", subsup "world"],
|
|
77
|
+
// [hardbreak, text "smile"]
|
|
78
|
+
// ]
|
|
79
|
+
var _contentSplitByHardBreaks = exports._contentSplitByHardBreaks = function _contentSplitByHardBreaks(content, schema) {
|
|
80
|
+
var wrapperContent = [];
|
|
81
|
+
var nextContent = [];
|
|
82
|
+
content.forEach(function (node) {
|
|
83
|
+
if (node.type === schema.nodes.hardBreak) {
|
|
84
|
+
if (nextContent.length !== 0) {
|
|
85
|
+
wrapperContent.push(nextContent);
|
|
86
|
+
}
|
|
87
|
+
nextContent = [node];
|
|
88
|
+
} else {
|
|
89
|
+
nextContent.push(node);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
wrapperContent.push(nextContent);
|
|
93
|
+
return wrapperContent;
|
|
94
|
+
};
|
|
95
|
+
var _extractListFromParagraphV2 = exports._extractListFromParagraphV2 = function _extractListFromParagraphV2(node, parent, schema) {
|
|
96
|
+
var content = (0, _utils.mapChildren)(node.content, function (node) {
|
|
97
|
+
return node;
|
|
98
|
+
});
|
|
99
|
+
var linesSplitByHardbreaks = _contentSplitByHardBreaks(content, schema);
|
|
100
|
+
var splitListsAndParagraphs = [];
|
|
101
|
+
var paragraphParts = [];
|
|
102
|
+
for (var index = 0; index < linesSplitByHardbreaks.length; index = index + 1) {
|
|
103
|
+
var _firstNonHardBreakNod;
|
|
104
|
+
var line = linesSplitByHardbreaks[index];
|
|
105
|
+
var listMatch = void 0;
|
|
106
|
+
if (index === 0) {
|
|
107
|
+
var _line$;
|
|
108
|
+
if (((_line$ = line[0]) === null || _line$ === void 0 ? void 0 : _line$.type) === schema.nodes.hardbreak) {
|
|
109
|
+
paragraphParts.push(line);
|
|
110
|
+
continue;
|
|
111
|
+
} else {
|
|
112
|
+
// the first line the potential list item is at postion 0
|
|
113
|
+
listMatch = getListType(line[0], schema);
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
// lines after the first the potential list item is at postion 1
|
|
117
|
+
if (line.length === 1) {
|
|
118
|
+
// if the line only has a line break return as is
|
|
119
|
+
paragraphParts.push(line);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
listMatch = getListType(line[1], schema);
|
|
123
|
+
}
|
|
124
|
+
if (!listMatch) {
|
|
125
|
+
// if there is not list match return as is
|
|
126
|
+
paragraphParts.push(line);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
var _listMatch = listMatch,
|
|
130
|
+
_listMatch2 = (0, _slicedToArray2.default)(_listMatch, 2),
|
|
131
|
+
nodeType = _listMatch2[0],
|
|
132
|
+
length = _listMatch2[1];
|
|
133
|
+
var firstNonHardBreakNode = line.find(function (node) {
|
|
134
|
+
return node.type !== schema.nodes.hardBreak;
|
|
135
|
+
});
|
|
136
|
+
var chunksWithoutLeadingHardBreaks = line.slice(line.findIndex(function (node) {
|
|
137
|
+
return node.type !== schema.nodes.hardBreak;
|
|
138
|
+
}));
|
|
139
|
+
|
|
140
|
+
// retain text after bullet or number-dot e.g. 1. Hello
|
|
141
|
+
var startingText = firstNonHardBreakNode === null || firstNonHardBreakNode === void 0 || (_firstNonHardBreakNod = firstNonHardBreakNode.text) === null || _firstNonHardBreakNod === void 0 ? void 0 : _firstNonHardBreakNod.substr(length);
|
|
142
|
+
var restOfChunk = startingText ? // apply transformation to first entry
|
|
143
|
+
[schema.text(startingText, firstNonHardBreakNode === null || firstNonHardBreakNode === void 0 ? void 0 : firstNonHardBreakNode.marks)].concat((0, _toConsumableArray2.default)(chunksWithoutLeadingHardBreaks.slice(1))) : chunksWithoutLeadingHardBreaks.slice(1);
|
|
144
|
+
|
|
145
|
+
// convert to list
|
|
146
|
+
var listItemNode = schema.nodes.listItem.createAndFill(undefined, schema.nodes.paragraph.createChecked(undefined, restOfChunk));
|
|
147
|
+
if (!listItemNode) {
|
|
148
|
+
paragraphParts.push(line);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
var attrs = nodeType === schema.nodes.orderedList ? {
|
|
152
|
+
order: parseInt(firstNonHardBreakNode.text.split('.')[0])
|
|
153
|
+
} : undefined;
|
|
154
|
+
var newList = nodeType.createChecked(attrs, [listItemNode]);
|
|
155
|
+
if (paragraphParts.length !== 0) {
|
|
156
|
+
splitListsAndParagraphs.push(schema.nodes.paragraph.createAndFill(undefined, paragraphParts.flat()));
|
|
157
|
+
paragraphParts = [];
|
|
158
|
+
}
|
|
159
|
+
splitListsAndParagraphs.push(newList);
|
|
160
|
+
}
|
|
161
|
+
if (paragraphParts.length !== 0) {
|
|
162
|
+
splitListsAndParagraphs.push(schema.nodes.paragraph.createAndFill(undefined, paragraphParts.flat()));
|
|
163
|
+
}
|
|
164
|
+
var result = splitListsAndParagraphs.flat();
|
|
165
|
+
// try to join
|
|
166
|
+
var mockState = _state.EditorState.create({
|
|
167
|
+
schema: schema
|
|
168
|
+
});
|
|
169
|
+
var joinedListsTr;
|
|
170
|
+
var mockDispatch = function mockDispatch(tr) {
|
|
171
|
+
joinedListsTr = tr;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// Return false to prevent replaceWith from wrapping the text node in a paragraph
|
|
175
|
+
// paragraph since that will be done later. If it's done here, it will fail
|
|
176
|
+
// the paragraph.validContent check.
|
|
177
|
+
// Dont return false if there are lists, as they arent validContent for paragraphs
|
|
178
|
+
// and will result in hanging textNodes
|
|
179
|
+
(0, _commands.autoJoin)(function (state, dispatch) {
|
|
180
|
+
if (!dispatch) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
var containsList = result.some(function (node) {
|
|
184
|
+
return node.type === schema.nodes.bulletList || node.type === schema.nodes.orderedList;
|
|
185
|
+
});
|
|
186
|
+
if (result.some(function (node) {
|
|
187
|
+
return node.isText;
|
|
188
|
+
}) && !containsList) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
dispatch(state.tr.replaceWith(0, 2, result));
|
|
192
|
+
return true;
|
|
193
|
+
}, function (before, after) {
|
|
194
|
+
return (0, _utils.isListNode)(before) && (0, _utils.isListNode)(after);
|
|
195
|
+
})(mockState, mockDispatch);
|
|
196
|
+
var fragment = joinedListsTr ? joinedListsTr.doc.content : _model.Fragment.from(result);
|
|
197
|
+
return fragment;
|
|
198
|
+
};
|
|
69
199
|
var extractListFromParagraph = function extractListFromParagraph(node, parent, schema) {
|
|
70
200
|
var _schema$nodes2 = schema.nodes,
|
|
71
201
|
hardBreak = _schema$nodes2.hardBreak,
|
|
@@ -88,9 +218,9 @@ var extractListFromParagraph = function extractListFromParagraph(node, parent, s
|
|
|
88
218
|
if (!listMatch || !child.text) {
|
|
89
219
|
return child;
|
|
90
220
|
}
|
|
91
|
-
var
|
|
92
|
-
nodeType =
|
|
93
|
-
length =
|
|
221
|
+
var _listMatch3 = (0, _slicedToArray2.default)(listMatch, 2),
|
|
222
|
+
nodeType = _listMatch3[0],
|
|
223
|
+
length = _listMatch3[1];
|
|
94
224
|
|
|
95
225
|
// convert to list item
|
|
96
226
|
var newText = child.text.substr(length);
|
|
@@ -170,7 +300,11 @@ var extractListFromParagraph = function extractListFromParagraph(node, parent, s
|
|
|
170
300
|
var upgradeTextToLists = exports.upgradeTextToLists = function upgradeTextToLists(slice, schema) {
|
|
171
301
|
return (0, _utils.mapSlice)(slice, function (node, parent) {
|
|
172
302
|
if (node.type === schema.nodes.paragraph) {
|
|
173
|
-
|
|
303
|
+
if ((0, _platformFeatureFlags.getBooleanFF)('platform.editor.extractlistfromparagraphv2')) {
|
|
304
|
+
return _extractListFromParagraphV2(node, parent, schema);
|
|
305
|
+
} else {
|
|
306
|
+
return extractListFromParagraph(node, parent, schema);
|
|
307
|
+
}
|
|
174
308
|
}
|
|
175
309
|
return node;
|
|
176
310
|
});
|
|
@@ -335,9 +335,16 @@ function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pl
|
|
|
335
335
|
if ((0, _analytics2.handleSelectedTableWithAnalytics)(editorAnalyticsAPI)(view, event, slice)(state, dispatch)) {
|
|
336
336
|
return true;
|
|
337
337
|
}
|
|
338
|
+
var isNestedMarkdownTable = false;
|
|
339
|
+
if ((0, _platformFeatureFlags.getBooleanFF)('platform.editor.paste-markdown-table-in-a-table')) {
|
|
340
|
+
// if paste a markdown table inside a table cell, we should treat it as a table slice
|
|
341
|
+
var isParentNodeTdOrTh = selectionParentType === schema.nodes.tableCell || selectionParentType === schema.nodes.tableHeader;
|
|
342
|
+
isNestedMarkdownTable = !!(markdownSlice && isPlainText && isParentNodeTdOrTh && (0, _analytics2.getContentNodeTypes)(markdownSlice.content).includes(schema.nodes.table.name));
|
|
343
|
+
slice = isNestedMarkdownTable ? markdownSlice : slice;
|
|
344
|
+
}
|
|
338
345
|
|
|
339
346
|
// If the clipboard only contains plain text, attempt to parse it as Markdown
|
|
340
|
-
if (isPlainText && markdownSlice) {
|
|
347
|
+
if (isPlainText && markdownSlice && !isNestedMarkdownTable) {
|
|
341
348
|
if ((0, _analytics2.handlePastePreservingMarksWithAnalytics)(view, event, markdownSlice, _analytics.PasteTypes.markdown, pluginInjectionApi)(state, dispatch)) {
|
|
342
349
|
return true;
|
|
343
350
|
}
|
|
@@ -362,7 +369,7 @@ function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pl
|
|
|
362
369
|
}
|
|
363
370
|
|
|
364
371
|
// finally, handle rich-text copy-paste
|
|
365
|
-
if (isRichText) {
|
|
372
|
+
if (isRichText || isNestedMarkdownTable) {
|
|
366
373
|
var _pluginInjectionApi$c2, _pluginInjectionApi$e2, _pluginInjectionApi$l;
|
|
367
374
|
// linkify the text where possible
|
|
368
375
|
slice = (0, _utils.linkifyContent)(state.schema)(slice);
|
package/dist/es2019/commands.js
CHANGED
|
@@ -2,6 +2,7 @@ import { isListNode, mapChildren, mapSlice } from '@atlaskit/editor-common/utils
|
|
|
2
2
|
import { autoJoin } from '@atlaskit/editor-prosemirror/commands';
|
|
3
3
|
import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
|
|
4
4
|
import { EditorState } from '@atlaskit/editor-prosemirror/state';
|
|
5
|
+
import { getBooleanFF } from '@atlaskit/platform-feature-flags';
|
|
5
6
|
import { PastePluginActionTypes as ActionTypes } from './actions';
|
|
6
7
|
import { createCommand } from './pm-plugins/plugin-factory';
|
|
7
8
|
|
|
@@ -55,6 +56,121 @@ const getListType = (node, schema) => {
|
|
|
55
56
|
return match ? [listType.node, match[0].length] : lastMatch;
|
|
56
57
|
}, null);
|
|
57
58
|
};
|
|
59
|
+
|
|
60
|
+
// Splits array of nodes by hardBreak. E.g.:
|
|
61
|
+
// [text "1. ", em "hello", date, hardbreak, text "2. ",
|
|
62
|
+
// subsup "world", hardbreak, text "smile"]
|
|
63
|
+
// => [
|
|
64
|
+
// [ text "1. ", em "hello", date ],
|
|
65
|
+
// [hardbreak, text "2. ", subsup "world"],
|
|
66
|
+
// [hardbreak, text "smile"]
|
|
67
|
+
// ]
|
|
68
|
+
export const _contentSplitByHardBreaks = (content, schema) => {
|
|
69
|
+
const wrapperContent = [];
|
|
70
|
+
let nextContent = [];
|
|
71
|
+
content.forEach(node => {
|
|
72
|
+
if (node.type === schema.nodes.hardBreak) {
|
|
73
|
+
if (nextContent.length !== 0) {
|
|
74
|
+
wrapperContent.push(nextContent);
|
|
75
|
+
}
|
|
76
|
+
nextContent = [node];
|
|
77
|
+
} else {
|
|
78
|
+
nextContent.push(node);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
wrapperContent.push(nextContent);
|
|
82
|
+
return wrapperContent;
|
|
83
|
+
};
|
|
84
|
+
export const _extractListFromParagraphV2 = (node, parent, schema) => {
|
|
85
|
+
const content = mapChildren(node.content, node => node);
|
|
86
|
+
const linesSplitByHardbreaks = _contentSplitByHardBreaks(content, schema);
|
|
87
|
+
const splitListsAndParagraphs = [];
|
|
88
|
+
let paragraphParts = [];
|
|
89
|
+
for (var index = 0; index < linesSplitByHardbreaks.length; index = index + 1) {
|
|
90
|
+
var _firstNonHardBreakNod;
|
|
91
|
+
const line = linesSplitByHardbreaks[index];
|
|
92
|
+
let listMatch;
|
|
93
|
+
if (index === 0) {
|
|
94
|
+
var _line$;
|
|
95
|
+
if (((_line$ = line[0]) === null || _line$ === void 0 ? void 0 : _line$.type) === schema.nodes.hardbreak) {
|
|
96
|
+
paragraphParts.push(line);
|
|
97
|
+
continue;
|
|
98
|
+
} else {
|
|
99
|
+
// the first line the potential list item is at postion 0
|
|
100
|
+
listMatch = getListType(line[0], schema);
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
// lines after the first the potential list item is at postion 1
|
|
104
|
+
if (line.length === 1) {
|
|
105
|
+
// if the line only has a line break return as is
|
|
106
|
+
paragraphParts.push(line);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
listMatch = getListType(line[1], schema);
|
|
110
|
+
}
|
|
111
|
+
if (!listMatch) {
|
|
112
|
+
// if there is not list match return as is
|
|
113
|
+
paragraphParts.push(line);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const [nodeType, length] = listMatch;
|
|
117
|
+
const firstNonHardBreakNode = line.find(node => node.type !== schema.nodes.hardBreak);
|
|
118
|
+
const chunksWithoutLeadingHardBreaks = line.slice(line.findIndex(node => node.type !== schema.nodes.hardBreak));
|
|
119
|
+
|
|
120
|
+
// retain text after bullet or number-dot e.g. 1. Hello
|
|
121
|
+
const startingText = firstNonHardBreakNode === null || firstNonHardBreakNode === void 0 ? void 0 : (_firstNonHardBreakNod = firstNonHardBreakNode.text) === null || _firstNonHardBreakNod === void 0 ? void 0 : _firstNonHardBreakNod.substr(length);
|
|
122
|
+
const restOfChunk = startingText ?
|
|
123
|
+
// apply transformation to first entry
|
|
124
|
+
[schema.text(startingText, firstNonHardBreakNode === null || firstNonHardBreakNode === void 0 ? void 0 : firstNonHardBreakNode.marks), ...chunksWithoutLeadingHardBreaks.slice(1)] : chunksWithoutLeadingHardBreaks.slice(1);
|
|
125
|
+
|
|
126
|
+
// convert to list
|
|
127
|
+
const listItemNode = schema.nodes.listItem.createAndFill(undefined, schema.nodes.paragraph.createChecked(undefined, restOfChunk));
|
|
128
|
+
if (!listItemNode) {
|
|
129
|
+
paragraphParts.push(line);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const attrs = nodeType === schema.nodes.orderedList ? {
|
|
133
|
+
order: parseInt(firstNonHardBreakNode.text.split('.')[0])
|
|
134
|
+
} : undefined;
|
|
135
|
+
const newList = nodeType.createChecked(attrs, [listItemNode]);
|
|
136
|
+
if (paragraphParts.length !== 0) {
|
|
137
|
+
splitListsAndParagraphs.push(schema.nodes.paragraph.createAndFill(undefined, paragraphParts.flat()));
|
|
138
|
+
paragraphParts = [];
|
|
139
|
+
}
|
|
140
|
+
splitListsAndParagraphs.push(newList);
|
|
141
|
+
}
|
|
142
|
+
if (paragraphParts.length !== 0) {
|
|
143
|
+
splitListsAndParagraphs.push(schema.nodes.paragraph.createAndFill(undefined, paragraphParts.flat()));
|
|
144
|
+
}
|
|
145
|
+
const result = splitListsAndParagraphs.flat();
|
|
146
|
+
// try to join
|
|
147
|
+
const mockState = EditorState.create({
|
|
148
|
+
schema
|
|
149
|
+
});
|
|
150
|
+
let joinedListsTr;
|
|
151
|
+
const mockDispatch = tr => {
|
|
152
|
+
joinedListsTr = tr;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Return false to prevent replaceWith from wrapping the text node in a paragraph
|
|
156
|
+
// paragraph since that will be done later. If it's done here, it will fail
|
|
157
|
+
// the paragraph.validContent check.
|
|
158
|
+
// Dont return false if there are lists, as they arent validContent for paragraphs
|
|
159
|
+
// and will result in hanging textNodes
|
|
160
|
+
autoJoin((state, dispatch) => {
|
|
161
|
+
if (!dispatch) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
const containsList = result.some(node => node.type === schema.nodes.bulletList || node.type === schema.nodes.orderedList);
|
|
165
|
+
if (result.some(node => node.isText) && !containsList) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
dispatch(state.tr.replaceWith(0, 2, result));
|
|
169
|
+
return true;
|
|
170
|
+
}, (before, after) => isListNode(before) && isListNode(after))(mockState, mockDispatch);
|
|
171
|
+
const fragment = joinedListsTr ? joinedListsTr.doc.content : Fragment.from(result);
|
|
172
|
+
return fragment;
|
|
173
|
+
};
|
|
58
174
|
const extractListFromParagraph = (node, parent, schema) => {
|
|
59
175
|
const {
|
|
60
176
|
hardBreak,
|
|
@@ -152,7 +268,11 @@ const extractListFromParagraph = (node, parent, schema) => {
|
|
|
152
268
|
export const upgradeTextToLists = (slice, schema) => {
|
|
153
269
|
return mapSlice(slice, (node, parent) => {
|
|
154
270
|
if (node.type === schema.nodes.paragraph) {
|
|
155
|
-
|
|
271
|
+
if (getBooleanFF('platform.editor.extractlistfromparagraphv2')) {
|
|
272
|
+
return _extractListFromParagraphV2(node, parent, schema);
|
|
273
|
+
} else {
|
|
274
|
+
return extractListFromParagraph(node, parent, schema);
|
|
275
|
+
}
|
|
156
276
|
}
|
|
157
277
|
return node;
|
|
158
278
|
});
|
|
@@ -304,9 +304,16 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
|
|
|
304
304
|
if (handleSelectedTableWithAnalytics(editorAnalyticsAPI)(view, event, slice)(state, dispatch)) {
|
|
305
305
|
return true;
|
|
306
306
|
}
|
|
307
|
+
let isNestedMarkdownTable = false;
|
|
308
|
+
if (getBooleanFF('platform.editor.paste-markdown-table-in-a-table')) {
|
|
309
|
+
// if paste a markdown table inside a table cell, we should treat it as a table slice
|
|
310
|
+
const isParentNodeTdOrTh = selectionParentType === schema.nodes.tableCell || selectionParentType === schema.nodes.tableHeader;
|
|
311
|
+
isNestedMarkdownTable = !!(markdownSlice && isPlainText && isParentNodeTdOrTh && getContentNodeTypes(markdownSlice.content).includes(schema.nodes.table.name));
|
|
312
|
+
slice = isNestedMarkdownTable ? markdownSlice : slice;
|
|
313
|
+
}
|
|
307
314
|
|
|
308
315
|
// If the clipboard only contains plain text, attempt to parse it as Markdown
|
|
309
|
-
if (isPlainText && markdownSlice) {
|
|
316
|
+
if (isPlainText && markdownSlice && !isNestedMarkdownTable) {
|
|
310
317
|
if (handlePastePreservingMarksWithAnalytics(view, event, markdownSlice, PasteTypes.markdown, pluginInjectionApi)(state, dispatch)) {
|
|
311
318
|
return true;
|
|
312
319
|
}
|
|
@@ -333,7 +340,7 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
|
|
|
333
340
|
}
|
|
334
341
|
|
|
335
342
|
// finally, handle rich-text copy-paste
|
|
336
|
-
if (isRichText) {
|
|
343
|
+
if (isRichText || isNestedMarkdownTable) {
|
|
337
344
|
var _pluginInjectionApi$c3, _pluginInjectionApi$c4, _pluginInjectionApi$e3, _pluginInjectionApi$e4, _pluginInjectionApi$l;
|
|
338
345
|
// linkify the text where possible
|
|
339
346
|
slice = linkifyContent(state.schema)(slice);
|
package/dist/esm/commands.js
CHANGED
|
@@ -4,6 +4,7 @@ import { isListNode, mapChildren, mapSlice } from '@atlaskit/editor-common/utils
|
|
|
4
4
|
import { autoJoin } from '@atlaskit/editor-prosemirror/commands';
|
|
5
5
|
import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
|
|
6
6
|
import { EditorState } from '@atlaskit/editor-prosemirror/state';
|
|
7
|
+
import { getBooleanFF } from '@atlaskit/platform-feature-flags';
|
|
7
8
|
import { PastePluginActionTypes as ActionTypes } from './actions';
|
|
8
9
|
import { createCommand } from './pm-plugins/plugin-factory';
|
|
9
10
|
|
|
@@ -60,6 +61,135 @@ var getListType = function getListType(node, schema) {
|
|
|
60
61
|
return match ? [listType.node, match[0].length] : lastMatch;
|
|
61
62
|
}, null);
|
|
62
63
|
};
|
|
64
|
+
|
|
65
|
+
// Splits array of nodes by hardBreak. E.g.:
|
|
66
|
+
// [text "1. ", em "hello", date, hardbreak, text "2. ",
|
|
67
|
+
// subsup "world", hardbreak, text "smile"]
|
|
68
|
+
// => [
|
|
69
|
+
// [ text "1. ", em "hello", date ],
|
|
70
|
+
// [hardbreak, text "2. ", subsup "world"],
|
|
71
|
+
// [hardbreak, text "smile"]
|
|
72
|
+
// ]
|
|
73
|
+
export var _contentSplitByHardBreaks = function _contentSplitByHardBreaks(content, schema) {
|
|
74
|
+
var wrapperContent = [];
|
|
75
|
+
var nextContent = [];
|
|
76
|
+
content.forEach(function (node) {
|
|
77
|
+
if (node.type === schema.nodes.hardBreak) {
|
|
78
|
+
if (nextContent.length !== 0) {
|
|
79
|
+
wrapperContent.push(nextContent);
|
|
80
|
+
}
|
|
81
|
+
nextContent = [node];
|
|
82
|
+
} else {
|
|
83
|
+
nextContent.push(node);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
wrapperContent.push(nextContent);
|
|
87
|
+
return wrapperContent;
|
|
88
|
+
};
|
|
89
|
+
export var _extractListFromParagraphV2 = function _extractListFromParagraphV2(node, parent, schema) {
|
|
90
|
+
var content = mapChildren(node.content, function (node) {
|
|
91
|
+
return node;
|
|
92
|
+
});
|
|
93
|
+
var linesSplitByHardbreaks = _contentSplitByHardBreaks(content, schema);
|
|
94
|
+
var splitListsAndParagraphs = [];
|
|
95
|
+
var paragraphParts = [];
|
|
96
|
+
for (var index = 0; index < linesSplitByHardbreaks.length; index = index + 1) {
|
|
97
|
+
var _firstNonHardBreakNod;
|
|
98
|
+
var line = linesSplitByHardbreaks[index];
|
|
99
|
+
var listMatch = void 0;
|
|
100
|
+
if (index === 0) {
|
|
101
|
+
var _line$;
|
|
102
|
+
if (((_line$ = line[0]) === null || _line$ === void 0 ? void 0 : _line$.type) === schema.nodes.hardbreak) {
|
|
103
|
+
paragraphParts.push(line);
|
|
104
|
+
continue;
|
|
105
|
+
} else {
|
|
106
|
+
// the first line the potential list item is at postion 0
|
|
107
|
+
listMatch = getListType(line[0], schema);
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
// lines after the first the potential list item is at postion 1
|
|
111
|
+
if (line.length === 1) {
|
|
112
|
+
// if the line only has a line break return as is
|
|
113
|
+
paragraphParts.push(line);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
listMatch = getListType(line[1], schema);
|
|
117
|
+
}
|
|
118
|
+
if (!listMatch) {
|
|
119
|
+
// if there is not list match return as is
|
|
120
|
+
paragraphParts.push(line);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
var _listMatch = listMatch,
|
|
124
|
+
_listMatch2 = _slicedToArray(_listMatch, 2),
|
|
125
|
+
nodeType = _listMatch2[0],
|
|
126
|
+
length = _listMatch2[1];
|
|
127
|
+
var firstNonHardBreakNode = line.find(function (node) {
|
|
128
|
+
return node.type !== schema.nodes.hardBreak;
|
|
129
|
+
});
|
|
130
|
+
var chunksWithoutLeadingHardBreaks = line.slice(line.findIndex(function (node) {
|
|
131
|
+
return node.type !== schema.nodes.hardBreak;
|
|
132
|
+
}));
|
|
133
|
+
|
|
134
|
+
// retain text after bullet or number-dot e.g. 1. Hello
|
|
135
|
+
var startingText = firstNonHardBreakNode === null || firstNonHardBreakNode === void 0 || (_firstNonHardBreakNod = firstNonHardBreakNode.text) === null || _firstNonHardBreakNod === void 0 ? void 0 : _firstNonHardBreakNod.substr(length);
|
|
136
|
+
var restOfChunk = startingText ? // apply transformation to first entry
|
|
137
|
+
[schema.text(startingText, firstNonHardBreakNode === null || firstNonHardBreakNode === void 0 ? void 0 : firstNonHardBreakNode.marks)].concat(_toConsumableArray(chunksWithoutLeadingHardBreaks.slice(1))) : chunksWithoutLeadingHardBreaks.slice(1);
|
|
138
|
+
|
|
139
|
+
// convert to list
|
|
140
|
+
var listItemNode = schema.nodes.listItem.createAndFill(undefined, schema.nodes.paragraph.createChecked(undefined, restOfChunk));
|
|
141
|
+
if (!listItemNode) {
|
|
142
|
+
paragraphParts.push(line);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
var attrs = nodeType === schema.nodes.orderedList ? {
|
|
146
|
+
order: parseInt(firstNonHardBreakNode.text.split('.')[0])
|
|
147
|
+
} : undefined;
|
|
148
|
+
var newList = nodeType.createChecked(attrs, [listItemNode]);
|
|
149
|
+
if (paragraphParts.length !== 0) {
|
|
150
|
+
splitListsAndParagraphs.push(schema.nodes.paragraph.createAndFill(undefined, paragraphParts.flat()));
|
|
151
|
+
paragraphParts = [];
|
|
152
|
+
}
|
|
153
|
+
splitListsAndParagraphs.push(newList);
|
|
154
|
+
}
|
|
155
|
+
if (paragraphParts.length !== 0) {
|
|
156
|
+
splitListsAndParagraphs.push(schema.nodes.paragraph.createAndFill(undefined, paragraphParts.flat()));
|
|
157
|
+
}
|
|
158
|
+
var result = splitListsAndParagraphs.flat();
|
|
159
|
+
// try to join
|
|
160
|
+
var mockState = EditorState.create({
|
|
161
|
+
schema: schema
|
|
162
|
+
});
|
|
163
|
+
var joinedListsTr;
|
|
164
|
+
var mockDispatch = function mockDispatch(tr) {
|
|
165
|
+
joinedListsTr = tr;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Return false to prevent replaceWith from wrapping the text node in a paragraph
|
|
169
|
+
// paragraph since that will be done later. If it's done here, it will fail
|
|
170
|
+
// the paragraph.validContent check.
|
|
171
|
+
// Dont return false if there are lists, as they arent validContent for paragraphs
|
|
172
|
+
// and will result in hanging textNodes
|
|
173
|
+
autoJoin(function (state, dispatch) {
|
|
174
|
+
if (!dispatch) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
var containsList = result.some(function (node) {
|
|
178
|
+
return node.type === schema.nodes.bulletList || node.type === schema.nodes.orderedList;
|
|
179
|
+
});
|
|
180
|
+
if (result.some(function (node) {
|
|
181
|
+
return node.isText;
|
|
182
|
+
}) && !containsList) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
dispatch(state.tr.replaceWith(0, 2, result));
|
|
186
|
+
return true;
|
|
187
|
+
}, function (before, after) {
|
|
188
|
+
return isListNode(before) && isListNode(after);
|
|
189
|
+
})(mockState, mockDispatch);
|
|
190
|
+
var fragment = joinedListsTr ? joinedListsTr.doc.content : Fragment.from(result);
|
|
191
|
+
return fragment;
|
|
192
|
+
};
|
|
63
193
|
var extractListFromParagraph = function extractListFromParagraph(node, parent, schema) {
|
|
64
194
|
var _schema$nodes2 = schema.nodes,
|
|
65
195
|
hardBreak = _schema$nodes2.hardBreak,
|
|
@@ -82,9 +212,9 @@ var extractListFromParagraph = function extractListFromParagraph(node, parent, s
|
|
|
82
212
|
if (!listMatch || !child.text) {
|
|
83
213
|
return child;
|
|
84
214
|
}
|
|
85
|
-
var
|
|
86
|
-
nodeType =
|
|
87
|
-
length =
|
|
215
|
+
var _listMatch3 = _slicedToArray(listMatch, 2),
|
|
216
|
+
nodeType = _listMatch3[0],
|
|
217
|
+
length = _listMatch3[1];
|
|
88
218
|
|
|
89
219
|
// convert to list item
|
|
90
220
|
var newText = child.text.substr(length);
|
|
@@ -164,7 +294,11 @@ var extractListFromParagraph = function extractListFromParagraph(node, parent, s
|
|
|
164
294
|
export var upgradeTextToLists = function upgradeTextToLists(slice, schema) {
|
|
165
295
|
return mapSlice(slice, function (node, parent) {
|
|
166
296
|
if (node.type === schema.nodes.paragraph) {
|
|
167
|
-
|
|
297
|
+
if (getBooleanFF('platform.editor.extractlistfromparagraphv2')) {
|
|
298
|
+
return _extractListFromParagraphV2(node, parent, schema);
|
|
299
|
+
} else {
|
|
300
|
+
return extractListFromParagraph(node, parent, schema);
|
|
301
|
+
}
|
|
168
302
|
}
|
|
169
303
|
return node;
|
|
170
304
|
});
|
|
@@ -322,9 +322,16 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
|
|
|
322
322
|
if (handleSelectedTableWithAnalytics(editorAnalyticsAPI)(view, event, slice)(state, dispatch)) {
|
|
323
323
|
return true;
|
|
324
324
|
}
|
|
325
|
+
var isNestedMarkdownTable = false;
|
|
326
|
+
if (getBooleanFF('platform.editor.paste-markdown-table-in-a-table')) {
|
|
327
|
+
// if paste a markdown table inside a table cell, we should treat it as a table slice
|
|
328
|
+
var isParentNodeTdOrTh = selectionParentType === schema.nodes.tableCell || selectionParentType === schema.nodes.tableHeader;
|
|
329
|
+
isNestedMarkdownTable = !!(markdownSlice && isPlainText && isParentNodeTdOrTh && getContentNodeTypes(markdownSlice.content).includes(schema.nodes.table.name));
|
|
330
|
+
slice = isNestedMarkdownTable ? markdownSlice : slice;
|
|
331
|
+
}
|
|
325
332
|
|
|
326
333
|
// If the clipboard only contains plain text, attempt to parse it as Markdown
|
|
327
|
-
if (isPlainText && markdownSlice) {
|
|
334
|
+
if (isPlainText && markdownSlice && !isNestedMarkdownTable) {
|
|
328
335
|
if (handlePastePreservingMarksWithAnalytics(view, event, markdownSlice, PasteTypes.markdown, pluginInjectionApi)(state, dispatch)) {
|
|
329
336
|
return true;
|
|
330
337
|
}
|
|
@@ -349,7 +356,7 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
|
|
|
349
356
|
}
|
|
350
357
|
|
|
351
358
|
// finally, handle rich-text copy-paste
|
|
352
|
-
if (isRichText) {
|
|
359
|
+
if (isRichText || isNestedMarkdownTable) {
|
|
353
360
|
var _pluginInjectionApi$c2, _pluginInjectionApi$e2, _pluginInjectionApi$l;
|
|
354
361
|
// linkify the text where possible
|
|
355
362
|
slice = linkifyContent(state.schema)(slice);
|
package/dist/types/commands.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
|
|
2
|
-
import type { Mark, Schema } from '@atlaskit/editor-prosemirror/model';
|
|
2
|
+
import type { Mark, Node, Schema } from '@atlaskit/editor-prosemirror/model';
|
|
3
3
|
/**
|
|
4
4
|
* Use this to register macro link positions during a paste operation, that you
|
|
5
5
|
* want to track in a document over time, through any document changes.
|
|
@@ -16,6 +16,8 @@ export declare const startTrackingPastedMacroPositions: (pastedMacroPositions: {
|
|
|
16
16
|
[key: string]: number;
|
|
17
17
|
}) => import("@atlaskit/editor-common/types").Command;
|
|
18
18
|
export declare const stopTrackingPastedMacroPositions: (pastedMacroPositionKeys: string[]) => import("@atlaskit/editor-common/types").Command;
|
|
19
|
+
export declare const _contentSplitByHardBreaks: (content: Array<Node>, schema: Schema) => Array<Node>[];
|
|
20
|
+
export declare const _extractListFromParagraphV2: (node: Node, parent: Node | null, schema: Schema) => Fragment;
|
|
19
21
|
export declare const upgradeTextToLists: (slice: Slice, schema: Schema) => Slice;
|
|
20
22
|
export declare const splitParagraphs: (slice: Slice, schema: Schema) => Slice;
|
|
21
23
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
|
|
2
|
-
import type { Mark, Schema } from '@atlaskit/editor-prosemirror/model';
|
|
2
|
+
import type { Mark, Node, Schema } from '@atlaskit/editor-prosemirror/model';
|
|
3
3
|
/**
|
|
4
4
|
* Use this to register macro link positions during a paste operation, that you
|
|
5
5
|
* want to track in a document over time, through any document changes.
|
|
@@ -16,6 +16,8 @@ export declare const startTrackingPastedMacroPositions: (pastedMacroPositions: {
|
|
|
16
16
|
[key: string]: number;
|
|
17
17
|
}) => import("@atlaskit/editor-common/types").Command;
|
|
18
18
|
export declare const stopTrackingPastedMacroPositions: (pastedMacroPositionKeys: string[]) => import("@atlaskit/editor-common/types").Command;
|
|
19
|
+
export declare const _contentSplitByHardBreaks: (content: Array<Node>, schema: Schema) => Array<Node>[];
|
|
20
|
+
export declare const _extractListFromParagraphV2: (node: Node, parent: Node | null, schema: Schema) => Fragment;
|
|
19
21
|
export declare const upgradeTextToLists: (slice: Slice, schema: Schema) => Slice;
|
|
20
22
|
export declare const splitParagraphs: (slice: Slice, schema: Schema) => Slice;
|
|
21
23
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-paste",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"description": "Paste plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -33,18 +33,18 @@
|
|
|
33
33
|
".": "./src/index.ts"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@atlaskit/editor-common": "^78.
|
|
36
|
+
"@atlaskit/editor-common": "^78.26.0",
|
|
37
37
|
"@atlaskit/editor-markdown-transformer": "^5.4.0",
|
|
38
38
|
"@atlaskit/editor-plugin-analytics": "^1.0.0",
|
|
39
39
|
"@atlaskit/editor-plugin-annotation": "^1.5.0",
|
|
40
40
|
"@atlaskit/editor-plugin-better-type-history": "^1.0.0",
|
|
41
|
-
"@atlaskit/editor-plugin-card": "^1.
|
|
41
|
+
"@atlaskit/editor-plugin-card": "^1.6.0",
|
|
42
42
|
"@atlaskit/editor-plugin-feature-flags": "^1.0.0",
|
|
43
43
|
"@atlaskit/editor-plugin-list": "^3.1.0",
|
|
44
|
-
"@atlaskit/editor-plugin-media": "^1.
|
|
44
|
+
"@atlaskit/editor-plugin-media": "^1.14.0",
|
|
45
45
|
"@atlaskit/editor-prosemirror": "3.0.0",
|
|
46
46
|
"@atlaskit/editor-tables": "^2.6.0",
|
|
47
|
-
"@atlaskit/media-client": "^26.
|
|
47
|
+
"@atlaskit/media-client": "^26.3.0",
|
|
48
48
|
"@atlaskit/media-common": "^11.1.0",
|
|
49
49
|
"@atlaskit/platform-feature-flags": "^0.2.0",
|
|
50
50
|
"@babel/runtime": "^7.0.0",
|
|
@@ -135,6 +135,12 @@
|
|
|
135
135
|
},
|
|
136
136
|
"platform.editor.table.copy-paste-in-bodied-extension": {
|
|
137
137
|
"type": "boolean"
|
|
138
|
+
},
|
|
139
|
+
"platform.editor.paste-markdown-table-in-a-table": {
|
|
140
|
+
"type": "boolean"
|
|
141
|
+
},
|
|
142
|
+
"platform.editor.extractlistfromparagraphv2": {
|
|
143
|
+
"type": "boolean"
|
|
138
144
|
}
|
|
139
145
|
}
|
|
140
146
|
}
|