Transforming copied nodes to maintain unique node IDs

I was looking at the previous topic How I can attach attribute with dynamic value when new paragraph is inserted?

I have these kind of “Atoms” which are represented by a simple attrs { uid: "<uuid-blob>" } so, my objective is when someone copies and pastes a node with uid attribute, I set the pasted node’s uidCopied attribute to the previous uid value, and then I either set uid to "unknown" to be assigned in a different plugin as @marijn suggested previously OR I could set the pasted uid to a unique ID at the time of pasting.

I’m confident there’s a way to manage the second part (reassigning to new UID), but I’m uncertain about the first part (how to differentiate between serializeFragment & moving nodes from pasting nodes). I can easily keep an index Set of all currently used UIDs and previously used UIDs, but I don’t know how to intercept the parsing.

Thanks,

Cole

I think that I found an approach that will work for my use case using a Plugin

My plugin state is essentially:

enum UIDPresence {
  Present,
  Removed,
}
type UIDPluginState = {
  /** UID Value to isAlive */
  uids: Map<String, UIDPresence>;
  updates: ((tr: GTransaction) => GTransaction)[],
};

I have an apply set up to create the transaction updates passed to appendTransactions and I basically used prosemirror-utils's findChildrenByType(oldState.doc, grammarSchema.nodes.atom) and findChildrenByType(newState.doc, grammarSchema.nodes.atom) to get lists of atom nodes in both before and after documents.

This allowed me to basically create transaction updates based on if the transactions introduce an existing UID into the doc, or insert a previously “Removed” UID into the doc.

for (const [addedUID, addedAt] of diffUIDs.added) {
  if (addedUID === "unknown") {
    const newUID = getUID()
    value.updates.push(tr => tr.setNodeMarkup(addedAt.pos, undefined, { ...addedAt.attrs, uid: newUID, uidCopied: "unknown" }))
    value.uids.set(newUID, UIDPresence.Present)
  } else {
    const addedUIDPresence = value.uids.get(addedUID)
    if (addedUIDPresence === UIDPresence.Present) {
      // already exists, so we need to ensure the new atom is a "copy"
      const newUID = getUID()
      value.updates.push(tr => tr.setNodeMarkup(addedAt.pos, undefined, { ...addedAt.attrs, uid: newUID, uidCopied: addedUID }))
      value.uids.set(newUID, UIDPresence.Present)
    } else if (addedUIDPresence === UIDPresence.Removed) {
      // existed previously, but is now being reinserted
      value.uids.set(addedUID, UIDPresence.Present)
    } else {
      value.uids.set(addedUID, UIDPresence.Present)
    }
  }
}

for (const [removedUID, _removedAt] of diffUIDs.toRemove) {
  value.uids.set(removedUID, UIDPresence.Removed)
}

So far it looks like it’s working really well, and I think this approach gives me several ideas for how to approach other problems I’m aiming to solve!