@atlaskit/editor-plugin-paste 0.1.22 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +18 -0
- package/CHANGELOG.md +12 -0
- package/dist/cjs/actions.js +12 -0
- package/dist/cjs/commands.js +255 -0
- package/dist/cjs/edge-cases/index.js +88 -0
- package/dist/cjs/edge-cases/lists.js +107 -0
- package/dist/cjs/handlers.js +939 -0
- package/dist/cjs/index.js +8 -1
- package/dist/cjs/plugin.js +43 -0
- package/dist/cjs/plugins/media.js +207 -0
- package/dist/cjs/pm-plugins/analytics.js +376 -0
- package/dist/cjs/pm-plugins/clipboard-text-serializer.js +43 -0
- package/dist/cjs/pm-plugins/main.js +484 -0
- package/dist/cjs/pm-plugins/plugin-factory.js +42 -0
- package/dist/cjs/reducer.js +41 -0
- package/dist/cjs/util/index.js +214 -0
- package/dist/cjs/util/tinyMCE.js +183 -0
- package/dist/es2019/actions.js +6 -0
- package/dist/es2019/commands.js +236 -0
- package/dist/es2019/edge-cases/index.js +87 -0
- package/dist/es2019/edge-cases/lists.js +113 -0
- package/dist/es2019/handlers.js +919 -0
- package/dist/es2019/index.js +1 -1
- package/dist/es2019/plugin.js +38 -0
- package/dist/es2019/plugins/media.js +204 -0
- package/dist/es2019/pm-plugins/analytics.js +332 -0
- package/dist/es2019/pm-plugins/clipboard-text-serializer.js +37 -0
- package/dist/es2019/pm-plugins/main.js +453 -0
- package/dist/es2019/pm-plugins/plugin-factory.js +30 -0
- package/dist/es2019/reducer.js +32 -0
- package/dist/es2019/util/index.js +209 -0
- package/dist/es2019/util/tinyMCE.js +168 -0
- package/dist/esm/actions.js +6 -0
- package/dist/esm/commands.js +249 -0
- package/dist/esm/edge-cases/index.js +81 -0
- package/dist/esm/edge-cases/lists.js +98 -0
- package/dist/esm/handlers.js +918 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/plugin.js +37 -0
- package/dist/esm/plugins/media.js +199 -0
- package/dist/esm/pm-plugins/analytics.js +364 -0
- package/dist/esm/pm-plugins/clipboard-text-serializer.js +37 -0
- package/dist/esm/pm-plugins/main.js +471 -0
- package/dist/esm/pm-plugins/plugin-factory.js +36 -0
- package/dist/esm/reducer.js +34 -0
- package/dist/esm/util/index.js +194 -0
- package/dist/esm/util/tinyMCE.js +176 -0
- package/dist/types/actions.d.ts +21 -0
- package/dist/types/commands.d.ts +29 -0
- package/dist/types/edge-cases/index.d.ts +11 -0
- package/dist/types/edge-cases/lists.d.ts +18 -0
- package/dist/types/handlers.d.ts +55 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/plugin.d.ts +2 -0
- package/dist/types/plugins/media.d.ts +23 -0
- package/dist/types/pm-plugins/analytics.d.ts +44 -0
- package/dist/types/pm-plugins/clipboard-text-serializer.d.ts +13 -0
- package/dist/types/pm-plugins/main.d.ts +12 -0
- package/dist/types/pm-plugins/plugin-factory.d.ts +3 -0
- package/dist/types/reducer.d.ts +3 -0
- package/dist/types/util/index.d.ts +21 -0
- package/dist/types/util/tinyMCE.d.ts +32 -0
- package/dist/types-ts4.5/actions.d.ts +21 -0
- package/dist/types-ts4.5/commands.d.ts +29 -0
- package/dist/types-ts4.5/edge-cases/index.d.ts +11 -0
- package/dist/types-ts4.5/edge-cases/lists.d.ts +18 -0
- package/dist/types-ts4.5/handlers.d.ts +55 -0
- package/dist/types-ts4.5/index.d.ts +1 -0
- package/dist/types-ts4.5/plugin.d.ts +2 -0
- package/dist/types-ts4.5/plugins/media.d.ts +23 -0
- package/dist/types-ts4.5/pm-plugins/analytics.d.ts +44 -0
- package/dist/types-ts4.5/pm-plugins/clipboard-text-serializer.d.ts +13 -0
- package/dist/types-ts4.5/pm-plugins/main.d.ts +12 -0
- package/dist/types-ts4.5/pm-plugins/plugin-factory.d.ts +3 -0
- package/dist/types-ts4.5/reducer.d.ts +3 -0
- package/dist/types-ts4.5/util/index.d.ts +21 -0
- package/dist/types-ts4.5/util/tinyMCE.d.ts +32 -0
- package/package.json +18 -6
|
@@ -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
|
+
};
|