UNPKG

@atlaskit/editor-plugin-clipboard

Version:

Clipboard plugin for @atlaskit/editor-core

153 lines (145 loc) 8.62 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.setLastEventType = exports.sendClipboardAnalytics = exports.createPlugin = exports.createClipboardSerializer = exports.ClipboardEventType = void 0; var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _analytics = require("@atlaskit/editor-common/analytics"); var _clipboard = require("@atlaskit/editor-common/clipboard"); var _safePlugin = require("@atlaskit/editor-common/safe-plugin"); var _model = require("@atlaskit/editor-prosemirror/model"); var _utils = require("@atlaskit/editor-prosemirror/utils"); var _pluginKey = require("./plugin-key"); 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; } 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) { (0, _defineProperty2.default)(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; } var ClipboardEventType = exports.ClipboardEventType = /*#__PURE__*/function (ClipboardEventType) { ClipboardEventType["CUT"] = "CUT"; ClipboardEventType["COPY"] = "COPY"; return ClipboardEventType; }({}); var lastEventType = null; var createPlugin = exports.createPlugin = function createPlugin(_ref) { var dispatchAnalyticsEvent = _ref.dispatchAnalyticsEvent, schema = _ref.schema; var editorView; var getEditorView = function getEditorView() { return editorView; }; return new _safePlugin.SafePlugin({ key: _pluginKey.clipboardPluginKey, view: function view(_view) { editorView = _view; return { update: function update(view) { editorView = view; } }; }, props: { handleDOMEvents: { cut: function cut(view) { setLastEventType(ClipboardEventType.CUT); return sendClipboardAnalytics(view, dispatchAnalyticsEvent, _analytics.ACTION.CUT); }, copy: function copy(view) { setLastEventType(ClipboardEventType.COPY); return sendClipboardAnalytics(view, dispatchAnalyticsEvent, _analytics.ACTION.COPIED); } }, clipboardSerializer: createClipboardSerializer(schema, getEditorView) } }); }; /** * Overrides Prosemirror's default clipboardSerializer, in order to fix table row copy/paste bug raised in ED-13003. * This allows us to store the original table’s attributes on the new table that the row is wrapped with when it is being copied. * e.g. keeping the layout on a row that is copied. * We store the default serializer in order to call it after we handle the table row case. */ var createClipboardSerializer = exports.createClipboardSerializer = function createClipboardSerializer(schema, getEditorView) { var oldSerializer = _model.DOMSerializer.fromSchema(schema); var newSerializer = new _model.DOMSerializer(oldSerializer.nodes, oldSerializer.marks); var originalSerializeFragment = newSerializer.serializeFragment.bind(newSerializer); newSerializer.serializeFragment = function (content) { var _content$firstChild, _content$firstChild2; var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var target = arguments.length > 2 ? arguments[2] : undefined; var editorView = getEditorView(); var selection = editorView.state.selection; // We do not need to handle when a user copies a tableRow + other content. // In that scenario it already wraps the Row with correct Table and attributes. if (!options.tableWrapperExists) { var i = 0; while (i < content.childCount) { var _content$maybeChild; if (((_content$maybeChild = content.maybeChild(i)) === null || _content$maybeChild === void 0 ? void 0 : _content$maybeChild.type.name) === 'table') { options.tableWrapperExists = true; break; } i++; } } // When the content being copied includes a tableRow that is not already wrapped with a table, // We will wrap it with one ourselves, while preserving the parent table's attributes. if (((_content$firstChild = content.firstChild) === null || _content$firstChild === void 0 ? void 0 : _content$firstChild.type.name) === 'tableRow' && !options.tableWrapperExists) { // We only want 1 table wrapping the rows. // tableWrapperExist is a custom prop added solely for the purposes of this recursive algorithm. // The function is recursively called for each node in the tree captured in the fragment. // For recursive logic see the bind call above and the prosemirror-model (https://github.com/ProseMirror/prosemirror-model/blob/master/src/to_dom.js#L44 // and https://github.com/ProseMirror/prosemirror-model/blob/master/src/to_dom.js#L87) options.tableWrapperExists = true; var parentTable = (0, _utils.findParentNodeOfType)(schema.nodes.table)(selection); var attributes = parentTable === null || parentTable === void 0 ? void 0 : parentTable.node.attrs; var newTable = schema.nodes.table; // Explicitly remove local id since we are creating a new table and it should have a unique local id which will be generated. var newTableNode = newTable.createChecked(_objectSpread(_objectSpread({}, attributes), {}, { localId: undefined }), content); var newContent = _model.Fragment.from(newTableNode); // Pass updated content into original ProseMirror serializeFragment function. // Currently incorrectly typed in @Types. See this GitHub thread: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/57668 //@ts-ignore return originalSerializeFragment(newContent, options, target); } // Remove annotations from media nodes when copying to clipboard, only do this for copy operations // and keep existing content nodes from the parent. if (lastEventType === ClipboardEventType.COPY && ((_content$firstChild2 = content.firstChild) === null || _content$firstChild2 === void 0 ? void 0 : _content$firstChild2.type.name) === 'media') { var _mediaNode$marks; var mediaNode = content.firstChild; var strippedMediaNode = schema.nodes.media.createChecked(mediaNode.attrs, mediaNode.content, (_mediaNode$marks = mediaNode.marks) === null || _mediaNode$marks === void 0 ? void 0 : _mediaNode$marks.filter(function (mark) { return mark.type.name !== 'annotation'; })); // Content for media parents can include multiple content nodes (media and captions). We now take that // into consideration when we are stripping annotations. var contentArray = [strippedMediaNode]; content.forEach(function (node) { if (node.type.name !== 'media') { contentArray = [].concat((0, _toConsumableArray2.default)(contentArray), [node]); } }); var _newContent = _model.Fragment.fromArray(contentArray); // Currently incorrectly typed, see comment above // @ts-ignore return originalSerializeFragment(_newContent, options, target); } // If we're not copying any rows or media nodes, just run default serializeFragment function. // Currently incorrectly typed in @Types. See this GitHub thread: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/57668 //@ts-ignore return originalSerializeFragment(content, options, target); }; return newSerializer; }; var sendClipboardAnalytics = exports.sendClipboardAnalytics = function sendClipboardAnalytics(view, dispatchAnalyticsEvent, action) { var clipboardAnalyticsPayload = (0, _clipboard.getAnalyticsPayload)(view.state, action); if (clipboardAnalyticsPayload) { dispatchAnalyticsEvent(clipboardAnalyticsPayload); } // return false so we don't block any other plugins' cut or copy handlers // from running just because we are sending an analytics event return false; }; var setLastEventType = exports.setLastEventType = function setLastEventType(eventType) { return lastEventType = eventType; };