Skip to content

Commit 29894ab

Browse files
committed
feat(presence): Add support for presences
1 parent 3de5116 commit 29894ab

File tree

5 files changed

+156
-3
lines changed

5 files changed

+156
-3
lines changed

lib/json1.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
// import log from './log'
2121
import deepEqual from './deepEqual.js'
2222
import deepClone from './deepClone.js'
23-
import {ReadCursor, WriteCursor, readCursor, writeCursor, advancer, eachChildOf, isValidPathItem} from './cursor.js'
24-
import { Doc, JSONOpComponent, Path, Key, JSONOp, JSONOpList, Conflict, ConflictType } from './types.js'
23+
import { ReadCursor, WriteCursor, readCursor, writeCursor, advancer, eachChildOf, isValidPathItem } from './cursor.js'
24+
import { Doc, JSONOpComponent, Path, Presence, Key, JSONOp, JSONOpList, Conflict, ConflictType } from './types.js'
2525

2626
const RELEASE_MODE = process.env.JSON1_RELEASE_MODE
2727
const log: (...args: any) => void = RELEASE_MODE ? (() => {}) : require('./log').default
@@ -57,6 +57,7 @@ export const type = {
5757

5858
apply,
5959
transformPosition,
60+
transformPresence,
6061
compose,
6162
tryTransform,
6263
transform,
@@ -688,7 +689,11 @@ function transformPosition(path: Path, op: JSONOp): Path | null {
688689
if (et) {
689690
const e = getEdit(c!)
690691
log('Embedded edit', e, et)
691-
if (et.transformPosition) path[i] = et.transformPosition(path[i], e)
692+
if (et.transformPosition) {
693+
path[i] = et.transformPosition(path[i], e)
694+
} else if (et.transformCursor) {
695+
path[i] = et.transformCursor(path[i], e)
696+
}
692697
// Its invalid for the operation to have drops inside the embedded edit here.
693698
break
694699
}
@@ -721,6 +726,25 @@ function transformPosition(path: Path, op: JSONOp): Path | null {
721726
return path
722727
}
723728

729+
function transformPresence(presence: Presence | null, op: JSONOp, isOwnOperation: boolean): Presence | null {
730+
if (!presence || !presence.start || !presence.end) {
731+
return null
732+
}
733+
734+
const start = transformPosition(presence.start, op)
735+
const end = transformPosition(presence.end, op)
736+
737+
if (start && end) {
738+
return { start, end }
739+
} else if (start) {
740+
return { start, end: start }
741+
} else if (end) {
742+
return { start: end, end }
743+
}
744+
745+
return null
746+
}
747+
724748

725749
// ****** Compose
726750

lib/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ export type JSONOp = null | JSONOpList
5757

5858
export type Key = number | string
5959
export type Path = Key[]
60+
export type Presence = {
61+
start: Path,
62+
end: Path,
63+
}
6064

6165
/**
6266
* JSON documents must be able to round-trip through JSON.stringify /

package-lock.json

Lines changed: 38 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"mocha": "^7.1.1",
1313
"ot-fuzzer": "1.3",
1414
"ot-simple": "^1.0.0",
15+
"rich-text": "^4.1.0",
1516
"terser": "^4.6.7",
1617
"typescript": "^3.9.5"
1718
},

test/presence.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
const assert = require("assert");
2+
const Delta = require("quill-delta");
3+
const { type } = require("../dist/json1.release");
4+
5+
type.registerSubtype(require("rich-text"));
6+
7+
describe("presences", () => {
8+
it("transforms json1 only presences", () => {
9+
const op = ["z", 0, { i: "hello" }];
10+
const presenceBefore = { start: ["z", 0], end: ["z", 1] };
11+
const presenceAfter = { start: ["z", 1], end: ["z", 2] };
12+
13+
assert.deepStrictEqual(
14+
type.transformPresence(presenceBefore, op),
15+
presenceAfter
16+
);
17+
});
18+
19+
it("transforms json1 + text-unicode presences", () => {
20+
const op = ["z", [0, { i: "hello" }], [2, { es: ["hi"] }]];
21+
const presenceBefore = { start: ["z", 1, 1], end: ["z", 2, 0] };
22+
const presenceAfter = { start: ["z", 2, 3], end: ["z", 3, 0] };
23+
24+
assert.deepStrictEqual(
25+
type.transformPresence(presenceBefore, op),
26+
presenceAfter
27+
);
28+
});
29+
30+
it("transforms json1 + rich-text presences", () => {
31+
const op = [
32+
"z",
33+
[0, { i: "hello" }],
34+
[4, { et: "rich-text", e: new Delta([{ insert: "bye" }]) }],
35+
];
36+
const presenceBefore = { start: ["z", 3, 1], end: ["z", 4, 0] };
37+
const presenceAfter = { start: ["z", 4, 4], end: ["z", 5, 0] };
38+
39+
assert.deepStrictEqual(
40+
type.transformPresence(presenceBefore, op),
41+
presenceAfter
42+
);
43+
});
44+
45+
it("does nothing on null presences", () => {
46+
const op = ["z", 0, { i: "hello" }];
47+
48+
assert.deepStrictEqual(type.transformPresence(null, op), null);
49+
});
50+
51+
it("returns null on missing start or end", () => {
52+
const op = ["z", 0, { i: "hello" }];
53+
54+
assert.deepStrictEqual(
55+
type.transformPresence({ start: null, end: [] }, op),
56+
null
57+
);
58+
59+
assert.deepStrictEqual(
60+
type.transformPresence({ start: [], end: null }, op),
61+
null
62+
);
63+
});
64+
65+
it("returns collapsed presence when start container is removed", () => {
66+
const op = ["z", 0, { r: true }];
67+
const presenceBefore = { start: ["z", 0], end: ["z", 1] };
68+
const presenceAfter = { start: ["z", 0], end: ["z", 0] };
69+
70+
assert.deepStrictEqual(
71+
type.transformPresence(presenceBefore, op),
72+
presenceAfter
73+
);
74+
});
75+
76+
it("returns collapsed presence when end container is removed", () => {
77+
const op = ["z", 1, { r: true }];
78+
const presenceBefore = { start: ["z", 0], end: ["z", 1] };
79+
const presenceAfter = { start: ["z", 0], end: ["z", 0] };
80+
81+
assert.deepStrictEqual(
82+
type.transformPresence(presenceBefore, op),
83+
presenceAfter
84+
);
85+
});
86+
});

0 commit comments

Comments
 (0)