@atlaskit/editor-plugin-media
Version:
Media plugin for @atlaskit/editor-core
292 lines (290 loc) • 12.6 kB
JavaScript
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
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 React, { useCallback, useEffect, useMemo, useState } from 'react';
import { injectIntl } from 'react-intl';
import { usePreviousState } from '@atlaskit/editor-common/hooks';
import { nodeViewsMessages as messages } from '@atlaskit/editor-common/media';
import { isNodeSelectedOrInRange, SelectedState, setNodeSelection } from '@atlaskit/editor-common/utils';
import EditorCloseIcon from '@atlaskit/icon/core/cross';
import { getMediaFeatureFlag } from '@atlaskit/media-common';
import { Filmstrip } from '@atlaskit/media-filmstrip';
import { fg } from '@atlaskit/platform-feature-flags';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
import { stateKey as mediaStateKey } from '../pm-plugins/plugin-key';
import { createMediaNodeUpdater } from './mediaNodeUpdater';
var getIdentifier = function getIdentifier(item) {
if (item.attrs.type === 'external') {
return {
mediaItemType: 'external-image',
dataURI: item.attrs.url
};
}
return {
id: item.attrs.id,
mediaItemType: 'file',
collectionName: item.attrs.collection
};
};
var isNodeSelected = function isNodeSelected(props) {
return function (mediaItemPos, mediaGroupPos) {
var selected = isNodeSelectedOrInRange(props.anchorPos, props.headPos, mediaGroupPos, props.nodeSize);
if (selected === SelectedState.selectedInRange) {
return true;
}
if (selected === SelectedState.selectedInside && props.anchorPos === mediaItemPos) {
return true;
}
return false;
};
};
var prepareFilmstripItem = function prepareFilmstripItem(_ref) {
var allowLazyLoading = _ref.allowLazyLoading,
allowMediaInlineImages = _ref.allowMediaInlineImages,
enableDownloadButton = _ref.enableDownloadButton,
handleMediaNodeRemoval = _ref.handleMediaNodeRemoval,
getPos = _ref.getPos,
intl = _ref.intl,
isMediaItemSelected = _ref.isMediaItemSelected,
setMediaGroupNodeSelection = _ref.setMediaGroupNodeSelection,
featureFlags = _ref.featureFlags;
return function (item, idx) {
// We declared this to get a fresh position every time
var getNodePos = function getNodePos() {
var pos = getPos();
if (typeof pos !== 'number') {
// That may seems weird, but the previous type wasn't match with the real ProseMirror code. And a lot of Media API was built expecting a number
// Because the original code would return NaN on runtime
// We are just make it explict now.
// We may run a deep investagation on Media code to figure out a better fix. But, for now, we want to keep the current behavior.
// TODO: ED-13910 - prosemirror-bump leftovers
return NaN;
}
return pos + idx + 1;
};
// Media Inline creates a floating toolbar with the same options, excludes these options if enabled
var mediaInlineOptions = function mediaInlineOptions() {
var allowMediaInline = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
if (!allowMediaInline) {
return {
shouldEnableDownloadButton: enableDownloadButton,
actions: [{
handler: handleMediaNodeRemoval.bind(null, undefined, getNodePos),
icon: /*#__PURE__*/React.createElement(EditorCloseIcon, {
label: intl.formatMessage(messages.mediaGroupDeleteLabel)
})
}]
};
}
};
var mediaGroupPos = getPos();
return _objectSpread({
identifier: getIdentifier(item),
isLazy: allowLazyLoading,
selected: isMediaItemSelected(getNodePos(), typeof mediaGroupPos === 'number' ? mediaGroupPos : NaN),
onClick: function onClick() {
setMediaGroupNodeSelection(getNodePos());
}
}, mediaInlineOptions(fg('platform_editor_remove_media_inline_feature_flag') ? allowMediaInlineImages : getMediaFeatureFlag('mediaInline', featureFlags)));
};
};
/**
* Keep returning the same ProseMirror Node, unless the node content changed.
*
* React uses shallow comparation with `Object.is`,
* but that can cause multiple re-renders when the same node is given in a different instance.
*
* To avoid unnecessary re-renders, this hook uses the `Node.eq` from ProseMirror API to compare
* previous and new values.
*/
var useLatestMediaGroupNode = function useLatestMediaGroupNode(nextMediaNode) {
var previousMediaNode = usePreviousState(nextMediaNode);
var _React$useState = React.useState(nextMediaNode),
_React$useState2 = _slicedToArray(_React$useState, 2),
mediaNode = _React$useState2[0],
setMediaNode = _React$useState2[1];
React.useEffect(function () {
if (!previousMediaNode) {
return;
}
if (!previousMediaNode.eq(nextMediaNode)) {
setMediaNode(nextMediaNode);
}
}, [previousMediaNode, nextMediaNode]);
return mediaNode;
};
var runMediaNodeUpdate = /*#__PURE__*/function () {
var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(_ref2) {
var mediaNodeUpdater, getPos, node, updateAttrs, contextId, shouldNodeBeDeepCopied;
return _regeneratorRuntime.wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
mediaNodeUpdater = _ref2.mediaNodeUpdater, getPos = _ref2.getPos, node = _ref2.node, updateAttrs = _ref2.updateAttrs;
if (!updateAttrs) {
_context.next = 4;
break;
}
_context.next = 4;
return mediaNodeUpdater.updateNodeAttrs(getPos);
case 4:
contextId = mediaNodeUpdater.getNodeContextId();
if (contextId) {
_context.next = 8;
break;
}
_context.next = 8;
return mediaNodeUpdater.updateNodeContextId(getPos);
case 8:
_context.next = 10;
return mediaNodeUpdater.shouldNodeBeDeepCopied();
case 10:
shouldNodeBeDeepCopied = _context.sent;
if (!shouldNodeBeDeepCopied) {
_context.next = 14;
break;
}
_context.next = 14;
return mediaNodeUpdater.copyNodeFromPos(getPos, {
traceId: node.attrs.__mediaTraceId
});
case 14:
case "end":
return _context.stop();
}
}, _callee);
}));
return function runMediaNodeUpdate(_x) {
return _ref3.apply(this, arguments);
};
}();
var noop = function noop() {};
// eslint-disable-next-line @typescript-eslint/ban-types
export var MediaGroupNext = injectIntl( /*#__PURE__*/React.memo(function (props) {
var _props$mediaOptions = props.mediaOptions,
allowLazyLoading = _props$mediaOptions.allowLazyLoading,
allowMediaInlineImages = _props$mediaOptions.allowMediaInlineImages,
enableDownloadButton = _props$mediaOptions.enableDownloadButton,
featureFlags = _props$mediaOptions.featureFlags,
intl = props.intl,
_getPos = props.getPos,
anchorPos = props.anchorPos,
headPos = props.headPos,
view = props.view,
disabled = props.disabled,
editorViewMode = props.editorViewMode,
mediaProvider = props.mediaProvider,
contextIdentifierProvider = props.contextIdentifierProvider,
isCopyPasteEnabled = props.isCopyPasteEnabled;
var mediaGroupNode = useLatestMediaGroupNode(props.node);
var mediaPluginState = useMemo(function () {
return mediaStateKey.getState(view.state);
}, [view.state]);
var mediaClientConfig = mediaPluginState === null || mediaPluginState === void 0 ? void 0 : mediaPluginState.mediaClientConfig;
var handleMediaGroupUpdate = mediaPluginState === null || mediaPluginState === void 0 ? void 0 : mediaPluginState.handleMediaGroupUpdate;
var _useState = useState(undefined),
_useState2 = _slicedToArray(_useState, 2),
viewMediaClientConfig = _useState2[0],
setViewMediaClientConfig = _useState2[1];
var nodeSize = mediaGroupNode.nodeSize;
var mediaNodesWithOffsets = useMemo(function () {
var result = [];
mediaGroupNode.forEach(function (item, childOffset) {
result.push({
node: item,
offset: childOffset
});
});
return result;
}, [mediaGroupNode]);
var previousMediaNodesWithOffsets = usePreviousState(mediaNodesWithOffsets);
var handleMediaNodeRemoval = useMemo(function () {
return disabled || !mediaPluginState ? noop : mediaPluginState.handleMediaNodeRemoval;
}, [disabled, mediaPluginState]);
var setMediaGroupNodeSelection = useCallback(function (pos) {
setNodeSelection(view, pos);
}, [view]);
var isMediaItemSelected = useMemo(function () {
return isNodeSelected({
anchorPos: anchorPos,
headPos: headPos,
nodeSize: nodeSize
});
}, [anchorPos, headPos, nodeSize]);
var filmstripItem = useMemo(function () {
return prepareFilmstripItem({
allowLazyLoading: allowLazyLoading,
allowMediaInlineImages: allowMediaInlineImages,
enableDownloadButton: enableDownloadButton,
handleMediaNodeRemoval: handleMediaNodeRemoval,
getPos: _getPos,
intl: intl,
isMediaItemSelected: isMediaItemSelected,
setMediaGroupNodeSelection: setMediaGroupNodeSelection,
featureFlags: featureFlags
});
}, [allowLazyLoading, allowMediaInlineImages, enableDownloadButton, handleMediaNodeRemoval, _getPos, intl, isMediaItemSelected, setMediaGroupNodeSelection, featureFlags]);
var items = useMemo(function () {
return mediaNodesWithOffsets.map(function (_ref4) {
var node = _ref4.node,
offset = _ref4.offset;
return filmstripItem(node, offset);
});
}, [mediaNodesWithOffsets, filmstripItem]);
useEffect(function () {
setViewMediaClientConfig(mediaClientConfig);
}, [mediaClientConfig]);
useEffect(function () {
// eslint-disable-next-line @atlassian/perf-linting/no-chain-state-updates -- Ignored via go/ees017 (to be fixed)
mediaNodesWithOffsets.forEach(function (_ref5) {
var node = _ref5.node,
offset = _ref5.offset;
var mediaNodeUpdater = createMediaNodeUpdater({
view: view,
mediaProvider: mediaProvider,
contextIdentifierProvider: contextIdentifierProvider,
node: node,
isMediaSingle: false
});
var updateAttrs = isCopyPasteEnabled || isCopyPasteEnabled === undefined;
runMediaNodeUpdate({
mediaNodeUpdater: mediaNodeUpdater,
node: node,
updateAttrs: updateAttrs,
getPos: function getPos() {
var pos = _getPos();
if (typeof pos !== 'number') {
return undefined;
}
return pos + offset + 1;
}
});
});
}, [view, contextIdentifierProvider, _getPos, mediaProvider, mediaNodesWithOffsets, isCopyPasteEnabled]);
useEffect(function () {
if (!handleMediaGroupUpdate || !previousMediaNodesWithOffsets) {
return;
}
var old = previousMediaNodesWithOffsets.map(function (_ref6) {
var node = _ref6.node;
return node;
});
var next = mediaNodesWithOffsets.map(function (_ref7) {
var node = _ref7.node;
return node;
});
handleMediaGroupUpdate(old, next);
return function () {
handleMediaGroupUpdate(next, []);
};
}, [handleMediaGroupUpdate, mediaNodesWithOffsets, previousMediaNodesWithOffsets]);
return /*#__PURE__*/React.createElement(Filmstrip, {
items: items,
mediaClientConfig: viewMediaClientConfig,
featureFlags: featureFlags,
shouldOpenMediaViewer: editorViewMode && editorExperiment('platform_editor_controls', 'control')
});
}));
MediaGroupNext.displayName = 'MediaGroup';