@atlaskit/editor-plugin-extension
Version:
editor-plugin-extension plugin for @atlaskit/editor-core
456 lines (444 loc) • 23.6 kB
JavaScript
"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;
};
};