@atlaskit/editor-plugin-paste 0.1.22 → 0.2.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.
Files changed (78) hide show
  1. package/.eslintrc.js +18 -0
  2. package/CHANGELOG.md +6 -0
  3. package/dist/cjs/actions.js +12 -0
  4. package/dist/cjs/commands.js +255 -0
  5. package/dist/cjs/edge-cases/index.js +88 -0
  6. package/dist/cjs/edge-cases/lists.js +107 -0
  7. package/dist/cjs/handlers.js +939 -0
  8. package/dist/cjs/index.js +8 -1
  9. package/dist/cjs/plugin.js +43 -0
  10. package/dist/cjs/plugins/media.js +207 -0
  11. package/dist/cjs/pm-plugins/analytics.js +376 -0
  12. package/dist/cjs/pm-plugins/clipboard-text-serializer.js +43 -0
  13. package/dist/cjs/pm-plugins/main.js +484 -0
  14. package/dist/cjs/pm-plugins/plugin-factory.js +42 -0
  15. package/dist/cjs/reducer.js +41 -0
  16. package/dist/cjs/util/index.js +214 -0
  17. package/dist/cjs/util/tinyMCE.js +183 -0
  18. package/dist/es2019/actions.js +6 -0
  19. package/dist/es2019/commands.js +236 -0
  20. package/dist/es2019/edge-cases/index.js +87 -0
  21. package/dist/es2019/edge-cases/lists.js +113 -0
  22. package/dist/es2019/handlers.js +919 -0
  23. package/dist/es2019/index.js +1 -1
  24. package/dist/es2019/plugin.js +38 -0
  25. package/dist/es2019/plugins/media.js +204 -0
  26. package/dist/es2019/pm-plugins/analytics.js +332 -0
  27. package/dist/es2019/pm-plugins/clipboard-text-serializer.js +37 -0
  28. package/dist/es2019/pm-plugins/main.js +453 -0
  29. package/dist/es2019/pm-plugins/plugin-factory.js +30 -0
  30. package/dist/es2019/reducer.js +32 -0
  31. package/dist/es2019/util/index.js +209 -0
  32. package/dist/es2019/util/tinyMCE.js +168 -0
  33. package/dist/esm/actions.js +6 -0
  34. package/dist/esm/commands.js +249 -0
  35. package/dist/esm/edge-cases/index.js +81 -0
  36. package/dist/esm/edge-cases/lists.js +98 -0
  37. package/dist/esm/handlers.js +918 -0
  38. package/dist/esm/index.js +1 -1
  39. package/dist/esm/plugin.js +37 -0
  40. package/dist/esm/plugins/media.js +199 -0
  41. package/dist/esm/pm-plugins/analytics.js +364 -0
  42. package/dist/esm/pm-plugins/clipboard-text-serializer.js +37 -0
  43. package/dist/esm/pm-plugins/main.js +471 -0
  44. package/dist/esm/pm-plugins/plugin-factory.js +36 -0
  45. package/dist/esm/reducer.js +34 -0
  46. package/dist/esm/util/index.js +194 -0
  47. package/dist/esm/util/tinyMCE.js +176 -0
  48. package/dist/types/actions.d.ts +21 -0
  49. package/dist/types/commands.d.ts +29 -0
  50. package/dist/types/edge-cases/index.d.ts +11 -0
  51. package/dist/types/edge-cases/lists.d.ts +18 -0
  52. package/dist/types/handlers.d.ts +55 -0
  53. package/dist/types/index.d.ts +1 -0
  54. package/dist/types/plugin.d.ts +2 -0
  55. package/dist/types/plugins/media.d.ts +23 -0
  56. package/dist/types/pm-plugins/analytics.d.ts +44 -0
  57. package/dist/types/pm-plugins/clipboard-text-serializer.d.ts +13 -0
  58. package/dist/types/pm-plugins/main.d.ts +12 -0
  59. package/dist/types/pm-plugins/plugin-factory.d.ts +3 -0
  60. package/dist/types/reducer.d.ts +3 -0
  61. package/dist/types/util/index.d.ts +21 -0
  62. package/dist/types/util/tinyMCE.d.ts +32 -0
  63. package/dist/types-ts4.5/actions.d.ts +21 -0
  64. package/dist/types-ts4.5/commands.d.ts +29 -0
  65. package/dist/types-ts4.5/edge-cases/index.d.ts +11 -0
  66. package/dist/types-ts4.5/edge-cases/lists.d.ts +18 -0
  67. package/dist/types-ts4.5/handlers.d.ts +55 -0
  68. package/dist/types-ts4.5/index.d.ts +1 -0
  69. package/dist/types-ts4.5/plugin.d.ts +2 -0
  70. package/dist/types-ts4.5/plugins/media.d.ts +23 -0
  71. package/dist/types-ts4.5/pm-plugins/analytics.d.ts +44 -0
  72. package/dist/types-ts4.5/pm-plugins/clipboard-text-serializer.d.ts +13 -0
  73. package/dist/types-ts4.5/pm-plugins/main.d.ts +12 -0
  74. package/dist/types-ts4.5/pm-plugins/plugin-factory.d.ts +3 -0
  75. package/dist/types-ts4.5/reducer.d.ts +3 -0
  76. package/dist/types-ts4.5/util/index.d.ts +21 -0
  77. package/dist/types-ts4.5/util/tinyMCE.d.ts +32 -0
  78. package/package.json +17 -5
@@ -0,0 +1,471 @@
1
+ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
+ import _regeneratorRuntime from "@babel/runtime/regenerator";
3
+ import uuid from 'uuid';
4
+ import { ACTION, INPUT_METHOD, PasteTypes } from '@atlaskit/editor-common/analytics';
5
+ import { addLinkMetadata } from '@atlaskit/editor-common/card';
6
+ import { insideTable } from '@atlaskit/editor-common/core-utils';
7
+ import { getExtensionAutoConvertersFromProvider } from '@atlaskit/editor-common/extensions';
8
+ import { isPastedFile as isPastedFileFromEvent, md } from '@atlaskit/editor-common/paste';
9
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
10
+ import { transformSingleLineCodeBlockToCodeMark, transformSliceNestedExpandToExpand, transformSliceToDecisionList, transformSliceToJoinAdjacentCodeBlocks } from '@atlaskit/editor-common/transforms';
11
+ import { containsAnyAnnotations, extractSliceFromStep, linkifyContent, mapChildren, measureRender } from '@atlaskit/editor-common/utils';
12
+ import { MarkdownTransformer } from '@atlaskit/editor-markdown-transformer';
13
+ import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
14
+ import { hasParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
15
+ import { handlePaste as handlePasteTable } from '@atlaskit/editor-tables/utils';
16
+ import { PastePluginActionTypes } from '../actions';
17
+ import { splitParagraphs, upgradeTextToLists } from '../commands';
18
+ import { handleMacroAutoConvert, handleMention, handleParagraphBlockMarks } from '../handlers';
19
+ import { transformSliceForMedia, transformSliceToCorrectMediaWrapper, transformSliceToMediaSingleWithNewExperience, unwrapNestedMediaElements } from '../plugins/media';
20
+ import { escapeLinks, getPasteSource, htmlContainsSingleFile, htmlHasInvalidLinkTags, isPastedFromExcel, isPastedFromWord, removeDuplicateInvalidLinks, transformUnsupportedBlockCardToInline } from '../util';
21
+ import { htmlHasIncompleteTable, isPastedFromTinyMCEConfluence, tryRebuildCompleteTableHtml } from '../util/tinyMCE';
22
+ import { createPasteMeasurePayload, getContentNodeTypes, handleCodeBlockWithAnalytics, handleExpandWithAnalytics, handleMarkdownWithAnalytics, handleMediaSingleWithAnalytics, handlePasteAsPlainTextWithAnalytics, handlePasteIntoCaptionWithAnalytics, handlePasteIntoTaskAndDecisionWithAnalytics, handlePasteLinkOnSelectedTextWithAnalytics, handlePasteNonNestableBlockNodesIntoListWithAnalytics, handlePastePanelOrDecisionIntoListWithAnalytics, handlePastePreservingMarksWithAnalytics, handleRichTextWithAnalytics, handleSelectedTableWithAnalytics, sendPasteAnalyticsEvent } from './analytics';
23
+ import { clipboardTextSerializer } from './clipboard-text-serializer';
24
+ import { createPluginState, pluginKey as stateKey } from './plugin-factory';
25
+ export { pluginKey as stateKey } from './plugin-factory';
26
+ export var isInsideBlockQuote = function isInsideBlockQuote(state) {
27
+ var blockquote = state.schema.nodes.blockquote;
28
+ return hasParentNodeOfType(blockquote)(state.selection);
29
+ };
30
+ var PASTE = 'Editor Paste Plugin Paste Duration';
31
+ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pluginInjectionApi, cardOptions, sanitizePrivateContent, providerFactory) {
32
+ var _pluginInjectionApi$a;
33
+ var editorAnalyticsAPI = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$a = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a === void 0 ? void 0 : _pluginInjectionApi$a.actions;
34
+ var atlassianMarkDownParser = new MarkdownTransformer(schema, md);
35
+ function getMarkdownSlice(text, openStart, openEnd) {
36
+ var textInput = escapeBackslashExceptCodeblock(text);
37
+ var doc = atlassianMarkDownParser.parse(escapeLinks(textInput));
38
+ if (doc && doc.content) {
39
+ return new Slice(doc.content, openStart, openEnd);
40
+ }
41
+ return;
42
+ }
43
+ function escapeBackslashExceptCodeblock(textInput) {
44
+ var codeToken = '```';
45
+ if (!textInput.includes(codeToken)) {
46
+ return textInput.replace(/\\/g, '\\\\');
47
+ }
48
+ var isInsideCodeblock = false;
49
+ var textSplitByNewLine = textInput.split('\n');
50
+ // In the splitted array, we traverse through every line and check if it will be parsed as a codeblock.
51
+ textSplitByNewLine = textSplitByNewLine.map(function (text) {
52
+ if (text === codeToken) {
53
+ isInsideCodeblock = !isInsideCodeblock;
54
+ } else if (text.startsWith(codeToken) && isInsideCodeblock === false) {
55
+ // if there is some text after the ``` mark , it gets counted as language attribute only at the start of codeblock
56
+ isInsideCodeblock = true;
57
+ }
58
+ if (!isInsideCodeblock) {
59
+ // only escape text which is not inside a codeblock
60
+ text = text.replace(/\\/g, '\\\\');
61
+ }
62
+ return text;
63
+ });
64
+ textInput = textSplitByNewLine.join('\n');
65
+ return textInput;
66
+ }
67
+ var extensionAutoConverter;
68
+ function setExtensionAutoConverter(_x, _x2) {
69
+ return _setExtensionAutoConverter.apply(this, arguments);
70
+ }
71
+ function _setExtensionAutoConverter() {
72
+ _setExtensionAutoConverter = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(name, extensionProviderPromise) {
73
+ return _regeneratorRuntime.wrap(function _callee$(_context) {
74
+ while (1) switch (_context.prev = _context.next) {
75
+ case 0:
76
+ if (!(name !== 'extensionProvider' || !extensionProviderPromise)) {
77
+ _context.next = 2;
78
+ break;
79
+ }
80
+ return _context.abrupt("return");
81
+ case 2:
82
+ _context.prev = 2;
83
+ _context.next = 5;
84
+ return getExtensionAutoConvertersFromProvider(extensionProviderPromise);
85
+ case 5:
86
+ extensionAutoConverter = _context.sent;
87
+ _context.next = 11;
88
+ break;
89
+ case 8:
90
+ _context.prev = 8;
91
+ _context.t0 = _context["catch"](2);
92
+ // eslint-disable-next-line no-console
93
+ console.error(_context.t0);
94
+ case 11:
95
+ case "end":
96
+ return _context.stop();
97
+ }
98
+ }, _callee, null, [[2, 8]]);
99
+ }));
100
+ return _setExtensionAutoConverter.apply(this, arguments);
101
+ }
102
+ if (providerFactory) {
103
+ providerFactory.subscribe('extensionProvider', setExtensionAutoConverter);
104
+ }
105
+ var mostRecentPasteEvent;
106
+ var pastedFromBitBucket = false;
107
+ return new SafePlugin({
108
+ key: stateKey,
109
+ state: createPluginState(dispatch, {
110
+ pastedMacroPositions: {},
111
+ lastContentPasted: null
112
+ }),
113
+ props: {
114
+ // For serialising to plain text
115
+ clipboardTextSerializer: clipboardTextSerializer,
116
+ handleDOMEvents: {
117
+ paste: function paste(view, event) {
118
+ mostRecentPasteEvent = event;
119
+ return false;
120
+ }
121
+ },
122
+ handlePaste: function handlePaste(view, rawEvent, slice) {
123
+ var _text, _pluginInjectionApi$a2, _analyticsPlugin$perf, _schema$nodes, _schema$nodes2, _schema$nodes3, _pluginInjectionApi$m;
124
+ var event = rawEvent;
125
+ if (!event.clipboardData) {
126
+ return false;
127
+ }
128
+ var text = event.clipboardData.getData('text/plain');
129
+ var html = event.clipboardData.getData('text/html');
130
+ var uriList = event.clipboardData.getData('text/uri-list');
131
+ // Links copied from iOS Safari share button only have the text/uri-list data type
132
+ // ProseMirror don't do anything with this type so we want to make our own open slice
133
+ // with url as text content so link is pasted inline
134
+ if (uriList && !text && !html) {
135
+ text = uriList;
136
+ slice = new Slice(Fragment.from(schema.text(text)), 1, 1);
137
+ }
138
+ if ((_text = text) !== null && _text !== void 0 && _text.includes('\r')) {
139
+ text = text.replace(/\r/g, '');
140
+ }
141
+ var isPastedFile = isPastedFileFromEvent(event);
142
+ var isPlainText = text && !html;
143
+ var isRichText = !!html;
144
+
145
+ // Bail if copied content has files
146
+ if (isPastedFile) {
147
+ if (!html) {
148
+ /**
149
+ * Microsoft Office, Number, Pages, etc. adds an image to clipboard
150
+ * with other mime-types so we don't let the event reach media.
151
+ * The detection ration here is that if the payload has both `html` and
152
+ * `files`, then it could be one of above or an image copied from web.
153
+ * Here, we don't have html, so we return true to allow default event behaviour
154
+ */
155
+ return true;
156
+ }
157
+
158
+ /**
159
+ * We want to return false for external copied image to allow
160
+ * it to be uploaded by the client.
161
+ */
162
+ if (htmlContainsSingleFile(html)) {
163
+ return true;
164
+ }
165
+ event.stopImmediatePropagation();
166
+ }
167
+ var state = view.state;
168
+ var analyticsPlugin = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$a2 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a2 === void 0 || (_pluginInjectionApi$a2 = _pluginInjectionApi$a2.sharedState) === null || _pluginInjectionApi$a2 === void 0 ? void 0 : _pluginInjectionApi$a2.currentState();
169
+ var pasteTrackingEnabled = analyticsPlugin === null || analyticsPlugin === void 0 || (_analyticsPlugin$perf = analyticsPlugin.performanceTracking) === null || _analyticsPlugin$perf === void 0 || (_analyticsPlugin$perf = _analyticsPlugin$perf.pasteTracking) === null || _analyticsPlugin$perf === void 0 ? void 0 : _analyticsPlugin$perf.enabled;
170
+ if (pasteTrackingEnabled) {
171
+ var content = getContentNodeTypes(slice.content);
172
+ var pasteId = uuid();
173
+ var measureName = "".concat(PASTE, "_").concat(pasteId);
174
+ measureRender(measureName, function (_ref) {
175
+ var duration = _ref.duration,
176
+ distortedDuration = _ref.distortedDuration;
177
+ var payload = createPasteMeasurePayload({
178
+ view: view,
179
+ duration: duration,
180
+ content: content,
181
+ distortedDuration: distortedDuration
182
+ });
183
+ if (payload) {
184
+ dispatchAnalyticsEvent(payload);
185
+ }
186
+ });
187
+ }
188
+ // creating a custom dispatch because we want to add a meta whenever we do a paste.
189
+ var dispatch = function dispatch(tr) {
190
+ var _state$doc$resolve$no, _input;
191
+ // https://product-fabric.atlassian.net/browse/ED-12633
192
+ // don't add closeHistory call if we're pasting a text inside placeholder text as we want the whole action
193
+ // to be atomic
194
+ var placeholder = state.schema.nodes.placeholder;
195
+ var isPastingTextInsidePlaceholderText = ((_state$doc$resolve$no = state.doc.resolve(state.selection.$anchor.pos).nodeAfter) === null || _state$doc$resolve$no === void 0 ? void 0 : _state$doc$resolve$no.type) === placeholder;
196
+
197
+ // Don't add closeHistory if we're pasting over layout columns, as we will appendTransaction
198
+ // to cleanup the layout's structure and we want to keep the paste and re-structuring as
199
+ // one event.
200
+ var isPastingOverLayoutColumns = hasParentNodeOfType(state.schema.nodes.layoutColumn)(state.selection);
201
+
202
+ // don't add closeHistory call if we're pasting a table, as some tables may involve additional
203
+ // appendedTransactions to repair them (if they're partial or incomplete) and we don't want
204
+ // to split those repairing transactions in prosemirror-history when they're being added to the
205
+ // "done" stack
206
+ var isPastingTable = tr.steps.some(function (step) {
207
+ var _slice$content;
208
+ var slice = extractSliceFromStep(step);
209
+ var tableExists = false;
210
+ slice === null || slice === void 0 || (_slice$content = slice.content) === null || _slice$content === void 0 || _slice$content.forEach(function (node) {
211
+ if (node.type === state.schema.nodes.table) {
212
+ tableExists = true;
213
+ }
214
+ });
215
+ return tableExists;
216
+ });
217
+ if (!isPastingTextInsidePlaceholderText && !isPastingTable && !isPastingOverLayoutColumns && pluginInjectionApi !== null && pluginInjectionApi !== void 0 && pluginInjectionApi.betterTypeHistory) {
218
+ var _pluginInjectionApi$b;
219
+ tr = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$b = pluginInjectionApi.betterTypeHistory) === null || _pluginInjectionApi$b === void 0 ? void 0 : _pluginInjectionApi$b.actions.flagPasteEvent(tr);
220
+ }
221
+ addLinkMetadata(view.state.selection, tr, {
222
+ action: isPlainText ? ACTION.PASTED_AS_PLAIN : ACTION.PASTED,
223
+ inputMethod: INPUT_METHOD.CLIPBOARD
224
+ });
225
+ var pasteStartPos = Math.min(state.selection.anchor, state.selection.head);
226
+ var pasteEndPos = tr.selection.to;
227
+ var contentPasted = {
228
+ pasteStartPos: pasteStartPos,
229
+ pasteEndPos: pasteEndPos,
230
+ text: text,
231
+ isShiftPressed: Boolean(
232
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
233
+ view.shiftKey || ((_input = view.input) === null || _input === void 0 ? void 0 : _input.shiftKey)),
234
+ isPlainText: Boolean(isPlainText),
235
+ pastedSlice: tr.doc.slice(pasteStartPos, pasteEndPos),
236
+ pastedAt: Date.now(),
237
+ pasteSource: getPasteSource(event)
238
+ };
239
+ tr.setMeta(stateKey, {
240
+ type: PastePluginActionTypes.ON_PASTE,
241
+ contentPasted: contentPasted
242
+ });
243
+ view.dispatch(tr);
244
+ };
245
+ slice = handleParagraphBlockMarks(state, slice);
246
+ var plainTextPasteSlice = linkifyContent(state.schema)(slice);
247
+ if (handlePasteAsPlainTextWithAnalytics(editorAnalyticsAPI)(view, event, plainTextPasteSlice)(state, dispatch, view)) {
248
+ return true;
249
+ }
250
+
251
+ // transform slices based on destination
252
+ slice = transformSliceForMedia(slice, schema)(state.selection);
253
+ var markdownSlice;
254
+ if (isPlainText) {
255
+ var _markdownSlice;
256
+ markdownSlice = getMarkdownSlice(text, slice.openStart, slice.openEnd);
257
+
258
+ // https://product-fabric.atlassian.net/browse/ED-15134
259
+ // Lists are not allowed within Blockquotes at this time. Attempting to
260
+ // paste a markdown list ie. ">- foo" will yeild a markdownSlice of size 0.
261
+ // Rather then blocking the paste action with no UI feedback, this will instead
262
+ // force a "paste as plain text" action by clearing the markdownSlice.
263
+ markdownSlice = !((_markdownSlice = markdownSlice) !== null && _markdownSlice !== void 0 && _markdownSlice.size) ? undefined : markdownSlice;
264
+ if (markdownSlice) {
265
+ var _pluginInjectionApi$c, _pluginInjectionApi$e;
266
+ // linkify text prior to converting to macro
267
+ if (handlePasteLinkOnSelectedTextWithAnalytics(editorAnalyticsAPI)(view, event, markdownSlice, PasteTypes.markdown)(state, dispatch)) {
268
+ return true;
269
+ }
270
+
271
+ // run macro autoconvert prior to other conversions
272
+ if (handleMacroAutoConvert(text, markdownSlice, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c = pluginInjectionApi.card) === null || _pluginInjectionApi$c === void 0 || (_pluginInjectionApi$c = _pluginInjectionApi$c.actions) === null || _pluginInjectionApi$c === void 0 ? void 0 : _pluginInjectionApi$c.queueCardsFromChangedTr, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$e = pluginInjectionApi.extension) === null || _pluginInjectionApi$e === void 0 || (_pluginInjectionApi$e = _pluginInjectionApi$e.actions) === null || _pluginInjectionApi$e === void 0 ? void 0 : _pluginInjectionApi$e.runMacroAutoConvert, cardOptions, extensionAutoConverter)(state, dispatch, view)) {
273
+ // TODO: handleMacroAutoConvert dispatch twice, so we can't use the helper
274
+ sendPasteAnalyticsEvent(editorAnalyticsAPI)(view, event, markdownSlice, {
275
+ type: PasteTypes.markdown
276
+ });
277
+ return true;
278
+ }
279
+ }
280
+ }
281
+ slice = transformUnsupportedBlockCardToInline(slice, state, cardOptions);
282
+
283
+ // Handles edge case so that when copying text from the top level of the document
284
+ // it can be pasted into nodes like panels/actions/decisions without removing them.
285
+ // Overriding openStart to be 1 when only pasting a paragraph makes the preferred
286
+ // depth favour the text, rather than the paragraph node.
287
+ // https://github.com/ProseMirror/prosemirror-transform/blob/master/src/replace.js#:~:text=Transform.prototype.-,replaceRange,-%3D%20function(from%2C%20to
288
+ var selectionDepth = state.selection.$head.depth;
289
+ var selectionParentNode = state.selection.$head.node(selectionDepth - 1);
290
+ var selectionParentType = selectionParentNode === null || selectionParentNode === void 0 ? void 0 : selectionParentNode.type;
291
+ var edgeCaseNodeTypes = [(_schema$nodes = schema.nodes) === null || _schema$nodes === void 0 ? void 0 : _schema$nodes.panel, (_schema$nodes2 = schema.nodes) === null || _schema$nodes2 === void 0 ? void 0 : _schema$nodes2.taskList, (_schema$nodes3 = schema.nodes) === null || _schema$nodes3 === void 0 ? void 0 : _schema$nodes3.decisionList];
292
+ if (slice.openStart === 0 && slice.openEnd !== 1 && selectionParentNode && edgeCaseNodeTypes.includes(selectionParentType)) {
293
+ // @ts-ignore - [unblock prosemirror bump] assigning to readonly prop
294
+ slice.openStart = 1;
295
+ }
296
+ if (handlePasteIntoTaskAndDecisionWithAnalytics(view, event, slice, isPlainText ? PasteTypes.plain : PasteTypes.richText, pluginInjectionApi)(state, dispatch)) {
297
+ return true;
298
+ }
299
+
300
+ // If we're in a code block, append the text contents of clipboard inside it
301
+ if (handleCodeBlockWithAnalytics(editorAnalyticsAPI)(view, event, slice, text)(state, dispatch)) {
302
+ return true;
303
+ }
304
+ if (handleMediaSingleWithAnalytics(editorAnalyticsAPI)(view, event, slice, isPastedFile ? PasteTypes.binary : PasteTypes.richText, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$m = pluginInjectionApi.media) === null || _pluginInjectionApi$m === void 0 ? void 0 : _pluginInjectionApi$m.actions.insertMediaAsMediaSingle)(state, dispatch, view)) {
305
+ return true;
306
+ }
307
+ if (handleSelectedTableWithAnalytics(editorAnalyticsAPI)(view, event, slice)(state, dispatch)) {
308
+ return true;
309
+ }
310
+
311
+ // If the clipboard only contains plain text, attempt to parse it as Markdown
312
+ if (isPlainText && markdownSlice) {
313
+ if (handlePastePreservingMarksWithAnalytics(view, event, markdownSlice, PasteTypes.markdown, pluginInjectionApi)(state, dispatch)) {
314
+ return true;
315
+ }
316
+ return handleMarkdownWithAnalytics(view, event, markdownSlice, pluginInjectionApi)(state, dispatch);
317
+ }
318
+ if (isRichText && isInsideBlockQuote(state)) {
319
+ //If pasting inside blockquote
320
+ //Skip the blockquote node and keep remaining nodes as they are
321
+ var blockquote = schema.nodes.blockquote;
322
+ var children = [];
323
+ mapChildren(slice.content, function (node) {
324
+ if (node.type === blockquote) {
325
+ for (var i = 0; i < node.childCount; i++) {
326
+ children.push(node.child(i));
327
+ }
328
+ } else {
329
+ children.push(node);
330
+ }
331
+ });
332
+ slice = new Slice(Fragment.fromArray(children), slice.openStart, slice.openEnd);
333
+ }
334
+
335
+ // finally, handle rich-text copy-paste
336
+ if (isRichText) {
337
+ var _pluginInjectionApi$c2, _pluginInjectionApi$e2, _pluginInjectionApi$l;
338
+ // linkify the text where possible
339
+ slice = linkifyContent(state.schema)(slice);
340
+ if (handlePasteLinkOnSelectedTextWithAnalytics(editorAnalyticsAPI)(view, event, slice, PasteTypes.richText)(state, dispatch)) {
341
+ return true;
342
+ }
343
+
344
+ // run macro autoconvert prior to other conversions
345
+ if (handleMacroAutoConvert(text, slice, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c2 = pluginInjectionApi.card) === null || _pluginInjectionApi$c2 === void 0 || (_pluginInjectionApi$c2 = _pluginInjectionApi$c2.actions) === null || _pluginInjectionApi$c2 === void 0 ? void 0 : _pluginInjectionApi$c2.queueCardsFromChangedTr, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$e2 = pluginInjectionApi.extension) === null || _pluginInjectionApi$e2 === void 0 || (_pluginInjectionApi$e2 = _pluginInjectionApi$e2.actions) === null || _pluginInjectionApi$e2 === void 0 ? void 0 : _pluginInjectionApi$e2.runMacroAutoConvert, cardOptions, extensionAutoConverter)(state, dispatch, view)) {
346
+ // TODO: handleMacroAutoConvert dispatch twice, so we can't use the helper
347
+ sendPasteAnalyticsEvent(editorAnalyticsAPI)(view, event, slice, {
348
+ type: PasteTypes.richText
349
+ });
350
+ return true;
351
+ }
352
+
353
+ // get editor-tables to handle pasting tables if it can
354
+ // otherwise, just the replace the selection with the content
355
+ if (handlePasteTable(view, null, slice)) {
356
+ sendPasteAnalyticsEvent(editorAnalyticsAPI)(view, event, slice, {
357
+ type: PasteTypes.richText
358
+ });
359
+ return true;
360
+ }
361
+
362
+ // remove annotation marks from the pasted data if they are not present in the document
363
+ // for the cases when they are pasted from external pages
364
+ if (slice.content.size && containsAnyAnnotations(slice, state)) {
365
+ var _pluginInjectionApi$a3;
366
+ pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$a3 = pluginInjectionApi.annotation) === null || _pluginInjectionApi$a3 === void 0 || _pluginInjectionApi$a3.actions.stripNonExistingAnnotations(slice, state);
367
+ }
368
+
369
+ // ED-4732
370
+ if (handlePastePreservingMarksWithAnalytics(view, event, slice, PasteTypes.richText, pluginInjectionApi)(state, dispatch)) {
371
+ return true;
372
+ }
373
+
374
+ // Check that we are pasting in a location that does not accept
375
+ // breakout marks, if so we strip the mark and paste. Note that
376
+ // breakout marks are only valid in the root document.
377
+ if (selectionParentType !== state.schema.nodes.doc) {
378
+ var sliceCopy = Slice.fromJSON(state.schema, slice.toJSON() || {});
379
+ sliceCopy.content.descendants(function (node) {
380
+ // @ts-ignore - [unblock prosemirror bump] assigning to readonly prop
381
+ node.marks = node.marks.filter(function (mark) {
382
+ return mark.type.name !== 'breakout';
383
+ });
384
+ // as breakout marks should only be on top level nodes,
385
+ // we don't traverse the entire document
386
+ return false;
387
+ });
388
+ slice = sliceCopy;
389
+ }
390
+ if (handleExpandWithAnalytics(editorAnalyticsAPI)(view, event, slice)(state, dispatch)) {
391
+ return true;
392
+ }
393
+ if (!insideTable(state)) {
394
+ slice = transformSliceNestedExpandToExpand(slice, state.schema);
395
+ }
396
+
397
+ // Create a custom handler to avoid handling with handleRichText method
398
+ // As SafeInsert is used inside handleRichText which caused some bad UX like this:
399
+ // https://product-fabric.atlassian.net/browse/MEX-1520
400
+ if (handlePasteIntoCaptionWithAnalytics(editorAnalyticsAPI)(view, event, slice, PasteTypes.richText)(state, dispatch)) {
401
+ return true;
402
+ }
403
+ if (handlePastePanelOrDecisionIntoListWithAnalytics(editorAnalyticsAPI)(view, event, slice, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$l = pluginInjectionApi.list) === null || _pluginInjectionApi$l === void 0 ? void 0 : _pluginInjectionApi$l.actions.findRootParentListNode)(state, dispatch)) {
404
+ return true;
405
+ }
406
+ if (handlePasteNonNestableBlockNodesIntoListWithAnalytics(editorAnalyticsAPI)(view, event, slice)(state, dispatch)) {
407
+ return true;
408
+ }
409
+ return handleRichTextWithAnalytics(view, event, slice, pluginInjectionApi)(state, dispatch);
410
+ }
411
+ return false;
412
+ },
413
+ transformPasted: function transformPasted(slice) {
414
+ if (sanitizePrivateContent) {
415
+ slice = handleMention(slice, schema);
416
+ }
417
+
418
+ /* Bitbucket copies diffs as multiple adjacent code blocks
419
+ * so we merge ALL adjacent code blocks to support paste here */
420
+ if (pastedFromBitBucket) {
421
+ slice = transformSliceToJoinAdjacentCodeBlocks(slice);
422
+ }
423
+ slice = transformSingleLineCodeBlockToCodeMark(slice, schema);
424
+ slice = transformSliceToCorrectMediaWrapper(slice, schema);
425
+ slice = transformSliceToMediaSingleWithNewExperience(slice, schema);
426
+ slice = transformSliceToDecisionList(slice, schema);
427
+
428
+ // splitting linebreaks into paragraphs must happen before upgrading text to lists
429
+ slice = splitParagraphs(slice, schema);
430
+ slice = upgradeTextToLists(slice, schema);
431
+ if (slice.content.childCount && slice.content.lastChild.type === schema.nodes.codeBlock) {
432
+ slice = new Slice(slice.content, 0, 0);
433
+ }
434
+ return slice;
435
+ },
436
+ transformPastedHTML: function transformPastedHTML(html) {
437
+ // Fix for issue ED-4438
438
+ // text from google docs should not be pasted as inline code
439
+ if (html.indexOf('id="docs-internal-guid-') >= 0) {
440
+ html = html.replace(/white-space:pre/g, '');
441
+ html = html.replace(/white-space:pre-wrap/g, '');
442
+ }
443
+
444
+ // Partial fix for ED-7331: During a copy/paste from the legacy tinyMCE
445
+ // confluence editor, if we encounter an incomplete table (e.g. table elements
446
+ // not wrapped in <table>), we try to rebuild a complete, valid table if possible.
447
+ if (mostRecentPasteEvent && isPastedFromTinyMCEConfluence(mostRecentPasteEvent, html) && htmlHasIncompleteTable(html)) {
448
+ var completeTableHtml = tryRebuildCompleteTableHtml(html);
449
+ if (completeTableHtml) {
450
+ html = completeTableHtml;
451
+ }
452
+ }
453
+ if (!isPastedFromWord(html) && !isPastedFromExcel(html) && html.indexOf('<img ') >= 0) {
454
+ html = unwrapNestedMediaElements(html);
455
+ }
456
+
457
+ // https://product-fabric.atlassian.net/browse/ED-11714
458
+ // Checking for edge case when copying a list item containing links from Notion
459
+ // The html from this case is invalid with duplicate nested links
460
+ if (htmlHasInvalidLinkTags(html)) {
461
+ html = removeDuplicateInvalidLinks(html);
462
+ }
463
+
464
+ // Fix for ED-13568: Code blocks being copied/pasted when next to each other get merged
465
+ pastedFromBitBucket = html.indexOf('data-qa="code-line"') >= 0;
466
+ mostRecentPasteEvent = null;
467
+ return html;
468
+ }
469
+ }
470
+ });
471
+ }
@@ -0,0 +1,36 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
4
+ 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; }
5
+ import { pluginFactory } from '@atlaskit/editor-common/utils';
6
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
7
+ import { reducer } from '../reducer';
8
+ export var pluginKey = new PluginKey('pastePlugin');
9
+ var _pluginFactory = pluginFactory(pluginKey, reducer, {
10
+ mapping: function mapping(tr, pluginState) {
11
+ if (tr.docChanged) {
12
+ var atLeastOnePositionChanged = false;
13
+ var positionsMappedThroughChanges = Object.entries(pluginState.pastedMacroPositions).reduce(function (acc, _ref) {
14
+ var _ref2 = _slicedToArray(_ref, 2),
15
+ key = _ref2[0],
16
+ position = _ref2[1];
17
+ var mappedPosition = tr.mapping.map(position);
18
+ if (position !== mappedPosition) {
19
+ atLeastOnePositionChanged = true;
20
+ }
21
+ acc[key] = tr.mapping.map(position);
22
+ return acc;
23
+ }, {});
24
+ if (atLeastOnePositionChanged) {
25
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
26
+ pastedMacroPositions: positionsMappedThroughChanges
27
+ });
28
+ }
29
+ }
30
+ return pluginState;
31
+ }
32
+ }),
33
+ createPluginState = _pluginFactory.createPluginState,
34
+ createCommand = _pluginFactory.createCommand,
35
+ getPluginState = _pluginFactory.getPluginState;
36
+ export { createPluginState, createCommand, getPluginState };
@@ -0,0 +1,34 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
4
+ 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; }
5
+ import { PastePluginActionTypes as ActionTypes } from './actions';
6
+ export var reducer = function reducer(state, action) {
7
+ switch (action.type) {
8
+ case ActionTypes.START_TRACKING_PASTED_MACRO_POSITIONS:
9
+ {
10
+ return _objectSpread(_objectSpread({}, state), {}, {
11
+ pastedMacroPositions: _objectSpread(_objectSpread({}, state.pastedMacroPositions), action.pastedMacroPositions)
12
+ });
13
+ }
14
+ case ActionTypes.STOP_TRACKING_PASTED_MACRO_POSITIONS:
15
+ {
16
+ var filteredMacroPositions = Object.fromEntries(Object.entries(state.pastedMacroPositions).filter(function (_ref) {
17
+ var _ref2 = _slicedToArray(_ref, 1),
18
+ key = _ref2[0];
19
+ return !action.pastedMacroPositionKeys.includes(key);
20
+ }));
21
+ return _objectSpread(_objectSpread({}, state), {}, {
22
+ pastedMacroPositions: filteredMacroPositions
23
+ });
24
+ }
25
+ case ActionTypes.ON_PASTE:
26
+ {
27
+ return _objectSpread(_objectSpread({}, state), {}, {
28
+ lastContentPasted: action.contentPasted
29
+ });
30
+ }
31
+ default:
32
+ return state;
33
+ }
34
+ };