mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-15 05:37:32 +00:00
fix(core): insert diff not displayed after the expected block (#13086)
> CLOSE AI-319 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Improved block insertion behavior by specifying the reference block after which new blocks are inserted. * **Bug Fixes** * Enhanced accuracy and clarity of block diffing and patch application, ensuring correct handling of insertions and deletions. * **Tests** * Added and updated test cases to verify correct handling of interval insertions, deletions, and complete block replacements. * Updated test expectations to include explicit insertion context for greater consistency. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -87,6 +87,7 @@ describe('applyPatchToDoc', () => {
|
||||
{
|
||||
op: 'insert',
|
||||
index: 2,
|
||||
after: 'paragraph-1',
|
||||
block: {
|
||||
id: 'paragraph-3',
|
||||
type: 'affine:paragraph',
|
||||
|
||||
@@ -334,4 +334,58 @@ Inserted at tail.
|
||||
updates: {},
|
||||
});
|
||||
});
|
||||
|
||||
test('should handle interval insertions & deletions', () => {
|
||||
const oldMd = `
|
||||
<!-- block_id=block-001 flavour=title -->
|
||||
# 1
|
||||
|
||||
<!-- block_id=block-002 flavour=paragraph -->
|
||||
2
|
||||
|
||||
<!-- block_id=block-003 flavour=paragraph -->
|
||||
3
|
||||
|
||||
<!-- block_id=block-004 flavour=paragraph -->
|
||||
4
|
||||
|
||||
<!-- block_id=block-005 flavour=paragraph -->
|
||||
5
|
||||
`;
|
||||
const newMd = `
|
||||
<!-- block_id=block-001 flavour=title -->
|
||||
# 1
|
||||
|
||||
<!-- block_id=block-002 flavour=paragraph -->
|
||||
2
|
||||
|
||||
<!-- block_id=block-004 flavour=paragraph -->
|
||||
4
|
||||
|
||||
<!-- block_id=block-006 flavour=paragraph -->
|
||||
6
|
||||
|
||||
<!-- block_id=block-007 flavour=paragraph -->
|
||||
7
|
||||
`;
|
||||
const diff = generateRenderDiff(oldMd, newMd);
|
||||
expect(diff).toEqual({
|
||||
deletes: ['block-003', 'block-005'],
|
||||
inserts: {
|
||||
'block-004': [
|
||||
{
|
||||
id: 'block-006',
|
||||
type: 'paragraph',
|
||||
content: '6',
|
||||
},
|
||||
{
|
||||
id: 'block-007',
|
||||
type: 'paragraph',
|
||||
content: '7',
|
||||
},
|
||||
],
|
||||
},
|
||||
updates: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ This is a new paragraph.
|
||||
{
|
||||
op: 'insert',
|
||||
index: 1,
|
||||
after: 'block-001',
|
||||
block: {
|
||||
id: 'block-002',
|
||||
type: 'paragraph',
|
||||
@@ -104,6 +105,7 @@ New paragraph.
|
||||
{
|
||||
op: 'insert',
|
||||
index: 2,
|
||||
after: 'block-002',
|
||||
block: {
|
||||
id: 'block-004',
|
||||
type: 'paragraph',
|
||||
@@ -138,6 +140,7 @@ Second inserted paragraph.
|
||||
{
|
||||
op: 'insert',
|
||||
index: 1,
|
||||
after: 'block-001',
|
||||
block: {
|
||||
id: 'block-002',
|
||||
type: 'paragraph',
|
||||
@@ -147,6 +150,7 @@ Second inserted paragraph.
|
||||
{
|
||||
op: 'insert',
|
||||
index: 2,
|
||||
after: 'block-002',
|
||||
block: {
|
||||
id: 'block-003',
|
||||
type: 'paragraph',
|
||||
@@ -213,6 +217,7 @@ This is a new paragraph inserted after deletion.
|
||||
{
|
||||
op: 'insert',
|
||||
index: 2,
|
||||
after: 'block-003',
|
||||
block: {
|
||||
id: 'block-004',
|
||||
type: 'paragraph',
|
||||
@@ -225,4 +230,133 @@ This is a new paragraph inserted after deletion.
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('should diff interval insertions & deletions', () => {
|
||||
const oldMd = `
|
||||
<!-- block_id=block-001 flavour=title -->
|
||||
# 1
|
||||
|
||||
<!-- block_id=block-002 flavour=paragraph -->
|
||||
2
|
||||
|
||||
<!-- block_id=block-003 flavour=paragraph -->
|
||||
3
|
||||
|
||||
<!-- block_id=block-004 flavour=paragraph -->
|
||||
4
|
||||
|
||||
<!-- block_id=block-005 flavour=paragraph -->
|
||||
5
|
||||
`;
|
||||
|
||||
const newMd = `
|
||||
<!-- block_id=block-001 flavour=title -->
|
||||
# 1
|
||||
|
||||
<!-- block_id=block-002 flavour=paragraph -->
|
||||
2
|
||||
|
||||
<!-- block_id=block-004 flavour=paragraph -->
|
||||
4
|
||||
|
||||
<!-- block_id=block-006 flavour=paragraph -->
|
||||
6
|
||||
|
||||
<!-- block_id=block-007 flavour=paragraph -->
|
||||
7
|
||||
`;
|
||||
const { patches } = diffMarkdown(oldMd, newMd);
|
||||
expect(patches).toEqual([
|
||||
{
|
||||
op: 'insert',
|
||||
index: 3,
|
||||
after: 'block-004',
|
||||
block: {
|
||||
id: 'block-006',
|
||||
type: 'paragraph',
|
||||
content: '6',
|
||||
},
|
||||
},
|
||||
{
|
||||
op: 'insert',
|
||||
index: 4,
|
||||
after: 'block-006',
|
||||
block: {
|
||||
id: 'block-007',
|
||||
type: 'paragraph',
|
||||
content: '7',
|
||||
},
|
||||
},
|
||||
{
|
||||
op: 'delete',
|
||||
id: 'block-003',
|
||||
},
|
||||
{
|
||||
op: 'delete',
|
||||
id: 'block-005',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('should diff insertions after remove all', () => {
|
||||
const oldMd = `
|
||||
<!-- block_id=block-001 flavour=title -->
|
||||
# 1
|
||||
|
||||
<!-- block_id=block-002 flavour=paragraph -->
|
||||
2
|
||||
`;
|
||||
|
||||
const newMd = `
|
||||
<!-- block_id=block-003 flavour=paragraph -->
|
||||
3
|
||||
|
||||
<!-- block_id=block-004 flavour=paragraph -->
|
||||
4
|
||||
|
||||
<!-- block_id=block-005 flavour=paragraph -->
|
||||
5
|
||||
`;
|
||||
const { patches } = diffMarkdown(oldMd, newMd);
|
||||
expect(patches).toEqual([
|
||||
{
|
||||
op: 'insert',
|
||||
index: 0,
|
||||
after: 'HEAD',
|
||||
block: {
|
||||
id: 'block-003',
|
||||
type: 'paragraph',
|
||||
content: '3',
|
||||
},
|
||||
},
|
||||
{
|
||||
op: 'insert',
|
||||
index: 1,
|
||||
after: 'block-003',
|
||||
block: {
|
||||
id: 'block-004',
|
||||
type: 'paragraph',
|
||||
content: '4',
|
||||
},
|
||||
},
|
||||
{
|
||||
op: 'insert',
|
||||
index: 2,
|
||||
after: 'block-004',
|
||||
block: {
|
||||
id: 'block-005',
|
||||
type: 'paragraph',
|
||||
content: '5',
|
||||
},
|
||||
},
|
||||
{
|
||||
op: 'delete',
|
||||
id: 'block-001',
|
||||
},
|
||||
{
|
||||
op: 'delete',
|
||||
id: 'block-002',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -264,7 +264,7 @@ export class BlockDiffService extends Extension implements BlockDiffProvider {
|
||||
}
|
||||
for (const [offset, block] of blocks.entries()) {
|
||||
await applyPatchToDoc(doc, [
|
||||
{ op: 'insert', index: baseIndex + offset, block },
|
||||
{ op: 'insert', index: baseIndex + offset, after: from, block },
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -301,7 +301,12 @@ export class BlockDiffService extends Extension implements BlockDiffProvider {
|
||||
baseIndex = this.getBlockIndexById(doc, payload.from) + 1;
|
||||
}
|
||||
await applyPatchToDoc(doc, [
|
||||
{ op: 'insert', index: baseIndex + payload.offset, block },
|
||||
{
|
||||
op: 'insert',
|
||||
index: baseIndex + payload.offset,
|
||||
after: payload.from,
|
||||
block,
|
||||
},
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -22,50 +22,57 @@ export interface RenderDiffs {
|
||||
*
|
||||
* <!-- block_id=004 flavour=paragraph -->
|
||||
* This is the fourth paragraph
|
||||
*
|
||||
* <!-- block_id=005 flavour=paragraph -->
|
||||
* This is the fifth paragraph
|
||||
* ```
|
||||
*
|
||||
* New markdown:
|
||||
* ```md
|
||||
* <!-- block_id=001 flavour=paragraph -->
|
||||
* This is the first paragraph
|
||||
* This is the 1st paragraph
|
||||
*
|
||||
* <!-- block_id=003 flavour=paragraph -->
|
||||
* This is the 3rd paragraph
|
||||
* <!-- block_id=002 flavour=paragraph -->
|
||||
* This is the second paragraph
|
||||
*
|
||||
* <!-- block_id=005 flavour=paragraph -->
|
||||
* New inserted paragraph 1
|
||||
* <!-- block_id=004 flavour=paragraph -->
|
||||
* This is the fourth paragraph
|
||||
*
|
||||
* <!-- block_id=006 flavour=paragraph -->
|
||||
* New inserted paragraph 1
|
||||
*
|
||||
* <!-- block_id=007 flavour=paragraph -->
|
||||
* New inserted paragraph 2
|
||||
* ```
|
||||
*
|
||||
* The generated patches:
|
||||
* ```js
|
||||
* [
|
||||
* { op: 'insert', index: 2, block: { id: '005', ... } },
|
||||
* { op: 'insert', index: 3, bthirdlock: { id: '006', ... } },
|
||||
* { op: 'update', id: '003', content: 'This is the 3rd paragraph' },
|
||||
* { op: 'delete', id: '002' },
|
||||
* { op: 'delete', id: '004' }
|
||||
* { op: 'insert', index: 3, after: '004', block: { id: '006', ... } },
|
||||
* { op: 'insert', index: 4, after: '006', block: { id: '007', ... } },
|
||||
* { op: 'update', id: '001', content: 'This is the 1st paragraph' },
|
||||
* { op: 'delete', id: '003' },
|
||||
* { op: 'delete', id: '005' }
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
* UI expected:
|
||||
* ```
|
||||
* This is the first paragraph
|
||||
* [DELETE DIFF] This is the second paragraph
|
||||
* This is the third paragraph
|
||||
* [DELETE DIFF] This is the fourth paragraph
|
||||
* [UPDATE DIFF]This is the first paragraph
|
||||
* This is the second paragraph
|
||||
* [DELETE DIFF]This is the third paragraph
|
||||
* This is the fourth paragraph
|
||||
* [INSERT DIFF] New inserted paragraph 1
|
||||
* [INSERT DIFF] New inserted paragraph 2
|
||||
* [DELETE DIFF] This is the fifth paragraph
|
||||
* ```
|
||||
*
|
||||
* The resulting diffMap:
|
||||
* ```js
|
||||
* {
|
||||
* deletes: ['002', '004'],
|
||||
* inserts: { 3: [block_005, block_006] },
|
||||
* updates: {}
|
||||
* deletes: ['003', '005'],
|
||||
* inserts: { '004': [block_006, block_007] },
|
||||
* updates: { '001': 'This is the 1st paragraph' }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@@ -73,10 +80,7 @@ export function generateRenderDiff(
|
||||
originalMarkdown: string,
|
||||
changedMarkdown: string
|
||||
) {
|
||||
const { patches, oldBlocks } = diffMarkdown(
|
||||
originalMarkdown,
|
||||
changedMarkdown
|
||||
);
|
||||
const { patches } = diffMarkdown(originalMarkdown, changedMarkdown);
|
||||
|
||||
const diffMap: RenderDiffs = {
|
||||
deletes: [],
|
||||
@@ -84,19 +88,6 @@ export function generateRenderDiff(
|
||||
updates: {},
|
||||
};
|
||||
|
||||
const indexToBlockId: Record<number, string> = {};
|
||||
oldBlocks.forEach((block, idx) => {
|
||||
indexToBlockId[idx] = block.id;
|
||||
});
|
||||
|
||||
function getPrevBlock(index: number) {
|
||||
let start = index - 1;
|
||||
while (!indexToBlockId[start] && start >= 0) {
|
||||
start--;
|
||||
}
|
||||
return indexToBlockId[start] || 'HEAD';
|
||||
}
|
||||
|
||||
const insertGroups: Record<string, Block[]> = {};
|
||||
let lastInsertKey: string | null = null;
|
||||
let lastInsertIndex: number | null = null;
|
||||
@@ -107,7 +98,7 @@ export function generateRenderDiff(
|
||||
diffMap.deletes.push(patch.id);
|
||||
break;
|
||||
case 'insert': {
|
||||
const prevBlockId = getPrevBlock(patch.index);
|
||||
const prevBlockId = patch.after;
|
||||
if (
|
||||
lastInsertKey !== null &&
|
||||
lastInsertIndex !== null &&
|
||||
|
||||
@@ -7,7 +7,7 @@ export type Block = {
|
||||
export type PatchOp =
|
||||
| { op: 'replace'; id: string; content: string }
|
||||
| { op: 'delete'; id: string }
|
||||
| { op: 'insert'; index: number; block: Block };
|
||||
| { op: 'insert'; index: number; after: string; block: Block };
|
||||
|
||||
const BLOCK_MATCH_REGEXP = /^\s*<!--\s*block_id=(.*?)\s+flavour=(.*?)\s*-->/;
|
||||
|
||||
@@ -61,7 +61,6 @@ function diffBlockLists(oldBlocks: Block[], newBlocks: Block[]): PatchOp[] {
|
||||
// Mark old blocks that have been handled
|
||||
const handledOld = new Set<string>();
|
||||
|
||||
// First process newBlocks in order
|
||||
newBlocks.forEach((newBlock, newIdx) => {
|
||||
const old = oldMap.get(newBlock.id);
|
||||
if (old) {
|
||||
@@ -74,9 +73,11 @@ function diffBlockLists(oldBlocks: Block[], newBlocks: Block[]): PatchOp[] {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const after = newIdx > 0 ? newBlocks[newIdx - 1].id : 'HEAD';
|
||||
patch.push({
|
||||
op: 'insert',
|
||||
index: newIdx,
|
||||
after,
|
||||
block: {
|
||||
id: newBlock.id,
|
||||
type: newBlock.type,
|
||||
|
||||
Reference in New Issue
Block a user