UNPKG

@atlaskit/editor-plugin-extension

Version:

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

223 lines (221 loc) 8.92 kB
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 }); })); };