UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

615 lines (605 loc) • 25.6 kB
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 };