UNPKG

@atlaskit/editor-plugin-paste

Version:

Paste plugin for @atlaskit/editor-core

232 lines (218 loc) 11.5 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.isImage = void 0; exports.transformSliceForMedia = transformSliceForMedia; exports.unwrapNestedMediaElements = exports.transformSliceToMediaSingleWithNewExperience = exports.transformSliceToCorrectMediaWrapper = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _mediaSingle = require("@atlaskit/editor-common/media-single"); var _utils = require("@atlaskit/editor-common/utils"); var _utils2 = require("@atlaskit/editor-prosemirror/utils"); var _mediaCommon = require("@atlaskit/media-common"); var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals"); 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; } /** * Ensure correct layout in nested mode * * TODO: ED-26959 - this func is only used in handlePaste, so layout update won't work for drop event */ function transformSliceForMedia(slice, schema, api) { var _schema$nodes = schema.nodes, mediaSingle = _schema$nodes.mediaSingle, layoutSection = _schema$nodes.layoutSection, table = _schema$nodes.table, bulletList = _schema$nodes.bulletList, orderedList = _schema$nodes.orderedList, expand = _schema$nodes.expand, nestedExpand = _schema$nodes.nestedExpand; return function (selection) { var newSlice = slice; if ((0, _utils2.hasParentNodeOfType)([layoutSection, table, bulletList, orderedList, expand, nestedExpand])(selection)) { newSlice = (0, _utils.mapSlice)(newSlice, function (node) { var _api$media, _mediaState$mediaOpti; var mediaState = api === null || api === void 0 || (_api$media = api.media) === null || _api$media === void 0 ? void 0 : _api$media.sharedState.currentState(); var extendedOrLegacyAttrs = mediaState !== null && mediaState !== void 0 && (_mediaState$mediaOpti = mediaState.mediaOptions) !== null && _mediaState$mediaOpti !== void 0 && _mediaState$mediaOpti.allowPixelResizing ? { layout: node.attrs.layout, widthType: node.attrs.widthType, width: node.attrs.width } : { layout: node.attrs.layout }; var attrs = {}; if ((0, _utils2.hasParentNodeOfType)([layoutSection, table])(selection)) { // Supports layouts attrs = _objectSpread({}, extendedOrLegacyAttrs); } else if ((0, _utils2.hasParentNodeOfType)([bulletList, orderedList, expand, nestedExpand])(selection)) { // does not support other layouts attrs = _objectSpread(_objectSpread({}, extendedOrLegacyAttrs), {}, { layout: 'center' }); } return node.type.name === 'mediaSingle' ? mediaSingle.createChecked(attrs, node.content, node.marks) : node; }); } return newSlice; }; } // Ignored via go/ees007 // eslint-disable-next-line @atlaskit/editor/enforce-todo-comment-format // TODO move this to editor-common var isImage = exports.isImage = function isImage(fileType) { return !!fileType && (fileType.indexOf('image/') > -1 || fileType.indexOf('video/') > -1); }; var transformSliceToCorrectMediaWrapper = exports.transformSliceToCorrectMediaWrapper = function transformSliceToCorrectMediaWrapper(slice, schema) { var _schema$nodes2 = schema.nodes, mediaGroup = _schema$nodes2.mediaGroup, mediaSingle = _schema$nodes2.mediaSingle, media = _schema$nodes2.media; return (0, _utils.mapSlice)(slice, function (node, parent) { if (!parent && node.type === media) { if (mediaSingle && (isImage(node.attrs.__fileMimeType) || node.attrs.type === 'external')) { return mediaSingle.createChecked({}, node); } else { return mediaGroup.createChecked({}, [node]); } } return node; }); }; /** * This func will be called when copy & paste, drag & drop external html with media, media files, and slices from editor * Because width may not be available when transform, DEFAULT_IMAGE_WIDTH is used as a fallback * */ var transformSliceToMediaSingleWithNewExperience = exports.transformSliceToMediaSingleWithNewExperience = function transformSliceToMediaSingleWithNewExperience(slice, schema, api) { var _schema$nodes3 = schema.nodes, mediaInline = _schema$nodes3.mediaInline, mediaSingle = _schema$nodes3.mediaSingle, media = _schema$nodes3.media; var newSlice = (0, _utils.mapSlice)(slice, function (node) { // This logic is duplicated in editor-plugin-ai where external images can be inserted // from external sources through the use of AI. The editor-plugin-ai package is avoiding // sharing dependencies with editor-core to support products using it with various versions // of editor packages. // The duplication is in the following file: // packages/editor/editor-plugin-ai/src/prebuilt/content-transformers/markdown-to-pm/markdown-transformer.ts if (node.type === mediaSingle) { var _api$media2, _mediaState$mediaOpti2; var mediaState = api === null || api === void 0 || (_api$media2 = api.media) === null || _api$media2 === void 0 ? void 0 : _api$media2.sharedState.currentState(); return mediaState !== null && mediaState !== void 0 && (_mediaState$mediaOpti2 = mediaState.mediaOptions) !== null && _mediaState$mediaOpti2 !== void 0 && _mediaState$mediaOpti2.allowPixelResizing ? mediaSingle.createChecked({ width: node.attrs.width || _mediaSingle.DEFAULT_IMAGE_WIDTH, widthType: node.attrs.widthType || 'pixel', layout: node.attrs.layout }, node.content, node.marks) : node; } return node; }); return (0, _utils.mapSlice)(newSlice, function (node) { var __mediaTraceId = (0, _mediaCommon.getRandomHex)(8); if (node.type === media) { var _api$media3; api === null || api === void 0 || api.core.actions.execute(api === null || api === void 0 || (_api$media3 = api.media) === null || _api$media3 === void 0 ? void 0 : _api$media3.commands.trackMediaPaste(node.attrs)); return media.createChecked(_objectSpread(_objectSpread({}, node.attrs), {}, { __external: node.attrs.type === 'external', __mediaTraceId: node.attrs.type === 'external' ? null : __mediaTraceId }), node.content, node.marks); } if (node.type.name === 'mediaInline') { var _api$media4; api === null || api === void 0 || api.core.actions.execute(api === null || api === void 0 || (_api$media4 = api.media) === null || _api$media4 === void 0 ? void 0 : _api$media4.commands.trackMediaPaste(node.attrs)); return mediaInline.createChecked(_objectSpread(_objectSpread({}, node.attrs), {}, { __mediaTraceId: __mediaTraceId }), node.content, node.marks); } return node; }); }; /** * Check base styles to see if an element will be invisible when rendered in a document. * @param element */ var isElementInvisible = function isElementInvisible(element) { return element.style.opacity === '0' || element.style.display === 'none' || element.style.visibility === 'hidden'; }; var VALID_TAGS_CONTAINER = ['DIV', 'TD', 'BLOCKQUOTE']; function canContainImage(element) { if (!element) { return false; } return VALID_TAGS_CONTAINER.indexOf(element.tagName) !== -1; } /** * Given a html string, we attempt to hoist any nested `<img>` tags, * not directly wrapped by a `<div>` as ProseMirror no-op's * on those scenarios. * @param html */ var unwrapNestedMediaElements = exports.unwrapNestedMediaElements = function unwrapNestedMediaElements(html) { var parser = new DOMParser(); var doc = parser.parseFromString(html, 'text/html'); var wrapper = doc.body; // Remove Google Doc's wrapper <b> el var docsWrapper = wrapper.querySelector('b[id^="docs-internal-guid-"]'); if (docsWrapper) { (0, _utils.unwrap)(wrapper, docsWrapper); } var imageTags = wrapper.querySelectorAll('img'); if (!imageTags.length) { return html; } imageTags.forEach(function (imageTag) { // Capture the immediate parent, we may remove the media from here later. var mediaParent = imageTag.parentElement; if (!mediaParent) { return; } // Bypass emoji if (imageTag.className.includes('emoji-common-emoji-image')) { return; } // Bypass mediaInline images - don't hoist images that are inside a mediaInline wrapper // as this would break parseDOM matching for mediaInline nodes // We remove the img from the DOM since mediaInline is a leaf node with no content if (imageTag.closest('[data-node-type="mediaInline"]') && (0, _expValEquals.expValEquals)('platform_editor_inline_media_copy_paste_fix', 'isEnabled', true)) { // Remove the img element so ProseMirror doesn't try to parse it // mediaInline nodes are leaf nodes and cannot have children imageTag.remove(); return; } // If either the parent or the image itself contains styles that would make // them invisible on copy, dont paste them. if (isElementInvisible(mediaParent) || isElementInvisible(imageTag)) { mediaParent.removeChild(imageTag); return; } // If its wrapped by a valid container we assume its safe to bypass. // ProseMirror should handle these cases properly. if (canContainImage(mediaParent) || mediaParent instanceof HTMLSpanElement && mediaParent.closest('[class*="emoji-common"]')) { return; } // Find the top most element that the parent has a valid container for the image. // Stop just before found the wrapper var insertBeforeElement = (0, _utils.walkUpTreeUntil)(mediaParent, function (element) { // If is at the top just use this element as reference if (element.parentElement === wrapper) { return true; } return canContainImage(element.parentElement); }); // Here we try to insert the media right after its top most valid parent element // Unless its the last element in our structure then we will insert above it. if (insertBeforeElement && insertBeforeElement.parentElement) { // Insert as close as possible to the most closest valid element index in the tree. insertBeforeElement.parentElement.insertBefore(imageTag, insertBeforeElement.nextElementSibling || insertBeforeElement); // Attempt to clean up lines left behind by the image mediaParent.innerText = mediaParent.innerText.trim(); // Walk up and delete empty elements left over after removing the image tag (0, _utils.removeNestedEmptyEls)(mediaParent); } }); // If last child is a hardbreak we don't want it if (wrapper.lastElementChild && wrapper.lastElementChild.tagName === 'BR') { wrapper.removeChild(wrapper.lastElementChild); } return wrapper.innerHTML; };