@atlaskit/editor-plugin-extension
Version:
editor-plugin-extension plugin for @atlaskit/editor-core
223 lines (221 loc) • 8.92 kB
JavaScript
import React, { useEffect } from 'react';
import { getExtensionKeyAndNodeKey } from '@atlaskit/editor-common/extensions';
import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
import { Box } from '@atlaskit/primitives/compiled';
import { clearEditingContext, forceAutoSave } from '../editor-commands/commands';
import { getPluginState } from '../pm-plugins/plugin-factory';
import { getSelectedExtension } from '../pm-plugins/utils';
import ConfigPanelLoader from './ConfigPanel/ConfigPanelLoader';
import { CONFIG_PANEL_WIDTH } from './ConfigPanel/constants';
import HeaderAfterIconElement from './ConfigPanel/Header/HeaderAfterIconElement';
import HeaderIcon from './ConfigPanel/Header/HeaderIcon';
import { onChangeAction } from './context-panel';
import { SaveIndicator } from './SaveIndicator/SaveIndicator';
const selector = states => {
var _states$extensionStat, _states$extensionStat2, _states$extensionStat3;
return {
showContextPanel: (_states$extensionStat = states.extensionState) === null || _states$extensionStat === void 0 ? void 0 : _states$extensionStat.showContextPanel,
extensionProvider: (_states$extensionStat2 = states.extensionState) === null || _states$extensionStat2 === void 0 ? void 0 : _states$extensionStat2.extensionProvider,
processParametersAfter: (_states$extensionStat3 = states.extensionState) === null || _states$extensionStat3 === void 0 ? void 0 : _states$extensionStat3.processParametersAfter
};
};
export function useConfigPanelPluginHook({
editorView,
configPanelId,
api
}) {
const editorState = editorView.state;
const {
showContextPanel,
extensionProvider,
processParametersAfter
} = useSharedPluginStateWithSelector(api, ['extension'], selector);
useEffect(() => {
const nodeWithPos = getSelectedExtension(editorState, true);
// Adding checks to bail out early
if (!nodeWithPos) {
hideConfigPanel(configPanelId, api);
return;
}
if (showContextPanel && extensionProvider && processParametersAfter) {
showConfigPanel({
api,
configPanelId,
editorView,
extensionProvider,
nodeWithPos
});
} else {
hideConfigPanel(configPanelId, api);
}
}, [api, configPanelId, editorState, editorView, showContextPanel, extensionProvider, processParametersAfter]);
useEffect(() => {
return () => {
hideConfigPanel(configPanelId, api);
};
}, [api, configPanelId]);
}
export function hideConfigPanel(configPanelId, api) {
var _api$contextPanel, _api$contextPanel$act;
const closePanelById = api === null || api === void 0 ? void 0 : (_api$contextPanel = api.contextPanel) === null || _api$contextPanel === void 0 ? void 0 : (_api$contextPanel$act = _api$contextPanel.actions) === null || _api$contextPanel$act === void 0 ? void 0 : _api$contextPanel$act.closePanelById;
if (closePanelById) {
closePanelById(configPanelId);
}
}
export function showConfigPanel({
api,
configPanelId,
editorView,
extensionProvider,
nodeWithPos
}) {
var _api$contextPanel2, _api$contextPanel2$ac;
const showContextPanel = api === null || api === void 0 ? void 0 : (_api$contextPanel2 = api.contextPanel) === null || _api$contextPanel2 === void 0 ? void 0 : (_api$contextPanel2$ac = _api$contextPanel2.actions) === null || _api$contextPanel2$ac === void 0 ? void 0 : _api$contextPanel2$ac.showPanel;
if (showContextPanel) {
const nodeAttrs = nodeWithPos === null || nodeWithPos === void 0 ? void 0 : nodeWithPos.node.attrs;
const extensionType = nodeAttrs === null || nodeAttrs === void 0 ? void 0 : nodeAttrs.extensionType;
const extensionKey = nodeAttrs === null || nodeAttrs === void 0 ? void 0 : nodeAttrs.extensionKey;
/**
* Loading extension manifest fails when using
* extensionKey directly from nodeAttrs.
* Always get extensionKey from getExtensionKeyAndNodeKey to load
* extension manifest successfully.
*/
const [extKey, _] = getExtensionKeyAndNodeKey(extensionKey, extensionType);
const HeadeIconWrapper = () => {
return /*#__PURE__*/React.createElement(HeaderIcon, {
extensionProvider: extensionProvider,
extensionKey: extKey,
extensionType: extensionType
});
};
const HeaderAfterIconElementWrapper = () => {
return /*#__PURE__*/React.createElement(HeaderAfterIconElement, {
extensionProvider: extensionProvider,
extensionKey: extKey,
extensionType: extensionType
});
};
const BodyComponent = getContextPanelBodyComponent({
api,
editorView,
extensionProvider,
nodeWithPos
});
showContextPanel({
id: configPanelId,
headerComponentElements: {
HeaderIcon: HeadeIconWrapper,
HeaderAfterIconElement: HeaderAfterIconElementWrapper
},
BodyComponent,
closeOptions: {
canClosePanel: async () => {
// When navigating away from the editor, the editorView is destroyed.
if (editorView.isDestroyed) {
return true;
}
const extensionState = getPluginState(editorView.state);
/**
* If context panel is open, then first update extension plugin state.
* Updating extension plugin state will trigger useEffect in useConfigPanelPluginHook,
* which will call hideConfigPanel.
*/
if (extensionState !== null && extensionState !== void 0 && extensionState.showContextPanel) {
await startClosingConfigPanel({
api,
editorView
});
return false;
}
// Return true if extension plugin state has been updated and hideConfigPanel has been called.
return true;
}
}
}, 'push', CONFIG_PANEL_WIDTH);
}
}
export async function startClosingConfigPanel({
api,
editorView
}) {
var _api$contextPanel3;
const applyChange = api === null || api === void 0 ? void 0 : (_api$contextPanel3 = api.contextPanel) === null || _api$contextPanel3 === void 0 ? void 0 : _api$contextPanel3.actions.applyChange;
// Even if the save failed, we should proceed with closing the panel
clearEditingContext(applyChange)(editorView.state, editorView.dispatch);
try {
await new Promise((resolve, reject) => {
forceAutoSave(applyChange)(resolve, reject)(editorView.state, editorView.dispatch);
});
} catch (e) {
// Even if the save failed, we should proceed with closing the panel
// eslint-disable-next-line no-console
console.error(`Autosave failed with error`, e);
}
}
export const getContextPanelBodyComponent = ({
api,
editorView,
extensionProvider,
nodeWithPos
}) => {
var _api$featureFlags;
const featureFlags = (api === null || api === void 0 ? void 0 : (_api$featureFlags = api.featureFlags) === null || _api$featureFlags === void 0 ? void 0 : _api$featureFlags.sharedState.currentState()) || {};
const editorState = editorView.state;
const extensionState = getPluginState(editorState);
const {
autoSaveResolve,
autoSaveReject,
processParametersBefore
} = extensionState;
const {
extensionType,
extensionKey,
parameters
} = nodeWithPos.node.attrs;
const [extKey, nodeKey] = getExtensionKeyAndNodeKey(extensionKey, extensionType);
const configParams = processParametersBefore ? processParametersBefore(parameters || {}) : parameters;
return () => /*#__PURE__*/React.createElement(Box, {
padding: "space.200"
}, /*#__PURE__*/React.createElement(SaveIndicator, {
duration: 5000,
visible: true
}, ({
onSaveStarted,
onSaveEnded
}) => {
return /*#__PURE__*/React.createElement(ConfigPanelLoader, {
api: api,
showHeader: true,
closeOnEsc: true,
extensionType: extensionType,
extensionKey: extKey,
nodeKey: nodeKey,
extensionParameters: parameters,
parameters: configParams,
extensionProvider: extensionProvider,
autoSaveTrigger: autoSaveResolve,
autoSaveReject: autoSaveReject
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onChange: async updatedParameters => {
await onChangeAction(editorView, updatedParameters, parameters, nodeWithPos, onSaveStarted);
onSaveEnded();
if (autoSaveResolve) {
autoSaveResolve();
}
}
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onCancel: () => startClosingConfigPanel({
api,
editorView
}),
featureFlags: featureFlags
// Remove below prop when cleaning platform_editor_ai_object_sidebar_injection FG
// Becuase it will always be true
,
usingObjectSidebarPanel: true
});
}));
};