@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
309 lines (295 loc) • 12.6 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
Object.defineProperty(exports, "getInlineNodeViewProducer", {
enumerable: true,
get: function get() {
return _getInlineNodeViewProducer.getInlineNodeViewProducer;
}
});
Object.defineProperty(exports, "inlineNodeViewClassname", {
enumerable: true,
get: function get() {
return _getInlineNodeViewProducer.inlineNodeViewClassname;
}
});
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _react = _interopRequireDefault(require("react"));
var _analytics = require("../analytics");
var _eventDispatcher = require("../event-dispatcher");
var _ErrorBoundary = require("../ui/ErrorBoundary");
var _utils = require("../utils");
var _analytics2 = require("../utils/analytics");
var _generateUniqueNodeKey = require("./generateUniqueNodeKey");
var _getInlineNodeViewProducer = require("./getInlineNodeViewProducer");
// Disable no-re-export rule for entry point files
/* eslint-disable @atlaskit/editor/no-re-export */
var ReactNodeView = exports.default = /*#__PURE__*/function () {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/max-params
function ReactNodeView(_node, view, getPos, portalProviderAPI, eventDispatcher, reactComponentProps,
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
reactComponent, viewShouldUpdate) {
var _this = this;
(0, _classCallCheck2.default)(this, ReactNodeView);
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(0, _defineProperty2.default)(this, "decorations", []);
(0, _defineProperty2.default)(this, "handleRef", function (node) {
return _this._handleRef(node);
});
(0, _defineProperty2.default)(this, "dispatchAnalyticsEvent", function (payload) {
if (_this.eventDispatcher) {
var dispatch = (0, _eventDispatcher.createDispatch)(_this.eventDispatcher);
dispatch(_analytics2.analyticsEventKey, {
payload: payload
});
}
});
this.node = _node;
this.view = view;
this.getPos = getPos;
this.portalProviderAPI = portalProviderAPI;
this.reactComponentProps = reactComponentProps || {};
this.reactComponent = reactComponent;
this._viewShouldUpdate = viewShouldUpdate;
this.eventDispatcher = eventDispatcher;
this.key = (0, _generateUniqueNodeKey.generateUniqueNodeKey)();
}
/**
* This method exists to move initialization logic out of the constructor,
* so object can be initialized properly before calling render first time.
*
* Example:
* Instance properties get added to an object only after super call in
* constructor, which leads to some methods being undefined during the
* first render.
*/
return (0, _createClass2.default)(ReactNodeView, [{
key: "init",
value: function init() {
var _this2 = this;
this.domRef = this.createDomRef();
this.setDomAttrs(this.node, this.domRef);
var _ref = this.getContentDOM() || {
dom: undefined,
contentDOM: undefined
},
contentDOMWrapper = _ref.dom,
contentDOM = _ref.contentDOM;
if (this.domRef && contentDOMWrapper) {
this.domRef.appendChild(contentDOMWrapper);
this.contentDOM = contentDOM ? contentDOM : contentDOMWrapper;
this.contentDOMWrapper = contentDOMWrapper || contentDOM;
}
// @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
this.domRef.classList.add("".concat(this.node.type.name, "View-content-wrap"));
var _getPerformanceOption = (0, _utils.getPerformanceOptions)(this.view),
samplingRate = _getPerformanceOption.samplingRate,
slowThreshold = _getPerformanceOption.slowThreshold,
trackingEnabled = _getPerformanceOption.trackingEnabled;
trackingEnabled && (0, _utils.startMeasureReactNodeViewRendered)({
nodeTypeName: this.node.type.name
});
this.renderReactComponent(function () {
return _this2.render(_this2.reactComponentProps, _this2.handleRef);
});
trackingEnabled && (0, _utils.stopMeasureReactNodeViewRendered)({
nodeTypeName: this.node.type.name,
dispatchAnalyticsEvent: this.dispatchAnalyticsEvent,
samplingRate: samplingRate,
slowThreshold: slowThreshold
});
return this;
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}, {
key: "renderReactComponent",
value: function renderReactComponent(component) {
var _this3 = this;
if (!this.domRef || !component) {
return;
}
var componentWithErrorBoundary = function componentWithErrorBoundary() {
var _this3$node$type$name, _this3$node;
return /*#__PURE__*/_react.default.createElement(_ErrorBoundary.ErrorBoundary, {
component: _analytics.ACTION_SUBJECT.REACT_NODE_VIEW,
componentId: (_this3$node$type$name = _this3 === null || _this3 === void 0 || (_this3$node = _this3.node) === null || _this3$node === void 0 || (_this3$node = _this3$node.type) === null || _this3$node === void 0 ? void 0 : _this3$node.name) !== null && _this3$node$type$name !== void 0 ? _this3$node$type$name : _analytics.ACTION_SUBJECT_ID.UNKNOWN_NODE,
dispatchAnalyticsEvent: _this3.dispatchAnalyticsEvent
}, component());
};
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.portalProviderAPI.render(componentWithErrorBoundary, this.domRef, this.key);
}
}, {
key: "createDomRef",
value: function createDomRef() {
if (!this.node.isInline) {
return document.createElement('div');
}
var htmlElement = document.createElement('span');
return htmlElement;
}
}, {
key: "getContentDOM",
value: function getContentDOM() {
return undefined;
}
}, {
key: "_handleRef",
value: function _handleRef(node) {
var _this4 = this;
var contentDOM = this.contentDOMWrapper || this.contentDOM;
// @ts-ignore
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var oldIgnoreMutation;
var selectionBookmark;
var parentOffset = 0;
var mutationsIgnored = false;
// move the contentDOM node inside the inner reference after rendering
if (node && contentDOM && !node.contains(contentDOM)) {
var _this$view$state$sele;
// @ts-ignore - ignoreMutation may not be declared
oldIgnoreMutation = this.ignoreMutation; // store ref to previous ignoreMutation
// ignore all mutations caused by ProseMirror's MutationObserver triggering
// after DOM change, except selection changes
// @ts-ignore ProseMirror adds selection type to MutationRecord
this.ignoreMutation = function (m) {
var isSelectionMutation = m.type === 'selection';
if (!isSelectionMutation) {
mutationsIgnored = true;
}
return !isSelectionMutation;
};
// capture document selection state before React DOM changes triggers ProseMirror selection change transaction
if (this.view.state.selection.visible) {
selectionBookmark = this.view.state.selection.getBookmark();
}
// ... and capture parent offset before DOM change
if (((_this$view$state$sele = this.view.state.selection) === null || _this$view$state$sele === void 0 ? void 0 : _this$view$state$sele.ranges.length) > 0) {
var _this$view$state$sele2, _this$view$state$sele3;
parentOffset = (_this$view$state$sele2 = (_this$view$state$sele3 = this.view.state.selection) === null || _this$view$state$sele3 === void 0 || (_this$view$state$sele3 = _this$view$state$sele3.ranges[0].$from) === null || _this$view$state$sele3 === void 0 ? void 0 : _this$view$state$sele3.parentOffset) !== null && _this$view$state$sele2 !== void 0 ? _this$view$state$sele2 : 0;
}
node.appendChild(contentDOM);
requestAnimationFrame(function () {
// Reset ignoreMutation after frame ....
// @ts-ignore - this may not have been declared by implementing class
_this4.ignoreMutation = oldIgnoreMutation;
// only trigger selection re-sync if the selection is not at the start of the
// node, and DOM mutations were ignored
if (selectionBookmark && mutationsIgnored && parentOffset > 0) {
// ... and dispatch expected selection state
_this4.view.dispatch(_this4.view.state.tr.setSelection(selectionBookmark.resolve(_this4.view.state.tr.doc)));
}
});
}
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}, {
key: "render",
value: function render(props, forwardRef) {
return this.reactComponent ? /*#__PURE__*/_react.default.createElement(this.reactComponent, (0, _extends2.default)({
view: this.view,
getPos: this.getPos,
node: this.node,
forwardRef: forwardRef
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
}, props)) : null;
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/max-params
}, {
key: "update",
value: function update(node, decorations, _innerDecorations) {
var _this5 = this;
var validUpdate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : function () {
return true;
};
// @see https://github.com/ProseMirror/prosemirror/issues/648
var isValidUpdate = this.node.type === node.type && validUpdate(this.node, node);
this.decorations = decorations;
if (!isValidUpdate) {
return false;
}
if (this.domRef && !this.node.sameMarkup(node)) {
this.setDomAttrs(node, this.domRef);
}
// View should not process a re-render if this is false.
// We dont want to destroy the view, so we return true.
// TODO: ED-13910 Fix viewShouldUpdate readonly decoration array
if (!this.viewShouldUpdate(node, decorations)) {
this.node = node;
return true;
}
this.node = node;
this.renderReactComponent(function () {
return _this5.render(_this5.reactComponentProps, _this5.handleRef);
});
return true;
}
}, {
key: "viewShouldUpdate",
value: function viewShouldUpdate(nextNode, decorations) {
if (this._viewShouldUpdate) {
return this._viewShouldUpdate(nextNode);
}
return true;
}
/**
* Copies the attributes from a ProseMirror Node to a DOM node.
* @param node The Prosemirror Node from which to source the attributes
*/
}, {
key: "setDomAttrs",
value: function setDomAttrs(node, element) {
Object.keys(node.attrs || {}).forEach(function (attr) {
element.setAttribute(attr, node.attrs[attr]);
});
}
}, {
key: "dom",
get: function get() {
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
return this.domRef;
}
}, {
key: "destroy",
value: function destroy() {
if (!this.domRef) {
return;
}
this.portalProviderAPI.remove(this.key);
this.domRef = undefined;
this.contentDOM = undefined;
}
}], [{
key: "fromComponent",
value:
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/max-params
function fromComponent(
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
component, portalProviderAPI, eventDispatcher, props, viewShouldUpdate) {
return function (node, view, getPos) {
return new ReactNodeView(node, view, getPos, portalProviderAPI, eventDispatcher, props, component, viewShouldUpdate).init();
};
}
}]);
}();