@atlaskit/editor-plugin-clipboard
Version:
Clipboard plugin for @atlaskit/editor-core
153 lines (145 loc) • 8.62 kB
JavaScript
;
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;
};