UNPKG

@atlaskit/editor-plugin-extension

Version:

editor-plugin-extension plugin for @atlaskit/editor-core

539 lines (533 loc) 24.4 kB
import _extends from "@babel/runtime/helpers/extends"; import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator"; 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"; 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; } import _regeneratorRuntime from "@babel/runtime/regenerator"; 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, { useCallback, useEffect, useRef } from 'react'; import { bind } from 'bind-event-listener'; import _isEqual from 'lodash/isEqual'; import _mergeRecursive from 'lodash/merge'; import memoizeOne from 'memoize-one'; import { injectIntl } from 'react-intl'; import { withAnalyticsContext, withAnalyticsEvents } from '@atlaskit/analytics-next'; import { getDocument } from '@atlaskit/browser-apis'; import ButtonGroup from '@atlaskit/button/button-group'; import Button from '@atlaskit/button/new'; import { ACTION, ACTION_SUBJECT, EVENT_TYPE, fireAnalyticsEvent } from '@atlaskit/editor-common/analytics'; import { isTabGroup, configPanelMessages as messages } from '@atlaskit/editor-common/extensions'; import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks'; import Form, { FormFooter } from '@atlaskit/form'; import { fg } from '@atlaskit/platform-feature-flags'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; import { ALLOWED_LOGGED_MACRO_PARAMS } from './constants'; import { DescriptionSummary } from './DescriptionSummary'; import ErrorMessage from './ErrorMessage'; import FormContent from './FormContent'; import { FormErrorBoundary } from './FormErrorBoundary'; import Header from './Header'; import LoadingState from './LoadingState'; import { deserialize, findDuplicateFields, serialize } from './transformers'; import { getLoggedParameters } from './utils'; function ConfigForm(_ref) { var canSave = _ref.canSave, errorMessage = _ref.errorMessage, extensionManifest = _ref.extensionManifest, fields = _ref.fields, firstVisibleFieldName = _ref.firstVisibleFieldName, hasParsedParameters = _ref.hasParsedParameters, intl = _ref.intl, isLoading = _ref.isLoading, onCancel = _ref.onCancel, onFieldChange = _ref.onFieldChange, parameters = _ref.parameters, submitting = _ref.submitting, contextIdentifierProvider = _ref.contextIdentifierProvider, featureFlags = _ref.featureFlags, disableFields = _ref.disableFields; useEffect(function () { if (fields) { var firstDuplicateField = findDuplicateFields(fields); if (firstDuplicateField) { throw new Error("Possible duplicate field name: `".concat(firstDuplicateField.name, "`.")); } } }, [fields]); if (isLoading || !hasParsedParameters && errorMessage === null) { return /*#__PURE__*/React.createElement(LoadingState, null); } if (errorMessage || !fields) { return /*#__PURE__*/React.createElement(ErrorMessage, { errorMessage: errorMessage || '' }); } return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(FormContent, { fields: fields, parameters: parameters, extensionManifest: extensionManifest, onFieldChange: onFieldChange, firstVisibleFieldName: firstVisibleFieldName, contextIdentifierProvider: contextIdentifierProvider, featureFlags: featureFlags, isDisabled: disableFields }), /*#__PURE__*/React.createElement("div", { style: canSave ? {} : { display: 'none' } }, /*#__PURE__*/React.createElement(FormFooter, { align: "start" }, /*#__PURE__*/React.createElement(ButtonGroup, null, /*#__PURE__*/React.createElement(Button, { type: "submit", appearance: "primary" }, intl.formatMessage(messages.submit)), /*#__PURE__*/React.createElement(Button, { appearance: "default", isDisabled: submitting, onClick: onCancel }, intl.formatMessage(messages.cancel)))))); } var ConfigFormIntl = injectIntl(ConfigForm); var WithOnFieldChange = function WithOnFieldChange(_ref2) { var getState = _ref2.getState, autoSave = _ref2.autoSave, handleSubmit = _ref2.handleSubmit, children = _ref2.children; var getStateRef = useRef(getState); useEffect(function () { getStateRef.current = getState; }, [getState]); var handleFieldChange = useCallback(function (name, isDirty) { if (!autoSave) { return; } // Don't trigger submit if nothing actually changed if (!isDirty) { return; } var _getStateRef$current = getStateRef.current(), errors = _getStateRef$current.errors, values = _getStateRef$current.values; // Get only values that does not contain errors var validValues = {}; for (var _i = 0, _Object$keys = Object.keys(values); _i < _Object$keys.length; _i++) { var key = _Object$keys[_i]; if (!errors[key]) { // not has error validValues[key] = values[key]; } } handleSubmit(validValues); }, [autoSave, handleSubmit]); return children(handleFieldChange); }; // eslint-disable-next-line @repo/internal/react/no-class-components var ConfigPanel = /*#__PURE__*/function (_React$Component) { function ConfigPanel(props) { var _this; _classCallCheck(this, ConfigPanel); _this = _callSuper(this, ConfigPanel, [props]); _defineProperty(_this, "handleKeyDown", function (e) { if ((e.key === 'Esc' || e.key === 'Escape') && _this.props.closeOnEsc) { _this.props.onCancel(); } }); // https://product-fabric.atlassian.net/browse/DST-2697 // workaround for DST-2697, remove this function once fix. _defineProperty(_this, "backfillTabFormData", function (fields, formData, currentParameters) { var getRelevantData = function getRelevantData(field, formParams, currentParams, backfill) { if (field.hasGroupedValues && !(field.name in backfill)) { backfill[field.name] = {}; } var actualFormParams = field.hasGroupedValues ? formParams[field.name] || {} : formParams; var actualCurrentParams = field.hasGroupedValues ? currentParams[field.name] || {} : currentParams; var actualBackfillParams = field.hasGroupedValues ? backfill[field.name] : backfill; return { formParams: actualFormParams, currentParams: actualCurrentParams, backfillParams: actualBackfillParams }; }; // Traverse any tab structures and backfill field values on tabs // which aren't shown. This filter should be ok because tabs are // currently only allowed on top level var mergedTabGroups = fields.filter(isTabGroup).reduce(function (missingBackfill, tabGroup) { var _getRelevantData = getRelevantData(tabGroup, formData, currentParameters, missingBackfill), tabGroupFormData = _getRelevantData.formParams, tabGroupCurrentData = _getRelevantData.currentParams, tabGroupParams = _getRelevantData.backfillParams; // Loop through tabs and see what fields are missing from current data tabGroup.fields.forEach(function (tabField) { var _getRelevantData2 = getRelevantData(tabField, tabGroupFormData, tabGroupCurrentData, tabGroupParams), tabFormData = _getRelevantData2.formParams, tabCurrentData = _getRelevantData2.currentParams, tabParams = _getRelevantData2.backfillParams; tabField.fields.forEach(function (field) { if (field.name in tabFormData || !(field.name in tabCurrentData)) { return; } tabParams[field.name] = tabCurrentData[field.name]; }); }); return missingBackfill; }, {}); return _mergeRecursive({}, mergedTabGroups, formData); }); _defineProperty(_this, "handleSubmit", /*#__PURE__*/function () { var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(formData) { var _this$props, fields, extensionManifest, onChange, autoSaveReject, serializedData; return _regeneratorRuntime.wrap(function _callee$(_context) { while (1) switch (_context.prev = _context.next) { case 0: _this$props = _this.props, fields = _this$props.fields, extensionManifest = _this$props.extensionManifest, onChange = _this$props.onChange, autoSaveReject = _this$props.autoSaveReject; if (!(!extensionManifest || !fields)) { _context.next = 4; break; } if (!extensionManifest) { autoSaveReject === null || autoSaveReject === void 0 || autoSaveReject(new Error('Extension manifest not loaded')); } else if (!fields) { autoSaveReject === null || autoSaveReject === void 0 || autoSaveReject(new Error('Config fields not loaded')); } return _context.abrupt("return"); case 4: _context.prev = 4; _context.next = 7; return serialize(extensionManifest, _this.backfillTabFormData(fields, formData, _this.state.currentParameters), fields); case 7: serializedData = _context.sent; if (!editorExperiment('platform_editor_offline_editing_web', true, { exposure: true })) { _context.next = 13; break; } _context.next = 11; return onChange(serializedData); case 11: _context.next = 14; break; case 13: onChange(serializedData); case 14: _context.next = 20; break; case 16: _context.prev = 16; _context.t0 = _context["catch"](4); autoSaveReject === null || autoSaveReject === void 0 || autoSaveReject(_context.t0); // eslint-disable-next-line no-console console.error("Error serializing parameters", _context.t0); case 20: case "end": return _context.stop(); } }, _callee, null, [[4, 16]]); })); return function (_x) { return _ref3.apply(this, arguments); }; }()); _defineProperty(_this, "parseParameters", /*#__PURE__*/function () { var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(fields, parameters) { var extensionManifest, currentParameters; return _regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) switch (_context2.prev = _context2.next) { case 0: extensionManifest = _this.props.extensionManifest; if (!(!extensionManifest || !fields || fields.length === 0)) { _context2.next = 3; break; } return _context2.abrupt("return"); case 3: if (!(typeof parameters === 'undefined')) { _context2.next = 6; break; } _this.setState({ currentParameters: {}, hasParsedParameters: true }); return _context2.abrupt("return"); case 6: _context2.next = 8; return deserialize(extensionManifest, parameters, fields); case 8: currentParameters = _context2.sent; _this.setState({ currentParameters: currentParameters, hasParsedParameters: true }); case 10: case "end": return _context2.stop(); } }, _callee2); })); return function (_x2, _x3) { return _ref4.apply(this, arguments); }; }()); /** * Remove renderHeader when when cleaning platform_editor_ai_object_sidebar_injection FG * Because header will br rendered separately outside of ConfigPanel. * ConfigPanel will be body component of ContextPanel. */ // memoized to prevent rerender on new parameters _defineProperty(_this, "renderHeader", memoizeOne(function (extensionManifest) { // Remove below line when cleaning platform_editor_ai_object_sidebar_injection FG if (_this.props.usingObjectSidebarPanel) { return null; } var _this$props2 = _this.props, onCancel = _this$props2.onCancel, showHeader = _this$props2.showHeader; // Use a temporary allowlist of top 3 macros to test out a new "Documentation" CTA ("Need help?") // This will be removed when Top 5 Modernized Macros updates are rolled out var modernizedMacrosList = ['children', 'recently-updated', 'excerpt']; var enableHelpCTA = modernizedMacrosList.includes(extensionManifest.key); if (!showHeader) { return null; } return /*#__PURE__*/React.createElement(Header, { icon: extensionManifest.icons['48'], title: extensionManifest.title, description: extensionManifest.description, deprecation: extensionManifest.deprecation, summary: extensionManifest.summary, documentationUrl: extensionManifest.documentationUrl, onClose: onCancel, enableHelpCTA: enableHelpCTA }); })); _defineProperty(_this, "getFirstVisibleFieldName", memoizeOne(function (fields) { function nonHidden(field) { if ('isHidden' in field) { return !field.isHidden; } return true; } // finds the first visible field, true for FieldSets too var firstVisibleField = fields.find(nonHidden); var newFirstVisibleFieldName; if (firstVisibleField) { // if it was a fieldset, go deeper trying to locate the field if (firstVisibleField.type === 'fieldset') { var firstVisibleFieldWithinFieldset = firstVisibleField.fields.find(nonHidden); newFirstVisibleFieldName = firstVisibleFieldWithinFieldset && firstVisibleFieldWithinFieldset.name; } else { newFirstVisibleFieldName = firstVisibleField.name; } } return newFirstVisibleFieldName; })); _defineProperty(_this, "setFirstVisibleFieldName", function (fields) { var newFirstVisibleFieldName = _this.getFirstVisibleFieldName(fields); if (newFirstVisibleFieldName !== _this.state.firstVisibleFieldName) { _this.setState({ firstVisibleFieldName: newFirstVisibleFieldName }); } }); _this.state = { hasParsedParameters: false, currentParameters: {}, firstVisibleFieldName: props.fields ? _this.getFirstVisibleFieldName(props.fields) : undefined }; _this.onFieldChange = null; _this.unbindKeyDownHandler = null; return _this; } _inherits(ConfigPanel, _React$Component); return _createClass(ConfigPanel, [{ key: "componentDidMount", value: function componentDidMount() { var _this$props3 = this.props, fields = _this$props3.fields, parameters = _this$props3.parameters; this.parseParameters(fields, parameters); if (expValEquals('platform_editor_a11y_eslint_fix', 'isEnabled', true)) { var doc = getDocument(); if (doc) { this.unbindKeyDownHandler = bind(doc, { type: 'keydown', listener: this.handleKeyDown }); } } } }, { key: "componentWillUnmount", value: function componentWillUnmount() { var _this$unbindKeyDownHa; var _this$props4 = this.props, createAnalyticsEvent = _this$props4.createAnalyticsEvent, extensionManifest = _this$props4.extensionManifest, fields = _this$props4.fields; var currentParameters = this.state.currentParameters; (_this$unbindKeyDownHa = this.unbindKeyDownHandler) === null || _this$unbindKeyDownHa === void 0 || _this$unbindKeyDownHa.call(this); fireAnalyticsEvent(createAnalyticsEvent)({ payload: { action: ACTION.CLOSED, actionSubject: ACTION_SUBJECT.CONFIG_PANEL, eventType: EVENT_TYPE.UI, attributes: _objectSpread({ extensionKey: extensionManifest === null || extensionManifest === void 0 ? void 0 : extensionManifest.key, extensionType: extensionManifest === null || extensionManifest === void 0 ? void 0 : extensionManifest.type }, extensionManifest !== null && extensionManifest !== void 0 && extensionManifest.key && ALLOWED_LOGGED_MACRO_PARAMS[extensionManifest.key] ? { parameters: getLoggedParameters(extensionManifest.key, currentParameters, fields) } : {}) } }); } }, { key: "componentDidUpdate", value: function componentDidUpdate(prevProps) { var _this$props5 = this.props, parameters = _this$props5.parameters, fields = _this$props5.fields, autoSaveTrigger = _this$props5.autoSaveTrigger, extensionManifest = _this$props5.extensionManifest; if (parameters && parameters !== prevProps.parameters || fields && (!prevProps.fields || !_isEqual(fields, prevProps.fields))) { this.parseParameters(fields, parameters); } if (fields && (!prevProps.fields || !_isEqual(fields, prevProps.fields))) { this.setFirstVisibleFieldName(fields); } if (prevProps.autoSaveTrigger !== autoSaveTrigger) { if (this.onFieldChange) { this.onFieldChange('', true); } } if (prevProps.extensionManifest === undefined && prevProps.extensionManifest !== extensionManifest) { // This will only be fired once when extensionManifest is loaded initially // Can't do this in componentDidMount because extensionManifest is still undefined at that point fireAnalyticsEvent(this.props.createAnalyticsEvent)({ payload: { action: ACTION.OPENED, actionSubject: ACTION_SUBJECT.CONFIG_PANEL, eventType: EVENT_TYPE.UI, attributes: { extensionKey: extensionManifest === null || extensionManifest === void 0 ? void 0 : extensionManifest.key, extensionType: extensionManifest === null || extensionManifest === void 0 ? void 0 : extensionManifest.type } } }); } } }, { key: "render", value: function render() { var _this2 = this; var _this$props6 = this.props, extensionManifest = _this$props6.extensionManifest, featureFlags = _this$props6.featureFlags; if (!extensionManifest) { return /*#__PURE__*/React.createElement(LoadingState, null); } var _this$props7 = this.props, errorMessage = _this$props7.errorMessage, fields = _this$props7.fields, isLoading = _this$props7.isLoading, onCancel = _this$props7.onCancel, api = _this$props7.api; var _this$state = this.state, currentParameters = _this$state.currentParameters, hasParsedParameters = _this$state.hasParsedParameters, firstVisibleFieldName = _this$state.firstVisibleFieldName; var handleSubmit = this.handleSubmit, handleKeyDown = this.handleKeyDown; return /*#__PURE__*/React.createElement(Form, { onSubmit: handleSubmit }, function (_ref5) { var formProps = _ref5.formProps, getState = _ref5.getState, submitting = _ref5.submitting; return /*#__PURE__*/React.createElement(WithOnFieldChange, { autoSave: true, getState: getState, handleSubmit: handleSubmit }, function (onFieldChange) { _this2.onFieldChange = onFieldChange; return /*#__PURE__*/React.createElement("form", _extends({}, formProps, { noValidate: true, onKeyDown: expValEquals('platform_editor_a11y_eslint_fix', 'isEnabled', true) ? undefined : handleKeyDown, "data-testid": "extension-config-panel" }), _this2.renderHeader(extensionManifest), (!fg('platform_editor_conditionally_add_sidebar_summary') || _this2.props.usingObjectSidebarPanel) && fg('platform_editor_ai_object_sidebar_injection') && /*#__PURE__*/React.createElement(DescriptionSummary, { extensionManifest: extensionManifest }), /*#__PURE__*/React.createElement(ConfigFormIntlWithBoundary, { api: api, canSave: false, errorMessage: errorMessage, extensionManifest: extensionManifest, fields: fields !== null && fields !== void 0 ? fields : [], firstVisibleFieldName: firstVisibleFieldName, hasParsedParameters: hasParsedParameters, isLoading: isLoading || false, onCancel: onCancel, onFieldChange: onFieldChange, parameters: currentParameters, submitting: submitting, featureFlags: featureFlags, disableFields: _this2.props.disableFields })); }); }); } }]); }(React.Component); var selector = function selector(states) { var _states$contextIdenti; return { contextIdentifierProvider: (_states$contextIdenti = states.contextIdentifierState) === null || _states$contextIdenti === void 0 ? void 0 : _states$contextIdenti.contextIdentifierProvider }; }; function ConfigFormIntlWithBoundary(_ref6) { var api = _ref6.api, fields = _ref6.fields, submitting = _ref6.submitting, parameters = _ref6.parameters, featureFlags = _ref6.featureFlags, canSave = _ref6.canSave, extensionManifest = _ref6.extensionManifest, onFieldChange = _ref6.onFieldChange, onCancel = _ref6.onCancel, isLoading = _ref6.isLoading, hasParsedParameters = _ref6.hasParsedParameters, firstVisibleFieldName = _ref6.firstVisibleFieldName, errorMessage = _ref6.errorMessage, disableFields = _ref6.disableFields; var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['contextIdentifier'], selector), contextIdentifierProvider = _useSharedPluginState.contextIdentifierProvider; return /*#__PURE__*/React.createElement(FormErrorBoundary, { contextIdentifierProvider: contextIdentifierProvider, extensionKey: extensionManifest.key, fields: fields }, /*#__PURE__*/React.createElement(ConfigFormIntl, { canSave: canSave, errorMessage: errorMessage, extensionManifest: extensionManifest, fields: fields, firstVisibleFieldName: firstVisibleFieldName, hasParsedParameters: hasParsedParameters, isLoading: isLoading || false, onCancel: onCancel, onFieldChange: onFieldChange, parameters: parameters, submitting: submitting, contextIdentifierProvider: contextIdentifierProvider, featureFlags: featureFlags, disableFields: disableFields })); } var result = withAnalyticsContext({ source: 'ConfigPanel' })(withAnalyticsEvents()(ConfigPanel)); export default result;