@atlaskit/editor-plugin-show-diff
Version:
ShowDiff plugin for @atlaskit/editor-core
315 lines (308 loc) • 20.2 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.showDiffPluginKey = exports.createPlugin = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _expand = require("@atlaskit/editor-common/expand");
var _processRawValue = require("@atlaskit/editor-common/process-raw-value");
var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
var _state2 = require("@atlaskit/editor-prosemirror/state");
var _transformOverride = require("@atlaskit/editor-prosemirror/transform-override");
var _view = require("@atlaskit/editor-prosemirror/view");
var _fg = require("@atlaskit/platform-feature-flags/fg");
var _announceActiveDiff = require("../ui/DiffNavigation/announceActiveDiff");
var _calculateDiffDecorations = require("./calculateDiff/calculateDiffDecorations");
var _decorationKeys = require("./decorations/decorationKeys");
var _enforceCustomStepRegisters = require("./enforceCustomStepRegisters");
var _getDefaultDiffType = require("./getDefaultDiffType");
var _getScrollableDecorations = require("./getScrollableDecorations");
var _NodeViewSerializer = require("./NodeViewSerializer");
var _revealAnimation = require("./revealAnimation");
var _scrollToDiff = require("./scrollToDiff");
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) { (0, _defineProperty2.default)(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; }
var showDiffPluginKey = exports.showDiffPluginKey = new _state2.PluginKey('showDiffPlugin');
/**
* Tags whose diff decoration survived the transaction. A document change can map a decoration away
* without any `showDiff`/`hideDiff` following it, and a tag left behind has no host to render into.
* Returns the given array when every tag is still live.
*/
var dropOrphanedContributorTags = function dropOrphanedContributorTags(contributorTags, decorations) {
if (!(contributorTags !== null && contributorTags !== void 0 && contributorTags.length)) {
return contributorTags;
}
var liveDiffIds = new Set(decorations.find(undefined, undefined, _decorationKeys.isDiffDecorationSpec).filter(_decorationKeys.isDiffDecoration).map(function (_ref) {
var spec = _ref.spec;
return spec.diffId;
}));
var liveTags = contributorTags.filter(function (_ref2) {
var diffId = _ref2.diffId;
return liveDiffIds.has(diffId);
});
return liveTags.length === contributorTags.length ? contributorTags : liveTags;
};
var createPlugin = exports.createPlugin = function createPlugin(config, getIntl, api, onEditorView) {
(0, _enforceCustomStepRegisters.enforceCustomStepRegisters)();
var nodeViewSerializer = new _NodeViewSerializer.NodeViewSerializer({});
var setNodeViewSerializer = function setNodeViewSerializer(editorView) {
nodeViewSerializer.init({
editorView: editorView
});
};
/**
* This editor's own content root. The contributor tags scope every DOM lookup to it, so they take
* it from the view the plugin was mounted on rather than resolving `.ProseMirror` by class name
* from a decoration — a class this package does not own, and one whose nearest match is the wrong
* editor whenever editors are nested. A getter because the view arrives with the ProseMirror view
* lifecycle, which decorations are calculated independently of.
*/
var currentEditorView;
var tagMountContext = {
api: api,
getEditorRoot: function getEditorRoot() {
var _currentEditorView$do, _currentEditorView;
return (_currentEditorView$do = (_currentEditorView = currentEditorView) === null || _currentEditorView === void 0 ? void 0 : _currentEditorView.dom) !== null && _currentEditorView$do !== void 0 ? _currentEditorView$do : null;
},
getIntl: getIntl
};
return new _safePlugin.SafePlugin({
key: showDiffPluginKey,
state: {
init: function init(_, _state) {
// We do initial setup after we setup the editor view
return {
steps: [],
stepAttributions: [],
originalDoc: undefined,
decorations: _view.DecorationSet.empty,
isDisplayingChanges: false,
isInverted: false,
diffType: (0, _getDefaultDiffType.getDefaultDiffType)(),
hideDeletedDiffs: false,
hideAddedDiffsUnderline: false,
showIndicators: false,
diffDescriptors: []
};
},
apply: function apply(tr, currentPluginState, oldState, newState) {
var meta = tr.getMeta(showDiffPluginKey);
var newPluginState = currentPluginState;
if (meta) {
if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'SHOW_DIFF' || (meta === null || meta === void 0 ? void 0 : meta.action) === 'REVEAL_COMPLETE') {
var _newPluginState$color, _newPluginState, _newPluginState2, _newPluginState3, _newPluginState4, _newPluginState5, _newPluginState6, _newPluginState7, _newPluginState8, _newPluginState9, _newPluginState0;
// REVEAL_COMPLETE repaints with the reveal dropped, so the decorations render their
// ordinary resting style. Without it the reveal stays in state indefinitely and the
// next unrelated repaint re-emits the hidden, zero-width highlight with no animation
// left to bring it in — the highlight simply vanishes.
newPluginState = meta.action === 'REVEAL_COMPLETE' ? _objectSpread(_objectSpread({}, currentPluginState), {}, {
reveal: undefined
}) : _objectSpread(_objectSpread(_objectSpread({}, currentPluginState), meta), {}, {
isDisplayingChanges: true,
activeIndex: undefined,
// Explicit rather than left to the spread: an absent key in `meta` would let
// the previous paint's choreography leak into this one, so closing the diff
// would animate too.
reveal: meta.reveal
});
// Calculate and store decorations in state
var _calculateDiffDecorat = (0, _calculateDiffDecorations.calculateDiffDecorations)({
state: newState,
pluginState: newPluginState,
nodeViewSerializer: nodeViewSerializer,
colorScheme: (_newPluginState$color = (_newPluginState = newPluginState) === null || _newPluginState === void 0 ? void 0 : _newPluginState.colorScheme) !== null && _newPluginState$color !== void 0 ? _newPluginState$color : config === null || config === void 0 ? void 0 : config.colorScheme,
intl: getIntl(),
activeIndexPos: newPluginState.activeIndexPos,
api: api,
tagMountContext: tagMountContext,
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,
// SHOW_DIFF only. The scroll-to-next recalculation further down deliberately
// omits this so stepping through changes cannot replay the choreography.
reveal: (_newPluginState0 = newPluginState) === null || _newPluginState0 === void 0 ? void 0 : _newPluginState0.reveal
}),
contributorTags = _calculateDiffDecorat.contributorTags,
decorations = _calculateDiffDecorat.decorations,
diffDescriptors = _calculateDiffDecorat.diffDescriptors;
// Update the decorations and their ids
newPluginState.decorations = decorations;
newPluginState.contributorTags = contributorTags;
newPluginState.diffDescriptors = diffDescriptors;
} else if ((meta === null || meta === void 0 ? void 0 : meta.action) === 'HIDE_DIFF') {
newPluginState = _objectSpread(_objectSpread(_objectSpread({}, currentPluginState), meta), {}, {
decorations: _view.DecorationSet.empty,
isDisplayingChanges: false,
activeIndex: undefined,
contributorTags: [],
reveal: undefined,
// Per-call override — do not let it leak into the next, unrelated `showDiff` call.
colorScheme: undefined,
/**
* Reset isInverted & diffType state when hiding diffs
* Otherwise this should persist for the diff-showing session
*/
isInverted: false,
diffType: (0, _getDefaultDiffType.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') {
// Update the active index in plugin state and recalculate decorations
var _decorations = (0, _getScrollableDecorations.getScrollableDecorations)(currentPluginState.decorations, newState.doc);
if (_decorations.length > 0) {
var _currentPluginState$a, _newPluginState$color2, _newPluginState1;
// 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.
var 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;
}
}
var activeDecoration = _decorations[nextIndex];
newPluginState = _objectSpread(_objectSpread({}, currentPluginState), {}, {
activeIndex: nextIndex,
activeIndexPos: activeDecoration ? _objectSpread({
from: activeDecoration.from,
to: activeDecoration.to
}, (0, _fg.fg)('confluence_ncs_step_diffing_version_history') ? {
attributionKey: (0, _decorationKeys.isDiffDecoration)(activeDecoration) ? activeDecoration.spec.attributionKey : undefined
} : {}) : undefined
});
// Recalculate decorations with the new active index
var _calculateDiffDecorat2 = (0, _calculateDiffDecorations.calculateDiffDecorations)({
state: newState,
pluginState: newPluginState,
nodeViewSerializer: nodeViewSerializer,
colorScheme: (_newPluginState$color2 = (_newPluginState1 = newPluginState) === null || _newPluginState1 === void 0 ? void 0 : _newPluginState1.colorScheme) !== null && _newPluginState$color2 !== void 0 ? _newPluginState$color2 : config === null || config === void 0 ? void 0 : config.colorScheme,
intl: getIntl(),
activeIndexPos: newPluginState.activeIndexPos,
api: api,
tagMountContext: tagMountContext,
isInverted: newPluginState.isInverted,
diffType: newPluginState.diffType,
hideDeletedDiffs: newPluginState.hideDeletedDiffs,
hideAddedDiffsUnderline: newPluginState.hideAddedDiffsUnderline,
showIndicators: newPluginState.showIndicators,
smartThresholds: newPluginState.smartThresholds,
deletedDiffPlacement: newPluginState.deletedDiffPlacement,
inlineDeletedDiffPlacement: newPluginState.inlineDeletedDiffPlacement
}),
updatedContributorTags = _calculateDiffDecorat2.contributorTags,
updatedDecorations = _calculateDiffDecorat2.decorations,
updatedDiffDescriptors = _calculateDiffDecorat2.diffDescriptors;
newPluginState.decorations = updatedDecorations;
newPluginState.contributorTags = updatedContributorTags;
newPluginState.diffDescriptors = updatedDiffDescriptors;
}
} else {
newPluginState = _objectSpread(_objectSpread({}, currentPluginState), meta);
}
}
var mappedDecorations = newPluginState.decorations.map(tr.mapping, tr.doc);
var nextState = _objectSpread(_objectSpread({}, newPluginState), {}, {
decorations: mappedDecorations
});
if (tr.docChanged) {
nextState.contributorTags = dropOrphanedContributorTags(newPluginState.contributorTags, mappedDecorations);
}
return nextState;
}
},
view: function view(editorView) {
setNodeViewSerializer(editorView);
currentEditorView = editorView;
onEditorView === null || onEditorView === void 0 || onEditorView(editorView);
var isFirst = true;
var previousActiveIndex;
var cancelPendingScrollToDecoration = null;
return {
update: function 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(function (step) {
return _transformOverride.Step.fromJSON(view.state.schema, step);
}),
originalDoc: (0, _processRawValue.processRawValue)(view.state.schema, config.originalDoc)
}));
}
var pluginState = showDiffPluginKey.getState(view.state);
// A repaint rebuilds inline decorations, destroying any animation bound to them, so
// an in-flight reveal has to be re-attached to the new nodes. No-op when idle.
(0, _revealAnimation.rebindReveal)(view, view.dom);
// 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) {
var _cancelPendingScrollT;
(_cancelPendingScrollT = cancelPendingScrollToDecoration) === null || _cancelPendingScrollT === void 0 || _cancelPendingScrollT();
cancelPendingScrollToDecoration = (0, _scrollToDiff.scrollToDecoration)(view, (0, _getScrollableDecorations.getScrollableDecorations)(pluginState.decorations, view.state.doc), undefined, api);
// 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
var 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 || _cancelPendingScrollT2();
var scrollableDecorations = (0, _getScrollableDecorations.getScrollableDecorations)(pluginState.decorations, view.state.doc);
var 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 || api.core.actions.execute((0, _expand.toggleExpandRange)(activeDecoration.from, activeDecoration.to, true));
}
cancelPendingScrollToDecoration = (0, _scrollToDiff.scrollToDecoration)(view, scrollableDecorations, pluginState.activeIndex, api);
// Stepping only scrolls — no focus moves and no content changes — so without this a
// screen-reader user gets silence. Announced through the live region rather than by
// focusing the change, since the "next change" control has to stay focused to be
// pressed again. After the scroll above, so the announcement never precedes it.
var announcement = (0, _announceActiveDiff.getActiveDiffAnnouncement)({
activeIndex: pluginState.activeIndex,
contributorTags: pluginState.contributorTags,
decorations: scrollableDecorations,
intl: getIntl()
});
if (announcement) {
var _api$accessibilityUti;
api === null || api === void 0 || (_api$accessibilityUti = api.accessibilityUtils) === null || _api$accessibilityUti === void 0 || _api$accessibilityUti.actions.ariaNotify(announcement);
}
}
},
destroy: function destroy() {
var _cancelPendingScrollT3;
(_cancelPendingScrollT3 = cancelPendingScrollToDecoration) === null || _cancelPendingScrollT3 === void 0 || _cancelPendingScrollT3();
cancelPendingScrollToDecoration = null;
currentEditorView = undefined;
onEditorView === null || onEditorView === void 0 || onEditorView(undefined);
}
};
},
props: {
decorations: function decorations(state) {
var pluginState = showDiffPluginKey.getState(state);
return pluginState === null || pluginState === void 0 ? void 0 : pluginState.decorations;
}
}
});
};