-
-
Notifications
You must be signed in to change notification settings - Fork 501
feat: add support for diff rendering, apply and reject #2312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
7b1adb4
feat: add support for diff rendering, apply and reject
Saul-Mirone fe5dec4
fix: improve
Saul-Mirone 07fbf96
chore: f
Saul-Mirone 7b3b706
chore: f
Saul-Mirone c822bcf
chore: f
Saul-Mirone c4d07ca
chore: f
Saul-Mirone 81d92f7
docs: add doc
Saul-Mirone f87168d
chore: f
Saul-Mirone 225b57a
fix: doc
Saul-Mirone 0306d1b
chore: improve
Saul-Mirone bb51608
chore: f
Saul-Mirone a30e854
fix: test name
Saul-Mirone 59e98f4
test: add tests
Saul-Mirone File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| # @milkdown/plugin-diff | ||
|
|
||
| Diff review plugin for [milkdown](https://milkdown.dev/). Compares two documents and lets users accept or reject individual changes. | ||
|
|
||
| ## Usage | ||
|
|
||
| ```typescript | ||
| import { Editor } from '@milkdown/kit/core' | ||
| import { diff } from '@milkdown/kit/plugin/diff' | ||
| import { diffComponent } from '@milkdown/kit/component/diff' | ||
| import { commonmark } from '@milkdown/kit/preset/commonmark' | ||
|
|
||
| const editor = await Editor.make() | ||
| .use(commonmark) | ||
| .use(diff) | ||
| .use(diffComponent) | ||
| .create() | ||
| ``` | ||
|
|
||
| ### With Crepe | ||
|
|
||
| ```typescript | ||
| import { Crepe, CrepeFeature } from '@milkdown/crepe' | ||
|
|
||
| const crepe = new Crepe({ | ||
| root: '#editor', | ||
| features: { | ||
| [CrepeFeature.Diff]: true, | ||
| }, | ||
| }) | ||
| await crepe.create() | ||
| ``` | ||
|
|
||
| ## Starting a Diff Review | ||
|
|
||
| Pass the modified markdown to `startDiffReviewCmd`. The editor will show the differences and lock editing until the review is complete. | ||
|
|
||
| ```typescript | ||
| import { callCommand } from '@milkdown/kit/utils' | ||
| import { startDiffReviewCmd } from '@milkdown/kit/plugin/diff' | ||
|
|
||
| editor.action( | ||
| callCommand(startDiffReviewCmd.key, '# Updated content\n\nNew paragraph.') | ||
| ) | ||
| ``` | ||
|
|
||
| ## Accepting and Rejecting Changes | ||
|
|
||
| Users can click the Accept/Reject buttons on each change in the UI. You can also control this programmatically: | ||
|
|
||
| ```typescript | ||
| import { callCommand } from '@milkdown/kit/utils' | ||
| import { | ||
| acceptAllDiffsCmd, | ||
| rejectAllDiffsCmd, | ||
| clearDiffReviewCmd, | ||
| acceptDiffChunkCmd, | ||
| rejectDiffChunkCmd, | ||
| } from '@milkdown/kit/plugin/diff' | ||
|
|
||
| // Accept all remaining changes | ||
| editor.action(callCommand(acceptAllDiffsCmd.key)) | ||
|
|
||
| // Reject all remaining changes | ||
| editor.action(callCommand(rejectAllDiffsCmd.key)) | ||
|
|
||
| // Cancel the review without applying anything | ||
| editor.action(callCommand(clearDiffReviewCmd.key)) | ||
|
|
||
| // Accept/reject a specific change by index | ||
| editor.action(callCommand(acceptDiffChunkCmd.key, 0)) | ||
| editor.action(callCommand(rejectDiffChunkCmd.key, 0)) | ||
| ``` | ||
|
|
||
| The diff automatically deactivates and unlocks the editor when all changes have been resolved. | ||
|
|
||
| ## Plugin Configuration | ||
|
|
||
| ```typescript | ||
| import { diffConfig } from '@milkdown/kit/plugin/diff' | ||
|
|
||
| Editor.make() | ||
| .config((ctx) => { | ||
| ctx.update(diffConfig.key, (prev) => ({ | ||
| ...prev, | ||
| lockOnReview: false, // Allow editing during diff review (default: true) | ||
| })) | ||
| }) | ||
| .use(diff) | ||
| .use(diffComponent) | ||
| .create() | ||
| ``` | ||
|
|
||
| ## Component Configuration | ||
|
|
||
| The diff component handles the visual rendering of changes. It can be configured through `diffComponentConfig`: | ||
|
|
||
| ```typescript | ||
| import { diffComponentConfig } from '@milkdown/kit/component/diff' | ||
|
|
||
| Editor.make() | ||
| .config((ctx) => { | ||
| ctx.update(diffComponentConfig.key, (prev) => ({ | ||
| ...prev, | ||
| classPrefix: 'my-diff', // CSS class prefix (default: 'milkdown-diff') | ||
| acceptLabel: 'Apply', // Accept button text (default: 'Accept') | ||
| rejectLabel: 'Discard', // Reject button text (default: 'Reject') | ||
| customBlockTypes: [ | ||
| // Node types using custom node views | ||
| 'table', | ||
| 'image-block', | ||
| 'code_block', | ||
| ], | ||
| })) | ||
| }) | ||
| .use(diff) | ||
| .use(diffComponent) | ||
| .create() | ||
| ``` | ||
|
|
||
| ### Custom Block Types | ||
|
|
||
| ProseMirror's inline decorations cannot penetrate custom node views. The `customBlockTypes` option tells the diff component which node types need block-level replacement handling instead of inline decorations. | ||
|
|
||
| When using Crepe, this is pre-configured with `['table', 'image-block', 'code_block']`. | ||
|
|
||
| ## Styling | ||
|
|
||
| The diff component uses CSS classes that you need to style. When using Crepe, styles are included in the theme CSS automatically. | ||
|
|
||
| For standalone usage, the main CSS classes are: | ||
|
|
||
| | Class | Description | | ||
| | ------------------------------- | ------------------------------------------ | | ||
| | `.milkdown-diff-removed` | Inline deletion (strikethrough) | | ||
| | `.milkdown-diff-removed-block` | Block-level deletion (node overlay) | | ||
| | `.milkdown-diff-added` | Inline insertion | | ||
| | `.milkdown-diff-added-block` | Block-level insertion widget | | ||
| | `.milkdown-diff-controls` | Inline Accept/Reject button container | | ||
| | `.milkdown-diff-controls-block` | Block-level Accept/Reject button container | | ||
| | `.milkdown-diff-accept` | Accept button | | ||
| | `.milkdown-diff-reject` | Reject button | | ||
|
|
||
Saul-Mirone marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ## Plugin | ||
|
|
||
| @diff | ||
| @diffPlugin | ||
| @diffPluginKey | ||
| @diffConfig | ||
|
|
||
| ## Commands | ||
|
|
||
| @startDiffReviewCmd | ||
| @acceptDiffChunkCmd | ||
| @rejectDiffChunkCmd | ||
| @acceptDiffRangeCmd | ||
| @rejectDiffRangeCmd | ||
| @acceptAllDiffsCmd | ||
| @rejectAllDiffsCmd | ||
| @clearDiffReviewCmd | ||
|
|
||
| ## Utilities | ||
|
|
||
| @computeDocDiff | ||
| @getPendingChanges | ||
| @isChangeRejected | ||
|
|
||
| ## Types | ||
|
|
||
| @DiffState | ||
| @DiffConfig | ||
| @DiffRange | ||
| @DiffAction | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <title>Crepe Diff</title> | ||
| </head> | ||
| <body> | ||
| <div id="app"></div> | ||
| <script type="module" src="/crepe-diff/main.ts"></script> | ||
| </body> | ||
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| export const crepeDiff = { | ||
| title: 'Crepe Diff', | ||
| link: '/crepe-diff/', | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { Crepe, CrepeFeature } from '@milkdown/crepe' | ||
| import '@milkdown/crepe/theme/common/style.css' | ||
| import '@milkdown/crepe/theme/frame.css' | ||
| import { callCommand } from '@milkdown/utils' | ||
|
|
||
| import { setup } from '../utils' | ||
|
|
||
| // Command names match the string IDs registered by $command() in plugin-diff. | ||
| // Using strings here because @milkdown/kit is not a direct dependency of e2e. | ||
| setup(async () => { | ||
| const crepe = new Crepe({ | ||
| root: '#app', | ||
| features: { | ||
| [CrepeFeature.Diff]: true, | ||
| }, | ||
| }) | ||
| globalThis.__crepe__ = crepe | ||
| await crepe.create() | ||
|
|
||
| globalThis.__applyDiff__ = (markdown: string) => | ||
| crepe.editor.action(callCommand('StartDiffReview', markdown)) | ||
| globalThis.__acceptAll__ = () => | ||
| crepe.editor.action(callCommand('AcceptAllDiffs')) | ||
| globalThis.__rejectAll__ = () => | ||
| crepe.editor.action(callCommand('RejectAllDiffs')) | ||
| globalThis.__clearDiff__ = () => | ||
| crepe.editor.action(callCommand('ClearDiffReview')) | ||
| globalThis.__acceptChunk__ = (index: number) => | ||
| crepe.editor.action(callCommand('AcceptDiffChunk', index)) | ||
| globalThis.__rejectChunk__ = (index: number) => | ||
| crepe.editor.action(callCommand('RejectDiffChunk', index)) | ||
|
|
||
| return crepe.editor | ||
| }).catch(console.error) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.