UNPKG

@atlaskit/editor-plugin-media

Version:

Media plugin for @atlaskit/editor-core

292 lines (290 loc) 12.6 kB
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';