UNPKG

@atlaskit/editor-plugin-media

Version:

Media plugin for @atlaskit/editor-core

1,055 lines (1,038 loc) 53.9 kB
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator"; import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; import _createClass from "@babel/runtime/helpers/createClass"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } import _regeneratorRuntime from "@babel/runtime/regenerator"; 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) { _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; } import assert from 'assert'; import React from 'react'; import { RawIntlProvider } from 'react-intl'; // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead import uuid from 'uuid'; import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics'; import { getBrowserInfo } from '@atlaskit/editor-common/browser'; import { mediaInlineImagesEnabled } from '@atlaskit/editor-common/media-inline'; import { CAPTION_PLACEHOLDER_ID, getMaxWidthForNestedNodeNext } from '@atlaskit/editor-common/media-single'; import { SafePlugin } from '@atlaskit/editor-common/safe-plugin'; import { ErrorReporter } from '@atlaskit/editor-common/utils'; import { AllSelection, NodeSelection, Selection, TextSelection } from '@atlaskit/editor-prosemirror/state'; import { insertPoint } from '@atlaskit/editor-prosemirror/transform'; import { findDomRefAtPos, findParentNodeOfType, findSelectedNodeOfType, isNodeSelection } from '@atlaskit/editor-prosemirror/utils'; import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view'; import { CellSelection } from '@atlaskit/editor-tables/cell-selection'; import { isFileIdentifier } from '@atlaskit/media-client'; import { getMediaFeatureFlag } from '@atlaskit/media-common'; import { fg } from '@atlaskit/platform-feature-flags'; // Ignored via go/ees005 // eslint-disable-next-line import/no-namespace import * as helpers from '../pm-plugins/commands/helpers'; import { updateMediaNodeAttrs } from '../pm-plugins/commands/helpers'; import { getIdentifier, getMediaFromSupportedMediaNodesFromSelection, isNodeDoubleClickSupportedInLivePagesViewMode, removeMediaNode, splitMediaGroup } from '../pm-plugins/utils/media-common'; import { insertMediaGroupNode, insertMediaInlineNode } from '../pm-plugins/utils/media-files'; import { getMediaNodeInsertionType } from '../pm-plugins/utils/media-inline'; import { insertMediaSingleNode } from '../pm-plugins/utils/media-single'; import DropPlaceholder from '../ui/Media/DropPlaceholder'; import { ACTIONS } from './actions'; import { MediaTaskManager } from './mediaTaskManager'; import PickerFacade from './picker-facade'; import { stateKey } from './plugin-key'; export var MEDIA_CONTENT_WRAP_CLASS_NAME = 'media-content-wrap'; export var MEDIA_PLUGIN_IS_RESIZING_KEY = 'mediaSinglePlugin.isResizing'; export var MEDIA_PLUGIN_RESIZING_WIDTH_KEY = 'mediaSinglePlugin.resizing-width'; var createDropPlaceholder = function createDropPlaceholder(intl, nodeViewPortalProviderAPI, dropPlaceholderKey, allowDropLine) { // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage var dropPlaceholder = document.createElement('div'); var createElement = React.createElement; if (allowDropLine) { nodeViewPortalProviderAPI.render(function () { return createElement(RawIntlProvider, { value: intl }, createElement(DropPlaceholder, { type: 'single' })); }, dropPlaceholder, dropPlaceholderKey); } else { nodeViewPortalProviderAPI.render(function () { return createElement(RawIntlProvider, { value: intl }, createElement(DropPlaceholder)); }, dropPlaceholder, dropPlaceholderKey); } return dropPlaceholder; }; var MEDIA_RESOLVED_STATES = ['ready', 'error', 'cancelled']; export var MediaPluginStateImplementation = /*#__PURE__*/function () { function MediaPluginStateImplementation(_state, options, mediaOptions, _dispatch, pluginInjectionApi) { var _this = this; _classCallCheck(this, MediaPluginStateImplementation); _defineProperty(this, "allowsUploads", false); _defineProperty(this, "ignoreLinks", false); _defineProperty(this, "waitForMediaUpload", true); _defineProperty(this, "allUploadsFinished", true); _defineProperty(this, "showDropzone", false); _defineProperty(this, "isFullscreen", false); _defineProperty(this, "layout", 'center'); _defineProperty(this, "mediaNodes", []); _defineProperty(this, "isResizing", false); _defineProperty(this, "resizingWidth", 0); _defineProperty(this, "allowInlineImages", false); _defineProperty(this, "uploadInProgressSubscriptions", []); _defineProperty(this, "uploadInProgressSubscriptionsNotified", false); // this is only a temporary variable, which gets cleared after the last inserted node has been selected _defineProperty(this, "lastAddedMediaSingleFileIds", []); _defineProperty(this, "destroyed", false); _defineProperty(this, "removeOnCloseListener", function () {}); _defineProperty(this, "onPopupToggleCallback", function () {}); _defineProperty(this, "identifierCount", new Map()); // This is to enable mediaShallowCopySope to enable only shallow copying media referenced within the edtior // see: trackOutOfScopeIdentifier _defineProperty(this, "outOfEditorScopeIdentifierMap", new Map()); _defineProperty(this, "taskManager", new MediaTaskManager()); _defineProperty(this, "pickers", []); _defineProperty(this, "pickerPromises", []); _defineProperty(this, "getMediaOptions", function () { return _this.options; }); _defineProperty(this, "isMediaSchemaNode", function (_ref) { var _this$mediaOptions; var type = _ref.type; var _this$view$state$sche = _this.view.state.schema.nodes, mediaInline = _this$view$state$sche.mediaInline, mediaSingle = _this$view$state$sche.mediaSingle, media = _this$view$state$sche.media; if (getMediaFeatureFlag('mediaInline', (_this$mediaOptions = _this.mediaOptions) === null || _this$mediaOptions === void 0 ? void 0 : _this$mediaOptions.featureFlags)) { return type === mediaSingle || type === media || type === mediaInline; } return type === mediaSingle; }); // callback to flag that a node has been inserted _defineProperty(this, "onNodeInserted", function (id, selectionPosition) { _this.lastAddedMediaSingleFileIds.unshift({ id: id, selectionPosition: selectionPosition }); }); /** * we insert a new file by inserting a initial state for that file. * * called when we insert a new file via the picker (connected via pickerfacade) */ _defineProperty(this, "insertFile", function (mediaState, onMediaStateChanged, pickerType, insertMediaVia) { var _this$pluginInjection, _mediaState$collectio, _this$pluginInjection2; var state = _this.view.state; var editorAnalyticsAPI = (_this$pluginInjection = _this.pluginInjectionApi) === null || _this$pluginInjection === void 0 || (_this$pluginInjection = _this$pluginInjection.analytics) === null || _this$pluginInjection === void 0 ? void 0 : _this$pluginInjection.actions; var mediaStateWithContext = _objectSpread(_objectSpread({}, mediaState), {}, { contextId: _this.contextIdentifierProvider ? _this.contextIdentifierProvider.objectId : undefined }); var collection = (_mediaState$collectio = mediaState.collection) !== null && _mediaState$collectio !== void 0 ? _mediaState$collectio : _this.collectionFromProvider(); if (collection === undefined) { return; } // We need to dispatch the change to event dispatcher only for successful files if (mediaState.status !== 'error') { _this.updateAndDispatch({ allUploadsFinished: false }); } if (_this.uploadInProgressSubscriptions.length > 0 && !_this.uploadInProgressSubscriptionsNotified) { _this.uploadInProgressSubscriptions.forEach(function (fn) { return fn(true); }); _this.uploadInProgressSubscriptionsNotified = true; } switch (getMediaNodeInsertionType(state, _this.mediaOptions, mediaStateWithContext.fileMimeType)) { case 'inline': insertMediaInlineNode(editorAnalyticsAPI)(_this.view, mediaStateWithContext, collection, _this.allowInlineImages, _this.getInputMethod(pickerType), insertMediaVia); break; case 'block': // read width state right before inserting to get up-to-date and define values var widthPluginState = (_this$pluginInjection2 = _this.pluginInjectionApi) === null || _this$pluginInjection2 === void 0 || (_this$pluginInjection2 = _this$pluginInjection2.width) === null || _this$pluginInjection2 === void 0 ? void 0 : _this$pluginInjection2.sharedState.currentState(); insertMediaSingleNode(_this.view, mediaStateWithContext, _this.getInputMethod(pickerType), collection, _this.mediaOptions && _this.mediaOptions.alignLeftOnInsert, widthPluginState, editorAnalyticsAPI, _this.onNodeInserted, insertMediaVia, _this.mediaOptions && _this.mediaOptions.allowPixelResizing); break; case 'group': insertMediaGroupNode(editorAnalyticsAPI)(_this.view, [mediaStateWithContext], collection, _this.getInputMethod(pickerType), insertMediaVia); break; } // do events when media state changes onMediaStateChanged(_this.handleMediaState); // handle waiting for upload complete var isEndState = function isEndState(state) { return state.status && MEDIA_RESOLVED_STATES.indexOf(state.status) !== -1; }; if (!isEndState(mediaStateWithContext)) { var uploadingPromise = new Promise(function (resolve) { onMediaStateChanged(function (newState) { // When media item reaches its final state, remove listener and resolve if (isEndState(newState)) { resolve(newState); } }); }); if (fg('platform_editor_media_disable_save_during_upload')) { _this.taskManager.addPendingTask(uploadingPromise, mediaStateWithContext.id).then(function () { _this.updateAndDispatch({ allUploadsFinished: true }); _this.waitForPendingTasks().then(function () { if (_this.uploadInProgressSubscriptions.length > 0 && _this.uploadInProgressSubscriptionsNotified) { _this.uploadInProgressSubscriptions.forEach(function (fn) { return fn(false); }); _this.uploadInProgressSubscriptionsNotified = false; } }); }); } else { _this.taskManager.addPendingTask(uploadingPromise, mediaStateWithContext.id).then(function () { _this.updateAndDispatch({ allUploadsFinished: true }); }); } } // refocus the view var view = _this.view; if (!view.hasFocus()) { view.focus(); } if (isEndState(mediaStateWithContext) || !fg('platform_editor_media_disable_save_during_upload')) { _this.waitForPendingTasks().then(function () { if (_this.uploadInProgressSubscriptions.length > 0 && _this.uploadInProgressSubscriptionsNotified) { _this.uploadInProgressSubscriptions.forEach(function (fn) { return fn(false); }); _this.uploadInProgressSubscriptionsNotified = false; } }); } _this.selectLastAddedMediaNode(); }); // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any _defineProperty(this, "addPendingTask", function (task) { _this.taskManager.addPendingTask(task); }); _defineProperty(this, "splitMediaGroup", function () { return splitMediaGroup(_this.view); }); _defineProperty(this, "onPopupPickerClose", function () { _this.onPopupToggleCallback(false); }); _defineProperty(this, "showMediaPicker", function () { if (_this.openMediaPickerBrowser) { return _this.openMediaPickerBrowser(); } _this.onPopupToggleCallback(true); }); _defineProperty(this, "setBrowseFn", function (browseFn) { _this.openMediaPickerBrowser = browseFn; }); _defineProperty(this, "onPopupToggle", function (onPopupToggleCallback) { _this.onPopupToggleCallback = onPopupToggleCallback; }); /** * Returns a promise that is resolved after all pending operations have been finished. * An optional timeout will cause the promise to reject if the operation takes too long * * NOTE: The promise will resolve even if some of the media have failed to process. */ _defineProperty(this, "waitForPendingTasks", this.taskManager.waitForPendingTasks); /** * Called from React UI Component when user clicks on "Delete" icon * inside of it */ _defineProperty(this, "handleMediaNodeRemoval", function (node, getPos) { var getNode = node; if (!getNode) { var pos = getPos(); if (typeof pos !== 'number') { return; } getNode = _this.view.state.doc.nodeAt(pos); } removeMediaNode(_this.view, getNode, getPos); }); _defineProperty(this, "getIdentifierKey", function (identifier) { if (identifier.mediaItemType === 'file') { return identifier.id; } else { return identifier.dataURI; } }); _defineProperty(this, "trackMediaNodeAddition", function (node) { var _this$identifierCount; var identifier = getIdentifier(node.attrs); var key = _this.getIdentifierKey(identifier); var _ref2 = (_this$identifierCount = _this.identifierCount.get(key)) !== null && _this$identifierCount !== void 0 ? _this$identifierCount : { count: 0 }, count = _ref2.count; if (count === 0) { _this.taskManager.resumePendingTask(key); } _this.identifierCount.set(key, { identifier: identifier, count: count + 1 }); }); _defineProperty(this, "trackMediaNodeRemoval", function (node) { var _this$identifierCount2; var identifier = getIdentifier(node.attrs); var key = _this.getIdentifierKey(identifier); var _ref3 = (_this$identifierCount2 = _this.identifierCount.get(key)) !== null && _this$identifierCount2 !== void 0 ? _this$identifierCount2 : { count: 0 }, count = _ref3.count; if (count === 1) { _this.taskManager.cancelPendingTask(key); } _this.identifierCount.set(key, { identifier: identifier, count: count - 1 }); }); _defineProperty(this, "isIdentifierInEditorScope", function (identifier) { var key = _this.getIdentifierKey(identifier); // rely on has instead of count > 0 because if the user cuts and pastes the same media // the count will temporarily be 0 but the media is still in the scope of editor. return !_this.outOfEditorScopeIdentifierMap.has(key) && _this.identifierCount.has(key); }); /** * This is used in on Paste of media, this tracks which if the pasted media originated from a outside the editor * i.e. the pasted media was not uplaoded to the current editor. * This is to enable mediaShallowCopySope to enable only shallow copying media referenced within the edtior */ _defineProperty(this, "trackOutOfScopeIdentifier", function (identifier) { var key = _this.getIdentifierKey(identifier); _this.outOfEditorScopeIdentifierMap.set(key, { identifier: identifier }); }); /** * Called from React UI Component on componentDidMount */ _defineProperty(this, "handleMediaNodeMount", function (node, getPos) { _this.trackMediaNodeAddition(node); _this.mediaNodes.unshift({ node: node, getPos: getPos }); }); /** * Called from React UI Component on componentWillUnmount and UNSAFE_componentWillReceiveProps * when React component's underlying node property is replaced with a new node */ _defineProperty(this, "handleMediaNodeUnmount", function (oldNode) { _this.trackMediaNodeRemoval(oldNode); _this.mediaNodes = _this.mediaNodes.filter(function (_ref4) { var node = _ref4.node; return oldNode !== node; }); }); _defineProperty(this, "handleMediaGroupUpdate", function (oldNodes, newNodes) { var addedNodes = newNodes.filter(function (node) { return oldNodes.every(function (oldNode) { return oldNode.attrs.id !== node.attrs.id; }); }); var removedNodes = oldNodes.filter(function (node) { return newNodes.every(function (newNode) { return newNode.attrs.id !== node.attrs.id; }); }); addedNodes.forEach(function (node) { _this.trackMediaNodeAddition(node); }); removedNodes.forEach(function (oldNode) { _this.trackMediaNodeRemoval(oldNode); }); }); _defineProperty(this, "findMediaNode", function (id) { return helpers.findMediaNode(_this, id); }); _defineProperty(this, "destroyAllPickers", function (pickers) { pickers.forEach(function (picker) { return picker.destroy(); }); _this.pickers.splice(0, _this.pickers.length); }); _defineProperty(this, "destroyPickers", function () { var pickers = _this.pickers, pickerPromises = _this.pickerPromises; // If pickerPromises and pickers are the same length // All pickers have resolved and we safely destroy them // Otherwise wait for them to resolve then destroy. if (pickerPromises.length === pickers.length) { _this.destroyAllPickers(_this.pickers); } else { Promise.all(pickerPromises).then(function (resolvedPickers) { return _this.destroyAllPickers(resolvedPickers); }); } _this.customPicker = undefined; }); _defineProperty(this, "getInputMethod", function (pickerType) { switch (pickerType) { case INPUT_METHOD.PICKER_CLOUD: return INPUT_METHOD.PICKER_CLOUD; case INPUT_METHOD.MEDIA_PICKER: return INPUT_METHOD.MEDIA_PICKER; case 'clipboard': return INPUT_METHOD.CLIPBOARD; case 'dropzone': return INPUT_METHOD.DRAG_AND_DROP; case 'browser': return INPUT_METHOD.BROWSER; } return; }); _defineProperty(this, "updateMediaSingleNodeAttrs", function (id, attrs) { var view = _this.view; if (!view) { return; } return updateMediaNodeAttrs(id, attrs)(view.state, view.dispatch); }); _defineProperty(this, "handleMediaState", function (state) { switch (state.status) { case 'error': var uploadErrorHandler = _this.options.uploadErrorHandler; if (uploadErrorHandler) { uploadErrorHandler(state); } break; } }); _defineProperty(this, "removeSelectedMediaContainer", function () { var view = _this.view; var selectedNode = _this.selectedMediaContainerNode(); if (!selectedNode) { return false; } var from = view.state.selection.from; // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion removeMediaNode(view, selectedNode.firstChild, function () { return from + 1; }); return true; }); _defineProperty(this, "selectedMediaContainerNode", function () { var _this$view; var selection = (_this$view = _this.view) === null || _this$view === void 0 || (_this$view = _this$view.state) === null || _this$view === void 0 ? void 0 : _this$view.selection; if (selection instanceof NodeSelection && _this.isMediaSchemaNode(selection.node)) { return selection.node; } return; }); _defineProperty(this, "handleDrag", function (dragState) { var isActive = dragState === 'enter'; if (_this.showDropzone === isActive) { return; } _this.showDropzone = isActive; var _this$view2 = _this.view, dispatch = _this$view2.dispatch, state = _this$view2.state; var tr = state.tr, selection = state.selection, doc = state.doc; var _state$schema$nodes = state.schema.nodes, media = _state$schema$nodes.media, mediaGroup = _state$schema$nodes.mediaGroup; // Workaround for wrong upload position // @see https://product-fabric.atlassian.net/browse/MEX-2457 // If the media node is the last selectable item in the current cursor position and it is located within a mediaGroup, // we relocate the cursor to the first child of the mediaGroup. var pos = Math.max(0, selection.$from.pos - 1); var sel = Selection.findFrom(doc.resolve(pos), -1); if (sel && findSelectedNodeOfType(media)(sel)) { var parent = findParentNodeOfType(mediaGroup)(sel); if (parent) { tr.setSelection(NodeSelection.create(tr.doc, parent.start)); } } // Trigger state change to be able to pick it up in the decorations handler dispatch(tr); }); this.options = options; this.mediaOptions = mediaOptions; this.dispatch = _dispatch; this.pluginInjectionApi = pluginInjectionApi; this.waitForMediaUpload = options.waitForMediaUpload === undefined ? true : options.waitForMediaUpload; var nodes = _state.schema.nodes; assert(nodes.media && (nodes.mediaGroup || nodes.mediaSingle), 'Editor: unable to init media plugin - media or mediaGroup/mediaSingle node absent in schema'); if (mediaOptions !== null && mediaOptions !== void 0 && mediaOptions.syncProvider) { this.setMediaProvider(mediaOptions === null || mediaOptions === void 0 ? void 0 : mediaOptions.syncProvider); } else if (mediaOptions !== null && mediaOptions !== void 0 && mediaOptions.provider) { this.setMediaProvider(mediaOptions === null || mediaOptions === void 0 ? void 0 : mediaOptions.provider); } if (fg('platform_editor_remove_media_inline_feature_flag')) { var _this$mediaOptions2; if ((_this$mediaOptions2 = this.mediaOptions) !== null && _this$mediaOptions2 !== void 0 && _this$mediaOptions2.allowMediaInlineImages) { this.allowInlineImages = true; } } else { var _this$mediaOptions3, _this$mediaOptions4; if (mediaInlineImagesEnabled(getMediaFeatureFlag('mediaInline', (_this$mediaOptions3 = this.mediaOptions) === null || _this$mediaOptions3 === void 0 ? void 0 : _this$mediaOptions3.featureFlags), (_this$mediaOptions4 = this.mediaOptions) === null || _this$mediaOptions4 === void 0 ? void 0 : _this$mediaOptions4.allowMediaInlineImages)) { this.allowInlineImages = true; } } this.errorReporter = options.errorReporter || new ErrorReporter(); this.singletonCreatedAt = (performance || Date).now(); } return _createClass(MediaPluginStateImplementation, [{ key: "clone", value: function clone() { var _originalTarget; var clonedAt = (performance || Date).now(); // Prevent double wrapping // If clone is repeatedly called, we want to proxy the underlying MediaPluginStateImplementation target, rather than the proxy itself // If we proxy the proxy, then calling get in future will need to recursively unwrap proxies to find the original target, which causes performance issues // Instead, we check if there is an original target stored on "this", and if so, we use that as the proxy target instead return new Proxy((_originalTarget = this.originalTarget) !== null && _originalTarget !== void 0 ? _originalTarget : this, { get: function get(target, prop, receiver) { if (prop === 'singletonCreatedAt') { return clonedAt; } if (prop === 'originalTarget') { return target; } return Reflect.get(target, prop, receiver); } }); } }, { key: "subscribeToUploadInProgressState", value: function subscribeToUploadInProgressState(fn) { this.uploadInProgressSubscriptions.push(fn); } }, { key: "unsubscribeFromUploadInProgressState", value: function unsubscribeFromUploadInProgressState(fn) { this.uploadInProgressSubscriptions = this.uploadInProgressSubscriptions.filter(function (subscribedFn) { return subscribedFn !== fn; }); } }, { key: "setMediaProvider", value: function () { var _setMediaProvider = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(mediaProvider) { var viewMediaClientConfig, wrappedError, view, allowsUploads; return _regeneratorRuntime.wrap(function _callee$(_context) { while (1) switch (_context.prev = _context.next) { case 0: if (!(this.previousMediaProvider === mediaProvider)) { _context.next = 2; break; } return _context.abrupt("return"); case 2: this.previousMediaProvider = mediaProvider; if (mediaProvider) { _context.next = 8; break; } this.destroyPickers(); this.allowsUploads = false; if (!this.destroyed) { this.view.dispatch(this.view.state.tr.setMeta(stateKey, { allowsUploads: this.allowsUploads })); } return _context.abrupt("return"); case 8: _context.prev = 8; if (!(mediaProvider instanceof Promise)) { _context.next = 15; break; } _context.next = 12; return mediaProvider; case 12: this.mediaProvider = _context.sent; _context.next = 16; break; case 15: this.mediaProvider = mediaProvider; case 16: // Ignored via go/ees007 // eslint-disable-next-line @atlaskit/editor/enforce-todo-comment-format // TODO [MS-2038]: remove once context api is removed // We want to re assign the view and upload configs if they are missing for backwards compatibility // as currently integrators can pass context || mediaClientConfig if (!this.mediaProvider.viewMediaClientConfig) { viewMediaClientConfig = this.mediaProvider.viewMediaClientConfig; if (viewMediaClientConfig) { this.mediaProvider.viewMediaClientConfig = viewMediaClientConfig; } } assert(this.mediaProvider.viewMediaClientConfig, "MediaProvider promise did not resolve to a valid instance of MediaProvider - ".concat(this.mediaProvider)); _context.next = 28; break; case 20: _context.prev = 20; _context.t0 = _context["catch"](8); wrappedError = new Error("Media functionality disabled due to rejected provider: ".concat(_context.t0 instanceof Error ? _context.t0.message : String(_context.t0))); this.errorReporter.captureException(wrappedError); this.destroyPickers(); this.allowsUploads = false; if (!this.destroyed) { this.view.dispatch(this.view.state.tr.setMeta(stateKey, { allowsUploads: this.allowsUploads })); } return _context.abrupt("return"); case 28: this.mediaClientConfig = this.mediaProvider.viewMediaClientConfig; this.allowsUploads = !!this.mediaProvider.uploadMediaClientConfig; view = this.view, allowsUploads = this.allowsUploads; // make sure editable DOM node is mounted if (!this.destroyed && view && view.dom.parentNode) { // make PM plugin aware of the state change to update UI during 'apply' hook view.dispatch(view.state.tr.setMeta(stateKey, { allowsUploads: allowsUploads })); } if (!this.allowsUploads) { _context.next = 42; break; } this.uploadMediaClientConfig = this.mediaProvider.uploadMediaClientConfig; if (!(this.mediaProvider.uploadParams && this.uploadMediaClientConfig)) { _context.next = 39; break; } _context.next = 37; return this.initPickers(this.mediaProvider.uploadParams, PickerFacade); case 37: _context.next = 40; break; case 39: this.destroyPickers(); case 40: _context.next = 43; break; case 42: this.destroyPickers(); case 43: case "end": return _context.stop(); } }, _callee, this, [[8, 20]]); })); function setMediaProvider(_x) { return _setMediaProvider.apply(this, arguments); } return setMediaProvider; }() }, { key: "setIsResizing", value: function setIsResizing(isResizing) { this.isResizing = isResizing; } }, { key: "setResizingWidth", value: function setResizingWidth(width) { this.resizingWidth = width; } }, { key: "updateElement", value: function updateElement() { var newElement; var selectedContainer = this.selectedMediaContainerNode(); if (selectedContainer && this.isMediaSchemaNode(selectedContainer)) { newElement = this.getDomElement(this.view.domAtPos.bind(this.view)); if (selectedContainer.type === this.view.state.schema.nodes.mediaSingle) { this.currentMaxWidth = getMaxWidthForNestedNodeNext(this.view, this.view.state.selection.$anchor.pos) || undefined; } else { this.currentMaxWidth = undefined; } } if (this.element !== newElement) { this.element = newElement; } } }, { key: "getDomElement", value: function getDomElement(domAtPos) { var selection = this.view.state.selection; if (!(selection instanceof NodeSelection)) { return; } if (!this.isMediaSchemaNode(selection.node)) { return; } var node = findDomRefAtPos(selection.from, domAtPos); if (node) { if (!node.childNodes.length) { return node.parentNode; } return node; } return; } }, { key: "contextIdentifierProvider", get: function get() { var _this$pluginInjection3; return (_this$pluginInjection3 = this.pluginInjectionApi) === null || _this$pluginInjection3 === void 0 || (_this$pluginInjection3 = _this$pluginInjection3.contextIdentifier) === null || _this$pluginInjection3 === void 0 || (_this$pluginInjection3 = _this$pluginInjection3.sharedState.currentState()) === null || _this$pluginInjection3 === void 0 ? void 0 : _this$pluginInjection3.contextIdentifierProvider; } }, { key: "selectLastAddedMediaNode", value: function selectLastAddedMediaNode() { var _this$mediaOptions5, _this2 = this; // if preventAutoFocusOnUpload is enabled, skip auto-selection and just clear the tracking array if ((_this$mediaOptions5 = this.mediaOptions) !== null && _this$mediaOptions5 !== void 0 && _this$mediaOptions5.preventAutoFocusOnUpload) { this.lastAddedMediaSingleFileIds = []; return; } // if lastAddedMediaSingleFileIds is empty exit because there are no added media single nodes to be selected if (this.lastAddedMediaSingleFileIds.length !== 0) { this.waitForPendingTasks().then(function () { var lastTrackedAddedNode = _this2.lastAddedMediaSingleFileIds[0]; // execute selection only if selection did not change after the node has been inserted if ((lastTrackedAddedNode === null || lastTrackedAddedNode === void 0 ? void 0 : lastTrackedAddedNode.selectionPosition) === _this2.view.state.selection.from) { var lastAddedNode = _this2.mediaNodes.find(function (node) { return node.node.attrs.id === lastTrackedAddedNode.id; }); var lastAddedNodePos = lastAddedNode === null || lastAddedNode === void 0 ? void 0 : lastAddedNode.getPos(); if (lastAddedNodePos) { var _this2$view = _this2.view, dispatch = _this2$view.dispatch, state = _this2$view.state; var tr = state.tr; tr.setSelection(NodeSelection.create(tr.doc, lastAddedNodePos)); if (dispatch) { dispatch(tr); } } } // reset temp constant after uploads finished _this2.lastAddedMediaSingleFileIds = []; }); } } }, { key: "setView", value: function setView(view) { this.view = view; } }, { key: "destroy", value: function destroy() { if (this.destroyed) { return; } this.destroyed = true; var mediaNodes = this.mediaNodes; mediaNodes.splice(0, mediaNodes.length); this.removeOnCloseListener(); this.destroyPickers(); } }, { key: "initPickers", value: function () { var _initPickers = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(uploadParams, Picker) { var _this3 = this; var errorReporter, pickers, pickerPromises, pickerFacadeConfig, customPicker; return _regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) switch (_context2.prev = _context2.next) { case 0: if (!(this.destroyed || !this.uploadMediaClientConfig)) { _context2.next = 2; break; } return _context2.abrupt("return"); case 2: errorReporter = this.errorReporter, pickers = this.pickers, pickerPromises = this.pickerPromises; // create pickers if they don't exist, re-use otherwise if (pickers.length) { _context2.next = 14; break; } pickerFacadeConfig = { mediaClientConfig: this.uploadMediaClientConfig, errorReporter: errorReporter }; if (!this.options.customMediaPicker) { _context2.next = 13; break; } customPicker = new Picker('customMediaPicker', pickerFacadeConfig, this.options.customMediaPicker).init(); pickerPromises.push(customPicker); _context2.t0 = pickers; _context2.next = 11; return customPicker; case 11: _context2.t1 = this.customPicker = _context2.sent; _context2.t0.push.call(_context2.t0, _context2.t1); case 13: pickers.forEach(function (picker) { picker.onNewMedia(_this3.insertFile); }); case 14: // set new upload params for the pickers pickers.forEach(function (picker) { return picker.setUploadParams(uploadParams); }); case 15: case "end": return _context2.stop(); } }, _callee2, this); })); function initPickers(_x2, _x3) { return _initPickers.apply(this, arguments); } return initPickers; }() }, { key: "collectionFromProvider", value: function collectionFromProvider() { return this.mediaProvider && this.mediaProvider.uploadParams && this.mediaProvider.uploadParams.collection; } }, { key: "updateAndDispatch", value: function updateAndDispatch(props) { var _this4 = this; // update plugin state Object.keys(props).forEach(function (_key) { var key = _key; var value = props[key]; if (value !== undefined) { _this4[key] = value; } }); if (this.dispatch) { this.dispatch(stateKey, _objectSpread({}, this)); } } }]); }(); export var getMediaPluginState = function getMediaPluginState(state) { return stateKey.getState(state); }; export var createPlugin = function createPlugin(_schema, options, getIntl, pluginInjectionApi, nodeViewPortalProviderAPI, dispatch, mediaOptions) { var intl = getIntl(); return new SafePlugin({ state: { init: function init(_config, state) { return new MediaPluginStateImplementation(state, options, mediaOptions, dispatch, pluginInjectionApi); }, apply: function apply(tr, pluginState) { var _tr$getMeta; var isResizing = tr.getMeta(MEDIA_PLUGIN_IS_RESIZING_KEY); var resizingWidth = tr.getMeta(MEDIA_PLUGIN_RESIZING_WIDTH_KEY); var mediaProvider = (_tr$getMeta = tr.getMeta(stateKey)) === null || _tr$getMeta === void 0 ? void 0 : _tr$getMeta.mediaProvider; // Yes, I agree with you; this approach, using the clone() fuction, below is horrifying. // However, we needed to implement this workaround to solve the singleton Media PluginState. // The entire PluginInjectionAPI relies on the following axiom: "A PluginState that reflects a new EditorState.". We can not have the mutable singleton instance for all EditorState. // Unfortunately, we can't implement a proper fix for this media state situation. So, we are faking a new object using a Proxy instance. var nextPluginState = pluginState; if (isResizing !== undefined) { pluginState.setIsResizing(isResizing); nextPluginState = nextPluginState.clone(); } if (mediaProvider) { pluginState.setMediaProvider(mediaProvider); } if (resizingWidth) { pluginState.setResizingWidth(resizingWidth); nextPluginState = nextPluginState.clone(); } // remap editing media single position if we're in collab if (typeof pluginState.editingMediaSinglePos === 'number') { pluginState.editingMediaSinglePos = tr.mapping.map(pluginState.editingMediaSinglePos); nextPluginState = nextPluginState.clone(); } var meta = tr.getMeta(stateKey); if (meta) { var allowsUploads = meta.allowsUploads; pluginState.updateAndDispatch({ allowsUploads: typeof allowsUploads === 'undefined' ? pluginState.allowsUploads : allowsUploads }); nextPluginState = nextPluginState.clone(); } // ACTIONS switch (meta === null || meta === void 0 ? void 0 : meta.type) { case ACTIONS.SHOW_MEDIA_VIEWER: pluginState.mediaViewerSelectedMedia = meta.mediaViewerSelectedMedia; pluginState.isMediaViewerVisible = meta.isMediaViewerVisible; nextPluginState = nextPluginState.clone(); break; case ACTIONS.HIDE_MEDIA_VIEWER: pluginState.mediaViewerSelectedMedia = undefined; pluginState.isMediaViewerVisible = meta.isMediaViewerVisible; nextPluginState = nextPluginState.clone(); break; case ACTIONS.TRACK_MEDIA_PASTE: var identifier = meta.identifier; var isIdentifierInEditorScope = pluginState.isIdentifierInEditorScope(identifier); if (!isIdentifierInEditorScope && isFileIdentifier(identifier)) { pluginState.trackOutOfScopeIdentifier(identifier); nextPluginState = pluginState.clone(); } break; } // NOTE: We're not calling passing new state to the Editor, because we depend on the view.state reference // throughout the lifetime of view. We injected the view into the plugin state, because we dispatch() // transformations from within the plugin state (i.e. when adding a new file). return nextPluginState; } }, appendTransaction: function appendTransaction(transactions, _oldState, newState) { var _iterator = _createForOfIteratorHelper(transactions), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var transaction = _step.value; var isSelectionOnMediaInsideMediaSingle = transaction.selectionSet && isNodeSelection(transaction.selection) && transaction.selection.node.type === newState.schema.nodes.media && transaction.selection.$anchor.parent.type === newState.schema.nodes.mediaSingle; // Note: this causes an additional transaction when selecting a media node // through clicking on it with the cursor. if (isSelectionOnMediaInsideMediaSingle) { // If a selection has been placed on a media inside a media single, // we shift it to the media single parent as other code is opinionated about // the selection landing there. In particular the caption insertion and selection // action. return newState.tr.setSelection(NodeSelection.create(newState.doc, transaction.selection.$from.pos - 1)); } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return; }, key: stateKey, view: function view(_view) { var pluginState = getMediaPluginState(_view.state); pluginState.setView(_view); pluginState.updateElement(); return { update: function update() { pluginState.updateElement(); } }; }, props: { decorations: function decorations(state) { // Use this to indicate that the media node is selected var mediaNodes = []; var schema = state.schema, $anchor = state.selection.$anchor, doc = state.doc; // Find any media nodes in the current selection if (state.selection instanceof TextSelection || state.selection instanceof AllSelection || state.selection instanceof CellSelection) { doc.nodesBetween(state.selection.from, state.selection.to, function (node, pos) { if (node.type === schema.nodes.media) { mediaNodes.push(Decoration.node(pos, pos + node.nodeSize, {}, { type: 'media', selected: true })); return false; } return true; }); } else if (state.selection instanceof NodeSelection) { var _state$selection = state.selection, node = _state$selection.node, $from = _state$selection.$from; if (node.type === schema.nodes.mediaSingle || node.type === schema.nodes.mediaGroup) { doc.nodesBetween($from.pos, $from.pos + node.nodeSize, function (mediaNode, mediaPos) { if (mediaNode.type === schema.nodes.media) { mediaNodes.push(Decoration.node(mediaPos, mediaPos + mediaNode.nodeSize, {}, { type: 'media', selected: true })); return false; } return true; }); } } var pluginState = getMediaPluginState(state); if (!pluginState.showDropzone) { return DecorationSet.create(state.doc, mediaNodes); } // When a media is already selected if (state.selection instanceof NodeSelection) { var _node = state.selection.node; if (_node.type === schema.nodes.mediaSingle) { var deco = Decoration.node(state.selection.from, state.selection.to, { class: 'richMedia-selected' }); return DecorationSet.create(state.doc, [deco].concat(mediaNodes)); } return DecorationSet.create(state.doc, mediaNodes); } var pos = $anchor.pos; if ($anchor.parent.type !== schema.nodes.paragraph && $anchor.parent.type !== schema.nodes.codeBlock) { pos = insertPoint(state.doc, pos, schema.nodes.mediaGroup); } if (pos === null || pos === undefined) { return DecorationSet.create(state.doc, mediaNodes); } // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead var dropPlaceholderKey = uuid(); var dropPlaceholders = [Decoration.widget(pos, function () { return createDropPlaceholder(intl, nodeViewPortalProviderAPI, dropPlaceholderKey, mediaOptions && mediaOptions.allowDropzoneDropLine); }, { key: 'drop-placeholder', destroy: function destroy(elem) { if (elem instanceof HTMLElement) { nodeViewPortalProviderAPI.remove(dropPlaceholderKey); } } })].concat(mediaNodes); return DecorationSet.create(state.doc, dropPlaceholders); }, nodeViews: options.nodeViews, handleTextInput: function handleTextInput(view, from, to, text) { var selection = view.state.selection; if (text === ' ' && selection instanceof NodeSelection && selection.node.type.name === 'mediaSingle') { var _stateKey$getState; var videoControlsWrapperRef = (_stateKey$getState = stateKey.getState(view.state)) === null || _stateKey$getState === void 0 ? void 0 : _stateKey$getState.element; var videoControls = videoControlsWrapperRef === null || videoControlsWrapperRef === void 0 ? void 0 : videoControlsWrapperRef.querySelectorAll('button, [tabindex]:not([tabindex="-1"])'); if (videoControls) { var isVideoControl = Array.from(videoControls).some(function (videoControl) { // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage return document.activeElement === videoControl; }); if (isVideoControl) { return true; } } } getMediaPluginState(view.state).splitMediaGroup(); return false; }, handleClick: function handleClick(_editorView, _pos, event) { var _event$target; // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting var clickedInsideCaptionPlaceholder = (_event$target = event.target) === null || _event$target === void 0 ? void 0 : _event$target.closest("[data-id=\"".concat(CAPTION_PLACEHOLDER_ID, "\"]")); var browser = getBrowserInfo(); // Workaround for Chrome given a regression introduced in prosemirror-view@1.18.6 // Returning true prevents that updateSelection() is getting called in the commit below: // @see https://github.com/ProseMirror/prosemirror-view/compare/1.18.5...1.18.6 if ((browser.chrome || browser.safari) && clickedInsideCaptionPlaceholder) { return true; } // Workaound for iOS 16 Caption selection issue // @see https://product-fabric.atlassian.net/browse/MEX-2012 if (browser.ios) { var _event$target2; // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting return !!((_event$target2 = event.target) !== null && _event$target2 !== void 0 && _event$target2.closest("[class=\"".concat(MEDIA_CONTENT_WRAP_CLASS_NAME, "\"]"))); } return false; }, handleDoubleClickOn: function handleDoubleClickOn(view) { var _pluginState$mediaOpt, _pluginInjectionApi$e; // Check if media view