@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
615 lines (605 loc) • 25.6 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import _extends from "@babel/runtime/helpers/extends";
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";
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; }
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 PropTypes from 'prop-types';
import { fg } from '@atlaskit/platform-feature-flags';
import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '../analytics';
import { createDispatch } from '../event-dispatcher';
import { startMeasure, stopMeasure } from '../performance-measures';
import { EditorContext } from '../ui/EditorContext';
import { analyticsEventKey } from '../utils';
var DEFAULT_SAMPLING_RATE = 100;
var DEFAULT_SLOW_THRESHOLD = 4;
// That context was extract from the old WithPluginState from editor-core
// It was using some private types from
// - EditorAction: packages/editor/editor-core/src/actions/index.ts
// - EditorSharedConfig: packages/editor/editor-core/src/labs/next/internal/context/shared-config.tsx
/**
* @private
* @deprecated
*
* Using this component is deprecated. It should be replaced with `useSharedPluginState`.
* This requires having access to the injection API from the plugin itself.
*
* An example of the refactor with the new hook (using hyperlink as an example) is:
*
* Before:
* ```ts
* <WithPluginState
* editorView={editorView}
* plugins={{
* hyperlinkState: hyperlinkPluginKey
* }}
* render={({ hyperlinkState }) =>
* renderComponent({ hyperlinkState })
* }
* />
* ```
*
* After:
* ```ts
* import { useSharedPluginState } from '@atlaskit/editor-common/hooks';
* import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
*
* function ComponentWithState(
* api: ExtractInjectionAPI<typeof hyperlinkPlugin> | undefined
* ) {
* const { hyperlinkState } = useSharedPluginState(api, ['hyperlink']);
* return renderComponent({ hyperlinkState })
* }
* ```
*
*/
// Ignored via go/ees005
// eslint-disable-next-line @repo/internal/react/no-class-components, react/prefer-stateless-function
var WithPluginState = /*#__PURE__*/function (_React$Component) {
function WithPluginState(props) {
_classCallCheck(this, WithPluginState);
return _callSuper(this, WithPluginState, [props]);
}
_inherits(WithPluginState, _React$Component);
return _createClass(WithPluginState, [{
key: "render",
value: function render() {
if (fg('platform_editor_react18_phase2_v2')) {
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
return /*#__PURE__*/React.createElement(WithPluginStateNew, this.props);
}
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
return /*#__PURE__*/React.createElement(WithPluginStateOld, this.props);
}
}]);
}(React.Component);
function WithPluginStateNew(props) {
var context = React.useContext(EditorContext);
return /*#__PURE__*/React.createElement(WithPluginStateInner
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
, _extends({}, props, {
editorActions: context === null || context === void 0 ? void 0 : context.editorActions
}));
}
// Ignored via go/ees005
// eslint-disable-next-line @repo/internal/react/no-class-components
export var WithPluginStateInner = /*#__PURE__*/function (_React$Component2) {
function WithPluginStateInner(props) {
var _this;
_classCallCheck(this, WithPluginStateInner);
_this = _callSuper(this, WithPluginStateInner, [props]);
_defineProperty(_this, "listeners", {});
_defineProperty(_this, "debounce", null);
_defineProperty(_this, "notAppliedState", {});
_defineProperty(_this, "isSubscribed", false);
_defineProperty(_this, "callsCount", 0);
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/max-params
_defineProperty(_this, "handlePluginStateChange", function (propName, pluginName, performanceOptions, skipEqualityCheck) {
return (
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function (pluginState) {
// skipEqualityCheck is being used for old plugins since they are mutating plugin state instead of creating a new one
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (_this.state[propName] !== pluginState || skipEqualityCheck) {
_this.updateState({
stateSubset: _defineProperty({}, propName, pluginState),
pluginName: pluginName,
performanceOptions: performanceOptions
});
}
}
);
});
/**
* Debounces setState calls in order to reduce number of re-renders caused by several plugin state changes.
*/
_defineProperty(_this, "updateState", function (_ref) {
var stateSubset = _ref.stateSubset,
pluginName = _ref.pluginName,
performanceOptions = _ref.performanceOptions;
_this.notAppliedState = _objectSpread(_objectSpread({}, _this.notAppliedState), stateSubset);
if (_this.debounce) {
window.clearTimeout(_this.debounce);
}
var debounce = _this.props.debounce !== false ? function (fn) {
return window.setTimeout(fn, 0);
} : function (fn) {
return fn();
};
_this.debounce = debounce(function () {
var measure = "\uD83E\uDD89".concat(pluginName, "::WithPluginState");
performanceOptions.trackingEnabled && startMeasure(measure);
_this.setState(_this.notAppliedState, function () {
performanceOptions.trackingEnabled && stopMeasure(measure, function (duration) {
// Each WithPluginState component will fire analytics event no more than once every `samplingLimit` times
if (++_this.callsCount % performanceOptions.samplingRate === 0 && duration > performanceOptions.slowThreshold) {
_this.dispatchAnalyticsEvent({
action: ACTION.WITH_PLUGIN_STATE_CALLED,
actionSubject: ACTION_SUBJECT.EDITOR,
eventType: EVENT_TYPE.OPERATIONAL,
attributes: {
plugin: pluginName,
duration: duration
}
});
}
});
});
_this.debounce = null;
_this.notAppliedState = {};
});
});
_defineProperty(_this, "dispatchAnalyticsEvent", function (payload) {
var eventDispatcher = _this.getEventDispatcher();
if (eventDispatcher) {
var dispatch = createDispatch(eventDispatcher);
dispatch(analyticsEventKey, {
payload: payload
});
}
});
_this.state = _this.getPluginsStates(_this.props.plugins, _this.getEditorView(props));
return _this;
}
_inherits(WithPluginStateInner, _React$Component2);
return _createClass(WithPluginStateInner, [{
key: "getEditorView",
value: function getEditorView(maybeProps) {
var props = maybeProps || this.props;
var editorActions = props.editorActions;
return props.editorView || (editorActions === null || editorActions === void 0 ? void 0 : editorActions._privateGetEditorView());
}
}, {
key: "getEventDispatcher",
value: function getEventDispatcher(maybeProps) {
var _props$editorActions;
var props = maybeProps || this.props;
return props.eventDispatcher || ((_props$editorActions = props.editorActions) === null || _props$editorActions === void 0 ? void 0 : _props$editorActions._privateGetEventDispatcher());
}
}, {
key: "getPluginsStates",
value: function getPluginsStates(plugins, editorView) {
if (!editorView || !plugins) {
return {};
}
var keys = Object.keys(plugins);
return keys.reduce(function (acc, propName) {
var pluginKey = plugins[propName];
if (!pluginKey) {
return acc;
}
acc[propName] = pluginKey.getState(editorView.state);
return acc;
}, {});
}
}, {
key: "subscribe",
value: function subscribe(props) {
var _uiTracking$samplingR,
_uiTracking$slowThres,
_this2 = this;
var plugins = props.plugins;
var eventDispatcher = this.getEventDispatcher(props);
var editorView = this.getEditorView(props);
if (!eventDispatcher || !editorView || this.isSubscribed) {
return;
}
// TODO: ED-15663
// Please, do not copy or use this kind of code below
// @ts-ignore
var fakePluginKey = {
key: 'analyticsPlugin$',
getState: function getState(state) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return state['analyticsPlugin$'];
}
};
var analyticsPlugin = fakePluginKey.getState(editorView.state);
var uiTracking = analyticsPlugin && analyticsPlugin.performanceTracking ? analyticsPlugin.performanceTracking.uiTracking || {} : {};
var trackingEnabled = uiTracking.enabled === true;
var samplingRate = (_uiTracking$samplingR = uiTracking.samplingRate) !== null && _uiTracking$samplingR !== void 0 ? _uiTracking$samplingR : DEFAULT_SAMPLING_RATE;
var slowThreshold = (_uiTracking$slowThres = uiTracking.slowThreshold) !== null && _uiTracking$slowThres !== void 0 ? _uiTracking$slowThres : DEFAULT_SLOW_THRESHOLD;
this.isSubscribed = true;
var pluginsStates = this.getPluginsStates(plugins, editorView);
this.setState(pluginsStates);
Object.keys(plugins).forEach(function (propName) {
var pluginKey = plugins[propName];
if (!pluginKey) {
return;
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var pluginName = pluginKey.key;
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var pluginState = pluginsStates[propName];
var isPluginWithSubscribe = pluginState && pluginState.subscribe;
var handler = _this2.handlePluginStateChange(propName, pluginName, {
samplingRate: samplingRate,
slowThreshold: slowThreshold,
trackingEnabled: trackingEnabled
}, isPluginWithSubscribe);
if (isPluginWithSubscribe) {
pluginState.subscribe(handler);
} else {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
eventDispatcher.on(pluginKey.key, handler);
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_this2.listeners[pluginKey.key] = {
handler: handler,
pluginKey: pluginKey
};
});
}
}, {
key: "unsubscribe",
value: function unsubscribe() {
var _this3 = this;
var eventDispatcher = this.getEventDispatcher();
var editorView = this.getEditorView();
if (!eventDispatcher || !editorView || !this.isSubscribed) {
return;
}
Object.keys(this.listeners).forEach(function (key) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var pluginState = _this3.listeners[key].pluginKey.getState(editorView.state);
if (pluginState && pluginState.unsubscribe) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
pluginState.unsubscribe(_this3.listeners[key].handler);
} else {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
eventDispatcher.off(key, _this3.listeners[key].handler);
}
});
this.listeners = [];
}
}, {
key: "subscribeToContextUpdates",
value: function subscribeToContextUpdates() {
var _this$props$editorAct,
_this4 = this;
(_this$props$editorAct = this.props.editorActions) === null || _this$props$editorAct === void 0 || _this$props$editorAct._privateSubscribe(function () {
return _this4.subscribe(_this4.props);
});
}
}, {
key: "unsubscribeFromContextUpdates",
value: function unsubscribeFromContextUpdates() {
var _this$props$editorAct2,
_this5 = this;
(_this$props$editorAct2 = this.props.editorActions) === null || _this$props$editorAct2 === void 0 || _this$props$editorAct2._privateUnsubscribe(function () {
return _this5.subscribe(_this5.props);
});
}
}, {
key: "componentDidMount",
value: function componentDidMount() {
this.subscribe(this.props);
this.subscribeToContextUpdates();
}
// Ignored via go/ees005
// eslint-disable-next-line react/no-unsafe
}, {
key: "UNSAFE_componentWillReceiveProps",
value: function UNSAFE_componentWillReceiveProps(nextProps) {
if (!this.isSubscribed) {
this.subscribe(nextProps);
}
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
if (this.debounce) {
window.clearTimeout(this.debounce);
}
this.unsubscribeFromContextUpdates();
this.unsubscribe();
}
}, {
key: "render",
value: function render() {
var render = this.props.render;
return render(this.state);
}
}]);
}(React.Component);
// Ignored via go/ees005
// eslint-disable-next-line @repo/internal/react/no-class-components
_defineProperty(WithPluginStateInner, "displayName", 'WithPluginState');
export var WithPluginStateOld = /*#__PURE__*/function (_React$Component3) {
function WithPluginStateOld(props, context) {
var _this6;
_classCallCheck(this, WithPluginStateOld);
_this6 = _callSuper(this, WithPluginStateOld, [props, context]);
_defineProperty(_this6, "listeners", {});
_defineProperty(_this6, "debounce", null);
_defineProperty(_this6, "notAppliedState", {});
_defineProperty(_this6, "isSubscribed", false);
_defineProperty(_this6, "callsCount", 0);
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/max-params
_defineProperty(_this6, "handlePluginStateChange", function (propName, pluginName, performanceOptions, skipEqualityCheck) {
return (
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function (pluginState) {
// skipEqualityCheck is being used for old plugins since they are mutating plugin state instead of creating a new one
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (_this6.state[propName] !== pluginState || skipEqualityCheck) {
_this6.updateState({
stateSubset: _defineProperty({}, propName, pluginState),
pluginName: pluginName,
performanceOptions: performanceOptions
});
}
}
);
});
/**
* Debounces setState calls in order to reduce number of re-renders caused by several plugin state changes.
*/
_defineProperty(_this6, "updateState", function (_ref2) {
var stateSubset = _ref2.stateSubset,
pluginName = _ref2.pluginName,
performanceOptions = _ref2.performanceOptions;
_this6.notAppliedState = _objectSpread(_objectSpread({}, _this6.notAppliedState), stateSubset);
if (_this6.debounce) {
window.clearTimeout(_this6.debounce);
}
var debounce = _this6.props.debounce !== false ? function (fn) {
return window.setTimeout(fn, 0);
} : function (fn) {
return fn();
};
_this6.debounce = debounce(function () {
var measure = "\uD83E\uDD89".concat(pluginName, "::WithPluginState");
performanceOptions.trackingEnabled && startMeasure(measure);
_this6.setState(_this6.notAppliedState, function () {
performanceOptions.trackingEnabled && stopMeasure(measure, function (duration) {
// Each WithPluginState component will fire analytics event no more than once every `samplingLimit` times
if (++_this6.callsCount % performanceOptions.samplingRate === 0 && duration > performanceOptions.slowThreshold) {
_this6.dispatchAnalyticsEvent({
action: ACTION.WITH_PLUGIN_STATE_CALLED,
actionSubject: ACTION_SUBJECT.EDITOR,
eventType: EVENT_TYPE.OPERATIONAL,
attributes: {
plugin: pluginName,
duration: duration
}
});
}
});
});
_this6.debounce = null;
_this6.notAppliedState = {};
});
});
_defineProperty(_this6, "dispatchAnalyticsEvent", function (payload) {
var eventDispatcher = _this6.getEventDispatcher();
if (eventDispatcher) {
var dispatch = createDispatch(eventDispatcher);
dispatch(analyticsEventKey, {
payload: payload
});
}
});
_defineProperty(_this6, "onContextUpdate", function () {
_this6.subscribe(_this6.props);
});
_this6.state = _this6.getPluginsStates(_this6.props.plugins, _this6.getEditorView(props, context));
return _this6;
}
_inherits(WithPluginStateOld, _React$Component3);
return _createClass(WithPluginStateOld, [{
key: "getEditorView",
value: function getEditorView(maybeProps, maybeContext) {
var props = maybeProps || this.props;
var context = maybeContext || this.context;
return props.editorView || context && context.editorActions && context.editorActions._privateGetEditorView() || context && context.editorSharedConfig && context.editorSharedConfig.editorView;
}
}, {
key: "getEventDispatcher",
value: function getEventDispatcher(maybeProps) {
var props = maybeProps || this.props;
return props.eventDispatcher || this.context && this.context.editorActions && this.context.editorActions._privateGetEventDispatcher() || this.context && this.context.editorSharedConfig && this.context.editorSharedConfig.eventDispatcher;
}
}, {
key: "getPluginsStates",
value: function getPluginsStates(plugins, editorView) {
if (!editorView || !plugins) {
return {};
}
var keys = Object.keys(plugins);
return keys.reduce(function (acc, propName) {
var pluginKey = plugins[propName];
if (!pluginKey) {
return acc;
}
acc[propName] = pluginKey.getState(editorView.state);
return acc;
}, {});
}
}, {
key: "subscribe",
value: function subscribe(props) {
var _uiTracking$samplingR2,
_uiTracking$slowThres2,
_this7 = this;
var plugins = props.plugins;
var eventDispatcher = this.getEventDispatcher(props);
var editorView = this.getEditorView(props);
if (!eventDispatcher || !editorView || this.isSubscribed) {
return;
}
// TODO: ED-15663
// Please, do not copy or use this kind of code below
// @ts-ignore
var fakePluginKey = {
key: 'analyticsPlugin$',
getState: function getState(state) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return state['analyticsPlugin$'];
}
};
var analyticsPlugin = fakePluginKey.getState(editorView.state);
var uiTracking = analyticsPlugin && analyticsPlugin.performanceTracking ? analyticsPlugin.performanceTracking.uiTracking || {} : {};
var trackingEnabled = uiTracking.enabled === true;
var samplingRate = (_uiTracking$samplingR2 = uiTracking.samplingRate) !== null && _uiTracking$samplingR2 !== void 0 ? _uiTracking$samplingR2 : DEFAULT_SAMPLING_RATE;
var slowThreshold = (_uiTracking$slowThres2 = uiTracking.slowThreshold) !== null && _uiTracking$slowThres2 !== void 0 ? _uiTracking$slowThres2 : DEFAULT_SLOW_THRESHOLD;
this.isSubscribed = true;
var pluginsStates = this.getPluginsStates(plugins, editorView);
this.setState(pluginsStates);
Object.keys(plugins).forEach(function (propName) {
var pluginKey = plugins[propName];
if (!pluginKey) {
return;
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var pluginName = pluginKey.key;
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var pluginState = pluginsStates[propName];
var isPluginWithSubscribe = pluginState && pluginState.subscribe;
var handler = _this7.handlePluginStateChange(propName, pluginName, {
samplingRate: samplingRate,
slowThreshold: slowThreshold,
trackingEnabled: trackingEnabled
}, isPluginWithSubscribe);
if (isPluginWithSubscribe) {
pluginState.subscribe(handler);
} else {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
eventDispatcher.on(pluginKey.key, handler);
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_this7.listeners[pluginKey.key] = {
handler: handler,
pluginKey: pluginKey
};
});
}
}, {
key: "unsubscribe",
value: function unsubscribe() {
var _this8 = this;
var eventDispatcher = this.getEventDispatcher();
var editorView = this.getEditorView();
if (!eventDispatcher || !editorView || !this.isSubscribed) {
return;
}
Object.keys(this.listeners).forEach(function (key) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var pluginState = _this8.listeners[key].pluginKey.getState(editorView.state);
if (pluginState && pluginState.unsubscribe) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
pluginState.unsubscribe(_this8.listeners[key].handler);
} else {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
eventDispatcher.off(key, _this8.listeners[key].handler);
}
});
this.listeners = [];
}
}, {
key: "subscribeToContextUpdates",
value: function subscribeToContextUpdates(context) {
if (context && context.editorActions) {
context.editorActions._privateSubscribe(this.onContextUpdate);
}
}
}, {
key: "unsubscribeFromContextUpdates",
value: function unsubscribeFromContextUpdates(context) {
if (context && context.editorActions) {
context.editorActions._privateUnsubscribe(this.onContextUpdate);
}
}
}, {
key: "componentDidMount",
value: function componentDidMount() {
this.subscribe(this.props);
this.subscribeToContextUpdates(this.context);
}
// Ignored via go/ees005
// eslint-disable-next-line react/no-unsafe
}, {
key: "UNSAFE_componentWillReceiveProps",
value: function UNSAFE_componentWillReceiveProps(nextProps) {
if (!this.isSubscribed) {
this.subscribe(nextProps);
}
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
if (this.debounce) {
window.clearTimeout(this.debounce);
}
this.unsubscribeFromContextUpdates(this.context);
this.unsubscribe();
}
}, {
key: "render",
value: function render() {
var render = this.props.render;
return render(this.state);
}
}]);
}(React.Component);
_defineProperty(WithPluginStateOld, "displayName", 'WithPluginState');
_defineProperty(WithPluginStateOld, "contextTypes", {
editorActions: PropTypes.object,
editorSharedConfig: PropTypes.object
});
export { WithPluginState };