Prosemirror Track changes formatting need to track

Hi I am using text editor as prosemirror I am trying to track changes like word I am using prosemirror-changset for tracking the changes,I can able to track the text changes only I am trying try style,Marks also, They issue is When try meger the old changes to new changes , I need merge the old set with new one

Any Help Thanks

/* eslint-disable */

import { Node as PMNode } from "prosemirror-model"
import { AddMarkStep, RemoveMarkStep, ReplaceAroundStep, ReplaceStep, Step, StepMap } from "prosemirror-transform"

import { computeDiff } from "./diff"
import { Change, BlockChange, Span, merge } from './change'

import { IChange } from './change/types'

interface ChangeSetConfig {
  doc: PMNode
  combine: (data1: any, data2: any) => null | any
}

// ::- A change set tracks the changes to a document from a given
// point in the past. It condenses a number of step maps down to a
// flat sequence of replacements, and simplifies replacments that
// partially undo themselves by comparing their content.
export class ChangeSet {

  config: ChangeSetConfig
  changes: IChange[]

  constructor(config: ChangeSetConfig, changes: IChange[]) {
    this.config = config
    // :: [Change] Replaced regions.
    this.changes = changes
  }

  // :: (Node, [StepMap], union<[any], any>) → ChangeSet
  // Computes a new changeset by adding the given step maps and
  // metadata (either as an array, per-map, or as a single value to be
  // associated with all maps) to the current set. Will not mutate the
  // old set.
  //
  // Note that due to simplification that happens after each add,
  // incrementally adding steps might create a different final set
  // than adding all those changes at once, since different document
  // tokens might be matched during simplification depending on the
  // boundaries of the current changed ranges.
  addSteps(oldDoc: PMNode, newDoc: PMNode, steps: Step[], data?: any | any[]) {
    // This works by inspecting the position maps for the changes,
    // which indicate what parts of the document were replaced by new
    // content, and the size of that new content. It uses these to
    // build up Change objects.
    //
    // These change objects are put in sets and merged together using
    // Change.merge, giving us the changes created by the new steps.
    // Those changes can then be merged with the existing set of
    // changes.
    //
    // For each change that was touched by the new steps, we recompute
    // a diff to try to minimize the change by dropping matching
    // pieces of the old and new document from the change.
    let stepChanges: IChange[] = []

    // Add spans for new steps.
    for (let i = 0; i < steps.length; i++) {
      const step = steps[i]

      let d = Array.isArray(data) ? data[i] : data
      let off = 0
      if (step instanceof ReplaceStep) {

        step.getMap().forEach((fromA: number, toA: number, fromB: number, toB: number) => {

          const changedText = oldDoc.content.textBetween(fromA, toA) ?? ''

          stepChanges.push(new Change(fromA + off, toA + off, fromB, toB,
            fromA == toA ? Span.none : [new Span(toA - fromA, changedText)],
            fromB == toB ? Span.none : [new Span(toB - fromB, changedText)],

          ))
          off = (toB - fromB) - (toA - fromA)
        })
      } else if (step instanceof ReplaceAroundStep) {
        let insideReplaceAroundStep = false
        let changeId = Math.random().toString()
        let node: PMNode | null | undefined
        // @ts-ignore
        const { slice } = step
        const nodeDeleted = slice.size === 0
        step.getMap().forEach((fromA: number, toA: number, fromB: number, toB: number) => {
          if (!insideReplaceAroundStep) {
            node = nodeDeleted ? oldDoc.nodeAt(fromA) : newDoc.nodeAt(fromB)
            insideReplaceAroundStep = true
            d = { ...d, blockChange: 'start', changeId, nodeType: node?.type.name }
          } else {
            insideReplaceAroundStep = false
            d = { ...d, blockChange: 'end', changeId, nodeType: node?.type.name }
            changeId = Math.random().toString()
          }
          const newtext = oldDoc.content.textBetween(fromA, toA) ?? ''

          const inserted = fromA == toA ? Span.none : [new Span(toA - fromA, newtext)]
          const deleted = fromB == toB ? Span.none : [new Span(toB - fromB, newtext)]

          stepChanges.push(new BlockChange(fromA + off, toA + off, fromB, toB, inserted, deleted, insideReplaceAroundStep))

          off = (toB - fromB) - (toA - fromA)
        })
      } else if (step instanceof AddMarkStep) {
        const { from, to, mark } = step
        const change = new Change(from, to, from, to, Span.none, [new Span(to - from, mark)])
        stepChanges.push(change)
      } else if (step instanceof RemoveMarkStep) {
        const { from, to, mark } = step
        const change = new Change(from, to, from, to, [new Span(to - from, mark)], Span.none)
        stepChanges.push(change)
      } else {
        console.error('Unknown step type! Change not tracked and possibly current changes have become inconsistent', step)
      }
    }
    if (stepChanges.length == 0) return this

    let newChanges = mergeAll(stepChanges, this.config.combine)

    console.log("changes", newChanges)

    let changes = merge(this.changes, newChanges, this.config.combine)


    // Minimize changes when possible
    for (let i = 0; i < changes.length; i++) {
      let change = changes[i]
      if (change.fromA == change.toA || change.fromB == change.toB ||
        // Only look at changes that touch newly added changed ranges
        !newChanges.some(r => r.toB > change.fromB && r.fromB < change.toB)) continue
      let diff = computeDiff(this.config.doc.content, newDoc.content, change)

      // Fast path: If they are completely different, don't do anything
      if (diff.length == 1 && diff[0].fromB == 0 && diff[0].toB == change.toB - change.fromB)
        continue

      if (diff.length == 1) {
        changes[i] = diff[0]
      } else {
        changes.splice(i, 1, ...diff)
        i += diff.length - 1
      }
    }
    return new ChangeSet(this.config, changes)
  }

  // :: Node
  // The starting document of the change set.
  get startDoc() { return this.config.doc }

  // :: (f: (range: Change) → any) → ChangeSet
  // Map the span's data values in the given set through a function
  // and construct a new set with the resulting data.
  map(f: (range: IChange) => any) {
    return new ChangeSet(this.config, this.changes.map(change => {
      let result = f(change)
      return result === change ? change :
        change.create(change.fromA, change.toA, change.fromB, change.toB, change.deleted, change.inserted)
    }))
  }

  // :: (ChangeSet, ?[StepMap]) → ?{from: number, to: number}
  // Compare two changesets and return the range in which they are
  // changed, if any. If the document changed between the maps, pass
  // the maps for the steps that changed it as second argument, and
  // make sure the method is called on the old set and passed the new
  // set. The returned positions will be in new document coordinates.
  changedRange(b: ChangeSet, maps?: StepMap[]) {
    if (b == this) return null
    let touched = maps && touchedRange(maps)
    let moved = touched ? (touched.toB - touched.fromB) - (touched.toA - touched.fromA) : 0
    function map(p: number) {
      return !touched || p <= touched.fromA ? p : p + moved
    }

    let from = touched ? touched.fromB : 2e8, to = touched ? touched.toB : -2e8
    function add(start: number, end = start) {
      from = Math.min(start, from); to = Math.max(end, to)
    }

    let rA = this.changes, rB = b.changes
    for (let iA = 0, iB = 0; iA < rA.length && iB < rB.length;) {
      let rangeA = rA[iA], rangeB = rB[iB]
      if (rangeA && rangeB && sameRanges(rangeA, rangeB, map)) { iA++; iB++ }
      else if (rangeB && (!rangeA || map(rangeA.fromB) >= rangeB.fromB)) { add(rangeB.fromB, rangeB.toB); iB++ }
      else { add(map(rangeA.fromB), map(rangeA.toB)); iA++ }
    }

    return from <= to ? { from, to } : null
  }

  // :: (Node, ?(a: any, b: any) → any) → ChangeSet
  // Create a changeset with the given base object and configuration.
  // The `combine` function is used to compare and combine metadata—it
  // should return null when metadata isn't compatible, and a combined
  // version for a merged range when it is.
  static create(doc: PMNode, combine = (a: any, b: any): any => a === b ? a : null) {
    return new ChangeSet({ combine, doc }, [])
  }
}

// Exported for testing
// @ts-ignore
ChangeSet.computeDiff = computeDiff

// : ([[Change]], (any, any) → any, number, number) → [Change]
// Divide-and-conquer approach to merging a series of ranges.
function mergeAll(
  ranges: IChange[],
  combine: (data1: any, data2: any) => null | any,
  start = 0,
  end = ranges.length
): IChange[] {
  if (end == start + 1) return [ranges[start]]
  let mid = (start + end) >> 1
  return merge(mergeAll(ranges, combine, start, mid),
    mergeAll(ranges, combine, mid, end), combine)
}

function endRange(maps: StepMap[]) {
  let from = 2e8, to = -2e8
  for (let i = 0; i < maps.length; i++) {
    let map = maps[i]
    if (from != 2e8) {
      from = map.map(from, -1)
      to = map.map(to, 1)
    }
    map.forEach((_s, _e, start, end) => {
      from = Math.min(from, start)
      to = Math.max(to, end)
    })
  }
  return from == 2e8 ? null : { from, to }
}

function touchedRange(maps: StepMap[]) {
  let b = endRange(maps)
  if (!b) return null
  let a = endRange(maps.map(m => m.invert()).reverse())
  if (!a) throw Error('endRange was null!')
  return { fromA: a.from, toA: a.to, fromB: b.from, toB: b.to }
}

function sameRanges(a: IChange, b: IChange, map: (pos: number) => number) {
  return map(a.fromB) == b.fromB && map(a.toB) == b.toB &&
    sameSpans(a.deleted, b.deleted) && sameSpans(a.inserted, b.inserted)
}

function sameSpans(a: Span[], b: Span[]) {
  if (a.length != b.length) return false
  for (let i = 0; i < a.length; i++)
    if (a[i].length != b[i].length || a[i].data !== b[i].data) return false
  return true
}
/* eslint-disable */

import { Span } from './span'
import { Change } from './change'
import type { IChange } from './types'

// : ([Change], [Change], (any, any) → any) → [Change]
// This merges two changesets (the end document of x should be the
// start document of y) into a single one spanning the start of x to
// the end of y.
export function merge(x: IChange[], y: IChange[], combine: (a: any, b: any) => any): IChange[] {
  if (x.length == 0) return y
  if (y.length == 0) return x

  let result = []
  // Iterate over both sets in parallel, using the middle coordinate
  // system (B in x, A in y) to synchronize.
  for (let iX = 0, iY = 0, curX: IChange = x[0], curY: IChange = y[0]; ;) {
    if (!curX && !curY) {
      return result
    } else if (curX && (!curY || curX.toB < curY.fromA)) { // curX entirely in front of curY
      let off = iY ? y[iY - 1].toB - y[iY - 1].toA : 0
      result.push(curX.createNewWithOffset(off, true))
      // result.push(off == 0 ? curX :
      //             new Change(curX.fromA, curX.toA, curX.fromB + off, curX.toB + off,
      //                         curX.deleted, curX.inserted))
      // @ts-ignore
      curX = iX++ == x.length ? null : x[iX]
    } else if (curY && (!curX || curY.toA < curX.fromB)) { // curY entirely in front of curX
      let off = iX ? x[iX - 1].toB - x[iX - 1].toA : 0
      result.push(curY.createNewWithOffset(off, false))
      // result.push(off == 0 ? curY :
      //             new Change(curY.fromA - off, curY.toA - off, curY.fromB, curY.toB,
      //                         curY.deleted, curY.inserted))
      // @ts-ignore
      curY = iY++ == y.length ? null : y[iY]
    } else if (curX.isBlockChange() && curY.isBlockChange() || curX.isBlockChange() && curY.isChange()) {
      let off = iY ? y[iY - 1].toB - y[iY - 1].toA : 0
      result.push(curX.createNewWithOffset(off, true))
      off = iX ? x[iX - 1].toB - x[iX - 1].toA : 0
      result.push(curY.createNewWithOffset(off, false))
      // @ts-ignore
      curX = iX++ == x.length ? null : x[iX]
      // @ts-ignore
      curY = iY++ == y.length ? null : y[iY]
      // debugger
    }
    // else if (curX.isChange() && curY.isBlockChange()) {
    //   throw Error('TODO')
    // } 
    else { // Touch, need to merge
      // The rules for merging ranges are that deletions from the
      // old set and insertions from the new are kept. Areas of the
      // middle document covered by a but not by b are insertions
      // from a that need to be added, and areas covered by b but
      // not a are deletions from b that need to be added.
      let pos = Math.min(curX.fromB, curY.fromA)
      let fromA = Math.min(curX.fromA, curY.fromA - (iX ? x[iX - 1].toB - x[iX - 1].toA : 0)), toA = fromA
      let fromB = Math.min(curY.fromB, curX.fromB + (iY ? y[iY - 1].toB - y[iY - 1].toA : 0)), toB = fromB
      let deleted = Span.none, inserted = Span.none

      // Used to prevent appending ins/del range for the same Change twice
      let enteredX = false, enteredY = false

      // Need to have an inner loop since any number of further
      // ranges might be touching this group
      for (; ;) {
        let nextX = !curX ? 2e8 : pos >= curX.fromB ? curX.toB : curX.fromB
        let nextY = !curY ? 2e8 : pos >= curY.fromA ? curY.toA : curY.fromA
        let next = Math.min(nextX, nextY)
        let inX = curX && pos >= curX.fromB, inY = curY && pos >= curY.fromA
        if (!inX && !inY) break
        if (inX && pos == curX.fromB && !enteredX) {
          deleted = Span.join(deleted, curX.deleted, combine)
          toA += curX.lenA
          enteredX = true
        }
        if (inX && !inY) {
          inserted = Span.join(inserted, Span.slice(curX.inserted, pos - curX.fromB, next - curX.fromB), combine)
          toB += next - pos
        }
        if (inY && pos == curY.fromA && !enteredY) {
          inserted = Span.join(inserted, curY.inserted, combine)
          toB += curY.lenB
          enteredY = true
        }
        if (inY && !inX) {
          deleted = Span.join(deleted, Span.slice(curY.deleted, pos - curY.fromA, next - curY.fromA), combine)
          toA += next - pos
        }

        if (inX && next == curX.toB) {
          // @ts-ignore
          curX = iX++ == x.length ? null : x[iX]
          enteredX = false
        }
        if (inY && next == curY.toA) {
          // @ts-ignore
          curY = iY++ == y.length ? null : y[iY]
          enteredY = false
        }
        pos = next
      }
      if (fromA < toA || fromB < toB)
        result.push(new Change(fromA, toA, fromB, toB, deleted, inserted))
    }
  }
}