Commit 33e2ce6
authored
feat: diffing extension for comparing documents (SD-1324 and SD-89) (#2306)
* feat: add function for mapping doc paragraphs by id
* feat: add function for flattening paragraph text but keep track of positions
* feat: add function for calculating text diffs using LCS
* feat: add function for calculating paragraph-level diffing
* feat: add diffing extension
* test: add diffing tests
* refactor: switch LCS algorith to Myers for performance
* refactor: code structure
* feat: compute text similarity using Levenshtein distance
* feat: identify contiguous text changes as single operation
* feat: implement logic for diffing paragraph attributes
* refactor: extract generic sequence diffing helper
* refactor: modify paragraph diffing to reuse generic helper
* refactor: extract operation reordering function
This function can then be reused when diffing paragraphs and runs. It
helps identifying modifications instead of delete/insert pairs
* fix: standardize positions for text diffing
Always maps starting/ending positions to the old document instead of the
new one.
* refactor: change text diffing logic to account for formatting
* refactor: change from "type" to "action"
* feat: support diffing non-textual inline nodes
* feat: include previous element when building add diff
* docs: add JSDoc to inline diffing
* fix: handle arrays in attributes diff
* feat: implement generic diffing for all nodes
* refactor: move paragraph utility functions
* feat: use generic diffing for diff computation command
* refactor: simplify grouping logic during inline diffing
* refactor: convert modules to typescript and improve documentation
* fix: diffing of inline node attributes
* fix: diff positions for modified non-paragraph nodes
* feat: improve diff comparison for table rows
* fix: emit single diff when container node is deleted
* fix: compute correct insertion position for first child node
* fix: remove unused position resolver for inline diffing
* refactor: remove serialization/deserialization of run attrs during diff
* fix: include node type when extracting content from paragraph
* refactor: include JSON nodes instead of PM nodes in diffs
* refactor: simplify typescript interfaces
* refactor: extract insertion position calculation logic into helper
* refactor: simplify resolution of inline node positions
* refactor: remove unecessary exports
* refactor: standardize diff attribute names for modifications
* docs: add JSDoc to compareDocuments command
* feat: add function for diffing marks
* feat: take marks into account during diff computation
* refactor: modify computeDiff signature to account for comments diffing
* feat: add generic inline content tokenization function
* refactor: use inline content tokenization function for paragraphs
* feat: implement tokenization of comment data
* refactor: change diffNodes signature to facilitate reuse
* feat: allow specifying keys to be ignored when diffing attributes
* feat: implement comments diffing hooks
* feat: update compareDocuments command to support diffing comments
* test: move test files to separate directory
* docs: improve TSDoc comments for interfaces
* feat: initialize directory structure for diff replays
* feat: implement replay of non-paragraph nodes
* feat: implement deriving new marks from diff
* docs: improve TSDoc comments
* docs: improve typing
* feat: implement replay of inline nodes and text
* feat: implement replay of paragraph nodes
* feat: implement diff replay entrypoint
* feat: add command for replaying diffs onto current document
* feat: add button to dev playground to allow comparing documents
* feat: include diffing extension
* feat: improve replay accuracy for inline/run attrs and tracked changes
* fix: replay of formatting changes
* fix: tracked change comment update
* fix: set selection during track changes
* fix: adjust track changes during diff replay
* fix: linting errors
* feat: add logic for replaying comment differences
* fix: adjust computation of comment position key
* fix: adjust comment diffing logic
* fix: diffing of track change marks
* test: diff with track changes
* fix: duplicate function defs
* fix: replaying diffs with track changes
* chore: add param type
* fix: invalid test
* test: adjust existing tests
* fix: replaying tracked changes
* fix: syncing of tracked changes after diff replay
* fix: correctly apply run attribute changes
* fix(diffing): preserve duplicate same-type marks in mark diff and replay
* fix(diffing): preserve multi-block comment body edits in modified comment diffs
* fix(diffing): allow replayDifferences to be called without options
* fix(diffing): make replayDifferences side-effect free in can() checks
* fix(superdoc): clear active reply when replay deletion removes parent thread
* fix(diffing): match inline node types by name across editor schemas
* fix(superdoc): always flush replay tracked-change sync even with no comment positions
* fix(superdoc): delete full comment subtree on replay thread removal
* fix(diffing): preserve existing comments when compareDocuments omits updatedComments
* fix(comments-store): scope tracked-change stale pruning to active document
* fix(diffing): reuse transaction
* fix(superdoc): match replay comment updates by importedId before commentId
- prefer importedId when resolving existing comments for COMMENT_EVENTS.UPDATE
- fall back to commentId only if imported-id lookup misses
- prevent duplicate comment creation when replay updates carry a new runtime commentId for an existing imported thread
- add regression test covering update reconciliation with stable importedId and changed commentId
* fix(diffing): honor applyTrackedChanges=false by setting skipTrackChanges on replay
- set skipTrackChanges meta in replayDifferences when tracked replay is disabled
- keep forceTrackChanges behavior when tracked replay is enabled
- prevent replay from generating tracked marks when track changes mode is already active
- add regression test covering replay with active track changes and applyTrackedChanges:
false
* fix(superdoc): handle replay delete events with importedId-first identity fallback
- resolve delete targets from both importedId and commentId (importedId prioritized)
- seed subtree removal with all resolved ids to match re-keyed replay comments
- keep active comment clearing aligned with the expanded removal set
- add regression test for stale runtime commentId + stable importedId delete payloads to prevent orphaned sidebar threads
* fix(diffing): prevent compareDocuments from dispatching read-only transactions
- set preventDispatch meta in compareDocuments so CommandService skips dispatch
- keep compare behavior read-only with no transaction/update side effects
- add regression assertions that compare calls do not emit transaction events (with omitted and explicit comments inputs)
* fix(diffing): derive insertion anchors from old sequence context
- refactor getInsertionPos to use only oldNodes + oldIdx and derive the previous node internally
- remove redundant previousOldNodeInfo plumbing from generic and paragraph add-diff builders
- keep depth-aware insertion behavior for deeper/same/shallower transitions
- update diff utility and paragraph diff tests to match the new API and validate anchor resolution
* fix(superdoc): scope replay comment updates to active document
- reuse getCommentDocumentId / belongsToDocument from comments store in replay update reconciliation
- expose those helpers from comments-store to avoid duplicating document-scoping logic in SuperDoc.vue
- constrain replay UPDATE comment matching to the active editor document when IDs collide
- add regression coverage for same importedId across multiple documents so only the active document comment is updated
* fix(superdoc): scope replay thread deletions to active document
- restrict replay DELETED handling to comments that belong to the active editor document
- apply document scoping consistently across seed matching, subtree expansion, and final filtering
- prevent cross-document comment/thread removal when IDs overlap across open docs
- keep active-comment clearing document-aware using pre-delete comment snapshot
- add regression test for overlapping IDs across documents during replay deletion
* fix(superdoc): preserve comment identity on replay updates
- replace raw replay Object.assign with a safe field-level updater for existing comment models
- update only an explicit allowlist of mutable replay fields and normalize commentText from payload text/commentText
- preserve useComment identity invariants (commentId/importedId and constructor-captured metadata)
- strengthen replay update test to assert model identity remains stable via getValues() after updates
* fix(diff-replay): include document identity in replayed comment events
- add replay comment payload normalization to always emit commentId and preserve/add ownership metadata (documentId, fileId)
- use editor.options.documentId as fallback when replay comment payload lacks document scope
- keep existing payload documentId/fileId unchanged when already present
- update replay editor typing in replayComments/replayDiffs to include optional options.documentId
- add tests covering both fallback ownership injection and preservation of existing ownership fields
* fix(comments-store): treat importedId as live in tracked-change prune
- update tracked-change stale-prune liveness check to consider both commentId and importedId
- prevent pruning live tracked-change threads when runtime commentId diverges but mark IDs still match importedId
- keep existing descendant removal behavior unchanged
- add regression test for replay/import flows where importedId remains stable while commentId changes
* fix(comments-store): dedupe tracked-change sync by commentId and importedId
- seed tracked-change existingIds with both runtime commentId and stable importedId
- prevent duplicate tracked-change thread creation when grouped mark id matches an existing imported id
- add created sync IDs (id, params.changeId, params.importedId) back into dedupe set during sync pass
- add regression test for mixed-ID replay/sync scenario where commentId diverges but importedId remains live
* fix(comments): keep replayed docxCommentJSON in export payloads
- make useComment store docxCommentJSON as reactive state instead of a construction-time constant
- update getValues() to return the current docxCommentJSON value
- ensure replay-updated imported comment structure is reflected in translateCommentsForExport output
- add unit test verifying getValues() returns updated docxCommentJSON after mutation
* fix(superdoc): preserve DOCX comment structure on replay add
- add replay payload normalization for comment model creation (text -> commentText,
elements -> docxCommentJSON)
- apply normalization in replay ADD path before useComment(...)
- reuse the same normalization in replay UPDATE fallback when creating missing comments
- ensure replay-added imported comments keep DOCX-native body structure for export/
round-trip
- add regression test verifying replay ADD maps elements into docxCommentJSON
* fix(superdoc): replayed comment resolution persistence for export
- map replay isDone updates to resolvedTime/resolvedBy* when payload resolved fields are null/missing
- apply the same fallback during replay payload normalization and model updates
- refactor shared isDone resolution fallback logic to avoid duplicated code
- add regression test covering replay update payloads with isDone: true and null resolved fields, ensuring resolved state is persisted and can be exported
* fix(superdoc): scope replay update active comment id to matched document thread
- return the matched comment’s concrete id (prefer commentId) from replay update matching
- avoid cross-document active-thread misselection when importedId overlaps across open documents
- update replay regression coverage to assert setActiveComment receives the active document’s thread id
* fix(superdoc): stop replay comment updates from forcing active-thread reselection
- only sync active comment when activeCommentId is explicitly present in the event
payload
- avoid inferring active selection from replay add/update events to prevent repeated
focus/unfocus churn
- preserve explicit active clear behavior on replay deletions
- update replay update test expectation to reflect non-selecting replay events
* test: add missing test documents
* test: adjust diff replay test
* fix(superdoc): update replay comment parent linkage fields on thread remap
* fix(superdoc): scope replay add deduplication to active document context
* fix(track-changes): preserve property attrs for ReplaceAroundStep updates
* feat: implement diffing for styles
* feat: implement replay for style differences
* feat: implement diffing for numbering
* feat: implement replay for numbering differences
* fix(comments): scope tracked-change dedupe to active document
* refactor(diffing): share deepEquals helper across replay modules
* chore: add diffing example
* fix(numbering): missing imports
* chore: update lock file
* test: add missing test documents
* chore: fix diff test1 parent 2db8f16 commit 33e2ce6
99 files changed
Lines changed: 30260 additions & 7919 deletions
File tree
- examples/features
- diffing
- src
- packages
- layout-engine/pm-adapter/src
- super-editor/src
- core
- helpers
- presentation-editor/tests
- extensions
- comment
- diffing
- algorithm
- replay
- track-changes/trackChangesHelpers
- tests
- data/diffing
- export/export-helpers
- superdoc/src
- components/CommentsLayer
- dev/components
- stores
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
0 commit comments