@atlaskit/editor-plugin-show-diff
Version:
ShowDiff plugin for @atlaskit/editor-core
237 lines (234 loc) • 13.5 kB
JavaScript
import { toggleExpandRange } from '@atlaskit/editor-common/expand';
import { processRawValue } from '@atlaskit/editor-common/process-raw-value';
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
import { Step as ProseMirrorStep } from '@atlaskit/editor-prosemirror/transform-override';
import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
import { calculateDiffDecorations } from './calculateDiff/calculateDiffDecorations';
import { enforceCustomStepRegisters } from './enforceCustomStepRegisters';
import { getScrollableDecorations } from './getScrollableDecorations';
import { getDefaultDiffType, isExtendedEnabled } from './isExtendedEnabled';
import { NodeViewSerializer } from './NodeViewSerializer';
import { scrollToDecoration } from './scrollToDiff';
export const showDiffPluginKey = new PluginKey('showDiffPlugin');
export const createPlugin = (config, getIntl, api, onEditorView) => {
enforceCustomStepRegisters();
const nodeViewSerializer = new NodeViewSerializer({});
const setNodeViewSerializer = editorView => {
nodeViewSerializer.init({
editorView
});
};
return new SafePlugin({
key: showDiffPluginKey,
state: {
init(_, _state) {
// We do initial setup after we setup the editor view
const defaultDiffType = getDefaultDiffType();
return {
steps: [],
originalDoc: undefined,
decorations: DecorationSet.empty,
isDisplayingChanges: false,
...(isExtendedEnabled(defaultDiffType) ? {
isInverted: false,
diffType: defaultDiffType,
hideDeletedDiffs: false,
hideAddedDiffsUnderline: false,
showIndicators: false,
diffDescriptors: []
} : {})
};
},
apply: (tr, currentPluginState, oldState, newState) => {
const meta = tr.getMeta(showDiffPluginKey);
let newPluginState = currentPluginState;
if (meta) {
if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'SHOW_DIFF') {
var _newPluginState, _newPluginState2, _newPluginState3, _newPluginState4, _newPluginState5, _newPluginState6, _newPluginState7, _newPluginState8, _newPluginState9, _newPluginState0;
// Update the plugin state with the new metadata
newPluginState = {
...currentPluginState,
...meta,
isDisplayingChanges: true,
activeIndex: undefined
};
// Calculate and store decorations in state
const {
decorations,
diffDescriptors
} = calculateDiffDecorations({
state: newState,
pluginState: newPluginState,
nodeViewSerializer,
colorScheme: config === null || config === void 0 ? void 0 : config.colorScheme,
intl: getIntl(),
activeIndexPos: newPluginState.activeIndexPos,
api,
...(isExtendedEnabled((_newPluginState = newPluginState) === null || _newPluginState === void 0 ? void 0 : _newPluginState.diffType) ? {
isInverted: (_newPluginState2 = newPluginState) === null || _newPluginState2 === void 0 ? void 0 : _newPluginState2.isInverted,
diffType: (_newPluginState3 = newPluginState) === null || _newPluginState3 === void 0 ? void 0 : _newPluginState3.diffType,
hideDeletedDiffs: (_newPluginState4 = newPluginState) === null || _newPluginState4 === void 0 ? void 0 : _newPluginState4.hideDeletedDiffs,
hideAddedDiffsUnderline: (_newPluginState5 = newPluginState) === null || _newPluginState5 === void 0 ? void 0 : _newPluginState5.hideAddedDiffsUnderline,
showIndicators: (_newPluginState6 = newPluginState) === null || _newPluginState6 === void 0 ? void 0 : _newPluginState6.showIndicators,
smartThresholds: (_newPluginState7 = newPluginState) === null || _newPluginState7 === void 0 ? void 0 : _newPluginState7.smartThresholds,
deletedDiffPlacement: (_newPluginState8 = newPluginState) === null || _newPluginState8 === void 0 ? void 0 : _newPluginState8.deletedDiffPlacement,
inlineDeletedDiffPlacement: (_newPluginState9 = newPluginState) === null || _newPluginState9 === void 0 ? void 0 : _newPluginState9.inlineDeletedDiffPlacement
} : {})
});
// Update the decorations and their ids
newPluginState.decorations = decorations;
if (isExtendedEnabled((_newPluginState0 = newPluginState) === null || _newPluginState0 === void 0 ? void 0 : _newPluginState0.diffType)) {
newPluginState.diffDescriptors = diffDescriptors;
}
} else if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'HIDE_DIFF') {
newPluginState = {
...currentPluginState,
...meta,
decorations: DecorationSet.empty,
isDisplayingChanges: false,
activeIndex: undefined,
/**
* Reset isInverted & diffType state when hiding diffs
* Otherwise this should persist for the diff-showing session
*/
...(isExtendedEnabled(currentPluginState.diffType) ? {
isInverted: false,
diffType: getDefaultDiffType(),
hideDeletedDiffs: false,
hideAddedDiffsUnderline: false,
diffDescriptors: []
} : {})
};
} else if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'SCROLL_TO_NEXT' || (meta === null || meta === void 0 ? void 0 : meta.action) === 'SCROLL_TO_PREVIOUS') {
var _newPluginState1;
// Update the active index in plugin state and recalculate decorations
const decorations = getScrollableDecorations(currentPluginState.decorations, newState.doc, (_newPluginState1 = newPluginState) === null || _newPluginState1 === void 0 ? void 0 : _newPluginState1.diffType);
if (decorations.length > 0) {
var _currentPluginState$a;
// Initialize to -1 if undefined so that the first "next" scroll takes us to index 0 (first change).
// This allows the UI to start with no selection and only highlight on first user interaction.
let nextIndex = (_currentPluginState$a = currentPluginState.activeIndex) !== null && _currentPluginState$a !== void 0 ? _currentPluginState$a : -1;
if (meta.action === 'SCROLL_TO_NEXT') {
nextIndex = (nextIndex + 1) % decorations.length;
} else {
// Handle scrolling backwards from the uninitialized state.
// If at -1 (no selection), wrap to the last decoration.
if (nextIndex === -1) {
nextIndex = decorations.length - 1;
} else {
nextIndex = (nextIndex - 1 + decorations.length) % decorations.length;
}
}
const activeDecoration = decorations[nextIndex];
newPluginState = {
...currentPluginState,
activeIndex: nextIndex,
activeIndexPos: activeDecoration ? {
from: activeDecoration.from,
to: activeDecoration.to
} : undefined
};
// Recalculate decorations with the new active index
const {
decorations: updatedDecorations,
diffDescriptors: updatedDiffDescriptors
} = calculateDiffDecorations({
state: newState,
pluginState: newPluginState,
nodeViewSerializer,
colorScheme: config === null || config === void 0 ? void 0 : config.colorScheme,
intl: getIntl(),
activeIndexPos: newPluginState.activeIndexPos,
api,
...(isExtendedEnabled(newPluginState.diffType) ? {
isInverted: newPluginState.isInverted,
diffType: newPluginState.diffType,
hideDeletedDiffs: newPluginState.hideDeletedDiffs,
hideAddedDiffsUnderline: newPluginState.hideAddedDiffsUnderline,
showIndicators: newPluginState.showIndicators,
smartThresholds: newPluginState.smartThresholds,
deletedDiffPlacement: newPluginState.deletedDiffPlacement,
inlineDeletedDiffPlacement: newPluginState.inlineDeletedDiffPlacement
} : {})
});
newPluginState.decorations = updatedDecorations;
if (isExtendedEnabled(newPluginState.diffType)) {
newPluginState.diffDescriptors = updatedDiffDescriptors;
}
}
} else {
newPluginState = {
...currentPluginState,
...meta
};
}
}
return {
...newPluginState,
decorations: newPluginState.decorations.map(tr.mapping, tr.doc)
};
}
},
view(editorView) {
setNodeViewSerializer(editorView);
onEditorView === null || onEditorView === void 0 ? void 0 : onEditorView(editorView);
let isFirst = true;
let previousActiveIndex;
let cancelPendingScrollToDecoration = null;
return {
update(view) {
// If we're using configuration to show diffs we initialise here once we setup the editor view
if (config !== null && config !== void 0 && config.originalDoc && config !== null && config !== void 0 && config.steps && config.steps.length > 0 && isFirst) {
isFirst = false;
view.dispatch(view.state.tr.setMeta(showDiffPluginKey, {
action: 'SHOW_DIFF',
steps: config.steps.map(step => ProseMirrorStep.fromJSON(view.state.schema, step)),
originalDoc: processRawValue(view.state.schema, config.originalDoc)
}));
}
const pluginState = showDiffPluginKey.getState(view.state);
// Scroll to the first decoration when scrollIntoView was requested.
// Use the same filtered/position-sorted list as the active-index path
// so "first" reliably means the topmost scrollable diff in the document.
if (pluginState !== null && pluginState !== void 0 && pluginState.scrollIntoView && isExtendedEnabled(pluginState === null || pluginState === void 0 ? void 0 : pluginState.diffType)) {
var _cancelPendingScrollT;
(_cancelPendingScrollT = cancelPendingScrollToDecoration) === null || _cancelPendingScrollT === void 0 ? void 0 : _cancelPendingScrollT();
cancelPendingScrollToDecoration = scrollToDecoration(view, getScrollableDecorations(pluginState.decorations, view.state.doc, pluginState === null || pluginState === void 0 ? void 0 : pluginState.diffType));
// Reset the flag so we don't scroll again on subsequent updates
view.dispatch(view.state.tr.setMeta(showDiffPluginKey, {
scrollIntoView: false
}));
}
// Check for any potential scroll side-effects
const activeIndexChanged = (pluginState === null || pluginState === void 0 ? void 0 : pluginState.activeIndex) !== undefined && pluginState.activeIndex !== previousActiveIndex;
previousActiveIndex = pluginState === null || pluginState === void 0 ? void 0 : pluginState.activeIndex;
if ((pluginState === null || pluginState === void 0 ? void 0 : pluginState.activeIndex) !== undefined && activeIndexChanged) {
var _cancelPendingScrollT2;
(_cancelPendingScrollT2 = cancelPendingScrollToDecoration) === null || _cancelPendingScrollT2 === void 0 ? void 0 : _cancelPendingScrollT2();
const scrollableDecorations = getScrollableDecorations(pluginState.decorations, view.state.doc, pluginState === null || pluginState === void 0 ? void 0 : pluginState.diffType);
const activeDecoration = scrollableDecorations[pluginState.activeIndex];
if (activeDecoration) {
// EDITOR-7926: use editor-common's command instead of the expand plugin API to
// avoid a circular dependency; expand picks up the meta if loaded (no-op if not).
api === null || api === void 0 ? void 0 : api.core.actions.execute(toggleExpandRange(activeDecoration.from, activeDecoration.to, true));
}
cancelPendingScrollToDecoration = scrollToDecoration(view, scrollableDecorations, pluginState.activeIndex);
}
},
destroy() {
var _cancelPendingScrollT3;
(_cancelPendingScrollT3 = cancelPendingScrollToDecoration) === null || _cancelPendingScrollT3 === void 0 ? void 0 : _cancelPendingScrollT3();
cancelPendingScrollToDecoration = null;
onEditorView === null || onEditorView === void 0 ? void 0 : onEditorView(undefined);
}
};
},
props: {
decorations: state => {
const pluginState = showDiffPluginKey.getState(state);
return pluginState === null || pluginState === void 0 ? void 0 : pluginState.decorations;
}
}
});
};