Skip to content

Commit 7dba6db

Browse files
committed
Fix invalid tree style changes
1 parent e086ad7 commit 7dba6db

File tree

3 files changed

+77
-12
lines changed

3 files changed

+77
-12
lines changed

src/document/crdt/rht.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export class RHT {
102102
/**
103103
* `set` sets the value of the given key.
104104
*/
105-
public set(key: string, value: string, executedAt: TimeTicket): void {
105+
public set(key: string, value: string, executedAt: TimeTicket): boolean {
106106
const prev = this.nodeMapByKey.get(key);
107107

108108
if (prev === undefined || executedAt.after(prev.getUpdatedAt())) {
@@ -111,7 +111,10 @@ export class RHT {
111111
}
112112
const node = RHTNode.of(key, value, executedAt, false);
113113
this.nodeMapByKey.set(key, node);
114+
return true;
114115
}
116+
117+
return false;
115118
}
116119

117120
/**

src/document/crdt/tree.ts

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -766,7 +766,9 @@ export class CRDTTree extends CRDTGCElement {
766766
const [toParent, toLeft] = this.findNodesAndSplitText(range[1], editedAt);
767767

768768
const changes: Array<TreeChange> = [];
769-
const value = attributes ? parseObjectValues(attributes) : undefined;
769+
const attrs: { [key: string]: any } = attributes
770+
? parseObjectValues(attributes)
771+
: {};
770772
const createdAtMapByActor = new Map<string, TimeTicket>();
771773
this.traverseInPosRange(
772774
fromParent,
@@ -795,19 +797,32 @@ export class CRDTTree extends CRDTGCElement {
795797
node.attrs = new RHT();
796798
}
797799

800+
const affectedKeys = new Set<string>();
798801
for (const [key, value] of Object.entries(attributes)) {
799-
node.attrs.set(key, value, editedAt);
802+
if (node.attrs.set(key, value, editedAt)) {
803+
affectedKeys.add(key);
804+
}
800805
}
801806

802-
changes.push({
803-
type: TreeChangeType.Style,
804-
from: this.toIndex(fromParent, fromLeft),
805-
to: this.toIndex(toParent, toLeft),
806-
fromPath: this.toPath(fromParent, fromLeft),
807-
toPath: this.toPath(toParent, toLeft),
808-
actor: editedAt.getActorID(),
809-
value,
810-
});
807+
if (affectedKeys.size > 0) {
808+
const affectedAttrs = Array.from(affectedKeys).reduce(
809+
(acc: { [key: string]: any }, key) => {
810+
acc[key] = attrs[key];
811+
return acc;
812+
},
813+
{},
814+
);
815+
816+
changes.push({
817+
type: TreeChangeType.Style,
818+
from: this.toIndex(fromParent, fromLeft),
819+
to: this.toIndex(toParent, toLeft),
820+
fromPath: this.toPath(fromParent, fromLeft),
821+
toPath: this.toPath(toParent, toLeft),
822+
actor: editedAt.getActorID(),
823+
value: affectedAttrs,
824+
});
825+
}
811826
}
812827
},
813828
);

test/integration/tree_test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4632,6 +4632,53 @@ describe('TreeChange', () => {
46324632
);
46334633
}, task.name);
46344634
});
4635+
4636+
it('Concurrent style and style', async function ({ task }) {
4637+
await withTwoClientsAndDocuments<{ t: Tree }>(async (c1, d1, c2, d2) => {
4638+
d1.update((root) => {
4639+
root.t = new Tree({
4640+
type: 'doc',
4641+
children: [
4642+
{ type: 'p', children: [{ type: 'text', value: 'hello' }] },
4643+
],
4644+
});
4645+
});
4646+
await c1.sync();
4647+
await c2.sync();
4648+
assert.equal(d1.getRoot().t.toXML(), d2.getRoot().t.toXML());
4649+
assert.equal(d1.getRoot().t.toXML(), /*html*/ `<doc><p>hello</p></doc>`);
4650+
4651+
const [ops1, ops2] = subscribeDocs(d1, d2);
4652+
4653+
d1.update((r) => r.t.style(0, 1, { bold: 'true' }));
4654+
d2.update((r) => r.t.style(0, 1, { bold: 'false' }));
4655+
await c1.sync();
4656+
await c2.sync();
4657+
await c1.sync();
4658+
assert.equal(d1.getRoot().t.toXML(), d2.getRoot().t.toXML());
4659+
assert.equal(
4660+
d1.getRoot().t.toXML(),
4661+
/*html*/ `<doc><p bold="false">hello</p></doc>`,
4662+
);
4663+
4664+
assert.deepEqual(
4665+
ops1.map((it) => {
4666+
return { type: it.type, from: it.from, to: it.to, value: it.value };
4667+
}),
4668+
[
4669+
{ type: 'tree-style', from: 0, to: 1, value: { bold: 'true' } },
4670+
{ type: 'tree-style', from: 0, to: 1, value: { bold: 'false' } },
4671+
],
4672+
);
4673+
4674+
assert.deepEqual(
4675+
ops2.map((it) => {
4676+
return { type: it.type, from: it.from, to: it.to, value: it.value };
4677+
}),
4678+
[{ type: 'tree-style', from: 0, to: 1, value: { bold: 'false' } }],
4679+
);
4680+
}, task.name);
4681+
});
46354682
});
46364683

46374684
function subscribeDocs(

0 commit comments

Comments
 (0)