@atlaskit/editor-plugin-selection-toolbar
Version:
@atlaskit/editor-plugin-selection-toolbar for @atlaskit/editor-core
393 lines (389 loc) • 23 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
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 React from 'react';
import { bind } from 'bind-event-listener';
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
import { calculateToolbarPositionAboveSelection, calculateToolbarPositionOnCellSelection, calculateToolbarPositionTrackHead } from '@atlaskit/editor-common/utils';
import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
import { fg } from '@atlaskit/platform-feature-flags';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
import { setToolbarDocking as _setToolbarDocking, toggleToolbar, updateToolbarDocking, forceToolbarDockingWithoutAnalytics as _forceToolbarDockingWithoutAnalytics } from './pm-plugins/commands';
import { selectionToolbarPluginKey } from './pm-plugins/plugin-key';
import { PageVisibilityWatcher } from './ui/PageVisibilityWatcher';
import { getPinOptionToolbarConfig } from './ui/pin-toolbar-config';
import { PrimaryToolbarComponent } from './ui/PrimaryToolbarComponent';
import { getToolbarComponents } from './ui/toolbar-components';
var getToolbarDocking = function getToolbarDocking(contextualFormattingEnabled, userPreferencesProvider) {
if (contextualFormattingEnabled && editorExperiment('platform_editor_controls', 'variant1')) {
var _userPreferencesProvi;
return (_userPreferencesProvi = userPreferencesProvider === null || userPreferencesProvider === void 0 ? void 0 : userPreferencesProvider.getPreference('toolbarDockingInitialPosition')) !== null && _userPreferencesProvi !== void 0 ? _userPreferencesProvi : 'none';
}
return 'top';
};
var getToolbarDockingV2 = function getToolbarDockingV2(contextualFormattingEnabled, dockingPreference) {
if (contextualFormattingEnabled && editorExperiment('platform_editor_controls', 'variant1')) {
return dockingPreference !== null && dockingPreference !== void 0 ? dockingPreference : 'none';
}
return 'top';
};
export var selectionToolbarPlugin = function selectionToolbarPlugin(_ref) {
var api = _ref.api,
config = _ref.config;
var __selectionToolbarHandlers = [];
var primaryToolbarComponent;
var isToolbarAIFCEnabled = Boolean(api === null || api === void 0 ? void 0 : api.toolbar);
var userPreferencesProvider = config.userPreferencesProvider,
contextualFormattingEnabled = config.contextualFormattingEnabled,
disablePin = config.disablePin;
if (isToolbarAIFCEnabled) {
var _api$toolbar;
/**
* If toolbar is set to always-pinned or always-inline, there is no control over toolbar placement
*/
if ((api === null || api === void 0 || (_api$toolbar = api.toolbar) === null || _api$toolbar === void 0 ? void 0 : _api$toolbar.actions.contextualFormattingMode()) === 'controlled') {
var _api$toolbar2;
api === null || api === void 0 || (_api$toolbar2 = api.toolbar) === null || _api$toolbar2 === void 0 || _api$toolbar2.actions.registerComponents(getToolbarComponents(api, true, disablePin));
}
} else {
if (editorExperiment('platform_editor_controls', 'variant1', {
exposure: true
})) {
var _api$primaryToolbar;
primaryToolbarComponent = function primaryToolbarComponent(_ref2) {
var disabled = _ref2.disabled;
return /*#__PURE__*/React.createElement(PrimaryToolbarComponent, {
api: api,
disabled: disabled
});
};
api === null || api === void 0 || (_api$primaryToolbar = api.primaryToolbar) === null || _api$primaryToolbar === void 0 || _api$primaryToolbar.actions.registerComponent({
name: 'pinToolbar',
component: primaryToolbarComponent
});
}
}
var previousToolbarDocking = (userPreferencesProvider === null || userPreferencesProvider === void 0 ? void 0 : userPreferencesProvider.getPreference('toolbarDockingInitialPosition')) || null;
var isPreferenceInitialized = false;
return {
name: 'selectionToolbar',
actions: {
suppressToolbar: function suppressToolbar() {
var _api$core$actions$exe;
return (_api$core$actions$exe = api === null || api === void 0 ? void 0 : api.core.actions.execute(toggleToolbar({
hide: true
}))) !== null && _api$core$actions$exe !== void 0 ? _api$core$actions$exe : false;
},
unsuppressToolbar: function unsuppressToolbar() {
var _api$core$actions$exe2;
return (_api$core$actions$exe2 = api === null || api === void 0 ? void 0 : api.core.actions.execute(toggleToolbar({
hide: false
}))) !== null && _api$core$actions$exe2 !== void 0 ? _api$core$actions$exe2 : false;
},
setToolbarDocking: function setToolbarDocking(toolbarDocking) {
var _api$core$actions$exe4, _api$analytics;
if (fg('platform_editor_use_preferences_plugin')) {
var _api$core$actions$exe3, _api$userPreferences;
return (_api$core$actions$exe3 = api === null || api === void 0 ? void 0 : api.core.actions.execute(api === null || api === void 0 || (_api$userPreferences = api.userPreferences) === null || _api$userPreferences === void 0 ? void 0 : _api$userPreferences.actions.updateUserPreference('toolbarDockingPosition', toolbarDocking))) !== null && _api$core$actions$exe3 !== void 0 ? _api$core$actions$exe3 : false;
}
return (_api$core$actions$exe4 = api === null || api === void 0 ? void 0 : api.core.actions.execute(_setToolbarDocking({
toolbarDocking: toolbarDocking,
userPreferencesProvider: userPreferencesProvider,
editorAnalyticsApi: api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions
}))) !== null && _api$core$actions$exe4 !== void 0 ? _api$core$actions$exe4 : false;
},
forceToolbarDockingWithoutAnalytics: function forceToolbarDockingWithoutAnalytics(toolbarDocking) {
var _api$core$actions$exe6;
if (fg('platform_editor_use_preferences_plugin')) {
var _api$core$actions$exe5, _api$userPreferences2;
return (_api$core$actions$exe5 = api === null || api === void 0 ? void 0 : api.core.actions.execute(api === null || api === void 0 || (_api$userPreferences2 = api.userPreferences) === null || _api$userPreferences2 === void 0 ? void 0 : _api$userPreferences2.actions.updateUserPreference('toolbarDockingPosition', toolbarDocking))) !== null && _api$core$actions$exe5 !== void 0 ? _api$core$actions$exe5 : false;
}
return (_api$core$actions$exe6 = api === null || api === void 0 ? void 0 : api.core.actions.execute(_forceToolbarDockingWithoutAnalytics({
toolbarDocking: toolbarDocking,
userPreferencesProvider: userPreferencesProvider
}))) !== null && _api$core$actions$exe6 !== void 0 ? _api$core$actions$exe6 : false;
},
refreshToolbarDocking: function refreshToolbarDocking() {
if (userPreferencesProvider) {
var _api$core$actions$exe7;
var userToolbarDockingPref = getToolbarDocking(contextualFormattingEnabled, userPreferencesProvider);
return (_api$core$actions$exe7 = api === null || api === void 0 ? void 0 : api.core.actions.execute(updateToolbarDocking({
toolbarDocking: userToolbarDockingPref
}))) !== null && _api$core$actions$exe7 !== void 0 ? _api$core$actions$exe7 : false;
}
return false;
}
},
getSharedState: function getSharedState(editorState) {
if (!editorState) {
return;
}
return selectionToolbarPluginKey.getState(editorState);
},
pmPlugins: function pmPlugins(selectionToolbarHandlers) {
var _api$userPreferences3;
if (selectionToolbarHandlers) {
__selectionToolbarHandlers.push.apply(__selectionToolbarHandlers, _toConsumableArray(selectionToolbarHandlers));
}
var initialToolbarDocking = fg('platform_editor_use_preferences_plugin') ? getToolbarDockingV2(contextualFormattingEnabled, api === null || api === void 0 || (_api$userPreferences3 = api.userPreferences) === null || _api$userPreferences3 === void 0 || (_api$userPreferences3 = _api$userPreferences3.sharedState.currentState()) === null || _api$userPreferences3 === void 0 || (_api$userPreferences3 = _api$userPreferences3.preferences) === null || _api$userPreferences3 === void 0 ? void 0 : _api$userPreferences3.toolbarDockingPosition) : getToolbarDocking(contextualFormattingEnabled, userPreferencesProvider);
return [{
name: 'selection-tracker',
plugin: function plugin() {
return new SafePlugin({
key: selectionToolbarPluginKey,
state: {
init: function init() {
return {
selectionStable: false,
hide: false,
toolbarDocking: initialToolbarDocking
};
},
apply: function apply(tr, pluginState) {
var meta = tr.getMeta(selectionToolbarPluginKey);
var newPluginState = pluginState;
if (meta) {
return _objectSpread(_objectSpread({}, newPluginState), meta);
}
if (editorExperiment('platform_editor_block_menu', true)) {
var _api$userIntent;
var isBlockMenuOpen = (api === null || api === void 0 || (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 || (_api$userIntent = _api$userIntent.sharedState.currentState()) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.currentUserIntent) === 'blockMenuOpen';
newPluginState = _objectSpread(_objectSpread({}, newPluginState), {}, {
isBlockMenuOpen: isBlockMenuOpen
});
}
// if the toolbarDockingInitialPosition preference has changed
// update the toolbarDocking state
if (!previousToolbarDocking) {
// we currently only check for the initial value
var toolbarDockingPreference = userPreferencesProvider === null || userPreferencesProvider === void 0 ? void 0 : userPreferencesProvider.getPreference('toolbarDockingInitialPosition');
if (toolbarDockingPreference && toolbarDockingPreference !== previousToolbarDocking) {
previousToolbarDocking = toolbarDockingPreference;
var userToolbarDockingPref = getToolbarDocking(contextualFormattingEnabled, userPreferencesProvider);
if (pluginState.toolbarDocking !== userToolbarDockingPref) {
return _objectSpread(_objectSpread({}, newPluginState), {}, {
toolbarDocking: userToolbarDockingPref
});
}
}
}
return newPluginState;
}
},
view: function view(_view) {
var unbind = bind(_view.root, {
type: 'mouseup',
listener: function listener(event) {
var _api$editorViewMode;
// We only want to set selectionStable to true if the editor has focus
// to prevent the toolbar from showing when the editor is blurred
// due to a click outside the editor.
var editorViewModePlugin = api === null || api === void 0 || (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 ? void 0 : _api$editorViewMode.sharedState.currentState();
var isViewModeEnabled = (editorViewModePlugin === null || editorViewModePlugin === void 0 ? void 0 : editorViewModePlugin.mode) === 'view';
var target = event.target;
if (target && target instanceof Element) {
var isRovoChangeToneButton = target.tagName === 'BUTTON' && _hasNestedSpanWithText(target, 'Change tone') || target.getAttribute('aria-label') === 'Change tone' || target.innerHTML === 'Change tone';
var isRovoTranslateButton = target.tagName === 'BUTTON' && _hasNestedSpanWithText(target, 'Translate options') || target.getAttribute('aria-label') === 'Translate options' || target.innerHTML === 'Translate options';
if (isRovoChangeToneButton || isRovoTranslateButton) {
return null;
}
}
_view.dispatch(_view.state.tr.setMeta(selectionToolbarPluginKey, {
selectionStable: !isViewModeEnabled ? _view.hasFocus() : true
}));
}
});
var unbindEditorViewFocus = bind(_view.dom, {
type: 'focus',
listener: function listener() {
_view.dispatch(_view.state.tr.setMeta(selectionToolbarPluginKey, {
selectionStable: true
}));
}
});
return {
destroy: function destroy() {
unbind();
unbindEditorViewFocus();
}
};
},
appendTransaction: function appendTransaction(_transactions, _oldState, newState) {
if (fg('platform_editor_use_preferences_plugin')) {
return null;
}
if (!isPreferenceInitialized && editorExperiment('platform_editor_controls', 'variant1')) {
var toolbarDockingPreference = userPreferencesProvider === null || userPreferencesProvider === void 0 ? void 0 : userPreferencesProvider.getPreference('toolbarDockingInitialPosition');
if (toolbarDockingPreference !== undefined) {
var _api$analytics2;
isPreferenceInitialized = true;
var userToolbarDockingPref = getToolbarDocking(contextualFormattingEnabled, userPreferencesProvider);
var tr = newState.tr;
api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || _api$analytics2.actions.attachAnalyticsEvent({
action: ACTION.INITIALISED,
actionSubject: ACTION_SUBJECT.USER_PREFERENCES,
actionSubjectId: ACTION_SUBJECT_ID.SELECTION_TOOLBAR_PREFERENCES,
attributes: {
toolbarDocking: userToolbarDockingPref
},
eventType: EVENT_TYPE.OPERATIONAL
})(tr);
return tr;
}
}
return null;
},
props: {
handleDOMEvents: {
mousedown: function mousedown(view) {
view.dispatch(view.state.tr.setMeta(selectionToolbarPluginKey, {
selectionStable: false
}));
return false;
}
}
}
});
}
}];
},
pluginsOptions: isToolbarAIFCEnabled ? {} : {
floatingToolbar: function floatingToolbar(state, intl, providerFactory) {
var _ref3 = selectionToolbarPluginKey.getState(state),
selectionStable = _ref3.selectionStable,
hide = _ref3.hide,
toolbarDocking = _ref3.toolbarDocking,
isBlockMenuOpen = _ref3.isBlockMenuOpen;
var isCellSelection = ('$anchorCell' in state.selection);
var isEditorControlsEnabled = editorExperiment('platform_editor_controls', 'variant1');
if (state.selection.empty || !selectionStable || hide || state.selection instanceof NodeSelection ||
// $anchorCell is only available in CellSelection, this check is to
// avoid importing CellSelection from @atlaskit/editor-tables
isCellSelection && !isEditorControlsEnabled // for Editor Controls we want to show the toolbar on CellSelection
) {
// If there is no active selection, or the selection is not stable, or the selection is a node selection,
// do not show the toolbar.
return;
}
if (isCellSelection && isEditorControlsEnabled) {
var _api$blockControls;
var isSelectedViaDragHandle = api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.sharedState.currentState()) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.isSelectedViaDragHandle;
if (isSelectedViaDragHandle) {
return;
}
}
if (isBlockMenuOpen && isEditorControlsEnabled && editorExperiment('platform_editor_block_menu', true)) {
// If the block menu is open, do not show the selection toolbar.
return;
}
// Resolve the selectionToolbarHandlers to a list of SelectionToolbarGroups
// and filter out any handlers which returned undefined
var resolved = __selectionToolbarHandlers.map(function (selectionToolbarHandler) {
return selectionToolbarHandler(state, intl, providerFactory);
}).filter(function (resolved) {
return resolved !== undefined;
});
// Sort the groups by rank
// This is intended to allow different plugins to control the order of the groups
// they add to the selection toolbar.
// ie. if you want to have your plugin's group appear first, set rank to -10 if there is currently another
// plugin you expect to be run at the same time as with an rank of -9
resolved.sort(function (_ref4, _ref5) {
var _ref4$rank = _ref4.rank,
rankA = _ref4$rank === void 0 ? 0 : _ref4$rank;
var _ref5$rank = _ref5.rank,
rankB = _ref5$rank === void 0 ? 0 : _ref5$rank;
if (rankA < rankB) {
return 1;
}
return -1;
});
var items = [];
// This flattens the groups passed into the floating toolbar into a single list of items
for (var i = 0; i < resolved.length; i++) {
var _resolved$i;
// add a seperator icon after each group except the last
if (Array.isArray((_resolved$i = resolved[i]) === null || _resolved$i === void 0 ? void 0 : _resolved$i.items)) {
items.push.apply(items, _toConsumableArray(resolved[i].items));
}
if (editorExperiment('platform_editor_controls', 'variant1')) {
if (resolved[i] && resolved[i + 1]) {
var _resolved;
if (((_resolved = resolved[i + 1]) === null || _resolved === void 0 ? void 0 : _resolved.pluginName) === 'annotation') {
items.push({
type: 'separator',
fullHeight: true
});
}
}
} else {
if (i !== resolved.length - 1) {
items.push({
type: 'separator'
});
}
}
}
if (items.length > 0 && contextualFormattingEnabled && isEditorControlsEnabled) {
var _api$userPreferences4;
var toolbarDockingPref = api !== null && api !== void 0 && api.userPreferences && fg('platform_editor_use_preferences_plugin') ? api === null || api === void 0 || (_api$userPreferences4 = api.userPreferences) === null || _api$userPreferences4 === void 0 || (_api$userPreferences4 = _api$userPreferences4.sharedState.currentState()) === null || _api$userPreferences4 === void 0 || (_api$userPreferences4 = _api$userPreferences4.preferences) === null || _api$userPreferences4 === void 0 ? void 0 : _api$userPreferences4.toolbarDockingPosition : toolbarDocking;
items.push.apply(items, _toConsumableArray(getPinOptionToolbarConfig({
api: api,
toolbarDocking: toolbarDockingPref,
intl: intl
})));
}
var onPositionCalculated;
var toolbarTitle = 'Selection toolbar';
if (isCellSelection && isEditorControlsEnabled) {
onPositionCalculated = calculateToolbarPositionOnCellSelection(toolbarTitle);
} else {
var calcToolbarPosition = config.preferenceToolbarAboveSelection ? calculateToolbarPositionAboveSelection : calculateToolbarPositionTrackHead;
onPositionCalculated = calcToolbarPosition(toolbarTitle);
}
var nodeType = getSelectionNodeTypes(state);
return _objectSpread(_objectSpread({
title: 'Selection toolbar',
nodeType: nodeType,
items: items
}, isEditorControlsEnabled && {
scrollable: true
}), {}, {
onPositionCalculated: onPositionCalculated
});
}
},
contentComponent: editorExperiment('platform_editor_controls', 'variant1') && !fg('platform_editor_use_preferences_plugin') && fg('platform_editor_user_preferences_provider_update') ? function () {
return /*#__PURE__*/React.createElement(PageVisibilityWatcher, {
api: api,
userPreferencesProvider: userPreferencesProvider
});
} : undefined,
primaryToolbarComponent: !(api !== null && api !== void 0 && api.primaryToolbar) && editorExperiment('platform_editor_controls', 'variant1', {
exposure: true
}) ? primaryToolbarComponent : undefined
};
};
function getSelectionNodeTypes(state) {
var selectionNodeTypes = [];
state.doc.nodesBetween(state.selection.from, state.selection.to, function (node) {
if (selectionNodeTypes.indexOf(node.type) !== 0) {
selectionNodeTypes.push(node.type);
}
});
return selectionNodeTypes;
}
var _hasNestedSpanWithText = function hasNestedSpanWithText(element, text) {
if (element.tagName === 'SPAN' && element.innerHTML === text) {
return true;
}
for (var _i = 0, _Array$from = Array.from(element.children); _i < _Array$from.length; _i++) {
var child = _Array$from[_i];
if (_hasNestedSpanWithText(child, text)) {
return true;
}
}
return false;
};