@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.
Files changed (78) hide show
  1. package/.eslintrc.js +18 -0
  2. package/CHANGELOG.md +12 -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 +18 -6
@@ -1 +1 @@
1
- export {};
1
+ export { pastePlugin } from './plugin';
@@ -0,0 +1,38 @@
1
+ import { createPlugin } from './pm-plugins/main';
2
+ import { pluginKey } from './pm-plugins/plugin-factory';
3
+ export const pastePlugin = ({
4
+ config,
5
+ api
6
+ }) => {
7
+ var _api$featureFlags;
8
+ const {
9
+ cardOptions,
10
+ sanitizePrivateContent
11
+ } = config !== null && config !== void 0 ? config : {};
12
+ const featureFlags = (api === null || api === void 0 ? void 0 : (_api$featureFlags = api.featureFlags) === null || _api$featureFlags === void 0 ? void 0 : _api$featureFlags.sharedState.currentState()) || {};
13
+ return {
14
+ name: 'paste',
15
+ pmPlugins() {
16
+ return [{
17
+ name: 'paste',
18
+ plugin: ({
19
+ schema,
20
+ providerFactory,
21
+ dispatchAnalyticsEvent,
22
+ dispatch
23
+ }) => createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, api, cardOptions, sanitizePrivateContent, providerFactory)
24
+ }];
25
+ },
26
+ getSharedState: editorState => {
27
+ if (!editorState) {
28
+ return {
29
+ lastContentPasted: null
30
+ };
31
+ }
32
+ const pluginState = pluginKey.getState(editorState);
33
+ return {
34
+ lastContentPasted: pluginState.lastContentPasted
35
+ };
36
+ }
37
+ };
38
+ };
@@ -0,0 +1,204 @@
1
+ import { DEFAULT_IMAGE_WIDTH } from '@atlaskit/editor-common/media-single';
2
+ import { mapSlice, removeNestedEmptyEls, unwrap, walkUpTreeUntil } from '@atlaskit/editor-common/utils';
3
+ import { hasParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
4
+ import { getRandomHex } from '@atlaskit/media-common';
5
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
6
+
7
+ /**
8
+ * Ensure correct layout in nested mode
9
+ *
10
+ * TODO: this func is only used in handlePaste, so layout update won't work for drop event
11
+ */
12
+ export function transformSliceForMedia(slice, schema) {
13
+ const {
14
+ mediaSingle,
15
+ layoutSection,
16
+ table,
17
+ bulletList,
18
+ orderedList,
19
+ expand,
20
+ nestedExpand
21
+ } = schema.nodes;
22
+ return selection => {
23
+ let newSlice = slice;
24
+ if (hasParentNodeOfType([layoutSection, table, bulletList, orderedList, expand, nestedExpand])(selection)) {
25
+ newSlice = mapSlice(newSlice, node => {
26
+ const extendedOrLegacyAttrs = getBooleanFF('platform.editor.media.extended-resize-experience') ? {
27
+ layout: node.attrs.layout,
28
+ widthType: node.attrs.widthType,
29
+ width: node.attrs.width
30
+ } : {
31
+ layout: node.attrs.layout
32
+ };
33
+ let attrs = {};
34
+ if (hasParentNodeOfType([layoutSection, table])(selection)) {
35
+ // Supports layouts
36
+ attrs = {
37
+ ...extendedOrLegacyAttrs
38
+ };
39
+ } else if (hasParentNodeOfType([bulletList, orderedList, expand, nestedExpand])(selection)) {
40
+ // does not support other layouts
41
+ attrs = {
42
+ ...extendedOrLegacyAttrs,
43
+ layout: 'center'
44
+ };
45
+ }
46
+ return node.type.name === 'mediaSingle' ? mediaSingle.createChecked(attrs, node.content, node.marks) : node;
47
+ });
48
+ }
49
+ return newSlice;
50
+ };
51
+ }
52
+
53
+ // TODO move this to editor-common
54
+ export const isImage = fileType => {
55
+ return !!fileType && (fileType.indexOf('image/') > -1 || fileType.indexOf('video/') > -1);
56
+ };
57
+ export const transformSliceToCorrectMediaWrapper = (slice, schema) => {
58
+ const {
59
+ mediaGroup,
60
+ mediaSingle,
61
+ media
62
+ } = schema.nodes;
63
+ return mapSlice(slice, (node, parent) => {
64
+ if (!parent && node.type === media) {
65
+ if (mediaSingle && (isImage(node.attrs.__fileMimeType) || node.attrs.type === 'external')) {
66
+ return mediaSingle.createChecked({}, node);
67
+ } else {
68
+ return mediaGroup.createChecked({}, [node]);
69
+ }
70
+ }
71
+ return node;
72
+ });
73
+ };
74
+
75
+ /**
76
+ * This func will be called when copy & paste, drag & drop external html with media, media files, and slices from editor
77
+ * Because width may not be available when transform, DEFAULT_IMAGE_WIDTH is used as a fallback
78
+ *
79
+ */
80
+ export const transformSliceToMediaSingleWithNewExperience = (slice, schema) => {
81
+ const {
82
+ mediaInline,
83
+ mediaSingle,
84
+ media
85
+ } = schema.nodes;
86
+ const newSlice = mapSlice(slice, node => {
87
+ // This logic is duplicated in editor-plugin-ai where external images can be inserted
88
+ // from external sources through the use of AI. The editor-plugin-ai package is avoiding
89
+ // sharing dependencies with editor-core to support products using it with various versions
90
+ // of editor packages.
91
+ // The duplication is in the following file:
92
+ // packages/editor/editor-plugin-ai/src/prebuilt/content-transformers/markdown-to-pm/markdown-transformer.ts
93
+ if (node.type === mediaSingle) {
94
+ return getBooleanFF('platform.editor.media.extended-resize-experience') ? mediaSingle.createChecked({
95
+ width: node.attrs.width || DEFAULT_IMAGE_WIDTH,
96
+ widthType: node.attrs.widthType || 'pixel',
97
+ layout: node.attrs.layout
98
+ }, node.content, node.marks) : node;
99
+ }
100
+ return node;
101
+ });
102
+ return mapSlice(newSlice, node => {
103
+ const __mediaTraceId = getRandomHex(8);
104
+ if (node.type === media) {
105
+ return media.createChecked({
106
+ ...node.attrs,
107
+ __external: node.attrs.type === 'external',
108
+ __mediaTraceId: node.attrs.type === 'external' ? null : __mediaTraceId
109
+ }, node.content, node.marks);
110
+ }
111
+ if (node.type.name === 'mediaInline') {
112
+ return mediaInline.createChecked({
113
+ ...node.attrs,
114
+ __mediaTraceId
115
+ }, node.content, node.marks);
116
+ }
117
+ return node;
118
+ });
119
+ };
120
+
121
+ /**
122
+ * Check base styles to see if an element will be invisible when rendered in a document.
123
+ * @param element
124
+ */
125
+ const isElementInvisible = element => {
126
+ return element.style.opacity === '0' || element.style.display === 'none' || element.style.visibility === 'hidden';
127
+ };
128
+ const VALID_TAGS_CONTAINER = ['DIV', 'TD'];
129
+ function canContainImage(element) {
130
+ if (!element) {
131
+ return false;
132
+ }
133
+ return VALID_TAGS_CONTAINER.indexOf(element.tagName) !== -1;
134
+ }
135
+
136
+ /**
137
+ * Given a html string, we attempt to hoist any nested `<img>` tags,
138
+ * not directly wrapped by a `<div>` as ProseMirror no-op's
139
+ * on those scenarios.
140
+ * @param html
141
+ */
142
+ export const unwrapNestedMediaElements = html => {
143
+ const parser = new DOMParser();
144
+ let doc = parser.parseFromString(html, 'text/html');
145
+ const wrapper = doc.body;
146
+
147
+ // Remove Google Doc's wrapper <b> el
148
+ const docsWrapper = wrapper.querySelector('b[id^="docs-internal-guid-"]');
149
+ if (docsWrapper) {
150
+ unwrap(wrapper, docsWrapper);
151
+ }
152
+ const imageTags = wrapper.querySelectorAll('img');
153
+ if (!imageTags.length) {
154
+ return html;
155
+ }
156
+ imageTags.forEach(imageTag => {
157
+ // Capture the immediate parent, we may remove the media from here later.
158
+ const mediaParent = imageTag.parentElement;
159
+ if (!mediaParent) {
160
+ return;
161
+ }
162
+
163
+ // If either the parent or the image itself contains styles that would make
164
+ // them invisible on copy, dont paste them.
165
+ if (isElementInvisible(mediaParent) || isElementInvisible(imageTag)) {
166
+ mediaParent.removeChild(imageTag);
167
+ return;
168
+ }
169
+
170
+ // If its wrapped by a valid container we assume its safe to bypass.
171
+ // ProseMirror should handle these cases properly.
172
+ if (canContainImage(mediaParent) || mediaParent instanceof HTMLSpanElement && mediaParent.closest('[class*="emoji-common"]')) {
173
+ return;
174
+ }
175
+
176
+ // Find the top most element that the parent has a valid container for the image.
177
+ // Stop just before found the wrapper
178
+ const insertBeforeElement = walkUpTreeUntil(mediaParent, element => {
179
+ // If is at the top just use this element as reference
180
+ if (element.parentElement === wrapper) {
181
+ return true;
182
+ }
183
+ return canContainImage(element.parentElement);
184
+ });
185
+
186
+ // Here we try to insert the media right after its top most valid parent element
187
+ // Unless its the last element in our structure then we will insert above it.
188
+ if (insertBeforeElement && insertBeforeElement.parentElement) {
189
+ // Insert as close as possible to the most closest valid element index in the tree.
190
+ insertBeforeElement.parentElement.insertBefore(imageTag, insertBeforeElement.nextElementSibling || insertBeforeElement);
191
+
192
+ // Attempt to clean up lines left behind by the image
193
+ mediaParent.innerText = mediaParent.innerText.trim();
194
+ // Walk up and delete empty elements left over after removing the image tag
195
+ removeNestedEmptyEls(mediaParent);
196
+ }
197
+ });
198
+
199
+ // If last child is a hardbreak we don't want it
200
+ if (wrapper.lastElementChild && wrapper.lastElementChild.tagName === 'BR') {
201
+ wrapper.removeChild(wrapper.lastElementChild);
202
+ }
203
+ return wrapper.innerHTML;
204
+ };
@@ -0,0 +1,332 @@
1
+ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD, PasteContents, PasteTypes } from '@atlaskit/editor-common/analytics';
2
+ import { getLinkDomain, mapSlice } from '@atlaskit/editor-common/utils';
3
+ import { findParentNode } from '@atlaskit/editor-prosemirror/utils';
4
+ import { handleCodeBlock, handleExpandPasteInTable, handleMarkdown, handleMediaSingle, handlePasteAsPlainText, handlePasteIntoCaption, handlePasteIntoTaskOrDecisionOrPanel, handlePasteLinkOnSelectedText, handlePasteNonNestableBlockNodesIntoList, handlePastePanelOrDecisionContentIntoList, handlePastePreservingMarks, handleRichText, handleSelectedTable } from '../handlers';
5
+ import { getPasteSource } from '../util';
6
+ const contentToPasteContent = {
7
+ url: PasteContents.url,
8
+ paragraph: PasteContents.text,
9
+ bulletList: PasteContents.bulletList,
10
+ orderedList: PasteContents.orderedList,
11
+ heading: PasteContents.heading,
12
+ blockquote: PasteContents.blockquote,
13
+ codeBlock: PasteContents.codeBlock,
14
+ panel: PasteContents.panel,
15
+ rule: PasteContents.rule,
16
+ mediaSingle: PasteContents.mediaSingle,
17
+ mediaCard: PasteContents.mediaCard,
18
+ mediaGroup: PasteContents.mediaGroup,
19
+ table: PasteContents.table,
20
+ tableCells: PasteContents.tableCells,
21
+ tableHeader: PasteContents.tableHeader,
22
+ tableRow: PasteContents.tableRow,
23
+ decisionList: PasteContents.decisionList,
24
+ decisionItem: PasteContents.decisionItem,
25
+ taskList: PasteContents.taskItem,
26
+ extension: PasteContents.extension,
27
+ bodiedExtension: PasteContents.bodiedExtension,
28
+ blockCard: PasteContents.blockCard,
29
+ layoutSection: PasteContents.layoutSection
30
+ };
31
+ const nodeToActionSubjectId = {
32
+ blockquote: ACTION_SUBJECT_ID.PASTE_BLOCKQUOTE,
33
+ blockCard: ACTION_SUBJECT_ID.PASTE_BLOCK_CARD,
34
+ bodiedExtension: ACTION_SUBJECT_ID.PASTE_BODIED_EXTENSION,
35
+ bulletList: ACTION_SUBJECT_ID.PASTE_BULLET_LIST,
36
+ codeBlock: ACTION_SUBJECT_ID.PASTE_CODE_BLOCK,
37
+ decisionList: ACTION_SUBJECT_ID.PASTE_DECISION_LIST,
38
+ extension: ACTION_SUBJECT_ID.PASTE_EXTENSION,
39
+ heading: ACTION_SUBJECT_ID.PASTE_HEADING,
40
+ mediaGroup: ACTION_SUBJECT_ID.PASTE_MEDIA_GROUP,
41
+ mediaSingle: ACTION_SUBJECT_ID.PASTE_MEDIA_SINGLE,
42
+ orderedList: ACTION_SUBJECT_ID.PASTE_ORDERED_LIST,
43
+ panel: ACTION_SUBJECT_ID.PASTE_PANEL,
44
+ rule: ACTION_SUBJECT_ID.PASTE_RULE,
45
+ table: ACTION_SUBJECT_ID.PASTE_TABLE,
46
+ tableCell: ACTION_SUBJECT_ID.PASTE_TABLE_CELL,
47
+ tableHeader: ACTION_SUBJECT_ID.PASTE_TABLE_HEADER,
48
+ tableRow: ACTION_SUBJECT_ID.PASTE_TABLE_ROW,
49
+ taskList: ACTION_SUBJECT_ID.PASTE_TASK_LIST
50
+ };
51
+ export function getContent({
52
+ schema,
53
+ slice
54
+ }) {
55
+ const {
56
+ nodes: {
57
+ paragraph
58
+ },
59
+ marks: {
60
+ link
61
+ }
62
+ } = schema;
63
+ const nodeOrMarkName = new Set();
64
+ slice.content.forEach(node => {
65
+ if (node.type === paragraph && node.content.size === 0) {
66
+ // Skip empty paragraph
67
+ return;
68
+ }
69
+ if (node.type.name === 'text' && link.isInSet(node.marks)) {
70
+ nodeOrMarkName.add('url');
71
+ return;
72
+ }
73
+
74
+ // Check node contain link
75
+ if (node.type === paragraph && node.rangeHasMark(0, node.nodeSize - 2, link)) {
76
+ nodeOrMarkName.add('url');
77
+ return;
78
+ }
79
+ nodeOrMarkName.add(node.type.name);
80
+ });
81
+ if (nodeOrMarkName.size > 1) {
82
+ return PasteContents.mixed;
83
+ }
84
+ if (nodeOrMarkName.size === 0) {
85
+ return PasteContents.uncategorized;
86
+ }
87
+ const type = nodeOrMarkName.values().next().value;
88
+ const pasteContent = contentToPasteContent[type];
89
+ return pasteContent ? pasteContent : PasteContents.uncategorized;
90
+ }
91
+ export function getMediaTraceId(slice) {
92
+ let traceId;
93
+ mapSlice(slice, node => {
94
+ if (node.type.name === 'media' || node.type.name === 'mediaInline') {
95
+ traceId = node.attrs.__mediaTraceId;
96
+ }
97
+ return node;
98
+ });
99
+ return traceId;
100
+ }
101
+ function getActionSubjectId({
102
+ selection,
103
+ schema
104
+ }) {
105
+ const {
106
+ nodes: {
107
+ paragraph,
108
+ listItem,
109
+ taskItem,
110
+ decisionItem
111
+ }
112
+ } = schema;
113
+ const parent = findParentNode(node => {
114
+ if (node.type !== paragraph && node.type !== listItem && node.type !== taskItem && node.type !== decisionItem) {
115
+ return true;
116
+ }
117
+ return false;
118
+ })(selection);
119
+ if (!parent) {
120
+ return ACTION_SUBJECT_ID.PASTE_PARAGRAPH;
121
+ }
122
+ const parentType = parent.node.type;
123
+ const actionSubjectId = nodeToActionSubjectId[parentType.name];
124
+ return actionSubjectId ? actionSubjectId : ACTION_SUBJECT_ID.PASTE_PARAGRAPH;
125
+ }
126
+ function createPasteAsPlainPayload(actionSubjectId, text, linksInPasteCount) {
127
+ return {
128
+ action: ACTION.PASTED_AS_PLAIN,
129
+ actionSubject: ACTION_SUBJECT.DOCUMENT,
130
+ actionSubjectId,
131
+ eventType: EVENT_TYPE.TRACK,
132
+ attributes: {
133
+ inputMethod: INPUT_METHOD.KEYBOARD,
134
+ pasteSize: text.length,
135
+ linksInPasteCount
136
+ }
137
+ };
138
+ }
139
+ function createPastePayload(actionSubjectId, attributes, linkDomain) {
140
+ return {
141
+ action: ACTION.PASTED,
142
+ actionSubject: ACTION_SUBJECT.DOCUMENT,
143
+ actionSubjectId,
144
+ eventType: EVENT_TYPE.TRACK,
145
+ attributes: {
146
+ inputMethod: INPUT_METHOD.KEYBOARD,
147
+ ...attributes
148
+ },
149
+ ...(linkDomain && linkDomain.length > 0 ? {
150
+ nonPrivacySafeAttributes: {
151
+ linkDomain
152
+ }
153
+ } : {})
154
+ };
155
+ }
156
+ function createPasteAnalyticsPayloadBySelection(event, slice, pasteContext) {
157
+ return selection => {
158
+ const text = event.clipboardData ? event.clipboardData.getData('text/plain') || event.clipboardData.getData('text/uri-list') : '';
159
+ const actionSubjectId = getActionSubjectId({
160
+ selection: selection,
161
+ schema: selection.$from.doc.type.schema
162
+ });
163
+ const pasteSize = slice.size;
164
+ const content = getContent({
165
+ schema: selection.$from.doc.type.schema,
166
+ slice
167
+ });
168
+ const linkUrls = [];
169
+ const mediaTraceId = getMediaTraceId(slice);
170
+
171
+ // If we have a link among the pasted content, grab the
172
+ // domain and send it up with the analytics event
173
+ if (content === PasteContents.url || content === PasteContents.mixed) {
174
+ mapSlice(slice, node => {
175
+ const linkMark = node.marks.find(mark => mark.type.name === 'link');
176
+ if (linkMark) {
177
+ linkUrls.push(linkMark.attrs.href);
178
+ }
179
+ return node;
180
+ });
181
+ }
182
+ if (pasteContext.asPlain) {
183
+ return createPasteAsPlainPayload(actionSubjectId, text, linkUrls.length);
184
+ }
185
+ const source = getPasteSource(event);
186
+ if (pasteContext.type === PasteTypes.plain) {
187
+ return createPastePayload(actionSubjectId, {
188
+ pasteSize: text.length,
189
+ type: pasteContext.type,
190
+ content: PasteContents.text,
191
+ source,
192
+ hyperlinkPasteOnText: false,
193
+ linksInPasteCount: linkUrls.length,
194
+ pasteSplitList: pasteContext.pasteSplitList
195
+ });
196
+ }
197
+ const linkDomains = linkUrls.map(getLinkDomain);
198
+ return createPastePayload(actionSubjectId, {
199
+ type: pasteContext.type,
200
+ pasteSize,
201
+ content,
202
+ source,
203
+ hyperlinkPasteOnText: !!pasteContext.hyperlinkPasteOnText,
204
+ linksInPasteCount: linkUrls.length,
205
+ mediaTraceId,
206
+ pasteSplitList: pasteContext.pasteSplitList
207
+ }, linkDomains);
208
+ };
209
+ }
210
+ export function createPasteAnalyticsPayload(view, event, slice, pasteContext) {
211
+ return createPasteAnalyticsPayloadBySelection(event, slice, pasteContext)(view.state.selection);
212
+ }
213
+
214
+ // TODO: ED-6612 We should not dispatch only analytics, it's preferred to wrap each command with his own analytics.
215
+ // However, handlers like handleMacroAutoConvert dispatch multiple time,
216
+ // so pasteCommandWithAnalytics is useless in this case.
217
+ export const sendPasteAnalyticsEvent = editorAnalyticsAPI => (view, event, slice, pasteContext) => {
218
+ const tr = view.state.tr;
219
+ const payload = createPasteAnalyticsPayload(view, event, slice, pasteContext);
220
+ editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent(payload)(tr);
221
+ view.dispatch(tr);
222
+ };
223
+ export const handlePasteAsPlainTextWithAnalytics = editorAnalyticsAPI => (view, event, slice) => injectAnalyticsPayloadBeforeCommand(editorAnalyticsAPI)(createPasteAnalyticsPayloadBySelection(event, slice, {
224
+ type: PasteTypes.plain,
225
+ asPlain: true
226
+ }))(handlePasteAsPlainText(slice, event, editorAnalyticsAPI));
227
+ export const handlePasteIntoTaskAndDecisionWithAnalytics = (view, event, slice, type, pluginInjectionApi) => {
228
+ var _pluginInjectionApi$a, _pluginInjectionApi$c, _pluginInjectionApi$c2;
229
+ return injectAnalyticsPayloadBeforeCommand(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a === void 0 ? void 0 : _pluginInjectionApi$a.actions)(createPasteAnalyticsPayloadBySelection(event, slice, {
230
+ type
231
+ }))(handlePasteIntoTaskOrDecisionOrPanel(slice, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c = pluginInjectionApi.card) === null || _pluginInjectionApi$c === void 0 ? void 0 : (_pluginInjectionApi$c2 = _pluginInjectionApi$c.actions) === null || _pluginInjectionApi$c2 === void 0 ? void 0 : _pluginInjectionApi$c2.queueCardsFromChangedTr));
232
+ };
233
+ export const handlePasteIntoCaptionWithAnalytics = editorAnalyticsAPI => (view, event, slice, type) => injectAnalyticsPayloadBeforeCommand(editorAnalyticsAPI)(createPasteAnalyticsPayloadBySelection(event, slice, {
234
+ type
235
+ }))(handlePasteIntoCaption(slice));
236
+ export const handleCodeBlockWithAnalytics = editorAnalyticsAPI => (view, event, slice, text) => injectAnalyticsPayloadBeforeCommand(editorAnalyticsAPI)(createPasteAnalyticsPayloadBySelection(event, slice, {
237
+ type: PasteTypes.plain
238
+ }))(handleCodeBlock(text));
239
+ export const handleMediaSingleWithAnalytics = editorAnalyticsAPI => (view, event, slice, type, insertMediaAsMediaSingle) => injectAnalyticsPayloadBeforeCommand(editorAnalyticsAPI)(createPasteAnalyticsPayloadBySelection(event, slice, {
240
+ type
241
+ }))(handleMediaSingle(INPUT_METHOD.CLIPBOARD, insertMediaAsMediaSingle)(slice));
242
+ export const handlePastePreservingMarksWithAnalytics = (view, event, slice, type, pluginInjectionApi) => {
243
+ var _pluginInjectionApi$a2, _pluginInjectionApi$c3, _pluginInjectionApi$c4;
244
+ return injectAnalyticsPayloadBeforeCommand(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a2 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a2 === void 0 ? void 0 : _pluginInjectionApi$a2.actions)(createPasteAnalyticsPayloadBySelection(event, slice, {
245
+ type
246
+ }))(handlePastePreservingMarks(slice, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c3 = pluginInjectionApi.card) === null || _pluginInjectionApi$c3 === void 0 ? void 0 : (_pluginInjectionApi$c4 = _pluginInjectionApi$c3.actions) === null || _pluginInjectionApi$c4 === void 0 ? void 0 : _pluginInjectionApi$c4.queueCardsFromChangedTr));
247
+ };
248
+ export const handleMarkdownWithAnalytics = (view, event, slice, pluginInjectionApi) => {
249
+ var _pluginInjectionApi$a3, _pluginInjectionApi$c5, _pluginInjectionApi$c6;
250
+ return injectAnalyticsPayloadBeforeCommand(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a3 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a3 === void 0 ? void 0 : _pluginInjectionApi$a3.actions)(createPasteAnalyticsPayloadBySelection(event, slice, {
251
+ type: PasteTypes.markdown
252
+ }))(handleMarkdown(slice, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c5 = pluginInjectionApi.card) === null || _pluginInjectionApi$c5 === void 0 ? void 0 : (_pluginInjectionApi$c6 = _pluginInjectionApi$c5.actions) === null || _pluginInjectionApi$c6 === void 0 ? void 0 : _pluginInjectionApi$c6.queueCardsFromChangedTr));
253
+ };
254
+ export const handleRichTextWithAnalytics = (view, event, slice, pluginInjectionApi) => {
255
+ var _pluginInjectionApi$a4, _pluginInjectionApi$c7, _pluginInjectionApi$c8;
256
+ return injectAnalyticsPayloadBeforeCommand(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a4 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a4 === void 0 ? void 0 : _pluginInjectionApi$a4.actions)(createPasteAnalyticsPayloadBySelection(event, slice, {
257
+ type: PasteTypes.richText
258
+ }))(handleRichText(slice, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c7 = pluginInjectionApi.card) === null || _pluginInjectionApi$c7 === void 0 ? void 0 : (_pluginInjectionApi$c8 = _pluginInjectionApi$c7.actions) === null || _pluginInjectionApi$c8 === void 0 ? void 0 : _pluginInjectionApi$c8.queueCardsFromChangedTr));
259
+ };
260
+ const injectAnalyticsPayloadBeforeCommand = editorAnalyticsAPI => createPayloadByTransaction => {
261
+ return mainCommand => {
262
+ return (state, dispatch, view) => {
263
+ let originalTransaction = state.tr;
264
+ const fakeDispatch = tr => {
265
+ originalTransaction = tr;
266
+ };
267
+ const result = mainCommand(state, fakeDispatch, view);
268
+ if (!result) {
269
+ return false;
270
+ }
271
+ if (dispatch && originalTransaction.docChanged) {
272
+ // it needs to know the selection before the changes
273
+ const payload = createPayloadByTransaction(state.selection);
274
+ editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent(payload)(originalTransaction);
275
+ dispatch(originalTransaction);
276
+ }
277
+ return true;
278
+ };
279
+ };
280
+ };
281
+ export const handlePastePanelOrDecisionIntoListWithAnalytics = editorAnalyticsAPI => (view, event, slice, findRootParentListNode) => injectAnalyticsPayloadBeforeCommand(editorAnalyticsAPI)(createPasteAnalyticsPayloadBySelection(event, slice, {
282
+ type: PasteTypes.richText
283
+ }))(handlePastePanelOrDecisionContentIntoList(slice, findRootParentListNode));
284
+ export const handlePasteNonNestableBlockNodesIntoListWithAnalytics = editorAnalyticsAPI => (view, event, slice) => injectAnalyticsPayloadBeforeCommand(editorAnalyticsAPI)(createPasteAnalyticsPayloadBySelection(event, slice, {
285
+ type: PasteTypes.richText,
286
+ pasteSplitList: true
287
+ }))(handlePasteNonNestableBlockNodesIntoList(slice));
288
+ export const handleExpandWithAnalytics = editorAnalyticsAPI => (view, event, slice) => injectAnalyticsPayloadBeforeCommand(editorAnalyticsAPI)(createPasteAnalyticsPayloadBySelection(event, slice, {
289
+ type: PasteTypes.richText,
290
+ pasteSplitList: true
291
+ }))(handleExpandPasteInTable(slice));
292
+ export const handleSelectedTableWithAnalytics = editorAnalyticsAPI => (view, event, slice) => injectAnalyticsPayloadBeforeCommand(editorAnalyticsAPI)(createPasteAnalyticsPayloadBySelection(event, slice, {
293
+ type: PasteTypes.richText
294
+ }))(handleSelectedTable(editorAnalyticsAPI)(slice));
295
+ export const handlePasteLinkOnSelectedTextWithAnalytics = editorAnalyticsAPI => (view, event, slice, type) => injectAnalyticsPayloadBeforeCommand(editorAnalyticsAPI)(createPasteAnalyticsPayloadBySelection(event, slice, {
296
+ type,
297
+ hyperlinkPasteOnText: true
298
+ }))(handlePasteLinkOnSelectedText(slice));
299
+ export const createPasteMeasurePayload = ({
300
+ view,
301
+ duration,
302
+ content,
303
+ distortedDuration
304
+ }) => {
305
+ const pasteIntoNode = getActionSubjectId({
306
+ selection: view.state.selection,
307
+ schema: view.state.schema
308
+ });
309
+ return {
310
+ action: ACTION.PASTED_TIMED,
311
+ actionSubject: ACTION_SUBJECT.EDITOR,
312
+ eventType: EVENT_TYPE.OPERATIONAL,
313
+ attributes: {
314
+ pasteIntoNode,
315
+ content,
316
+ time: duration,
317
+ distortedDuration
318
+ }
319
+ };
320
+ };
321
+ export const getContentNodeTypes = content => {
322
+ let nodeTypes = new Set();
323
+ if (content.size) {
324
+ content.forEach(node => {
325
+ if (node.content && node.content.size) {
326
+ nodeTypes = new Set([...nodeTypes, ...getContentNodeTypes(node.content)]);
327
+ }
328
+ nodeTypes.add(node.type.name);
329
+ });
330
+ }
331
+ return Array.from(nodeTypes);
332
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Returns a plain text serialization of a given slice. This is used for populating the plain text
3
+ * section of the clipboard on copy.
4
+ * The current implementation is bare bones - only inlineCards, blockCards and mentions are tested (they
5
+ * previously were empty on plain text copy).
6
+ * Unknown nodes are passed to node.textBetween().
7
+ *
8
+ * By default (without this function passed to the editor), the editor uses
9
+ * `slice.content.textBetween(0, slice.content.size, "\n\n")`
10
+ * (see https://prosemirror.net/docs/ref/#view.EditorProps.clipboardTextSerializer)
11
+ */
12
+ export function clipboardTextSerializer(slice) {
13
+ let text = '';
14
+ const blockSeparater = '\n\n';
15
+ slice.content.nodesBetween(0, slice.content.size, node => {
16
+ if (node.type.isBlock) {
17
+ text += blockSeparater;
18
+ }
19
+ if (node.type.name === 'paragraph') {
20
+ return true;
21
+ } else if (node.type.name === 'hardBreak') {
22
+ text += '\n';
23
+ } else if (node.type.name === 'text') {
24
+ text += node.text;
25
+ } else if (node.type.name === 'inlineCard') {
26
+ text += node.attrs.url;
27
+ } else if (node.type.name === 'blockCard') {
28
+ text += node.attrs.url;
29
+ } else if (node.type.name === 'mention') {
30
+ text += node.attrs.text;
31
+ } else {
32
+ text += node.textBetween(0, node.content.size, '\n\n');
33
+ }
34
+ return false;
35
+ });
36
+ return text.trim();
37
+ }