@atlaskit/editor-plugin-card
Version:
Card plugin for @atlaskit/editor-core
297 lines (292 loc) • 13.6 kB
JavaScript
import _get from "@babel/runtime/helpers/get";
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
import _createClass from "@babel/runtime/helpers/createClass";
import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";
import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
import _inherits from "@babel/runtime/helpers/inherits";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
function _superPropGet(t, o, e, r) { var p = _get(_getPrototypeOf(1 & r ? t.prototype : t), o, e); return 2 & r && "function" == typeof p ? function (t) { return p.apply(e, t); } : p; }
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
import React from 'react';
import rafSchedule from 'raf-schd';
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
import uuid from 'uuid/v4';
import { getBrowserInfo } from '@atlaskit/editor-common/browser';
import ReactNodeView from '@atlaskit/editor-common/react-node-view';
import { findOverflowScrollParent, UnsupportedBlock } from '@atlaskit/editor-common/ui';
import { canRenderDatasource } from '@atlaskit/editor-common/utils';
import { SmartLinkDraggable, SMART_LINK_DRAG_TYPES, SMART_LINK_APPEARANCE } from '@atlaskit/editor-smart-link-draggable';
import { fg } from '@atlaskit/platform-feature-flags';
import { Card as SmartCard } from '@atlaskit/smart-card';
import { CardSSR } from '@atlaskit/smart-card/ssr';
import { Datasource } from '../nodeviews/datasource';
import { registerCard, removeCard as _removeCard } from '../pm-plugins/actions';
import { isDatasourceNode } from '../pm-plugins/utils';
import { Card } from './genericCard';
// eslint-disable-next-line @repo/internal/react/no-class-components
export var BlockCardComponent = /*#__PURE__*/function (_React$PureComponent) {
function BlockCardComponent(props) {
var _this;
_classCallCheck(this, BlockCardComponent);
_this = _callSuper(this, BlockCardComponent, [props]);
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
_defineProperty(_this, "onResolve", function (data) {
var _this$props = _this.props,
getPos = _this$props.getPos,
view = _this$props.view;
if (!getPos || typeof getPos === 'boolean') {
return;
}
var title = data.title,
url = data.url;
// don't dispatch immediately since we might be in the middle of
// rendering a nodeview
rafSchedule(function () {
var pos = getPos();
if (typeof pos !== 'number') {
return;
}
view.dispatch(registerCard({
title: title,
url: url,
pos: pos,
id: _this.props.id
})(view.state.tr));
})();
});
_defineProperty(_this, "removeCardDispatched", false);
_defineProperty(_this, "gapCursorSpan", function () {
var browser = getBrowserInfo();
// Don't render in EdgeHTMl version <= 18 (Edge version 44)
// as it forces the edit popup to render 24px lower than it should
if (browser.ie && browser.ie_version < 79) {
return;
}
// render an empty span afterwards to get around Webkit bug
// that puts caret in next editable text element
return /*#__PURE__*/React.createElement("span", {
contentEditable: true
});
});
_defineProperty(_this, "onError", function (_ref) {
var err = _ref.err;
if (err) {
throw err;
}
});
_this.scrollContainer = findOverflowScrollParent(props.view.dom) || undefined;
return _this;
}
_inherits(BlockCardComponent, _React$PureComponent);
return _createClass(BlockCardComponent, [{
key: "componentWillUnmount",
value: function componentWillUnmount() {
this.removeCard();
}
}, {
key: "removeCard",
value: function removeCard() {
if (this.removeCardDispatched) {
return;
}
this.removeCardDispatched = true;
var tr = this.props.view.state.tr;
_removeCard({
id: this.props.id
})(tr);
this.props.view.dispatch(tr);
}
}, {
key: "render",
value: function render() {
var _this$props2 = this.props,
node = _this$props2.node,
cardContext = _this$props2.cardContext,
actionOptions = _this$props2.actionOptions,
onClick = _this$props2.onClick,
CompetitorPrompt = _this$props2.CompetitorPrompt,
isPageSSRed = _this$props2.isPageSSRed;
var _node$attrs = node.attrs,
url = _node$attrs.url,
data = _node$attrs.data;
var cardInner = isPageSSRed ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(CardSSR, {
key: url,
url: url !== null && url !== void 0 ? url : data.url,
container: this.scrollContainer,
appearance: "block",
onClick: onClick,
onResolve: this.onResolve,
onError: this.onError,
platform: 'web',
actionOptions: actionOptions,
CompetitorPrompt: CompetitorPrompt,
hideIconLoadingSkeleton: true
}), this.gapCursorSpan()) : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SmartCard, {
key: url,
url: url !== null && url !== void 0 ? url : data.url,
container: this.scrollContainer,
appearance: "block",
onClick: onClick,
onResolve: this.onResolve,
onError: this.onError,
platform: 'web',
actionOptions: actionOptions,
CompetitorPrompt: CompetitorPrompt
}), this.gapCursorSpan());
// [WS-2307]: we only render card wrapped into a Provider when the value is ready,
// otherwise if we got data, we can render the card directly since it doesn't need the Provider
return /*#__PURE__*/React.createElement(SmartLinkDraggable, {
url: url,
appearance: SMART_LINK_APPEARANCE.BLOCK,
source: SMART_LINK_DRAG_TYPES.EDITOR
}, /*#__PURE__*/React.createElement("div", null, cardContext && cardContext.value ? /*#__PURE__*/React.createElement(cardContext.Provider, {
value: cardContext.value
}, cardInner) : data ? cardInner : null));
}
}]);
}(React.PureComponent);
var WrappedBlockCard = Card(BlockCardComponent, UnsupportedBlock);
export var BlockCard = /*#__PURE__*/function (_ReactNodeView) {
function BlockCard() {
var _this2;
_classCallCheck(this, BlockCard);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this2 = _callSuper(this, BlockCard, [].concat(args));
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
_defineProperty(_this2, "id", uuid());
_defineProperty(_this2, "updateContentEditable", function (editorViewModeState, divElement) {
divElement.contentEditable = (editorViewModeState === null || editorViewModeState === void 0 ? void 0 : editorViewModeState.mode) === 'view' ? 'false' : 'true';
});
return _this2;
}
_inherits(BlockCard, _ReactNodeView);
return _createClass(BlockCard, [{
key: "createDomRef",
value: function createDomRef() {
var _this$reactComponentP,
_this3 = this,
_this$reactComponentP2;
var domRef = document.createElement('div');
// workaround Chrome bug in https://product-fabric.atlassian.net/browse/ED-5379
// see also: https://github.com/ProseMirror/prosemirror/issues/884
this.unsubscribe = (_this$reactComponentP = this.reactComponentProps.pluginInjectionApi) === null || _this$reactComponentP === void 0 || (_this$reactComponentP = _this$reactComponentP.editorViewMode) === null || _this$reactComponentP === void 0 ? void 0 : _this$reactComponentP.sharedState.onChange(function (_ref2) {
var nextSharedState = _ref2.nextSharedState;
return _this3.updateContentEditable(nextSharedState, domRef);
});
this.updateContentEditable((_this$reactComponentP2 = this.reactComponentProps.pluginInjectionApi) === null || _this$reactComponentP2 === void 0 || (_this$reactComponentP2 = _this$reactComponentP2.editorViewMode) === null || _this$reactComponentP2 === void 0 ? void 0 : _this$reactComponentP2.sharedState.currentState(), domRef);
domRef.setAttribute('spellcheck', 'false');
return domRef;
}
}, {
key: "validUpdate",
value:
// Need this function to check if the datasource attribute was added or not to a blockCard.
// If so, we return false so we can get the node to re-render properly as a datasource node instead.
// Otherwise, the node view will still consider the node as a blockCard and render a regular blockCard.
function validUpdate(currentNode, newNode) {
var isCurrentNodeBlockCard = !isDatasourceNode(currentNode);
var isNewNodeDatasource = isDatasourceNode(newNode);
// need to return falsy to update node
return !(isCurrentNodeBlockCard && isNewNodeDatasource);
}
}, {
key: "update",
value: function update(node, decorations, _innerDecorations) {
return _superPropGet(BlockCard, "update", this, 3)([node, decorations, _innerDecorations, this.validUpdate]);
}
}, {
key: "render",
value: function render() {
var _this$reactComponentP3 = this.reactComponentProps,
actionOptions = _this$reactComponentP3.actionOptions,
pluginInjectionApi = _this$reactComponentP3.pluginInjectionApi,
onClickCallback = _this$reactComponentP3.onClickCallback,
CompetitorPrompt = _this$reactComponentP3.CompetitorPrompt,
isPageSSRed = _this$reactComponentP3.isPageSSRed,
provider = _this$reactComponentP3.provider;
return /*#__PURE__*/React.createElement(WrappedBlockCard, {
node: this.node,
view: this.view,
getPos: this.getPos,
actionOptions: actionOptions,
pluginInjectionApi: pluginInjectionApi,
onClickCallback: onClickCallback,
id: this.id,
CompetitorPrompt: CompetitorPrompt,
isPageSSRed: isPageSSRed,
provider: provider
});
}
/**
* Prevent ProseMirror from handling drag events on the smart-element-link,
* allowing native drag to work so SmartLinkDraggable can intercept it.
* @see {@link https://prosemirror.net/docs/ref/#view.NodeView.stopEvent}
*/
}, {
key: "stopEvent",
value: function stopEvent(event) {
if (event.type === 'dragstart') {
var target = event.target;
if (target instanceof HTMLElement && target.closest('[data-smart-element-link]') && fg('cc_drag_and_drop_smart_link_from_content_to_tree')) {
return true;
}
}
return false;
}
}, {
key: "destroy",
value: function destroy() {
var _this$unsubscribe;
(_this$unsubscribe = this.unsubscribe) === null || _this$unsubscribe === void 0 || _this$unsubscribe.call(this);
_superPropGet(BlockCard, "destroy", this, 3)([]);
}
}]);
}(ReactNodeView);
export var blockCardNodeView = function blockCardNodeView(_ref3) {
var pmPluginFactoryParams = _ref3.pmPluginFactoryParams,
actionOptions = _ref3.actionOptions,
pluginInjectionApi = _ref3.pluginInjectionApi,
onClickCallback = _ref3.onClickCallback,
allowDatasource = _ref3.allowDatasource,
inlineCardViewProducer = _ref3.inlineCardViewProducer,
CompetitorPrompt = _ref3.CompetitorPrompt,
isPageSSRed = _ref3.isPageSSRed,
provider = _ref3.provider;
return function (node, view, getPos, decorations) {
var portalProviderAPI = pmPluginFactoryParams.portalProviderAPI,
eventDispatcher = pmPluginFactoryParams.eventDispatcher;
var reactComponentProps = {
actionOptions: actionOptions,
pluginInjectionApi: pluginInjectionApi,
onClickCallback: onClickCallback,
CompetitorPrompt: CompetitorPrompt,
isPageSSRed: isPageSSRed,
provider: provider
};
var isDatasource = isDatasourceNode(node);
if (isDatasource) {
var _node$attrs2;
if (allowDatasource && canRenderDatasource(node === null || node === void 0 || (_node$attrs2 = node.attrs) === null || _node$attrs2 === void 0 || (_node$attrs2 = _node$attrs2.datasource) === null || _node$attrs2 === void 0 ? void 0 : _node$attrs2.id)) {
var datasourcePosition = typeof getPos === 'function' && getPos();
var datasourceResolvedPosition = datasourcePosition && view.state.doc.resolve(datasourcePosition);
var isNodeNested = !!(datasourceResolvedPosition && datasourceResolvedPosition.depth > 0);
return new Datasource({
node: node,
view: view,
getPos: getPos,
portalProviderAPI: portalProviderAPI,
eventDispatcher: eventDispatcher,
pluginInjectionApi: pluginInjectionApi,
isNodeNested: isNodeNested
}).init();
} else {
return inlineCardViewProducer(node, view, getPos, decorations);
}
}
return new BlockCard(node, view, getPos, portalProviderAPI, eventDispatcher, reactComponentProps, undefined).init();
};
};