UNPKG

@atlaskit/editor-plugin-extension

Version:

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

456 lines (444 loc) 23.6 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.getToolbarConfig = exports.createOnClickCopyButton = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _react = _interopRequireDefault(require("react")); var _analytics = require("@atlaskit/editor-common/analytics"); var _extensions = require("@atlaskit/editor-common/extensions"); var _messages = _interopRequireDefault(require("@atlaskit/editor-common/messages")); var _styles = require("@atlaskit/editor-common/styles"); var _toolbarFlagCheck = require("@atlaskit/editor-common/toolbar-flag-check"); var _utils = require("@atlaskit/editor-common/utils"); var _editorPluginConnectivity = require("@atlaskit/editor-plugin-connectivity"); var _utils2 = require("@atlaskit/editor-prosemirror/utils"); var _contentWidthNarrow = _interopRequireDefault(require("@atlaskit/icon/core/content-width-narrow")); var _contentWidthWide = _interopRequireDefault(require("@atlaskit/icon/core/content-width-wide")); var _delete = _interopRequireDefault(require("@atlaskit/icon/core/delete")); var _edit = _interopRequireDefault(require("@atlaskit/icon/core/edit")); var _expandHorizontal = _interopRequireDefault(require("@atlaskit/icon/core/expand-horizontal")); var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals"); var _experiments = require("@atlaskit/tmp-editor-statsig/experiments"); var _actions = require("../editor-actions/actions"); var _commands = require("../editor-commands/commands"); var _pluginKey = require("./macro/plugin-key"); var _pluginFactory = require("./plugin-factory"); var _utils3 = require("./utils"); 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) { (0, _defineProperty2.default)(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; } // non-bodied extensions nested inside panels, blockquotes and lists do not support layouts var isNestedNBM = function isNestedNBM(state, selectedExtNode) { var _state$schema$nodes = state.schema.nodes, extension = _state$schema$nodes.extension, panel = _state$schema$nodes.panel, blockquote = _state$schema$nodes.blockquote, listItem = _state$schema$nodes.listItem, selection = state.selection; if (!selectedExtNode) { return false; } return selectedExtNode.node.type === extension && (0, _utils2.hasParentNodeOfType)([panel, blockquote, listItem].filter(Boolean))(selection); }; var isLayoutSupported = function isLayoutSupported(state, selectedExtNode) { var _state$schema$nodes2 = state.schema.nodes, bodiedExtension = _state$schema$nodes2.bodiedExtension, extension = _state$schema$nodes2.extension, layoutSection = _state$schema$nodes2.layoutSection, table = _state$schema$nodes2.table, expand = _state$schema$nodes2.expand, multiBodiedExtension = _state$schema$nodes2.multiBodiedExtension, selection = state.selection; if (!selectedExtNode) { return false; } var isMultiBodiedExtension = selectedExtNode.node.type === multiBodiedExtension; var isNonEmbeddedBodiedExtension = selectedExtNode.node.type === bodiedExtension && !(0, _utils2.hasParentNodeOfType)([multiBodiedExtension].filter(Boolean))(selection); var isNonEmbeddedExtension = selectedExtNode.node.type === extension && !(0, _utils2.hasParentNodeOfType)([bodiedExtension, table, expand, multiBodiedExtension].filter(Boolean))(selection); // if selection belongs to layout supported extension category // and not inside a layoutSection return !!((isMultiBodiedExtension || isNonEmbeddedBodiedExtension || isNonEmbeddedExtension) && !(0, _utils2.hasParentNodeOfType)([layoutSection])(selection)); }; var breakoutButtonListOptions = function breakoutButtonListOptions(state, formatMessage, extensionState, breakoutEnabled, editorAnalyticsAPI) { var nodeWithPos = (0, _utils3.getSelectedExtension)(state, true); // we should only return breakout options when breakouts are enabled and the node supports them if (nodeWithPos && breakoutEnabled && isLayoutSupported(state, nodeWithPos) && !isNestedNBM(state, nodeWithPos)) { var layout = nodeWithPos.node.attrs.layout; return [{ type: 'button', icon: function icon() { return /*#__PURE__*/_react.default.createElement(_contentWidthNarrow.default, { label: formatMessage(_messages.default.layoutFixedWidth), spacing: "spacious" }); }, iconFallback: _contentWidthNarrow.default, onClick: (0, _commands.updateExtensionLayout)('default', editorAnalyticsAPI), selected: layout === 'default', title: formatMessage(_messages.default.layoutFixedWidth), tabIndex: null }, { type: 'button', icon: function icon() { return /*#__PURE__*/_react.default.createElement(_contentWidthWide.default, { label: formatMessage(_messages.default.layoutWide), spacing: "spacious" }); }, iconFallback: _contentWidthWide.default, onClick: (0, _commands.updateExtensionLayout)('wide', editorAnalyticsAPI), selected: layout === 'wide', title: formatMessage(_messages.default.layoutWide), tabIndex: null }, { type: 'button', icon: function icon() { return /*#__PURE__*/_react.default.createElement(_expandHorizontal.default, { label: formatMessage(_messages.default.layoutFullWidth), spacing: "spacious" }); }, iconFallback: _expandHorizontal.default, onClick: (0, _commands.updateExtensionLayout)('full-width', editorAnalyticsAPI), selected: layout === 'full-width', title: formatMessage(_messages.default.layoutFullWidth), tabIndex: null }]; } return []; }; var breakoutDropdownOptions = function breakoutDropdownOptions(state, formatMessage, breakoutEnabled, editorAnalyticsAPI) { var nodeWithPos = (0, _utils3.getSelectedExtension)(state, true); // we should only return breakout options when breakouts are enabled and the node supports them if (!nodeWithPos || !breakoutEnabled || !isLayoutSupported(state, nodeWithPos) || isNestedNBM(state, nodeWithPos)) { return []; } var layout = nodeWithPos.node.attrs.layout; var title = ''; var IconComponent; switch (layout) { case 'wide': title = formatMessage(_messages.default.layoutStateWide); IconComponent = _contentWidthWide.default; break; case 'full-width': title = formatMessage(_messages.default.layoutStateFullWidth); IconComponent = _expandHorizontal.default; break; case 'default': default: title = formatMessage(_messages.default.layoutStateFixedWidth); IconComponent = _contentWidthNarrow.default; break; } var options = [{ id: 'editor.extensions.width.default', title: formatMessage(_messages.default.layoutFixedWidth), onClick: (0, _commands.updateExtensionLayout)('default', editorAnalyticsAPI), selected: layout === 'default', icon: /*#__PURE__*/_react.default.createElement(_contentWidthNarrow.default, { color: "currentColor", spacing: "spacious", label: formatMessage(_messages.default.layoutFixedWidth) }) }, { id: 'editor.extensions.width.wide', title: formatMessage(_messages.default.layoutWide), onClick: (0, _commands.updateExtensionLayout)('wide', editorAnalyticsAPI), selected: layout === 'wide', icon: /*#__PURE__*/_react.default.createElement(_contentWidthWide.default, { color: "currentColor", spacing: "spacious", label: formatMessage(_messages.default.layoutWide) }) }, { id: 'editor.extensions.width.full-width', title: formatMessage(_messages.default.layoutFullWidth), onClick: (0, _commands.updateExtensionLayout)('full-width', editorAnalyticsAPI), selected: layout === 'full-width', icon: /*#__PURE__*/_react.default.createElement(_expandHorizontal.default, { color: "currentColor", spacing: "spacious", label: formatMessage(_messages.default.layoutFullWidth) }) }]; return [{ id: 'extensions-width-options-toolbar-item', testId: 'extensions-width-options-toolbar-dropdown', type: 'dropdown', options: options, title: title, iconBefore: function iconBefore() { return /*#__PURE__*/_react.default.createElement(IconComponent, { color: "currentColor", spacing: "spacious", label: title }); } }]; }; var breakoutOptions = function breakoutOptions(state, formatMessage, extensionState, breakoutEnabled, editorAnalyticsAPI, api) { return (0, _toolbarFlagCheck.areToolbarFlagsEnabled)(Boolean(api === null || api === void 0 ? void 0 : api.toolbar)) ? breakoutDropdownOptions(state, formatMessage, breakoutEnabled, editorAnalyticsAPI) : breakoutButtonListOptions(state, formatMessage, extensionState, breakoutEnabled, editorAnalyticsAPI); }; var editButton = function editButton(formatMessage, extensionState, applyChangeToContextPanel, editorAnalyticsAPI) { var isDisabled = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; var api = arguments.length > 5 ? arguments[5] : undefined; if (!extensionState.showEditButton) { return []; } var toolbarFlagsEnabled = (0, _toolbarFlagCheck.areToolbarFlagsEnabled)(Boolean(api === null || api === void 0 ? void 0 : api.toolbar)); var editButtonItems = [{ id: 'editor.extension.edit', type: 'button', icon: _edit.default, iconFallback: _edit.default, testId: 'extension-toolbar-edit-button', // Taking the latest `updateExtension` from plugin state to avoid race condition @see ED-8501 onClick: function onClick(state, dispatch, view) { var macroState = _pluginKey.pluginKey.getState(state); var _getPluginState = (0, _pluginFactory.getPluginState)(state), updateExtension = _getPluginState.updateExtension; (0, _actions.editExtension)(macroState && macroState.macroProvider, applyChangeToContextPanel, editorAnalyticsAPI, updateExtension)(state, dispatch, view); return true; }, title: formatMessage(_extensions.messages.edit), tabIndex: null, focusEditoronEnter: true, disabled: isDisabled, isRadioButton: (0, _expValEquals.expValEquals)('platform_editor_august_a11y', 'isEnabled', true) ? false : undefined }]; if (toolbarFlagsEnabled) { editButtonItems.push({ type: 'separator', fullHeight: true }); } return editButtonItems; }; /** * Calculates the position for the toolbar when dealing with nested extensions * @param editorView * @param nextPos * @param state * @param extensionNode * @example */ var calculateToolbarPosition = function calculateToolbarPosition(editorView, nextPos, state, extensionNode) { var _editorView$state = editorView.state, schema = _editorView$state.schema, selection = _editorView$state.selection; var possibleMbeParent = (0, _utils2.findParentNodeOfType)(schema.nodes.extensionFrame)(selection); // We only want to use calculated position in case of a bodiedExtension present inside an MBE node var isBodiedExtensionInsideMBE = possibleMbeParent && (extensionNode === null || extensionNode === void 0 ? void 0 : extensionNode.node.type.name) === 'bodiedExtension'; var scrollWrapper = editorView.dom.closest('.fabric-editor-popup-scroll-parent') || document.body; if (!extensionNode) { return nextPos; } var isInsideEditableExtensionArea = !!editorView.dom.closest('.extension-editable-area'); if (!isBodiedExtensionInsideMBE && !isInsideEditableExtensionArea) { return nextPos; } if (isInsideEditableExtensionArea && scrollWrapper.parentElement) { // The editable extension area may have its own scroll wrapper, so we want to keep searching up the tree for the page level scroll wrapper. scrollWrapper = scrollWrapper.parentElement.closest('.fabric-editor-popup-scroll-parent') || scrollWrapper; } // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting var nestedBodiedExtensionDomElement = editorView.nodeDOM(extensionNode.pos); var nestedBodiedExtensionRect = nestedBodiedExtensionDomElement === null || nestedBodiedExtensionDomElement === void 0 ? void 0 : nestedBodiedExtensionDomElement.getBoundingClientRect(); var wrapperBounds = scrollWrapper.getBoundingClientRect(); var toolbarTopPos = nestedBodiedExtensionRect.bottom - wrapperBounds.top + scrollWrapper.scrollTop + _styles.BODIED_EXT_MBE_MARGIN_TOP; return { top: toolbarTopPos, left: nextPos.left }; }; /** * Creates a function that copies the text content of the unsupported content extension to the clipboard * if the current selected extension is an unsupported content extension. */ var createOnClickCopyButton = exports.createOnClickCopyButton = function createOnClickCopyButton(_ref) { var formatMessage = _ref.formatMessage, extensionApi = _ref.extensionApi, extensionProvider = _ref.extensionProvider, getUnsupportedContent = _ref.getUnsupportedContent, state = _ref.state, locale = _ref.locale; if (!extensionProvider) { return; } var nodeWithPos = (0, _utils3.getSelectedExtension)(state, true); if (!nodeWithPos) { return; } var node = nodeWithPos.node; var _node$attrs = node.attrs, extensionType = _node$attrs.extensionType, extensionKey = _node$attrs.extensionKey; var extensionParams = { type: node.type.name, extensionKey: extensionKey, extensionType: extensionType, parameters: node.attrs.parameters, content: node.content, localId: node.attrs.localId }; var adf = getUnsupportedContent === null || getUnsupportedContent === void 0 ? void 0 : getUnsupportedContent(extensionParams); if (!adf) { return; } // this command copies the text content of the unsupported content extension to the clipboard return function (editorState) { var _extensionApi$analyti; extensionApi === null || extensionApi === void 0 || (_extensionApi$analyti = extensionApi.analytics) === null || _extensionApi$analyti === void 0 || _extensionApi$analyti.actions.fireAnalyticsEvent({ action: _analytics.ACTION.CLICKED, actionSubject: _analytics.ACTION_SUBJECT.COPY_BUTTON, eventType: _analytics.EVENT_TYPE.UI, actionSubjectId: _analytics.ACTION_SUBJECT_ID.EXTENSION, attributes: { extensionDynamicType: node.type.name, extensionType: node.attrs.extensionType, extensionKey: node.attrs.extensionKey } }); (0, _utils3.copyUnsupportedContentToClipboard)({ locale: locale, unsupportedContent: adf, schema: state.schema, api: extensionApi }).then(function () { var _extensionApi$copyBut; extensionApi === null || extensionApi === void 0 || (_extensionApi$copyBut = extensionApi.copyButton) === null || _extensionApi$copyBut === void 0 || _extensionApi$copyBut.actions.afterCopy(formatMessage(_messages.default.copiedToClipboard)); }).catch(function (error) { (0, _utils3.onCopyFailed)({ error: error, extensionApi: extensionApi, state: editorState }); }); return true; }; }; var getToolbarConfig = exports.getToolbarConfig = function getToolbarConfig(_ref2) { var _ref2$breakoutEnabled = _ref2.breakoutEnabled, breakoutEnabled = _ref2$breakoutEnabled === void 0 ? true : _ref2$breakoutEnabled, extensionApi = _ref2.extensionApi, getUnsupportedContent = _ref2.getUnsupportedContent; return function (state, intl) { var _extensionApi$decorat, _extensionApi$context, _extensionApi$analyti2, _extensionApi$connect; var formatMessage = intl.formatMessage, locale = intl.locale; var extensionState = (0, _pluginFactory.getPluginState)(state); var extensionProvider = extensionState.extensionProvider; var hoverDecoration = extensionApi === null || extensionApi === void 0 || (_extensionApi$decorat = extensionApi.decorations) === null || _extensionApi$decorat === void 0 ? void 0 : _extensionApi$decorat.actions.hoverDecoration; var applyChangeToContextPanel = extensionApi === null || extensionApi === void 0 || (_extensionApi$context = extensionApi.contextPanel) === null || _extensionApi$context === void 0 ? void 0 : _extensionApi$context.actions.applyChange; var editorAnalyticsAPI = extensionApi === null || extensionApi === void 0 || (_extensionApi$analyti2 = extensionApi.analytics) === null || _extensionApi$analyti2 === void 0 ? void 0 : _extensionApi$analyti2.actions; if (!extensionState || extensionState.showContextPanel || !extensionState.element) { return; } var nodeType = [state.schema.nodes.extension, state.schema.nodes.inlineExtension, state.schema.nodes.bodiedExtension, state.schema.nodes.multiBodiedExtension]; var editButtonItems = editButton(formatMessage, extensionState, applyChangeToContextPanel, editorAnalyticsAPI, (0, _experiments.editorExperiment)('platform_editor_offline_editing_web', true) && (0, _editorPluginConnectivity.isOfflineMode)(extensionApi === null || extensionApi === void 0 || (_extensionApi$connect = extensionApi.connectivity) === null || _extensionApi$connect === void 0 || (_extensionApi$connect = _extensionApi$connect.sharedState) === null || _extensionApi$connect === void 0 || (_extensionApi$connect = _extensionApi$connect.currentState()) === null || _extensionApi$connect === void 0 ? void 0 : _extensionApi$connect.mode), extensionApi); var breakoutItems = breakoutOptions(state, formatMessage, extensionState, breakoutEnabled, editorAnalyticsAPI, extensionApi); var extensionObj = (0, _utils3.getSelectedExtension)(state, true); // If this is a native-embed extension, skip providing a toolbar config to allow // the native-embed plugin to provide a custom toolbar config. if ((extensionObj === null || extensionObj === void 0 ? void 0 : extensionObj.node.attrs.extensionType) === _extensions.NATIVE_EMBED_EXTENSION_TYPE && extensionObj !== null && extensionObj !== void 0 && extensionObj.node.attrs.extensionKey.includes(_extensions.NATIVE_EMBED_EXTENSION_KEY)) { return; } // Check if we need to show confirm dialog for delete button var confirmDialog; if ((0, _utils.isReferencedSource)(state, extensionObj === null || extensionObj === void 0 ? void 0 : extensionObj.node)) { confirmDialog = function confirmDialog() { var localSourceName = formatMessage(_extensions.messages.unnamedSource); return { title: formatMessage(_extensions.messages.deleteElementTitle), okButtonLabel: formatMessage(_extensions.messages.confirmDeleteLinkedModalOKButton), message: formatMessage(_extensions.messages.confirmDeleteLinkedModalMessage, { nodeName: (0, _utils.getNodeName)(state, extensionObj === null || extensionObj === void 0 ? void 0 : extensionObj.node) || localSourceName }), isReferentialityDialog: true, getChildrenInfo: function getChildrenInfo() { return (0, _utils.getChildrenInfo)(state, extensionObj === null || extensionObj === void 0 ? void 0 : extensionObj.node); }, checkboxLabel: formatMessage(_extensions.messages.confirmModalCheckboxLabel), onConfirm: function onConfirm() { var isChecked = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; return clickWithCheckboxHandler(isChecked, extensionObj === null || extensionObj === void 0 ? void 0 : extensionObj.node); } }; }; } // disable copy button for legacy content macro var isLegacyContentMacro = (extensionObj === null || extensionObj === void 0 ? void 0 : extensionObj.node.attrs.extensionType) === 'com.atlassian.confluence.migration' && (extensionObj === null || extensionObj === void 0 ? void 0 : extensionObj.node.attrs.extensionKey) === 'legacy-content'; var shouldHideCopyButton = isLegacyContentMacro && (0, _expValEquals.expValEquals)('platform_editor_disable_lcm_copy_button', 'isEnabled', true); return { title: 'Extension floating controls', // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion getDomRef: function getDomRef() { return extensionState.element.parentElement || undefined; }, nodeType: nodeType, onPositionCalculated: function onPositionCalculated(editorView, nextPos) { return calculateToolbarPosition(editorView, nextPos, state, extensionObj); }, items: [].concat((0, _toConsumableArray2.default)(editButtonItems), (0, _toConsumableArray2.default)(breakoutItems), [{ type: 'separator', hidden: editButtonItems.length === 0 && breakoutItems.length === 0 }, { type: 'extensions-placeholder', separator: 'end' }, _objectSpread({ type: 'copy-button', items: [{ state: state, formatMessage: intl.formatMessage, nodeType: nodeType, onClick: (0, _expValEquals.expValEquals)('platform_editor_ai_edit_unsupported_content', 'isEnabled', true) ? createOnClickCopyButton({ formatMessage: formatMessage, extensionApi: extensionApi, extensionProvider: extensionProvider, getUnsupportedContent: getUnsupportedContent, state: state, locale: locale }) : undefined }] }, shouldHideCopyButton && { hidden: shouldHideCopyButton }), { type: 'separator' }, { id: 'editor.extension.delete', type: 'button', icon: _delete.default, iconFallback: _delete.default, appearance: 'danger', onClick: (0, _commands.removeExtension)(editorAnalyticsAPI, _analytics.INPUT_METHOD.FLOATING_TB), onMouseEnter: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, true), onMouseLeave: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false), onFocus: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, true), onBlur: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false), focusEditoronEnter: true, title: formatMessage(_messages.default.remove), tabIndex: null, confirmDialog: confirmDialog }]), scrollable: true }; }; }; var clickWithCheckboxHandler = function clickWithCheckboxHandler(isChecked, node) { return function (state, dispatch) { if (!node) { return false; } if (!isChecked) { (0, _commands.removeExtension)()(state, dispatch); } else { (0, _commands.removeDescendantNodes)(node)(state, dispatch); } return true; }; };