UNPKG

@atlaskit/editor-plugin-card

Version:

Card plugin for @atlaskit/editor-core

263 lines (258 loc) 10.2 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; 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 } 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 class BlockCardComponent extends React.PureComponent { constructor(props) { super(props); // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting _defineProperty(this, "onResolve", data => { const { getPos, view } = this.props; if (!getPos || typeof getPos === 'boolean') { return; } const { title, url } = data; // don't dispatch immediately since we might be in the middle of // rendering a nodeview rafSchedule(() => { const pos = getPos(); if (typeof pos !== 'number') { return; } view.dispatch(registerCard({ title, url, pos, id: this.props.id })(view.state.tr)); })(); }); _defineProperty(this, "removeCardDispatched", false); _defineProperty(this, "gapCursorSpan", () => { const 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", ({ err }) => { if (err) { throw err; } }); this.scrollContainer = findOverflowScrollParent(props.view.dom) || undefined; } componentWillUnmount() { this.removeCard(); } removeCard() { if (this.removeCardDispatched) { return; } this.removeCardDispatched = true; const { tr } = this.props.view.state; removeCard({ id: this.props.id })(tr); this.props.view.dispatch(tr); } render() { const { node, cardContext, actionOptions, onClick, CompetitorPrompt, isPageSSRed } = this.props; const { url, data } = node.attrs; const 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)); } } const WrappedBlockCard = Card(BlockCardComponent, UnsupportedBlock); export class BlockCard extends ReactNodeView { constructor(...args) { super(...args); // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead _defineProperty(this, "id", uuid()); _defineProperty(this, "updateContentEditable", (editorViewModeState, divElement) => { divElement.contentEditable = (editorViewModeState === null || editorViewModeState === void 0 ? void 0 : editorViewModeState.mode) === 'view' ? 'false' : 'true'; }); } createDomRef() { var _this$reactComponentP, _this$reactComponentP2, _this$reactComponentP3, _this$reactComponentP4; const 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 ? void 0 : (_this$reactComponentP2 = _this$reactComponentP.editorViewMode) === null || _this$reactComponentP2 === void 0 ? void 0 : _this$reactComponentP2.sharedState.onChange(({ nextSharedState }) => this.updateContentEditable(nextSharedState, domRef)); this.updateContentEditable((_this$reactComponentP3 = this.reactComponentProps.pluginInjectionApi) === null || _this$reactComponentP3 === void 0 ? void 0 : (_this$reactComponentP4 = _this$reactComponentP3.editorViewMode) === null || _this$reactComponentP4 === void 0 ? void 0 : _this$reactComponentP4.sharedState.currentState(), domRef); domRef.setAttribute('spellcheck', 'false'); return domRef; } // 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. validUpdate(currentNode, newNode) { const isCurrentNodeBlockCard = !isDatasourceNode(currentNode); const isNewNodeDatasource = isDatasourceNode(newNode); // need to return falsy to update node return !(isCurrentNodeBlockCard && isNewNodeDatasource); } update(node, decorations, _innerDecorations) { return super.update(node, decorations, _innerDecorations, this.validUpdate); } render() { const { actionOptions, pluginInjectionApi, onClickCallback, CompetitorPrompt, isPageSSRed, provider } = this.reactComponentProps; 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} */ stopEvent(event) { if (event.type === 'dragstart') { const 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; } destroy() { var _this$unsubscribe; (_this$unsubscribe = this.unsubscribe) === null || _this$unsubscribe === void 0 ? void 0 : _this$unsubscribe.call(this); super.destroy(); } } export const blockCardNodeView = ({ pmPluginFactoryParams, actionOptions, pluginInjectionApi, onClickCallback, allowDatasource, inlineCardViewProducer, CompetitorPrompt, isPageSSRed, provider }) => (node, view, getPos, decorations) => { const { portalProviderAPI, eventDispatcher } = pmPluginFactoryParams; const reactComponentProps = { actionOptions, pluginInjectionApi, onClickCallback: onClickCallback, CompetitorPrompt, isPageSSRed, provider }; const isDatasource = isDatasourceNode(node); if (isDatasource) { var _node$attrs, _node$attrs$datasourc; if (allowDatasource && canRenderDatasource(node === null || node === void 0 ? void 0 : (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : (_node$attrs$datasourc = _node$attrs.datasource) === null || _node$attrs$datasourc === void 0 ? void 0 : _node$attrs$datasourc.id)) { const datasourcePosition = typeof getPos === 'function' && getPos(); const datasourceResolvedPosition = datasourcePosition && view.state.doc.resolve(datasourcePosition); const isNodeNested = !!(datasourceResolvedPosition && datasourceResolvedPosition.depth > 0); return new Datasource({ node, view, getPos, portalProviderAPI, eventDispatcher, pluginInjectionApi, isNodeNested }).init(); } else { return inlineCardViewProducer(node, view, getPos, decorations); } } return new BlockCard(node, view, getPos, portalProviderAPI, eventDispatcher, reactComponentProps, undefined).init(); };