@atlaskit/editor-plugin-extension
Version:
editor-plugin-extension plugin for @atlaskit/editor-core
184 lines (179 loc) • 7.12 kB
JavaScript
import React from 'react';
import { getExtensionKeyAndNodeKey } from '@atlaskit/editor-common/extensions';
import { GapCursorSelection } from '@atlaskit/editor-common/selection';
import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
import { CellSelection } from '@atlaskit/editor-tables/cell-selection';
import { buildExtensionNode } from '../editor-actions/actions';
import { clearEditingContext, forceAutoSave, updateState } from '../editor-commands/commands';
import { getPluginState } from '../pm-plugins/plugin-factory';
import { getSelectedExtension } from '../pm-plugins/utils';
import ConfigPanelLoader from './ConfigPanel/ConfigPanelLoader';
import { SaveIndicator } from './SaveIndicator/SaveIndicator';
const areParametersEqual = (firstParameters, secondParameters) => {
if (typeof firstParameters === 'object' && typeof secondParameters === 'object' && firstParameters !== null && secondParameters !== null) {
const firstKeys = Object.keys(firstParameters);
const secondKeys = Object.keys(secondParameters);
return firstKeys.length === secondKeys.length && firstKeys.every(key => firstParameters[key] === secondParameters[key]);
}
return firstParameters === secondParameters;
};
export const duplicateSelection = (selectionToDuplicate, doc) => {
if (selectionToDuplicate instanceof NodeSelection) {
return NodeSelection.create(doc, selectionToDuplicate.from);
} else if (selectionToDuplicate instanceof TextSelection) {
return TextSelection.create(doc, selectionToDuplicate.from, selectionToDuplicate.to);
} else if (selectionToDuplicate instanceof GapCursorSelection) {
return new GapCursorSelection(doc.resolve(selectionToDuplicate.from), selectionToDuplicate.side);
} else if (selectionToDuplicate instanceof CellSelection) {
return new CellSelection(doc.resolve(selectionToDuplicate.$anchorCell.pos), doc.resolve(selectionToDuplicate.$headCell.pos));
}
};
export const getContextPanel = getEditorView => (api, featureFlags) => state => {
var _api$contextPanel;
const nodeWithPos = getSelectedExtension(state, true);
const applyChange = api === null || api === void 0 ? void 0 : (_api$contextPanel = api.contextPanel) === null || _api$contextPanel === void 0 ? void 0 : _api$contextPanel.actions.applyChange;
// Adding checks to bail out early
if (!nodeWithPos) {
return;
}
const extensionState = getPluginState(state);
const {
autoSaveResolve,
autoSaveReject,
showContextPanel,
extensionProvider,
processParametersBefore,
processParametersAfter
} = extensionState;
if (extensionState && showContextPanel && extensionProvider && processParametersAfter) {
const {
extensionType,
extensionKey,
parameters
} = nodeWithPos.node.attrs;
const [extKey, nodeKey] = getExtensionKeyAndNodeKey(extensionKey, extensionType);
const configParams = processParametersBefore ? processParametersBefore(parameters || {}) : parameters;
return /*#__PURE__*/React.createElement(SaveIndicator, {
duration: 5000,
visible: true
}, ({
onSaveStarted,
onSaveEnded
}) => {
const editorView = getEditorView === null || getEditorView === void 0 ? void 0 : getEditorView();
if (!editorView) {
return null;
}
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: async () => {
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);
}
clearEditingContext(applyChange)(editorView.state, editorView.dispatch);
},
featureFlags: featureFlags
});
});
}
};
// Added this interface to handle the macroParams on Parameters
export async function onChangeAction(editorView, updatedParameters = {}, oldParameters = {}, nodeWithPos, onSaving) {
// WARNING: editorView.state stales quickly, do not unpack
const {
processParametersAfter,
processParametersBefore
} = getPluginState(editorView.state);
if (!processParametersAfter) {
return;
}
const unwrappedOldParameters = processParametersBefore ? processParametersBefore(oldParameters) : oldParameters;
if (areParametersEqual(unwrappedOldParameters, updatedParameters)) {
return;
}
if (onSaving) {
onSaving();
}
const key = Date.now();
const {
positions: previousPositions
} = getPluginState(editorView.state);
await updateState({
positions: {
...previousPositions,
[key]: nodeWithPos.pos
}
})(editorView.state, editorView.dispatch);
// WARNING: after this, editorView.state may have changed
const newParameters = await processParametersAfter(updatedParameters);
const {
positions
} = getPluginState(editorView.state);
if (!positions) {
return;
}
if (!(key in positions)) {
return;
}
const {
node
} = nodeWithPos;
const newNode = buildExtensionNode(nodeWithPos.node.toJSON().type, editorView.state.schema, {
...node.attrs,
parameters: {
...oldParameters,
...newParameters
}
}, node.content, node.marks);
if (!newNode) {
return;
}
const positionUpdated = positions[key];
const transaction = editorView.state.tr.replaceWith(positionUpdated, positionUpdated + newNode.nodeSize, newNode);
// Ensure we preserve the selection, tr.replaceWith causes it to be lost in some cases
// when replacing the node
const {
selection: prevSelection
} = editorView.state;
if (!prevSelection.eq(transaction.selection)) {
const selection = duplicateSelection(prevSelection, transaction.doc);
if (selection) {
transaction.setSelection(selection);
}
}
const positionsLess = {
...getPluginState(editorView.state).positions
};
delete positionsLess[key];
await updateState({
positions: positionsLess
})(editorView.state, editorView.dispatch);
editorView.dispatch(transaction);
}