UNPKG

@atlaskit/editor-plugin-media

Version:

Media plugin for @atlaskit/editor-core

1,478 lines • 73.8 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 { v4 as uuid } from 'uuid';
import { SetAttrsStep } from '@atlaskit/adf-schema/steps/set-attrs';
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics/types/enums';
import { getBrowserInfo } from '@atlaskit/editor-common/browser';
import { ErrorReporter } from '@atlaskit/editor-common/error-reporter';
import { mediaInlineImagesEnabled } from '@atlaskit/editor-common/media-inline/utils';
import { CAPTION_PLACEHOLDER_ID } from '@atlaskit/editor-common/media-single/constants';
import { getMaxWidthForNestedNodeNext } from '@atlaskit/editor-common/media-single/utils';
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
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/is-file-identifier';
import { getMediaFeatureFlag } from '@atlaskit/media-common';
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
import { createMediaNodeUpdater } from '../nodeviews/mediaNodeUpdater';
// 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 { isRemixGeneratedMedia } from '../pm-plugins/utils/remix-media';
import DropPlaceholder from '../ui/Media/DropPlaceholder';
import { MediaDropIndicator } from '../ui/Media/MediaDropIndicator';
import { ACTIONS } from './actions';
import { isMediaDropPositionEnabled } from './media-drop-position-experiment';
import { findMediaDropPosition, findMediaDropReplacement, MediaDropTargets, mediaDropSelection } from './media-drop-target';
import { MediaTaskManager } from './mediaTaskManager';
import PickerFacade from './picker-facade';
import { stateKey } from './plugin-key';
import { reportMediaViewerItem } from './utils/report-media-viewer-item';
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, inline) {
  var usePointerIndicator = isMediaDropPositionEnabled();
  // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
  var dropPlaceholder = document.createElement(usePointerIndicator ? 'span' : 'div');
  var createElement = React.createElement;
  if (usePointerIndicator) {
    dropPlaceholder.dataset.mediaDropPlaceholder = 'true';
    dropPlaceholder.style.display = 'contents';
    nodeViewPortalProviderAPI.render(function () {
      return createElement(MediaDropIndicator, {
        inline: inline !== null && inline !== void 0 ? inline : false
      });
    }, dropPlaceholder, dropPlaceholderKey);
    return dropPlaceholder;
  }
  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,
      _this$mediaOptions2,
      _this$mediaOptions3;
    _classCallCheck(this, MediaPluginStateImplementation);
    _defineProperty(this, "allowsUploads", false);
    _defineProperty(this, "uploadStatus", 'pending');
    _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 () {});
    // When non-null, holds the file ID of the media node being replaced. Used both as a
    // flag (non-null = replace mode) and as a cross-check to ensure the correct node is
    // updated if the selection moves between the picker opening and the file being picked.
    _defineProperty(this, "replaceMediaFileId", null);
    // The display height (in pixels) of the mediaSingle being replaced, computed at replace time
    // from its display width and the old media node's intrinsic aspect ratio.
    // Used by the nodeview to recompute display width from the new file's aspect ratio after
    // dimensions are fetched, preserving visual height rather than visual width.
    _defineProperty(this, "replaceMediaTargetDisplayHeight", null);
    _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 _mediaState$fileMimeT, _this$pluginInjection, _mediaState$collectio, _this$pluginInjection3, _this$dropTargets3;
      var dropTargets = _this.dropTargets;
      if (dropTargets && _this.destroyed) {
        return;
      }
      var dropPos = dropTargets === null || dropTargets === void 0 ? void 0 : dropTargets.positionFor(mediaState.id);
      var replacement = dropPos !== undefined && (_mediaState$fileMimeT = mediaState.fileMimeType) !== null && _mediaState$fileMimeT !== void 0 && _mediaState$fileMimeT.startsWith('image/') ? dropTargets === null || dropTargets === void 0 ? void 0 : dropTargets.replacementFor(mediaState.id, _this.view.state.doc) : undefined;
      if (dropPos !== undefined) {
        _this.replaceMediaFileId = null;
        _this.replaceMediaTargetDisplayHeight = null;
        var selection = replacement ? NodeSelection.create(_this.view.state.doc, replacement.pos) : mediaDropSelection(_this.view.state.doc, dropPos);
        _this.view.dispatch(_this.view.state.tr.setSelection(selection));
      }
      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) {
        var _this$dropTargets;
        (_this$dropTargets = _this.dropTargets) === null || _this$dropTargets === void 0 || _this$dropTargets.inserted(mediaState.id);
        return;
      }

      // If replace mode was set but the selection has moved away from a mediaSingle
      // (e.g. the user cancelled the replace picker and then inserted media elsewhere),
      // clear replace state so this insertion proceeds as a normal insert.
      if (_this.replaceMediaFileId !== null) {
        var _state$selection$node;
        var mediaSingle = state.schema.nodes.mediaSingle;
        var isStillOnTargetMedia = state.selection instanceof NodeSelection && state.selection.node.type === mediaSingle && ((_state$selection$node = state.selection.node.firstChild) === null || _state$selection$node === void 0 ? void 0 : _state$selection$node.attrs.id) === _this.replaceMediaFileId;
        if (!isStillOnTargetMedia) {
          _this.replaceMediaFileId = null;
          _this.replaceMediaTargetDisplayHeight = null;
        }
      }

      // 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;
      }

      // Replace mode: if a media node is being replaced, update its attrs in-place
      // rather than inserting a new node. This preserves layout, width, and caption.
      if (_this.replaceMediaFileId !== null || replacement !== undefined) {
        var _mediaState$fileMimeT2, _mediaState$fileName, _mediaState$fileSize;
        // Clear replace mode immediately so subsequent insertions behave normally
        _this.replaceMediaFileId = null;
        var currentState = _this.view.state;
        var mediaSinglePos = currentState.selection.from;
        var mediaPos = mediaSinglePos + 1;
        var mediaNode = currentState.doc.nodeAt(mediaPos);
        if (!mediaNode || mediaNode.type.name !== 'media') {
          return;
        }

        // Alt text that Rovo Remix generated describes the image being replaced, so clear it.
        // Alt text on other images is kept. The experiment is only checked when there is
        // Remix alt text to clear.
        var shouldClearRemixAlt = Boolean(mediaNode.attrs.alt) && isRemixGeneratedMedia(mediaNode) && isExperimentEnabled('ccaig_clear-remix-alt-on-replace');

        // Build a single transaction that:
        // 1. Updates the media child node attrs (new file identity + cleared dimensions)
        // 2. Re-creates the NodeSelection so the toolbar picks up the fresh node
        var tr = currentState.tr;
        tr.step(new SetAttrsStep(mediaPos, _objectSpread({
          id: mediaState.id,
          collection: collection,
          type: 'file',
          __fileMimeType: (_mediaState$fileMimeT2 = mediaState.fileMimeType) !== null && _mediaState$fileMimeT2 !== void 0 ? _mediaState$fileMimeT2 : null,
          __fileName: (_mediaState$fileName = mediaState.fileName) !== null && _mediaState$fileName !== void 0 ? _mediaState$fileName : null,
          __fileSize: (_mediaState$fileSize = mediaState.fileSize) !== null && _mediaState$fileSize !== void 0 ? _mediaState$fileSize : null,
          __mediaTraceId: null,
          // Clear intrinsic dimensions — they'll be fetched once the file
          // is processed and applied via updateDimensions in a single tx
          // with the height-preserving mediaSingle width adjustment.
          width: null,
          height: null
        }, shouldClearRemixAlt ? {
          alt: ''
        } : {})));
        // Re-create the selection so the floating toolbar picks up
        // the updated node and renders the full set of controls.
        tr.setSelection(NodeSelection.create(tr.doc, mediaSinglePos));
        tr.setMeta('scrollIntoView', false);
        _this.view.dispatch(tr);

        // Still register the state-change listener so upload completion is tracked
        onMediaStateChanged(_this.handleMediaState);
        var _isEndState = function _isEndState(state) {
          return state.status && MEDIA_RESOLVED_STATES.includes(state.status);
        };

        // After the file finishes uploading/processing, trigger a dimension fetch.
        // getRemoteDimensions may fail if called too early (isImageRepresentationReady
        // returns false while processing), so we wait for the ready state first.
        var triggerDimensionFetch = function triggerDimensionFetch() {
          // Find the media node in the doc by id and create a temporary
          // MediaNodeUpdater to fetch and apply dimensions
          var editorState = _this.view.state;
          var mediaSingleType = editorState.schema.nodes.mediaSingle;
          var mediaChildNode = null;
          editorState.doc.descendants(function (node) {
            if (mediaChildNode) {
              return false;
            }
            if (node.type === mediaSingleType) {
              var child = node.firstChild;
              if (child && child.attrs.id === mediaState.id) {
                mediaChildNode = child;
              }
            }
            return true;
          });
          if (mediaChildNode) {
            var _this$pluginInjection2;
            var updater = createMediaNodeUpdater({
              view: _this.view,
              mediaProvider: _this.mediaProvider ? Promise.resolve(_this.mediaProvider) : undefined,
              contextIdentifierProvider: _this.contextIdentifierProvider ? Promise.resolve(_this.contextIdentifierProvider) : undefined,
              node: mediaChildNode,
              isMediaSingle: true,
              lineLength: (_this$pluginInjection2 = _this.pluginInjectionApi) === null || _this$pluginInjection2 === void 0 || (_this$pluginInjection2 = _this$pluginInjection2.width) === null || _this$pluginInjection2 === void 0 || (_this$pluginInjection2 = _this$pluginInjection2.sharedState.currentState()) === null || _this$pluginInjection2 === void 0 ? void 0 : _this$pluginInjection2.lineLength
            });
            updater.getRemoteDimensions().then(function (dims) {
              if (dims) {
                updater.updateDimensions(dims);
              }
            }).catch(function () {
              // Silently ignore — if dimensions can't be fetched (e.g. network error),
              // the image will render at its current size without the height-preserving
              // width adjustment. This is an acceptable degraded experience.
            });
          }
        };
        if (!_isEndState(mediaStateWithContext)) {
          var uploadingPromise = new Promise(function (resolve) {
            onMediaStateChanged(function (newState) {
              if (_isEndState(newState)) {
                resolve(newState);
              }
            });
          });
          _this.taskManager.addPendingTask(uploadingPromise, mediaStateWithContext.id).then(function () {
            _this.updateAndDispatch({
              allUploadsFinished: true
            });
            triggerDimensionFetch();
          });
        } else {
          // File is already in a resolved state — fetch dimensions immediately
          triggerDimensionFetch();
        }
        if (dropPos !== undefined) {
          var _this$dropTargets2;
          (_this$dropTargets2 = _this.dropTargets) === null || _this$dropTargets2 === void 0 || _this$dropTargets2.inserted(mediaState.id);
        }
        var _view = _this.view;
        if (!_view.hasFocus()) {
          _view.focus();
        }
        return;
      }
      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$pluginInjection3 = _this.pluginInjectionApi) === null || _this$pluginInjection3 === void 0 || (_this$pluginInjection3 = _this$pluginInjection3.width) === null || _this$pluginInjection3 === void 0 ? void 0 : _this$pluginInjection3.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, dropPos);
          break;
      }
      (_this$dropTargets3 = _this.dropTargets) === null || _this$dropTargets3 === void 0 || _this$dropTargets3.inserted(mediaState.id);

      // 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);
            }
          });
        });
        _this.taskManager.addPendingTask(_uploadingPromise, mediaStateWithContext.id).then(function () {
          _this.updateAndDispatch({
            allUploadsFinished: true
          });
        });
      }

      // refocus the view
      var view = _this.view;
      if (!view.hasFocus()) {
        view.focus();
      }
      _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);
    });
    /**
     * Opens the media picker in "replace" mode. The next file selected/uploaded
     * will replace the currently selected mediaSingle node's media child in-place,
     * preserving layout, width, and caption.
     *
     * The display height is computed and stored so that after the new file's intrinsic
     * dimensions are fetched, the mediaSingle display width can be adjusted to maintain
     * visual height stability rather than width stability.
     */
    _defineProperty(this, "showMediaPickerForReplace", function () {
      var _this$pluginInjection4, _this$pluginInjection5;
      var state = _this.view.state;
      var mediaSingle = state.schema.nodes.mediaSingle;
      var selection = state.selection;

      // Only activate replace mode when a mediaSingle is selected
      if (!(selection instanceof NodeSelection) || selection.node.type !== mediaSingle) {
        return;
      }
      var mediaSingleNode = selection.node;
      var mediaNode = mediaSingleNode.firstChild;
      if (!mediaNode) {
        return;
      }

      // Store the current media node's id so insertFile can identify and replace it
      _this.replaceMediaFileId = mediaNode.attrs.id;

      // Compute and store the current display height so we can preserve it after
      // the new file's intrinsic dimensions are known.
      // displayHeight = displayWidth * (intrinsicHeight / intrinsicWidth)
      var widthAttr = mediaSingleNode.attrs.width;
      var widthType = mediaSingleNode.attrs.widthType;
      var intrinsicWidth = mediaNode.attrs.width;
      var intrinsicHeight = mediaNode.attrs.height;

      // Resolve actual pixel display width from mediaSingle attrs.
      var lineLength = (_this$pluginInjection4 = (_this$pluginInjection5 = _this.pluginInjectionApi) === null || _this$pluginInjection5 === void 0 || (_this$pluginInjection5 = _this$pluginInjection5.width) === null || _this$pluginInjection5 === void 0 || (_this$pluginInjection5 = _this$pluginInjection5.sharedState.currentState()) === null || _this$pluginInjection5 === void 0 ? void 0 : _this$pluginInjection5.lineLength) !== null && _this$pluginInjection4 !== void 0 ? _this$pluginInjection4 : 760;
      var displayWidth = null;
      if (widthAttr && widthType === 'pixel') {
        displayWidth = widthAttr;
      } else if (widthAttr) {
        // Default widthType is 'percentage' — convert to pixels
        displayWidth = widthAttr / 100 * lineLength;
      } else if (intrinsicWidth) {
        // No width set at all (never resized) — fall back to intrinsic width
        displayWidth = intrinsicWidth;
      }
      if (displayWidth && intrinsicWidth && intrinsicHeight && intrinsicWidth > 0) {
        _this.replaceMediaTargetDisplayHeight = displayWidth * (intrinsicHeight / intrinsicWidth);
      } else {
        // Can't compute display height — fall back to preserving width
        _this.replaceMediaTargetDisplayHeight = null;
      }

      // Finally, show the media picker
      _this.showMediaPicker();
    });
    _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, "getDropTarget", function (event) {
      var _this$dropTargets4;
      if (!event.dataTransfer || !_this.allowsUploads || _this.destroyed) {
        return;
      }
      var hover = (_this$dropTargets4 = _this.dropTargets) === null || _this$dropTargets4 === void 0 ? void 0 : _this$dropTargets4.hover;
      if (event.target instanceof Element && event.target.closest('[data-media-drop-placeholder]') && hover !== undefined) {
        return {
          pos: hover
        };
      }
      var files = Array.from(event.dataTransfer.files);
      var fileTypes = files.length ? files.map(function (file) {
        return file.type;
      }) : Array.from(event.dataTransfer.items || []).filter(function (item) {
        return item.kind === 'file';
      }).map(function (item) {
        return item.type;
      });
      var replacement = findMediaDropReplacement(_this.view, event, fileTypes);
      if (replacement) {
        var _node$nodeSize;
        var node = _this.view.state.doc.nodeAt(replacement.pos);
        return {
          pos: replacement.pos + ((_node$nodeSize = node === null || node === void 0 ? void 0 : node.nodeSize) !== null && _node$nodeSize !== void 0 ? _node$nodeSize : 0),
          replacement: replacement
        };
      }
      var pos = findMediaDropPosition(_this.view, {
        left: event.clientX,
        top: event.clientY
      }, fileTypes.length ? fileTypes : [''], _this.mediaOptions);
      return pos === undefined ? undefined : {
        pos: pos
      };
    });
    _defineProperty(this, "handleDragOver", function (event) {
      var _dropTargets$hoverRep, _target$replacement, _dropTargets$hoverRep2, _target$replacement2;
      var dropTargets = _this.dropTargets;
      if (!dropTargets) {
        return;
      }
      var target = _this.getDropTarget(event);
      var isActive = target !== undefined;
      if (dropTargets.hover === (target === null || target === void 0 ? void 0 : target.pos) && _this.showDropzone === isActive && ((_dropTargets$hoverRep = dropTargets.hoverReplacement) === null || _dropTargets$hoverRep === void 0 ? void 0 : _dropTargets$hoverRep.pos) === (target === null || target === void 0 || (_target$replacement = target.replacement) === null || _target$replacement === void 0 ? void 0 : _target$replacement.pos) && ((_dropTargets$hoverRep2 = dropTargets.hoverReplacement) === null || _dropTargets$hoverRep2 === void 0 ? void 0 : _dropTargets$hoverRep2.media) === (target === null || target === void 0 || (_target$replacement2 = target.replacement) === null || _target$replacement2 === void 0 ? void 0 : _target$replacement2.media)) {
        return;
      }
      dropTargets.hover = target === null || target === void 0 ? void 0 : target.pos;
      dropTargets.hoverReplacement = target === null || target === void 0 ? void 0 : target.replacement;
      _this.showDropzone = isActive;
      _this.view.dispatch(_this.view.state.tr);
    });
    _defineProperty(this, "handleDrop", function (event, traceContext) {
      var dropTargets = _this.dropTargets;
      if (!dropTargets) {
        return false;
      }
      var target = _this.getDropTarget(event);
      _this.handleDrag('leave');
      if (!target) {
        return false;
      }
      return dropTargets.start(traceContext.traceId, target.pos, target.replacement);
    });
    _defineProperty(this, "handleDrag", function (dragState) {
      var dropTargets = _this.dropTargets;
      if (dropTargets) {
        if (dragState === 'enter' || _this.destroyed) {
          return;
        }
        if (!_this.showDropzone && dropTargets.hover === undefined) {
          return;
        }
        _this.showDropzone = false;
        dropTargets.clearHover();
        _this.view.dispatch(_this.view.state.tr);
        return;
      }
      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;
      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));
        }
      }
      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.syncProvider);
      if (mediaOptions.provider && isExperimentEnabled('platform_editor_ssr_toolbar_optimistic')) {
        // The synchronous SSR provider may only support viewing. Keep its view
        // config, but wait for the full provider before deciding upload availability.
        if (!this.allowsUploads) {
          this.uploadStatus = 'pending';
        }
        this.setMediaProvider(mediaOptions.provider);
      }
    } else if (mediaOptions !== null && mediaOptions !== void 0 && mediaOptions.provider) {
      this.setMediaProvider(mediaOptions === null || mediaOptions === void 0 ? void 0 : mediaOptions.provider);
    }
    if (mediaInlineImagesEnabled(getMediaFeatureFlag('mediaInline', (_this$mediaOptions2 = this.mediaOptions) === null || _this$mediaOptions2 === void 0 ? void 0 : _this$mediaOptions2.featureFlags), (_this$mediaOptions3 = this.mediaOptions) === null || _this$mediaOptions3 === void 0 ? void 0 : _this$mediaOptions3.allowMediaInlineImages)) {
      this.allowInlineImages = true;
    }
    this.errorReporter = options.errorReporter || new ErrorReporter();
    this.singletonCreatedAt = (performance || Date).now();
  }
  return _createClass(MediaPluginStateImplementation, [{
    key: "dropTargets",
    get:
    /**
     * Pending and hovered drop targets, allocated on first use and only for the test cohort, so
     * the control cohort runs none of this code and retains none of its state.
     */
    function get() {
      var _this$mediaDropTarget;
      if (!isMediaDropPositionEnabled()) {
        return undefined;
      }
      (_this$mediaDropTarget = this.mediaDropTargets) !== null && _this$mediaDropTarget !== void 0 ? _this$mediaDropTarget : this.mediaDropTargets = new MediaDropTargets();
      return this.mediaDropTargets;
    }
  }, {
    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 resolvedProvider, viewMediaClientConfig, wrappedError, view, allowsUploads, _t, _t2;
        return _regeneratorRuntime.wrap(function (_context) {
          while (1) switch (_context.prev = _context.next) {
            case 0:
              if (!(this.previousMediaProvider === mediaProvider && !(isExperimentEnabled('platform_editor_ssr_toolbar_optimistic') && this.uploadStatus === 'pending' && !mediaProvider))) {
                _context.next = 1;
                break;
              }
              return _context.abrupt("return");
            case 1:
              this.previousMediaProvider = mediaProvider;
              if (mediaProvider) {
                _context.next = 2;
                break;
              }
              this.destroyPickers();
              this.allowsUploads = false;
              this.uploadStatus = 'unavailable';
              if (!this.destroyed && (this.view || !isExperimentEnabled('platform_editor_ssr_toolbar_optimistic'))) {
                this.view.dispatch(this.view.state.tr.setMeta(stateKey, {
                  allowsUploads: this.allowsUploads
                }));
              }
              return _context.abrupt("return");
            case 2:
              _context.prev = 2;
              if (!(mediaProvider instanceof Promise)) {
                _context.next = 4;
                break;
              }
              _context.next = 3;
              return mediaProvider;
            case 3:
              _t = _context.sent;
              _context.next = 5;
              break;
            case 4:
              _t = mediaProvider;
            case 5:
              resolvedProvider = _t;
              if (!(isExperimentEnabled('platform_editor_ssr_toolbar_optimistic') && (this.destroyed || this.previousMediaProvider !== mediaProvider))) {
                _context.next = 6;
                break;
              }
              return _context.abrupt("return");
            case 6:
              this.mediaProvider = resolvedProvider;

              // 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 = 9;
              break;
            case 7:
              _context.prev = 7;
              _t2 = _context["catch"](2);
              if (!(isExperimentEnabled('platform_editor_ssr_toolbar_optimistic') && (this.destroyed || this.previousMediaProvider !== mediaProvider))) {
                _context.next = 8;
                break;
              }
              return _context.abrupt("return");
            case 8:
              wrappedError = new Error("Media functionality disabled due to rejected provider: ".concat(_t2 instanceof Error ? _t2.message : String(_t2)));
              this.errorReporter.captureException(wrappedError);
              this.destroyPickers();
              this.allowsUploads = false;
              this.uploadStatus = 'unavailable';
              if (!this.destroyed && (this.view || !isExperimentEnabled('platform_editor_ssr_toolbar_optimistic'))) {
                this.view.dispatch(this.view.state.tr.setMeta(stateKey, {
                  allowsUploads: this.allowsUploads
                }));
              }
              return _context.abrupt("return");
            case 9:
              this.mediaClientConfig = this.mediaProvider.viewMediaClientConfig;
              this.allowsUploads = !!this.mediaProvider.uploadMediaClientConfig;
              this.uploadStatus = this.allowsUploads ? 'available' : 'unavailable';
              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 = 13;
                break;
              }
              this.uploadMediaClientConfig = this.mediaProvider.uploadMediaClientConfig;
              if (!(this.mediaProvider.uploadParams && this.uploadMediaClientConfig)) {
                _context.next = 11;
                break;
              }
              _context.next = 10;
              return this.initPickers(this.mediaProvider.uploadParams, PickerFacade);
            case 10:
              _context.next = 12;
              break;
            case 11:
              this.destroyPickers();
            case 12:
              _context.next = 14;
              break;
            case 13:
              this.destroyPickers();
            case 14:
            case "end":
              return _context.stop();
          }
        }, _callee, this, [[2, 7]]);
      }));
      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$pluginInjection6;
      return (_this$pluginInjection6 = this.pluginInjectionApi) === null || _this$pluginInjection6 === void 0 || (_this$pluginInjection6 = _this$pluginInjection6.contextIdentifier) === null || _this$pluginInjection6 === void 0 || (_this$pluginInjection6 = _this$pluginInjection6.sharedState.currentState()) === null || _this$pluginInjection6 === void 0 ? void 0 : _this$pluginInjection6.contextIdentifierProvider;
    }
  }, {
    key: "selectLastAddedMediaNode",
    value: function selectLastAddedMediaNode() {
      var _this$mediaOptions4,
        _this2 = this;
      // if preventAutoFocusOnUpload is enabled, skip auto-selection and just clear the tracking array
      if ((_this$mediaOptions4 = this.mediaOptions) !== null && _this$mediaOptions4 !== void 0 && _this$mediaOptions4.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;
      if (isExperimentEnabled('platform_editor_ssr_toolbar_optimistic') && this.uploadStatus === 'pending' && !this.previousMediaProvider) {
        // No provider was configured for this client editor. SSR has no view and stays pending.
        this.uploadStatus = 'unavailable';
      }
    }
  }, {
    key: "destroy",
    value: function destroy() {
      var _this$mediaDropTarget2;
      if (this.destroyed) {
        return;
      }
      this.destroyed = true;
      (_this$mediaDropTarget2 = this.mediaDropTargets) === null || _this$mediaDropTarget2 === void 0 || _this$mediaDropTarget2.clear();
      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, _t3;
        return _regeneratorRuntime.wrap(function (_context2) {
          while (1) switch (_context2.prev = _context2.next) {
            case 0:
              if (!(this.destroyed || !this.uploadMediaClientConfig)) {
                _context2.next = 1;
                break;
              }
              return _context2.abrupt("return");
            case 1:
              errorReporter = this.errorReporter, pickers = this.pickers, pickerPromises = this.pickerPromises; // create pickers if they don't exist, re-use otherwise
              if (pickers.length) {
                _context2.next = 4;
                break;
              }
              pickerFacadeConfig = {
                mediaClientConfig: this.uploadMediaClientConfig,
                errorReporter: errorReporter
              };
              if (!this.options.customMediaPicker) {
                _context2.next = 3;
                break;
              }
              customPicker = new Picker('customMediaPicker', pickerFacadeConfig, this.options.customMediaPicker).init();
              pickerPromises.push(customPicker);
              _t3 = pickers;
              _context2.next = 2;
              return customPicker;
            case 2:
              _t3.push.call(_t3, this.customPicker = _context2.sent);
            case 3:
              pickers.forEach(function (picker) {
                picker.onNewMedia(_this3.insertFile);
              });
            case 4:
              // set new upload params for the pickers
              pickers.forEach(function (picker) {
                return picker.setUploadParams(uploadParams);
              });
            case 5:
            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 _pluginState$dropTarg, _tr$getMeta;
        (_pluginState$dropTarg = pluginState.dropTargets) === null || _pluginState$dropTarg === void 0 || _pluginState$dropTarg.map(tr.mapping);
        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(_view2) {
      var pluginState = getMediaPluginState(_view2.state);
      pluginState.setView(_view2);
      pluginState.updateElement();
      // Can't compare against `prevState`: this plugin's state is one shared object, so the previous
      // state already reads the new visibility. Remember it here instead.
      var wasMediaViewerVisible = !!pluginState.isMediaViewerVisible;
      return {
        update: function update() {
          pluginState.updateElement();
          var isMediaViewerVisible = !!pluginState.isMediaViewerVisible;
          // Report when the viewer's visibility changes: the item it opens on, or null when it
          // closes. This runs before the viewer renders, so the consumer already knows the item.
          if (isMediaViewerVisible !== wasMediaViewerVisible) {
            var _pluginState$mediaOpt, _pluginState$mediaVie;
            wasMediaViewerVisible = isMediaViewerVisible;
            reportMediaViewerItem((_pluginState$mediaOpt = pluginState.mediaOptions) === null || _pluginState$mediaOpt === void 0 ? void 0 : _pluginState$mediaOpt.mediaViewerExtensions, isMediaViewerVisible ? (_pluginState$mediaVie = pluginState.mediaViewerSelectedMedia) !== null && _pluginState$mediaVie !== void 0 ? _pluginState$mediaVie : null : null);
          }
        }
      };
    },
    props: {
      decorations: function decorations(state) {
        // Use this to indicate that the media node is selected
        var mediaNodes = [];
        var schema = state.schema,
          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);
        }
        var dropTargets = pluginState.dropTargets;
        var pos;
        if (dropTargets) {
          var replacement = dropTargets.hoverReplacementFor(state.doc);
          if (replacement) {
            var _node = state.doc.nodeAt(replacement.pos);
            if (_node) {
              return DecorationSet.create(state.doc, [Decoration.node(replacement.pos, replacement.pos + _node.nodeSize, {
                class: 'richMedia-selected'
              })].concat(mediaNodes));
            }
          }
          pos = dropTargets.hover;
        } else {
          if (state.selection instanceof NodeSelection) {
            var _node2 = state.selection.node;
            if (_node2.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 $anchor = state.selection.$anchor;
          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, dropTargets !== undefined && state.doc.resolve(pos).parent.inlineContent);
        }, {
          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$mediaOpt2, _pluginInjectionApi$e;
        // Check if media viewer is enabled
        var pluginState = getMediaPluginState(view.state);
        if (!((_pluginState$mediaOpt2 = pluginState.mediaOptions) !== null && _pluginState$mediaOpt2 !== void 0 && _pluginState$mediaOpt2.allowImagePreview)) {
          return false;
        }
        var isLivePagesViewMode = (pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$e = pluginInjectionApi.editorViewMode) === null || _pluginInjectionApi$e === void 0 || (_pluginInjectionApi$e = _pluginInjectionApi$e.sharedState.currentState()) === null || _pluginInjectionApi$e === void 0 ? void 0 : _pluginInjectionApi$e.mode) === 'view';

        // Double Click support for Media Viewer Nodes
        var maybeMediaNode = getMediaFromSupportedMediaNodesFromSelection(view.state);
        if (maybeMediaNode) {
          var _pluginInjectionApi$a;
          // If media type is video, do not open media viewer
          if (!isNodeDoubleClickSupportedInLivePagesViewMode(isLivePagesViewMode, maybeMediaNode)) {
            return false;
          }
          // Show media viewer
          pluginInjectionApi === null || pluginInjectionApi === void 0 || pluginInjectionApi.core.actions.execute(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : pluginInjectionApi.media.commands.showMediaViewer(maybeMediaNode.attrs));

          // Call analytics event
          pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$a = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a === void 0 || _pluginInjectionApi$a.actions.fireAnalyticsEvent({
            action: ACTION.OPENED,
            actionSubject: ACTION_SUBJECT.MEDIA_VIEWER,
            actionSubjectId: ACTION_SUBJECT_ID.MEDIA,
            eventType: EVENT_TYPE.UI,
            attributes: {
              nodeType: maybeMediaNode.type.name,
              inputMethod: INPUT_METHOD.DOUBLE_CLICK
            }
          });
          return true;
        }
        return false;
      },
      handleDOMEvents: {
        keydown: function keydown(view, event) {
          var selection = view.state.selection;
          if (selection instanceof NodeSelection && selection.node.type.name === 'mediaSingle') {
            // handle keydown events for video controls panel to prevent fire of rest prosemirror listeners;
            if ((event === null || event === void 0 ? void 0 : event.target) instanceof HTMLElement) {
              var a11yDefaultKeys = ['Tab', 'Space', 'Enter', 'Shift', 'Esc'];
              var targetsAndButtons = {
                button: a11yDefaultKeys,
                range: [].concat(a11yDefaultKeys, ['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight']),
                combobox: [].concat(a11yDefaultKeys, ['ArrowDown', 'ArrowUp', 'Esc']),
                slider: ['Tab', 'Shift', 'ArrowLeft', 'ArrowRight']
              };
              var targetRole = event.target.role;
              var targetType = event.target.type;
              var allowedTargets = targetRole || targetType;

              // only if targeting interactive elements fe. button, slider, range, dropdown
              if (allowedTargets && allowedTargets in targetsAndButtons) {
                var targetRelatedA11YKeys = targetsAndButtons[allowedTargets];
                var allowedKeys = new Set(targetRelatedA11YKeys);
                if (allowedKeys.has(event.key) || allowedKeys.has(event.code)) {
                  // allow event to bubble to be handled by react handlers
                  return true;
                } else {
                  // otherwise focus editor to allow setting gapCursor. (e.g.: arrowRightFromMediaSingle)
                  view.focus();
                }
              }
            }
          }
          // fire regular prosemirror listeners;
          return false;
        }
      }
    }
  });
};