@atlaskit/editor-plugin-paste 8.0.2 → 8.1.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 (38) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cjs/editor-actions/actions.js +1 -0
  3. package/dist/cjs/pastePlugin.js +18 -2
  4. package/dist/cjs/pastePluginType.js +9 -1
  5. package/dist/cjs/pm-plugins/main.js +4 -2
  6. package/dist/cjs/pm-plugins/media.js +6 -0
  7. package/dist/cjs/pm-plugins/reducer.js +14 -0
  8. package/dist/cjs/pm-plugins/util/sync-block.js +129 -0
  9. package/dist/cjs/ui/Flag.js +107 -0
  10. package/dist/es2019/editor-actions/actions.js +1 -0
  11. package/dist/es2019/pastePlugin.js +17 -2
  12. package/dist/es2019/pastePluginType.js +7 -1
  13. package/dist/es2019/pm-plugins/main.js +5 -3
  14. package/dist/es2019/pm-plugins/media.js +6 -0
  15. package/dist/es2019/pm-plugins/reducer.js +15 -0
  16. package/dist/es2019/pm-plugins/util/sync-block.js +121 -0
  17. package/dist/es2019/ui/Flag.js +102 -0
  18. package/dist/esm/editor-actions/actions.js +1 -0
  19. package/dist/esm/pastePlugin.js +17 -2
  20. package/dist/esm/pastePluginType.js +7 -1
  21. package/dist/esm/pm-plugins/main.js +5 -3
  22. package/dist/esm/pm-plugins/media.js +6 -0
  23. package/dist/esm/pm-plugins/reducer.js +14 -0
  24. package/dist/esm/pm-plugins/util/sync-block.js +122 -0
  25. package/dist/esm/ui/Flag.js +98 -0
  26. package/dist/types/editor-actions/actions.d.ts +8 -3
  27. package/dist/types/index.d.ts +1 -1
  28. package/dist/types/pastePluginType.d.ts +23 -1
  29. package/dist/types/pm-plugins/main.d.ts +2 -2
  30. package/dist/types/pm-plugins/util/sync-block.d.ts +5 -0
  31. package/dist/types/ui/Flag.d.ts +8 -0
  32. package/dist/types-ts4.5/editor-actions/actions.d.ts +8 -3
  33. package/dist/types-ts4.5/index.d.ts +1 -1
  34. package/dist/types-ts4.5/pastePluginType.d.ts +23 -1
  35. package/dist/types-ts4.5/pm-plugins/main.d.ts +2 -2
  36. package/dist/types-ts4.5/pm-plugins/util/sync-block.d.ts +5 -0
  37. package/dist/types-ts4.5/ui/Flag.d.ts +8 -0
  38. package/package.json +13 -7
@@ -0,0 +1,121 @@
1
+ import { uuid } from '@atlaskit/adf-schema';
2
+ import { mapSlice } from '@atlaskit/editor-common/utils';
3
+ import { fg } from '@atlaskit/platform-feature-flags';
4
+ import { PastePluginActionTypes } from '../../editor-actions/actions';
5
+ import { FLAG_TYPE } from '../../pastePluginType';
6
+ import { pluginKey } from '../../pm-plugins/plugin-factory';
7
+ var FLAG_ID = /*#__PURE__*/function (FLAG_ID) {
8
+ FLAG_ID["CANNOT_PASTE_CONTENT"] = "cannot-paste-content";
9
+ return FLAG_ID;
10
+ }(FLAG_ID || {});
11
+ const transformSyncBlockNode = (node, schema, isFromEditor) => {
12
+ // if copying from renderer, flatten out the content and remove the sync block
13
+ if (!isFromEditor) {
14
+ return node.content;
15
+ }
16
+
17
+ // sync blocks need a unique localId to function correctly
18
+ const newAttrs = {
19
+ ...node.attrs,
20
+ localId: uuid.generate()
21
+ };
22
+ return schema.nodes.syncBlock.create(newAttrs, null, [...node.marks]);
23
+ };
24
+ const transformBodiedSyncBlockNode = (node, isFromEditor) => {
25
+ // if copying from renderer, flatten out the content and remove the bodied sync block
26
+ if (!isFromEditor) {
27
+ return node.content;
28
+ }
29
+
30
+ // this is not possible as all bodiedSyncBlocks have already been converted into a syncBlock by now.
31
+ return node;
32
+ };
33
+ const showWarningFlag = ({
34
+ api,
35
+ title,
36
+ description,
37
+ urlText,
38
+ urlHref
39
+ }) => {
40
+ // Use setTimeout to dispatch transaction in next tick and avoid re-entrant dispatch
41
+ setTimeout(() => {
42
+ api === null || api === void 0 ? void 0 : api.core.actions.execute(({
43
+ tr
44
+ }) => {
45
+ const flag = {
46
+ id: FLAG_ID.CANNOT_PASTE_CONTENT,
47
+ description,
48
+ title,
49
+ urlText,
50
+ urlHref,
51
+ type: FLAG_TYPE.WARNING
52
+ };
53
+ return tr.setMeta(pluginKey, {
54
+ type: PastePluginActionTypes.SET_ACTIVE_FLAG,
55
+ activeFlag: flag
56
+ });
57
+ });
58
+ }, 0);
59
+ };
60
+
61
+ // Check if rawHtml contains a synced block
62
+ // example: "<meta charset='utf-8'><html><head></head><body><div data-sync-block=\"\" data-local-id=\"\" data-resource-id=\"d64883c8-1270-431d-a1d3-51d36a1ed5f4\" data-prosemirror-content-type=\"node\" data-prosemirror-node-name=\"syncBlock\" data-prosemirror-node-block=\"true\" data-pm-slice=\"0 0 []\"></div></body></html>"
63
+ const hasSyncedBlockInRawHtml = rawHtml => {
64
+ return rawHtml.includes('data-sync-block="');
65
+ };
66
+
67
+ /**
68
+ * If we are copying from editor, transform the copied source or reference sync block to a new reference sync block
69
+ * Otherwise, (e.g. if copying from renderer), flatten out the content and remove the sync block
70
+ * Also, show a warning flag if the pasted content contains a synced block and the paste warning options are configured.
71
+ */
72
+ const handleSyncBlocksPasteNew = (slice, schema, pasteSource, rawHtml, pasteWarningOptions, api) => {
73
+ const isFromEditor = pasteSource === 'fabric-editor';
74
+ const isSyncedBlockInRawHtml = hasSyncedBlockInRawHtml(rawHtml);
75
+ let hasSyncedBlockInSlice = false;
76
+ slice = mapSlice(slice, node => {
77
+ if (node.type === schema.nodes.syncBlock) {
78
+ hasSyncedBlockInSlice = true;
79
+ return transformSyncBlockNode(node, schema, isFromEditor);
80
+ } else if (node.type === schema.nodes.bodiedSyncBlock) {
81
+ hasSyncedBlockInSlice = true;
82
+ return transformBodiedSyncBlockNode(node, isFromEditor);
83
+ }
84
+ return node;
85
+ });
86
+ if (pasteWarningOptions !== null && pasteWarningOptions !== void 0 && pasteWarningOptions.cannotPasteSyncedBlock && !hasSyncedBlockInSlice && isSyncedBlockInRawHtml) {
87
+ var _pasteWarningOptions$, _pasteWarningOptions$2, _pasteWarningOptions$3, _pasteWarningOptions$4;
88
+ showWarningFlag({
89
+ api,
90
+ title: pasteWarningOptions === null || pasteWarningOptions === void 0 ? void 0 : (_pasteWarningOptions$ = pasteWarningOptions.cannotPasteSyncedBlock) === null || _pasteWarningOptions$ === void 0 ? void 0 : _pasteWarningOptions$.title,
91
+ description: pasteWarningOptions === null || pasteWarningOptions === void 0 ? void 0 : (_pasteWarningOptions$2 = pasteWarningOptions.cannotPasteSyncedBlock) === null || _pasteWarningOptions$2 === void 0 ? void 0 : _pasteWarningOptions$2.description,
92
+ urlText: pasteWarningOptions === null || pasteWarningOptions === void 0 ? void 0 : (_pasteWarningOptions$3 = pasteWarningOptions.cannotPasteSyncedBlock) === null || _pasteWarningOptions$3 === void 0 ? void 0 : _pasteWarningOptions$3.urlText,
93
+ urlHref: pasteWarningOptions === null || pasteWarningOptions === void 0 ? void 0 : (_pasteWarningOptions$4 = pasteWarningOptions.cannotPasteSyncedBlock) === null || _pasteWarningOptions$4 === void 0 ? void 0 : _pasteWarningOptions$4.urlHref
94
+ });
95
+ }
96
+ return slice;
97
+ };
98
+
99
+ /**
100
+ * If we are copying from editor, transform the copied source or reference sync block to a new reference sync block
101
+ * Otherwise, (e.g. if copying from renderer), flatten out the content and remove the sync block
102
+ */
103
+ const handleSyncBlocksPasteOld = (slice, schema, pasteSource) => {
104
+ const isFromEditor = pasteSource === 'fabric-editor';
105
+ slice = mapSlice(slice, node => {
106
+ if (node.type === schema.nodes.syncBlock) {
107
+ return transformSyncBlockNode(node, schema, isFromEditor);
108
+ } else if (node.type === schema.nodes.bodiedSyncBlock) {
109
+ return transformBodiedSyncBlockNode(node, isFromEditor);
110
+ }
111
+ return node;
112
+ });
113
+ return slice;
114
+ };
115
+ export const handleSyncBlocksPaste = (slice, schema, pasteSource, rawHtml, pasteWarningOptions, api) => {
116
+ if (fg('platform_synced_block_dogfooding')) {
117
+ return handleSyncBlocksPasteNew(slice, schema, pasteSource, rawHtml, pasteWarningOptions, api);
118
+ } else {
119
+ return handleSyncBlocksPasteOld(slice, schema, pasteSource);
120
+ }
121
+ };
@@ -0,0 +1,102 @@
1
+ import React from 'react';
2
+ import { useIntl } from 'react-intl-next';
3
+ import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
4
+ import AkFlag, { AutoDismissFlag, FlagGroup } from '@atlaskit/flag';
5
+ import StatusErrorIcon from '@atlaskit/icon/core/status-error';
6
+ import StatusSuccessIcon from '@atlaskit/icon/core/status-success';
7
+ import StatusWarningIcon from '@atlaskit/icon/core/status-warning';
8
+ import { PastePluginActionTypes } from '../editor-actions/actions';
9
+ import { FLAG_TYPE } from '../pastePluginType';
10
+ import { pluginKey } from '../pm-plugins/plugin-factory';
11
+ export const Flag = ({
12
+ api
13
+ }) => {
14
+ const {
15
+ activeFlag
16
+ } = useSharedPluginStateWithSelector(api, ['paste'], states => {
17
+ var _states$pasteState;
18
+ return {
19
+ activeFlag: (_states$pasteState = states.pasteState) === null || _states$pasteState === void 0 ? void 0 : _states$pasteState.activeFlag
20
+ };
21
+ });
22
+ const {
23
+ formatMessage
24
+ } = useIntl();
25
+ if (!activeFlag) {
26
+ return;
27
+ }
28
+ const {
29
+ type,
30
+ onDismissed: onDismissedCallback,
31
+ description: flagDescription,
32
+ title: flagTitle,
33
+ urlText: flagUrlText,
34
+ urlHref: flagUrlHref
35
+ } = activeFlag;
36
+ const description = flagDescription ? formatMessage(flagDescription) : undefined;
37
+ const title = flagTitle ? formatMessage(flagTitle) : undefined;
38
+ const urlText = flagUrlText ? formatMessage(flagUrlText) : undefined;
39
+ const onDismissed = () => {
40
+ api === null || api === void 0 ? void 0 : api.core.actions.execute(({
41
+ tr
42
+ }) => {
43
+ onDismissedCallback === null || onDismissedCallback === void 0 ? void 0 : onDismissedCallback(tr);
44
+ const oldMeta = tr.getMeta(pluginKey);
45
+ tr.setMeta(pluginKey, {
46
+ ...oldMeta,
47
+ type: PastePluginActionTypes.SET_ACTIVE_FLAG,
48
+ activeFlag: false
49
+ });
50
+ return tr;
51
+ });
52
+ api === null || api === void 0 ? void 0 : api.core.actions.focus();
53
+ };
54
+ const getActions = () => {
55
+ const action = urlText && flagUrlHref ? {
56
+ content: urlText,
57
+ href: flagUrlHref
58
+ } : undefined;
59
+ if (action) {
60
+ return [{
61
+ content: action.content,
62
+ href: action.href,
63
+ target: '_blank'
64
+ }];
65
+ }
66
+ return undefined;
67
+ };
68
+ const getFlagComponent = () => {
69
+ if (type === FLAG_TYPE.INFO || type === FLAG_TYPE.SUCCESS) {
70
+ return AutoDismissFlag;
71
+ }
72
+ return AkFlag;
73
+ };
74
+ const FlagComponent = getFlagComponent();
75
+ return /*#__PURE__*/React.createElement(FlagGroup, null, /*#__PURE__*/React.createElement(FlagComponent, {
76
+ onDismissed: onDismissed,
77
+ title: title,
78
+ description: description,
79
+ id: activeFlag.id,
80
+ testId: activeFlag.id,
81
+ icon: typeToIcon(type),
82
+ actions: getActions()
83
+ }));
84
+ };
85
+ const typeToIcon = type => {
86
+ if (type === 'error') {
87
+ return /*#__PURE__*/React.createElement(StatusErrorIcon, {
88
+ label: "",
89
+ color: "var(--ds-icon-danger, #C9372C)"
90
+ });
91
+ }
92
+ if (type === 'warning') {
93
+ return /*#__PURE__*/React.createElement(StatusWarningIcon, {
94
+ label: "",
95
+ color: "var(--ds-icon-warning, #E06C00)"
96
+ });
97
+ }
98
+ return /*#__PURE__*/React.createElement(StatusSuccessIcon, {
99
+ label: "",
100
+ color: "var(--ds-icon-success, #6A9A23)"
101
+ });
102
+ };
@@ -2,5 +2,6 @@ export var PastePluginActionTypes = /*#__PURE__*/function (PastePluginActionType
2
2
  PastePluginActionTypes["START_TRACKING_PASTED_MACRO_POSITIONS"] = "START_TRACKING_PASTED_MACRO_POSITIONS";
3
3
  PastePluginActionTypes["STOP_TRACKING_PASTED_MACRO_POSITIONS"] = "STOP_TRACKING_PASTED_MACRO_POSITIONS";
4
4
  PastePluginActionTypes["ON_PASTE"] = "ON_PASTE";
5
+ PastePluginActionTypes["SET_ACTIVE_FLAG"] = "SET_ACTIVE_FLAG";
5
6
  return PastePluginActionTypes;
6
7
  }({});
@@ -1,6 +1,10 @@
1
+ import React from 'react';
2
+ import { fg } from '@atlaskit/platform-feature-flags';
3
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
1
4
  import { createPlugin } from './pm-plugins/main';
2
5
  import { createPlugin as createMoveAnalyticsPlugin } from './pm-plugins/move-analytics/plugin';
3
6
  import { pluginKey } from './pm-plugins/plugin-factory';
7
+ import { Flag } from './ui/Flag';
4
8
  export var pastePlugin = function pastePlugin(_ref) {
5
9
  var _api$featureFlags, _api$analytics;
6
10
  var config = _ref.config,
@@ -8,7 +12,8 @@ export var pastePlugin = function pastePlugin(_ref) {
8
12
  var _ref2 = config !== null && config !== void 0 ? config : {},
9
13
  cardOptions = _ref2.cardOptions,
10
14
  sanitizePrivateContent = _ref2.sanitizePrivateContent,
11
- isFullPage = _ref2.isFullPage;
15
+ isFullPage = _ref2.isFullPage,
16
+ pasteWarningOptions = _ref2.pasteWarningOptions;
12
17
  var featureFlags = (api === null || api === void 0 || (_api$featureFlags = api.featureFlags) === null || _api$featureFlags === void 0 ? void 0 : _api$featureFlags.sharedState.currentState()) || {};
13
18
  var editorAnalyticsAPI = api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
14
19
  return {
@@ -22,7 +27,7 @@ export var pastePlugin = function pastePlugin(_ref) {
22
27
  dispatchAnalyticsEvent = _ref3.dispatchAnalyticsEvent,
23
28
  dispatch = _ref3.dispatch,
24
29
  getIntl = _ref3.getIntl;
25
- return createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, api, getIntl, cardOptions, sanitizePrivateContent, providerFactory);
30
+ return createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, api, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions);
26
31
  }
27
32
  }, {
28
33
  name: 'moveAnalyticsPlugin',
@@ -32,14 +37,24 @@ export var pastePlugin = function pastePlugin(_ref) {
32
37
  }
33
38
  }];
34
39
  },
40
+ contentComponent: !editorExperiment('platform_synced_block', true) || !fg('platform_synced_block_dogfooding') ? undefined : function () {
41
+ if (!pasteWarningOptions) {
42
+ return null;
43
+ }
44
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Flag, {
45
+ api: api
46
+ }));
47
+ },
35
48
  getSharedState: function getSharedState(editorState) {
36
49
  if (!editorState) {
37
50
  return {
51
+ activeFlag: null,
38
52
  lastContentPasted: null
39
53
  };
40
54
  }
41
55
  var pluginState = pluginKey.getState(editorState);
42
56
  return {
57
+ activeFlag: pluginState.activeFlag,
43
58
  lastContentPasted: pluginState.lastContentPasted
44
59
  };
45
60
  }
@@ -1 +1,7 @@
1
- export {};
1
+ export var FLAG_TYPE = /*#__PURE__*/function (FLAG_TYPE) {
2
+ FLAG_TYPE["WARNING"] = "warning";
3
+ FLAG_TYPE["ERROR"] = "error";
4
+ FLAG_TYPE["INFO"] = "info";
5
+ FLAG_TYPE["SUCCESS"] = "success";
6
+ return FLAG_TYPE;
7
+ }({});
@@ -14,7 +14,7 @@ import { isPastedFile as isPastedFileFromEvent, md } from '@atlaskit/editor-comm
14
14
  import { measureRender } from '@atlaskit/editor-common/performance/measure-render';
15
15
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
16
16
  import { SyncBlockRendererDataAttributeName } from '@atlaskit/editor-common/sync-block';
17
- import { transformSingleColumnLayout, transformSingleLineCodeBlockToCodeMark, transformSliceNestedExpandToExpand, transformSliceToDecisionList, transformSliceToJoinAdjacentCodeBlocks, transformSliceToRemoveLegacyContentMacro, transformSliceToRemoveMacroId, transformSyncBlock, removeBreakoutFromRendererSyncBlockHTML } from '@atlaskit/editor-common/transforms';
17
+ import { transformSingleColumnLayout, transformSingleLineCodeBlockToCodeMark, transformSliceNestedExpandToExpand, transformSliceToDecisionList, transformSliceToJoinAdjacentCodeBlocks, transformSliceToRemoveLegacyContentMacro, transformSliceToRemoveMacroId, removeBreakoutFromRendererSyncBlockHTML } from '@atlaskit/editor-common/transforms';
18
18
  import { containsAnyAnnotations, extractSliceFromStep, linkifyContent, mapChildren } from '@atlaskit/editor-common/utils';
19
19
  import { MarkdownTransformer } from '@atlaskit/editor-markdown-transformer';
20
20
  import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
@@ -33,6 +33,7 @@ import { createPluginState, pluginKey as stateKey } from './plugin-factory';
33
33
  import { escapeBackslashAndLinksExceptCodeBlock, getPasteSource, htmlContainsSingleFile, htmlHasInvalidLinkTags, isPastedFromExcel, isPastedFromWord, removeDuplicateInvalidLinks, transformUnsupportedBlockCardToInline } from './util';
34
34
  import { handleVSCodeBlock } from './util/edge-cases/handleVSCodeBlock';
35
35
  import { handleMacroAutoConvert, handleMention, handleParagraphBlockMarks, handleTableContentPasteInBodiedExtension } from './util/handlers';
36
+ import { handleSyncBlocksPaste } from './util/sync-block';
36
37
  import { htmlHasIncompleteTable, isPastedFromTinyMCEConfluence, tryRebuildCompleteTableHtml } from './util/tinyMCE';
37
38
  export var isInsideBlockQuote = function isInsideBlockQuote(state) {
38
39
  var blockquote = state.schema.nodes.blockquote;
@@ -60,7 +61,7 @@ export function isSharePointUrl(url) {
60
61
  return false;
61
62
  }
62
63
  }
63
- export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pluginInjectionApi, getIntl, cardOptions, sanitizePrivateContent, providerFactory) {
64
+ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pluginInjectionApi, getIntl, cardOptions, sanitizePrivateContent, providerFactory, pasteWarningOptions) {
64
65
  var _pluginInjectionApi$a;
65
66
  var editorAnalyticsAPI = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$a = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a === void 0 ? void 0 : _pluginInjectionApi$a.actions;
66
67
  var atlassianMarkDownParser = new MarkdownTransformer(schema, md);
@@ -115,6 +116,7 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
115
116
  return new SafePlugin({
116
117
  key: stateKey,
117
118
  state: createPluginState(dispatch, {
119
+ activeFlag: null,
118
120
  pastedMacroPositions: {},
119
121
  lastContentPasted: null
120
122
  }),
@@ -319,7 +321,7 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
319
321
  text: text
320
322
  });
321
323
  if (editorExperiment('platform_synced_block', true)) {
322
- slice = transformSyncBlock(slice, schema, getPasteSource(event));
324
+ slice = handleSyncBlocksPaste(slice, schema, getPasteSource(event), html, pasteWarningOptions, pluginInjectionApi);
323
325
  }
324
326
  var plainTextPasteSlice = linkifyContent(state.schema)(slice);
325
327
  if (handlePasteAsPlainTextWithAnalytics(editorAnalyticsAPI)(view, event, plainTextPasteSlice)(state, dispatch, view)) {
@@ -5,6 +5,7 @@ import { DEFAULT_IMAGE_WIDTH } from '@atlaskit/editor-common/media-single';
5
5
  import { mapSlice, removeNestedEmptyEls, unwrap, walkUpTreeUntil } from '@atlaskit/editor-common/utils';
6
6
  import { hasParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
7
7
  import { getRandomHex } from '@atlaskit/media-common';
8
+ import { expVal } from '@atlaskit/tmp-editor-statsig/expVal';
8
9
  /**
9
10
  * Ensure correct layout in nested mode
10
11
  *
@@ -163,6 +164,11 @@ export var unwrapNestedMediaElements = function unwrapNestedMediaElements(html)
163
164
  return;
164
165
  }
165
166
 
167
+ // Bypass emoji
168
+ if (imageTag.className.includes('emoji-common-emoji-image') && expVal('platform_editor_fix_emoji_paste_html', 'isEnabled', false)) {
169
+ return;
170
+ }
171
+
166
172
  // If either the parent or the image itself contains styles that would make
167
173
  // them invisible on copy, dont paste them.
168
174
  if (isElementInvisible(mediaParent) || isElementInvisible(imageTag)) {
@@ -2,6 +2,8 @@ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
2
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
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
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 { fg } from '@atlaskit/platform-feature-flags';
6
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
5
7
  import { PastePluginActionTypes as ActionTypes } from '../editor-actions/actions';
6
8
  export var reducer = function reducer(state, action) {
7
9
  switch (action.type) {
@@ -28,6 +30,18 @@ export var reducer = function reducer(state, action) {
28
30
  lastContentPasted: action.contentPasted
29
31
  });
30
32
  }
33
+ case ActionTypes.SET_ACTIVE_FLAG:
34
+ {
35
+ if (!editorExperiment('platform_synced_block', true)) {
36
+ return state;
37
+ }
38
+ if (!fg('platform_synced_block_dogfooding')) {
39
+ return state;
40
+ }
41
+ return _objectSpread(_objectSpread({}, state), {}, {
42
+ activeFlag: action.activeFlag
43
+ });
44
+ }
31
45
  default:
32
46
  return state;
33
47
  }
@@ -0,0 +1,122 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
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 { uuid } from '@atlaskit/adf-schema';
6
+ import { mapSlice } from '@atlaskit/editor-common/utils';
7
+ import { fg } from '@atlaskit/platform-feature-flags';
8
+ import { PastePluginActionTypes } from '../../editor-actions/actions';
9
+ import { FLAG_TYPE } from '../../pastePluginType';
10
+ import { pluginKey } from '../../pm-plugins/plugin-factory';
11
+ var FLAG_ID = /*#__PURE__*/function (FLAG_ID) {
12
+ FLAG_ID["CANNOT_PASTE_CONTENT"] = "cannot-paste-content";
13
+ return FLAG_ID;
14
+ }(FLAG_ID || {});
15
+ var transformSyncBlockNode = function transformSyncBlockNode(node, schema, isFromEditor) {
16
+ // if copying from renderer, flatten out the content and remove the sync block
17
+ if (!isFromEditor) {
18
+ return node.content;
19
+ }
20
+
21
+ // sync blocks need a unique localId to function correctly
22
+ var newAttrs = _objectSpread(_objectSpread({}, node.attrs), {}, {
23
+ localId: uuid.generate()
24
+ });
25
+ return schema.nodes.syncBlock.create(newAttrs, null, _toConsumableArray(node.marks));
26
+ };
27
+ var transformBodiedSyncBlockNode = function transformBodiedSyncBlockNode(node, isFromEditor) {
28
+ // if copying from renderer, flatten out the content and remove the bodied sync block
29
+ if (!isFromEditor) {
30
+ return node.content;
31
+ }
32
+
33
+ // this is not possible as all bodiedSyncBlocks have already been converted into a syncBlock by now.
34
+ return node;
35
+ };
36
+ var showWarningFlag = function showWarningFlag(_ref) {
37
+ var api = _ref.api,
38
+ title = _ref.title,
39
+ description = _ref.description,
40
+ urlText = _ref.urlText,
41
+ urlHref = _ref.urlHref;
42
+ // Use setTimeout to dispatch transaction in next tick and avoid re-entrant dispatch
43
+ setTimeout(function () {
44
+ api === null || api === void 0 || api.core.actions.execute(function (_ref2) {
45
+ var tr = _ref2.tr;
46
+ var flag = {
47
+ id: FLAG_ID.CANNOT_PASTE_CONTENT,
48
+ description: description,
49
+ title: title,
50
+ urlText: urlText,
51
+ urlHref: urlHref,
52
+ type: FLAG_TYPE.WARNING
53
+ };
54
+ return tr.setMeta(pluginKey, {
55
+ type: PastePluginActionTypes.SET_ACTIVE_FLAG,
56
+ activeFlag: flag
57
+ });
58
+ });
59
+ }, 0);
60
+ };
61
+
62
+ // Check if rawHtml contains a synced block
63
+ // example: "<meta charset='utf-8'><html><head></head><body><div data-sync-block=\"\" data-local-id=\"\" data-resource-id=\"d64883c8-1270-431d-a1d3-51d36a1ed5f4\" data-prosemirror-content-type=\"node\" data-prosemirror-node-name=\"syncBlock\" data-prosemirror-node-block=\"true\" data-pm-slice=\"0 0 []\"></div></body></html>"
64
+ var hasSyncedBlockInRawHtml = function hasSyncedBlockInRawHtml(rawHtml) {
65
+ return rawHtml.includes('data-sync-block="');
66
+ };
67
+
68
+ /**
69
+ * If we are copying from editor, transform the copied source or reference sync block to a new reference sync block
70
+ * Otherwise, (e.g. if copying from renderer), flatten out the content and remove the sync block
71
+ * Also, show a warning flag if the pasted content contains a synced block and the paste warning options are configured.
72
+ */
73
+ var handleSyncBlocksPasteNew = function handleSyncBlocksPasteNew(slice, schema, pasteSource, rawHtml, pasteWarningOptions, api) {
74
+ var isFromEditor = pasteSource === 'fabric-editor';
75
+ var isSyncedBlockInRawHtml = hasSyncedBlockInRawHtml(rawHtml);
76
+ var hasSyncedBlockInSlice = false;
77
+ slice = mapSlice(slice, function (node) {
78
+ if (node.type === schema.nodes.syncBlock) {
79
+ hasSyncedBlockInSlice = true;
80
+ return transformSyncBlockNode(node, schema, isFromEditor);
81
+ } else if (node.type === schema.nodes.bodiedSyncBlock) {
82
+ hasSyncedBlockInSlice = true;
83
+ return transformBodiedSyncBlockNode(node, isFromEditor);
84
+ }
85
+ return node;
86
+ });
87
+ if (pasteWarningOptions !== null && pasteWarningOptions !== void 0 && pasteWarningOptions.cannotPasteSyncedBlock && !hasSyncedBlockInSlice && isSyncedBlockInRawHtml) {
88
+ var _pasteWarningOptions$, _pasteWarningOptions$2, _pasteWarningOptions$3, _pasteWarningOptions$4;
89
+ showWarningFlag({
90
+ api: api,
91
+ title: pasteWarningOptions === null || pasteWarningOptions === void 0 || (_pasteWarningOptions$ = pasteWarningOptions.cannotPasteSyncedBlock) === null || _pasteWarningOptions$ === void 0 ? void 0 : _pasteWarningOptions$.title,
92
+ description: pasteWarningOptions === null || pasteWarningOptions === void 0 || (_pasteWarningOptions$2 = pasteWarningOptions.cannotPasteSyncedBlock) === null || _pasteWarningOptions$2 === void 0 ? void 0 : _pasteWarningOptions$2.description,
93
+ urlText: pasteWarningOptions === null || pasteWarningOptions === void 0 || (_pasteWarningOptions$3 = pasteWarningOptions.cannotPasteSyncedBlock) === null || _pasteWarningOptions$3 === void 0 ? void 0 : _pasteWarningOptions$3.urlText,
94
+ urlHref: pasteWarningOptions === null || pasteWarningOptions === void 0 || (_pasteWarningOptions$4 = pasteWarningOptions.cannotPasteSyncedBlock) === null || _pasteWarningOptions$4 === void 0 ? void 0 : _pasteWarningOptions$4.urlHref
95
+ });
96
+ }
97
+ return slice;
98
+ };
99
+
100
+ /**
101
+ * If we are copying from editor, transform the copied source or reference sync block to a new reference sync block
102
+ * Otherwise, (e.g. if copying from renderer), flatten out the content and remove the sync block
103
+ */
104
+ var handleSyncBlocksPasteOld = function handleSyncBlocksPasteOld(slice, schema, pasteSource) {
105
+ var isFromEditor = pasteSource === 'fabric-editor';
106
+ slice = mapSlice(slice, function (node) {
107
+ if (node.type === schema.nodes.syncBlock) {
108
+ return transformSyncBlockNode(node, schema, isFromEditor);
109
+ } else if (node.type === schema.nodes.bodiedSyncBlock) {
110
+ return transformBodiedSyncBlockNode(node, isFromEditor);
111
+ }
112
+ return node;
113
+ });
114
+ return slice;
115
+ };
116
+ export var handleSyncBlocksPaste = function handleSyncBlocksPaste(slice, schema, pasteSource, rawHtml, pasteWarningOptions, api) {
117
+ if (fg('platform_synced_block_dogfooding')) {
118
+ return handleSyncBlocksPasteNew(slice, schema, pasteSource, rawHtml, pasteWarningOptions, api);
119
+ } else {
120
+ return handleSyncBlocksPasteOld(slice, schema, pasteSource);
121
+ }
122
+ };
@@ -0,0 +1,98 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ 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; }
3
+ 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; }
4
+ import React from 'react';
5
+ import { useIntl } from 'react-intl-next';
6
+ import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
7
+ import AkFlag, { AutoDismissFlag, FlagGroup } from '@atlaskit/flag';
8
+ import StatusErrorIcon from '@atlaskit/icon/core/status-error';
9
+ import StatusSuccessIcon from '@atlaskit/icon/core/status-success';
10
+ import StatusWarningIcon from '@atlaskit/icon/core/status-warning';
11
+ import { PastePluginActionTypes } from '../editor-actions/actions';
12
+ import { FLAG_TYPE } from '../pastePluginType';
13
+ import { pluginKey } from '../pm-plugins/plugin-factory';
14
+ export var Flag = function Flag(_ref) {
15
+ var api = _ref.api;
16
+ var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['paste'], function (states) {
17
+ var _states$pasteState;
18
+ return {
19
+ activeFlag: (_states$pasteState = states.pasteState) === null || _states$pasteState === void 0 ? void 0 : _states$pasteState.activeFlag
20
+ };
21
+ }),
22
+ activeFlag = _useSharedPluginState.activeFlag;
23
+ var _useIntl = useIntl(),
24
+ formatMessage = _useIntl.formatMessage;
25
+ if (!activeFlag) {
26
+ return;
27
+ }
28
+ var type = activeFlag.type,
29
+ onDismissedCallback = activeFlag.onDismissed,
30
+ flagDescription = activeFlag.description,
31
+ flagTitle = activeFlag.title,
32
+ flagUrlText = activeFlag.urlText,
33
+ flagUrlHref = activeFlag.urlHref;
34
+ var description = flagDescription ? formatMessage(flagDescription) : undefined;
35
+ var title = flagTitle ? formatMessage(flagTitle) : undefined;
36
+ var urlText = flagUrlText ? formatMessage(flagUrlText) : undefined;
37
+ var onDismissed = function onDismissed() {
38
+ api === null || api === void 0 || api.core.actions.execute(function (_ref2) {
39
+ var tr = _ref2.tr;
40
+ onDismissedCallback === null || onDismissedCallback === void 0 || onDismissedCallback(tr);
41
+ var oldMeta = tr.getMeta(pluginKey);
42
+ tr.setMeta(pluginKey, _objectSpread(_objectSpread({}, oldMeta), {}, {
43
+ type: PastePluginActionTypes.SET_ACTIVE_FLAG,
44
+ activeFlag: false
45
+ }));
46
+ return tr;
47
+ });
48
+ api === null || api === void 0 || api.core.actions.focus();
49
+ };
50
+ var getActions = function getActions() {
51
+ var action = urlText && flagUrlHref ? {
52
+ content: urlText,
53
+ href: flagUrlHref
54
+ } : undefined;
55
+ if (action) {
56
+ return [{
57
+ content: action.content,
58
+ href: action.href,
59
+ target: '_blank'
60
+ }];
61
+ }
62
+ return undefined;
63
+ };
64
+ var getFlagComponent = function getFlagComponent() {
65
+ if (type === FLAG_TYPE.INFO || type === FLAG_TYPE.SUCCESS) {
66
+ return AutoDismissFlag;
67
+ }
68
+ return AkFlag;
69
+ };
70
+ var FlagComponent = getFlagComponent();
71
+ return /*#__PURE__*/React.createElement(FlagGroup, null, /*#__PURE__*/React.createElement(FlagComponent, {
72
+ onDismissed: onDismissed,
73
+ title: title,
74
+ description: description,
75
+ id: activeFlag.id,
76
+ testId: activeFlag.id,
77
+ icon: typeToIcon(type),
78
+ actions: getActions()
79
+ }));
80
+ };
81
+ var typeToIcon = function typeToIcon(type) {
82
+ if (type === 'error') {
83
+ return /*#__PURE__*/React.createElement(StatusErrorIcon, {
84
+ label: "",
85
+ color: "var(--ds-icon-danger, #C9372C)"
86
+ });
87
+ }
88
+ if (type === 'warning') {
89
+ return /*#__PURE__*/React.createElement(StatusWarningIcon, {
90
+ label: "",
91
+ color: "var(--ds-icon-warning, #E06C00)"
92
+ });
93
+ }
94
+ return /*#__PURE__*/React.createElement(StatusSuccessIcon, {
95
+ label: "",
96
+ color: "var(--ds-icon-success, #6A9A23)"
97
+ });
98
+ };
@@ -1,8 +1,9 @@
1
- import type { LastContentPasted } from '../pastePluginType';
1
+ import type { LastContentPasted, ActiveFlag } from '../pastePluginType';
2
2
  export declare enum PastePluginActionTypes {
3
3
  START_TRACKING_PASTED_MACRO_POSITIONS = "START_TRACKING_PASTED_MACRO_POSITIONS",
4
4
  STOP_TRACKING_PASTED_MACRO_POSITIONS = "STOP_TRACKING_PASTED_MACRO_POSITIONS",
5
- ON_PASTE = "ON_PASTE"
5
+ ON_PASTE = "ON_PASTE",
6
+ SET_ACTIVE_FLAG = "SET_ACTIVE_FLAG"
6
7
  }
7
8
  export interface StartTrackingPastedMacroPositions {
8
9
  pastedMacroPositions: {
@@ -18,4 +19,8 @@ export interface StopTrackingPastedMacroPositions {
18
19
  pastedMacroPositionKeys: string[];
19
20
  type: PastePluginActionTypes.STOP_TRACKING_PASTED_MACRO_POSITIONS;
20
21
  }
21
- export type PastePluginAction = StartTrackingPastedMacroPositions | StopTrackingPastedMacroPositions | OnPaste;
22
+ export interface SetActiveFlag {
23
+ activeFlag: ActiveFlag;
24
+ type: PastePluginActionTypes.SET_ACTIVE_FLAG;
25
+ }
26
+ export type PastePluginAction = StartTrackingPastedMacroPositions | StopTrackingPastedMacroPositions | OnPaste | SetActiveFlag;
@@ -1,2 +1,2 @@
1
- export type { PastePlugin, PastePluginOptions, PastePluginState, LastContentPasted, PastePluginDependencies, } from './pastePluginType';
1
+ export type { PastePlugin, PastePluginOptions, PastePluginState, LastContentPasted, PastePluginDependencies, ActiveFlag, } from './pastePluginType';
2
2
  export { pastePlugin } from './pastePlugin';