UNPKG

@atlaskit/editor-plugin-show-diff

Version:

ShowDiff plugin for @atlaskit/editor-core

128 lines (127 loc) 5.31 kB
import { simplifyChanges, ChangeSet } from 'prosemirror-changeset'; import { Mapping, ReplaceStep } from '@atlaskit/editor-prosemirror/transform'; import { optimizeChanges } from './optimizeChanges'; const mapPosition = (mapping, pos) => mapping.map(pos); const createMapping = maps => { const mapping = new Mapping(); for (const map of maps) { mapping.appendMap(map); } return mapping; }; const createSpans = length => length > 0 ? [{ length, data: null }] : []; const mergeOverlappingByNewDocRange = changes => { if (changes.length <= 1) { return changes; } const sortedChanges = [...changes].sort((left, right) => left.fromB - right.fromB); const merged = []; let current = { ...sortedChanges[0] }; for (let i = 1; i < sortedChanges.length; i++) { const next = sortedChanges[i]; const isOverlapping = next.fromB <= current.toB; if (isOverlapping) { current = { fromA: Math.min(current.fromA, next.fromA), toA: Math.max(current.toA, next.toA), fromB: Math.min(current.fromB, next.fromB), toB: Math.max(current.toB, next.toB), deleted: [...current.deleted, ...next.deleted], inserted: [...current.inserted, ...next.inserted] }; } else { merged.push(current); current = { ...next }; } } merged.push(current); return merged; }; const isReplaceStepForTextBlockNode = (step, before, from, to) => { var _replacedSlice$conten, _replacingSlice$conte, _replacedSlice$conten2; if (!(step instanceof ReplaceStep)) { return false; } if (step.slice.openStart !== 0 || step.slice.openEnd !== 0) { return false; } const replacedSlice = before.slice(from, to); const replacingSlice = step.slice; return Boolean(replacedSlice.openStart === 0 && replacedSlice.openEnd === 0 && replacedSlice.content.childCount === 1 && replacingSlice.content.childCount === 1 && ((_replacedSlice$conten = replacedSlice.content.firstChild) === null || _replacedSlice$conten === void 0 ? void 0 : _replacedSlice$conten.type.name) === ((_replacingSlice$conte = replacingSlice.content.firstChild) === null || _replacingSlice$conte === void 0 ? void 0 : _replacingSlice$conte.type.name) && ((_replacedSlice$conten2 = replacedSlice.content.firstChild) === null || _replacedSlice$conten2 === void 0 ? void 0 : _replacedSlice$conten2.type.isTextblock)); }; export const diffBySteps = (originalDoc, steps) => { const changes = []; let currentDoc = originalDoc; const successfulStepMaps = []; const rangedSteps = []; for (const step of steps) { const before = currentDoc; const result = step.apply(currentDoc); if (result.failed !== null || !result.doc) { continue; } const stepMap = step.getMap(); const rangeStep = step; if (typeof rangeStep.from === 'number' && typeof rangeStep.to === 'number') { rangedSteps.push({ before, doc: result.doc, from: rangeStep.from, to: rangeStep.to, mapIndex: successfulStepMaps.length, step, stepMap }); } successfulStepMaps.push(stepMap); currentDoc = result.doc; } for (const rangedStep of rangedSteps) { // Mapping from original -> doc before this step. const originalToBeforeStep = createMapping(successfulStepMaps.slice(0, rangedStep.mapIndex)); const beforeStepToOriginal = originalToBeforeStep.invert(); const fromA = mapPosition(beforeStepToOriginal, rangedStep.from); const toA = mapPosition(beforeStepToOriginal, rangedStep.to); // Map the step range into final steppedDoc coordinates. const fromAfterStep = rangedStep.stepMap.map(rangedStep.from, -1); const toAfterStep = rangedStep.stepMap.map(rangedStep.to, 1); const afterStepToFinal = createMapping(successfulStepMaps.slice(rangedStep.mapIndex + 1)); const fromB = mapPosition(afterStepToFinal, fromAfterStep); const toB = mapPosition(afterStepToFinal, toAfterStep); if (isReplaceStepForTextBlockNode(rangedStep.step, rangedStep.before, rangedStep.from, rangedStep.to)) { const granularStepChanges = ChangeSet.create(rangedStep.before).addSteps(rangedStep.doc, [rangedStep.stepMap], null); const optimizedGranularStepChanges = optimizeChanges(simplifyChanges(granularStepChanges.changes, granularStepChanges.startDoc)); for (const granularChange of optimizedGranularStepChanges) { const granularFromA = mapPosition(beforeStepToOriginal, granularChange.fromA); const granularToA = mapPosition(beforeStepToOriginal, granularChange.toA); const granularFromB = mapPosition(afterStepToFinal, granularChange.fromB); const granularToB = mapPosition(afterStepToFinal, granularChange.toB); changes.push({ fromA: granularFromA, toA: granularToA, fromB: granularFromB, toB: granularToB, deleted: createSpans(Math.max(0, granularToA - granularFromA)), inserted: createSpans(Math.max(0, granularToB - granularFromB)) }); } continue; } changes.push({ fromA, toA, fromB, toB, deleted: createSpans(Math.max(0, toA - fromA)), inserted: createSpans(Math.max(0, toB - fromB)) }); } return mergeOverlappingByNewDocRange(changes); };