@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
240 lines (227 loc) • 11.3 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
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; }
/**
* @jsxRuntime classic
* @jsx jsx
*/
import React from 'react';
// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
import { jsx } from '@emotion/react';
import { ACTION_SUBJECT, ACTION_SUBJECT_ID } from '../analytics';
import { ErrorBoundary } from '../ui/ErrorBoundary';
import { analyticsEventKey, browser, getPerformanceOptions, startMeasureReactNodeViewRendered, stopMeasureReactNodeViewRendered } from '../utils';
import { ZERO_WIDTH_SPACE } from '../whitespace';
import { generateUniqueNodeKey } from './generateUniqueNodeKey';
export var inlineNodeViewClassname = 'inlineNodeView';
function createNodeView(_ref) {
var nodeViewParams = _ref.nodeViewParams,
pmPluginFactoryParams = _ref.pmPluginFactoryParams,
Component = _ref.Component,
extraComponentProps = _ref.extraComponentProps,
extraNodeViewProps = _ref.extraNodeViewProps;
// We set a variable for the current node which is
// used for comparisions when doing updates, before being
// overwritten to the updated node.
var currentNode = nodeViewParams.node;
var key = generateUniqueNodeKey();
// First we setup the dom element which will be rendered and "tracked" by prosemirror
// and also used as a "editor portal" (not react portal) target by the editor
// portal provider api, for rendering the Component passed.
var domRef = document.createElement('span');
domRef.contentEditable = 'false';
setDomAttrs(nodeViewParams.node, domRef);
// @see ED-3790
// something gets messed up during mutation processing inside of a
// nodeView if DOM structure has nested plain "div"s, it doesn't see the
// difference between them and it kills the nodeView
domRef.classList.add("".concat(nodeViewParams.node.type.name, "View-content-wrap"), "".concat(inlineNodeViewClassname));
// This util is shared for tracking rendering, and the ErrorBoundary that
// is setup to wrap the Component when rendering
// NOTE: This is not a prosemirror dispatch
function dispatchAnalyticsEvent(payload) {
pmPluginFactoryParams.eventDispatcher.emit(analyticsEventKey, {
payload: payload
});
}
// This is called to render the Component into domRef which is inside the
// prosemirror View.
// Internally it uses the unstable_renderSubtreeIntoContainer api to render,
// to the passed dom element (domRef) which means it is automatically
// "cleaned up" when you do a "re render".
function renderComponent() {
pmPluginFactoryParams.portalProviderAPI.render(getPortalChildren({
dispatchAnalyticsEvent: dispatchAnalyticsEvent,
currentNode: currentNode,
nodeViewParams: nodeViewParams,
Component: Component,
extraComponentProps: extraComponentProps
}), domRef, key);
}
var _getPerformanceOption = getPerformanceOptions(nodeViewParams.view),
samplingRate = _getPerformanceOption.samplingRate,
slowThreshold = _getPerformanceOption.slowThreshold,
trackingEnabled = _getPerformanceOption.trackingEnabled;
trackingEnabled && startMeasureReactNodeViewRendered({
nodeTypeName: currentNode.type.name
});
// We render the component while creating the node view
renderComponent();
trackingEnabled && stopMeasureReactNodeViewRendered({
nodeTypeName: currentNode.type.name,
dispatchAnalyticsEvent: dispatchAnalyticsEvent,
samplingRate: samplingRate,
slowThreshold: slowThreshold
});
// https://prosemirror.net/docs/ref/#view.NodeView
var nodeView = _objectSpread({
get dom() {
return domRef;
},
update: function update(nextNode, _decorations) {
// Let ProseMirror handle the update if node types are different.
// This prevents an issue where it was not possible to select the
// inline node view then replace it by entering text - the node
// view contents would be deleted but the node view itself would
// stay in the view and become uneditable.
if (currentNode.type !== nextNode.type) {
return false;
}
// On updates, we only set the new attributes if the type, attributes, and marks
// have changed on the node.
// NOTE: this could mean attrs changes aren't reflected in the dom,
// when an attribute key which was previously present is no longer
// present.
// ie.
// -> Original attributes { text: "hello world", color: "red" }
// -> Updated attributes { color: "blue" }
// in this case, the dom text attribute will not be cleared.
//
// This may not be an issue with any of our current node schemas.
if (!currentNode.sameMarkup(nextNode)) {
setDomAttrs(nextNode, domRef);
}
currentNode = nextNode;
renderComponent();
return true;
},
destroy: function destroy() {
// When prosemirror destroys the node view, we need to clean up
// what we have previously rendered using the editor portal
// provider api.
pmPluginFactoryParams.portalProviderAPI.remove(key);
// @ts-expect-error Expect an error as domRef is expected to be
// of HTMLSpanElement type however once the node view has
// been destroyed no other consumers should still be using it.
domRef = undefined;
}
}, extraNodeViewProps);
return nodeView;
}
/**
* Copies the attributes from a ProseMirror Node to a DOM node.
* @param node The Prosemirror Node from which to source the attributes
*/
function setDomAttrs(node, element) {
Object.keys(node.attrs || {}).forEach(function (attr) {
element.setAttribute(attr, node.attrs[attr]);
});
}
function getPortalChildren(_ref2) {
var dispatchAnalyticsEvent = _ref2.dispatchAnalyticsEvent,
currentNode = _ref2.currentNode,
nodeViewParams = _ref2.nodeViewParams,
Component = _ref2.Component,
extraComponentProps = _ref2.extraComponentProps;
return function portalChildren() {
var _currentNode$type$nam, _currentNode$type;
// All inline nodes use `display: inline` to allow for multi-line
// wrapping. This does produce an issue in Chrome where it is not
// possible to click select the position after the node,
// see: https://product-fabric.atlassian.net/browse/ED-12003
// however this is only a problem for node views that use
// `display: inline-block` somewhere within the Component.
// Looking at the below structure, spans with className
// `inlineNodeViewAddZeroWidthSpace` have pseudo elements that
// add a zero width space which fixes the problem.
// Without the additional zero width space before the Component,
// it is not possible to use the keyboard to range select in Safari.
//
// Zero width spaces on either side of the Component also prevent
// the cursor from appearing inside the node view on all browsers.
//
// Note:
// In future it is worth considering prohibiting the use of `display: inline-block`
// within inline node view Components however would require a sizable
// refactor. A test suite to catch any instances of this is ideal however
// the refactor required is currently out of scope for https://product-fabric.atlassian.net/browse/ED-14176
return jsx(ErrorBoundary, {
component: ACTION_SUBJECT.REACT_NODE_VIEW,
componentId: (_currentNode$type$nam = currentNode === null || currentNode === void 0 || (_currentNode$type = currentNode.type) === null || _currentNode$type === void 0 ? void 0 : _currentNode$type.name) !== null && _currentNode$type$nam !== void 0 ? _currentNode$type$nam : ACTION_SUBJECT_ID.UNKNOWN_NODE,
dispatchAnalyticsEvent: dispatchAnalyticsEvent
}, jsx("span", {
className: "zeroWidthSpaceContainer"
}, jsx("span", {
className: "".concat(inlineNodeViewClassname, "AddZeroWidthSpace")
}), ZERO_WIDTH_SPACE), jsx(Component, _extends({
view: nodeViewParams.view
// TODO: ED-13910 - Remove the boolean to fix the prosemirror view type
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
,
getPos: nodeViewParams.getPos,
node: currentNode
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
}, extraComponentProps)), browser.android ?
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
jsx("span", {
className: "zeroWidthSpaceContainer",
contentEditable: "false"
}, jsx("span", {
className: "".concat(inlineNodeViewClassname, "AddZeroWidthSpace")
}), ZERO_WIDTH_SPACE) :
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
jsx("span", {
className: "".concat(inlineNodeViewClassname, "AddZeroWidthSpace")
}));
};
}
// https://prosemirror.net/docs/ref/#view.EditorProps.nodeViews
// The prosemirror EditorProps has a nodeViews key which has the rough shape:
// type nodeViews: {
// [nodeViewName: string]: (node, editorView, getPos, decorations, innerDecorations) => NodeView
// }
// So the value of the keys on the nodeViews object, are a function which should return a NodeView.
// The following type NodeViewProducer, refers to these functions which return a NodeView.
//
// So the above type could also be described as
// type NodeViewProducer = (node, editorView, getPos, decorations, innerDecorations) => NodeView
// nodeViews: {
// [nodeViewName: string]: NodeViewProducer
// }
// This return of this function is intended to be the value of a key
// in a ProseMirror nodeViews object.
export function getInlineNodeViewProducer(_ref3) {
var pmPluginFactoryParams = _ref3.pmPluginFactoryParams,
Component = _ref3.Component,
extraComponentProps = _ref3.extraComponentProps,
extraNodeViewProps = _ref3.extraNodeViewProps;
function nodeViewProducer() {
var nodeView = createNodeView({
nodeViewParams: {
node: arguments.length <= 0 ? undefined : arguments[0],
view: arguments.length <= 1 ? undefined : arguments[1],
getPos: arguments.length <= 2 ? undefined : arguments[2],
decorations: arguments.length <= 3 ? undefined : arguments[3]
},
pmPluginFactoryParams: pmPluginFactoryParams,
Component: Component,
extraComponentProps: extraComponentProps,
extraNodeViewProps: extraNodeViewProps
});
return nodeView;
}
return nodeViewProducer;
}