UNPKG

@atlaskit/editor-plugin-track-changes

Version:

ShowDiff plugin for @atlaskit/editor-core

126 lines (123 loc) 6.94 kB
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin'; import { PluginKey } from '@atlaskit/editor-prosemirror/state'; import { ReplaceAroundStep, ReplaceStep, AddMarkStep, RemoveMarkStep, AttrStep } from '@atlaskit/editor-prosemirror/transform'; import { filterSteps } from './filterSteps'; import { InvertableStep } from './invertableStep'; import { TOGGLE_TRACK_CHANGES_ACTION as ACTION } from './types'; export const trackChangesPluginKey = new PluginKey('trackChangesPlugin'); // Exported for test purposes export const getBaselineFromSteps = (doc, steps) => { try { // Filter out AttrStep's since attribute changes shouldn't affect baseline content comparison const contentSteps = steps.filter(step => !(step.step instanceof AttrStep)); for (const step of contentSteps.slice().reverse()) { const result = step.inverted.apply(doc); if (result.failed === null && result.doc) { doc = result.doc; } } return doc; } catch { // Temporary - we need to understand how this happens - but we want to unblock issues where this crashes the editor return undefined; } }; export const createTrackChangesPlugin = api => { // Mark the state to be reset on next time the document has a meaningful change let resetBaseline = false; return new SafePlugin({ key: trackChangesPluginKey, state: { init() { return { steps: [], shouldChangesBeDisplayed: false, isShowDiffAvailable: false, allocations: new Set() }; }, apply(tr, state) { var _api$history, _state$steps$at$alloc, _state$steps$at; const metadata = tr.getMeta(trackChangesPluginKey); if (metadata && metadata.action === ACTION.RESET_BASELINE) { return { ...state, steps: [], isShowDiffAvailable: false }; } if (metadata && metadata.action === ACTION.TOGGLE_TRACK_CHANGES) { resetBaseline = true; return { ...state, shouldChangesBeDisplayed: !state.shouldChangesBeDisplayed }; } const isDocChanged = tr.docChanged && tr.steps.some(step => step instanceof ReplaceStep || step instanceof ReplaceAroundStep || step instanceof AddMarkStep || step instanceof RemoveMarkStep || step instanceof AttrStep); const isAnnotationStep = step => step instanceof AddMarkStep && step.mark.type.name === 'annotation'; if (!isDocChanged || tr.getMeta('isRemote') || tr.getMeta('replaceDocument') || tr.steps.some(isAnnotationStep)) { // If the transaction is remote, we need to map the steps to the current document return { ...state, steps: state.steps.map(s => { const newStep = s.step.map(tr.mapping); const newInvertedStep = s.inverted.map(tr.mapping); if (newStep && newInvertedStep) { return new InvertableStep(newStep, newInvertedStep, s.allocation); } return undefined; }).filter(s => !!s) }; } // If we don't have the history plugin don't limit the change tracking const historyState = api === null || api === void 0 ? void 0 : (_api$history = api.history) === null || _api$history === void 0 ? void 0 : _api$history.sharedState.currentState(); const currentAllocation = historyState ? // Combine both done + undone so we have the total "distance". historyState.done.eventCount + historyState.undone.eventCount : ((_state$steps$at$alloc = (_state$steps$at = state.steps.at(-1)) === null || _state$steps$at === void 0 ? void 0 : _state$steps$at.allocation) !== null && _state$steps$at$alloc !== void 0 ? _state$steps$at$alloc : 0) + 1; const newSteps = tr.steps.map((step, idx) => new InvertableStep(step, step.invert(tr.docs[idx]), currentAllocation)); const concatSteps = resetBaseline ? newSteps : [...state.steps, ...newSteps]; resetBaseline = false; const { allocations, steps } = filterSteps(concatSteps, state.allocations.add(currentAllocation)); // Calculate if there are actual changes by comparing current doc with baseline const baselineDoc = getBaselineFromSteps(tr.doc, steps); const hasChangesFromBaseline = baselineDoc ? !tr.doc.eq(baselineDoc) : false; // Create a new ChangeSet based on document changes return { ...state, allocations, steps, shouldChangesBeDisplayed: false, isShowDiffAvailable: hasChangesFromBaseline }; } }, view() { return { update(view, prevState) { var _trackChangesPluginKe; const prevShouldChangesBeDisplayed = (_trackChangesPluginKe = trackChangesPluginKey.getState(prevState)) === null || _trackChangesPluginKe === void 0 ? void 0 : _trackChangesPluginKe.shouldChangesBeDisplayed; const currentPluginState = trackChangesPluginKey.getState(view.state); const currentShouldChangesBeDisplayed = currentPluginState === null || currentPluginState === void 0 ? void 0 : currentPluginState.shouldChangesBeDisplayed; if (prevShouldChangesBeDisplayed === false && currentShouldChangesBeDisplayed === true) { var _currentPluginState$s; const steps = (_currentPluginState$s = currentPluginState === null || currentPluginState === void 0 ? void 0 : currentPluginState.steps) !== null && _currentPluginState$s !== void 0 ? _currentPluginState$s : []; const originalDoc = getBaselineFromSteps(view.state.doc, steps); if (originalDoc) { var _api$core, _api$showDiff, _api$showDiff$command; api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(api === null || api === void 0 ? void 0 : (_api$showDiff = api.showDiff) === null || _api$showDiff === void 0 ? void 0 : (_api$showDiff$command = _api$showDiff.commands) === null || _api$showDiff$command === void 0 ? void 0 : _api$showDiff$command.showDiff({ originalDoc, steps: steps.map(s => s.step) })); } } else if (currentShouldChangesBeDisplayed === false && prevShouldChangesBeDisplayed === true) { var _api$core2, _api$showDiff2, _api$showDiff2$comman; api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.execute(api === null || api === void 0 ? void 0 : (_api$showDiff2 = api.showDiff) === null || _api$showDiff2 === void 0 ? void 0 : (_api$showDiff2$comman = _api$showDiff2.commands) === null || _api$showDiff2$comman === void 0 ? void 0 : _api$showDiff2$comman.hideDiff); } } }; } }); };