@atlaskit/editor-plugin-insert-block
Version:
Insert block plugin for @atlaskit/editor-core
244 lines (242 loc) • 11.4 kB
JavaScript
import React, { useRef, useState } from 'react';
import { useIntl } from 'react-intl';
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
import { getAriaKeyshortcuts, insertElements, ToolTipContent } from '@atlaskit/editor-common/keymaps';
import { toolbarInsertBlockMessages as messages } from '@atlaskit/editor-common/messages';
import { TOOLBAR_BUTTON_TEST_ID, useEditorToolbar } from '@atlaskit/editor-common/toolbar';
import { Popup } from '@atlaskit/editor-common/ui';
import { useSharedPluginStateSelector } from '@atlaskit/editor-common/use-shared-plugin-state-selector';
import { akEditorMenuZIndex } from '@atlaskit/editor-shared-styles';
import { ToolbarButton, ToolbarTooltip, AddIcon, useToolbarUI } from '@atlaskit/editor-toolbar';
import InsertMenu, { DEFAULT_HEIGHT } from '../ElementBrowser/InsertMenu';
import { LINK_BUTTON_KEY } from './hooks/filterDropdownItems';
import { useEmojiPickerPopup } from './hooks/useEmojiPickerPopup';
import { useInsertButtonState } from './hooks/useInsertButtonState';
import { useTableSelectorPopup } from './hooks/useTableSelectorPopup';
import { EmojiPickerPopup } from './popups/EmojiPickerPopup';
// This determines how the popup should fit. We prefer the insert menu
// opening on the bottom as we have a search bar and should only open on
// top if there is more than sufficient room.
const FIT_HEIGHT_BUFFER = 100;
export const InsertButton = ({
api,
breakpoint,
showElementBrowserLink = false,
isFullPageAppearance = false,
tableSelectorSupported,
nativeStatusSupported,
horizontalRuleEnabled,
expandEnabled,
insertMenuItems,
numberOfButtons,
onInsertBlockType,
toolbarConfig
}) => {
const {
editorView
} = useEditorToolbar();
const {
isDisabled,
popupsMountPoint,
popupsBoundariesElement,
popupsScrollableElement
} = useToolbarUI();
const {
formatMessage
} = useIntl();
const [insertMenuOpen, setInsertMenuOpen] = useState(false);
const insertButtonRef = useRef(null);
const emojiPickerPopup = useEmojiPickerPopup({
api,
buttonRef: insertButtonRef
});
const tableSelectorPopup = useTableSelectorPopup({
api,
buttonRef: insertButtonRef
});
const showMediaPicker = useSharedPluginStateSelector(api, 'media.showMediaPicker');
const {
dropdownItems,
emojiProvider,
isTypeAheadAllowed
} = useInsertButtonState({
api,
breakpoint,
editorView: editorView || undefined,
horizontalRuleEnabled,
insertMenuItems,
nativeStatusSupported,
numberOfButtons,
tableSelectorSupported,
expandEnabled,
showElementBrowserLink,
toolbarConfig
});
if (!(api !== null && api !== void 0 && api.insertBlock)) {
return null;
}
const toggleInsertMenuOpen = newState => {
setInsertMenuOpen(newState);
};
const onPopupUnmount = () => {
requestAnimationFrame(() => api === null || api === void 0 ? void 0 : api.core.actions.focus());
};
const onClick = () => {
toggleInsertMenuOpen(!insertMenuOpen);
};
const onItemActivated = ({
item,
inputMethod
}) => {
var _api$core, _api$hyperlink, _api$imageUpload, _api$mention, _api$mention$actions, _api$taskDecision, _api$rule, _api$core3, _api$date, _api$date$commands, _api$placeholderText, _api$layout, _api$core4, _api$status, _api$status$commands, _api$expand;
if (!editorView) {
return;
}
const {
state,
dispatch
} = editorView;
// need to do this before inserting nodes so scrollIntoView works properly
if (!editorView.hasFocus()) {
editorView.focus();
}
switch (item.value.name) {
case LINK_BUTTON_KEY:
api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(api === null || api === void 0 ? void 0 : (_api$hyperlink = api.hyperlink) === null || _api$hyperlink === void 0 ? void 0 : _api$hyperlink.commands.showLinkToolbar(inputMethod));
break;
case 'table':
// workaround to solve race condition where cursor is not placed correctly inside table
queueMicrotask(() => {
var _api$table, _api$table$actions$in, _api$table$actions;
api === null || api === void 0 ? void 0 : (_api$table = api.table) === null || _api$table === void 0 ? void 0 : (_api$table$actions$in = (_api$table$actions = _api$table.actions).insertTable) === null || _api$table$actions$in === void 0 ? void 0 : _api$table$actions$in.call(_api$table$actions, {
action: ACTION.INSERTED,
actionSubject: ACTION_SUBJECT.DOCUMENT,
actionSubjectId: ACTION_SUBJECT_ID.TABLE,
attributes: {
inputMethod
},
eventType: EVENT_TYPE.TRACK
})(state, dispatch);
});
break;
case 'table selector':
tableSelectorPopup.toggle(inputMethod);
break;
case 'image upload':
if (api !== null && api !== void 0 && (_api$imageUpload = api.imageUpload) !== null && _api$imageUpload !== void 0 && _api$imageUpload.actions.startUpload) {
api.imageUpload.actions.startUpload()(state, dispatch);
}
break;
case 'media':
if (showMediaPicker) {
var _api$mediaInsert, _api$core2, _api$mediaInsert2;
api !== null && api !== void 0 && (_api$mediaInsert = api.mediaInsert) !== null && _api$mediaInsert !== void 0 && _api$mediaInsert.commands.showMediaInsertPopup ? api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.execute(api === null || api === void 0 ? void 0 : (_api$mediaInsert2 = api.mediaInsert) === null || _api$mediaInsert2 === void 0 ? void 0 : _api$mediaInsert2.commands.showMediaInsertPopup()) : showMediaPicker === null || showMediaPicker === void 0 ? void 0 : showMediaPicker();
}
break;
case 'mention':
api === null || api === void 0 ? void 0 : (_api$mention = api.mention) === null || _api$mention === void 0 ? void 0 : (_api$mention$actions = _api$mention.actions) === null || _api$mention$actions === void 0 ? void 0 : _api$mention$actions.openTypeAhead(inputMethod);
break;
case 'emoji':
emojiPickerPopup.toggle(inputMethod);
break;
case 'codeblock':
case 'blockquote':
case 'panel':
onInsertBlockType === null || onInsertBlockType === void 0 ? void 0 : onInsertBlockType(item.value.name)(state, dispatch);
break;
case 'action':
case 'decision':
const listType = item.value.name === 'action' ? 'taskList' : 'decisionList';
api === null || api === void 0 ? void 0 : (_api$taskDecision = api.taskDecision) === null || _api$taskDecision === void 0 ? void 0 : _api$taskDecision.actions.insertTaskDecision(listType, inputMethod)(state, dispatch);
break;
case 'horizontalrule':
api === null || api === void 0 ? void 0 : (_api$rule = api.rule) === null || _api$rule === void 0 ? void 0 : _api$rule.actions.insertHorizontalRule(inputMethod)(state, dispatch);
break;
case 'date':
api === null || api === void 0 ? void 0 : (_api$core3 = api.core) === null || _api$core3 === void 0 ? void 0 : _api$core3.actions.execute(api === null || api === void 0 ? void 0 : (_api$date = api.date) === null || _api$date === void 0 ? void 0 : (_api$date$commands = _api$date.commands) === null || _api$date$commands === void 0 ? void 0 : _api$date$commands.insertDate({
inputMethod
}));
break;
case 'placeholder text':
api === null || api === void 0 ? void 0 : (_api$placeholderText = api.placeholderText) === null || _api$placeholderText === void 0 ? void 0 : _api$placeholderText.actions.showPlaceholderFloatingToolbar(state, dispatch);
break;
case 'layout':
api === null || api === void 0 ? void 0 : (_api$layout = api.layout) === null || _api$layout === void 0 ? void 0 : _api$layout.actions.insertLayoutColumns(inputMethod)(state, dispatch);
break;
case 'status':
api === null || api === void 0 ? void 0 : (_api$core4 = api.core) === null || _api$core4 === void 0 ? void 0 : _api$core4.actions.execute(api === null || api === void 0 ? void 0 : (_api$status = api.status) === null || _api$status === void 0 ? void 0 : (_api$status$commands = _api$status.commands) === null || _api$status$commands === void 0 ? void 0 : _api$status$commands.insertStatus(inputMethod));
break;
case 'expand':
api === null || api === void 0 ? void 0 : (_api$expand = api.expand) === null || _api$expand === void 0 ? void 0 : _api$expand.actions.insertExpand(state, dispatch);
break;
default:
if (item && item.onClick) {
item.onClick();
}
break;
}
toggleInsertMenuOpen(false);
};
const onInsert = ({
item
}) => {
onItemActivated({
item,
inputMethod: INPUT_METHOD.INSERT_MENU
});
};
const toggleVisibility = () => {
toggleInsertMenuOpen(!insertMenuOpen);
};
return /*#__PURE__*/React.createElement(React.Fragment, null, insertMenuOpen && insertButtonRef.current && editorView && /*#__PURE__*/React.createElement(Popup, {
target: insertButtonRef.current,
fitHeight: DEFAULT_HEIGHT + FIT_HEIGHT_BUFFER,
fitWidth: 350
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
offset: [0, 3],
mountTo: popupsMountPoint,
boundariesElement: popupsBoundariesElement,
scrollableElement: popupsScrollableElement,
onUnmount: onPopupUnmount,
focusTrap: true,
zIndex: akEditorMenuZIndex,
preventOverflow: true,
alignX: "right"
}, /*#__PURE__*/React.createElement(InsertMenu, {
editorView: editorView,
dropdownItems: dropdownItems,
onInsert: onInsert,
toggleVisiblity: toggleVisibility,
showElementBrowserLink: showElementBrowserLink,
pluginInjectionApi: api,
isFullPageAppearance: isFullPageAppearance
})), emojiProvider && /*#__PURE__*/React.createElement(EmojiPickerPopup, {
isOpen: emojiPickerPopup.isOpen,
targetRef: insertButtonRef,
emojiProvider: Promise.resolve(emojiProvider),
onSelection: emojiPickerPopup.handleSelectedEmoji,
onClickOutside: emojiPickerPopup.handleClickOutside,
onEscapeKeydown: emojiPickerPopup.handleEscapeKeydown,
onUnmount: emojiPickerPopup.onPopupUnmount,
popupsMountPoint: popupsMountPoint,
popupsBoundariesElement: popupsBoundariesElement,
popupsScrollableElement: popupsScrollableElement
}), /*#__PURE__*/React.createElement(ToolbarTooltip, {
content: /*#__PURE__*/React.createElement(ToolTipContent, {
description: formatMessage(messages.insertMenu),
keymap: insertElements
})
}, /*#__PURE__*/React.createElement(ToolbarButton, {
iconBefore: /*#__PURE__*/React.createElement(AddIcon, {
size: "small",
label: formatMessage(messages.insertMenu)
}),
ariaKeyshortcuts: getAriaKeyshortcuts(insertElements),
ref: insertButtonRef,
onClick: onClick,
isSelected: insertMenuOpen,
isDisabled: !isTypeAheadAllowed || isDisabled,
testId: TOOLBAR_BUTTON_TEST_ID.INSERT
})));
};