@atlaskit/editor-plugin-extension
Version:
editor-plugin-extension plugin for @atlaskit/editor-core
539 lines (533 loc) • 24.4 kB
JavaScript
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;