Skip to content
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

Proposal – Add support for presences #25

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions lib/json1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
// import log from './log'
import deepEqual from './deepEqual.js'
import deepClone from './deepClone.js'
import {ReadCursor, WriteCursor, readCursor, writeCursor, advancer, eachChildOf, isValidPathItem} from './cursor.js'
import { Doc, JSONOpComponent, Path, Key, JSONOp, JSONOpList, Conflict, ConflictType } from './types.js'
import { ReadCursor, WriteCursor, readCursor, writeCursor, advancer, eachChildOf, isValidPathItem } from './cursor.js'
import { Doc, JSONOpComponent, Path, Presence, Key, JSONOp, JSONOpList, Conflict, ConflictType } from './types.js'

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

apply,
transformPosition,
transformPresence,
compose,
tryTransform,
transform,
Expand Down Expand Up @@ -688,7 +689,11 @@ function transformPosition(path: Path, op: JSONOp): Path | null {
if (et) {
const e = getEdit(c!)
log('Embedded edit', e, et)
if (et.transformPosition) path[i] = et.transformPosition(path[i], e)
if (et.transformPosition) {
path[i] = et.transformPosition(path[i], e)
} else if (et.transformCursor) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where would transformCursor come from? Is this for rich text?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@curran curran Aug 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I got it backward. As per the main OTType documentation it's transformCursor that the type should implement. And it's text-unicode (the default subtype in JSON1), that is missing it but implements transformPosition.

path[i] = et.transformCursor(path[i], e)
}
// Its invalid for the operation to have drops inside the embedded edit here.
break
}
Expand Down Expand Up @@ -721,6 +726,25 @@ function transformPosition(path: Path, op: JSONOp): Path | null {
return path
}

function transformPresence(presence: Presence | null, op: JSONOp, isOwnOperation: boolean): Presence | null {
if (!presence || !presence.start || !presence.end) {
return null
}

const start = transformPosition(presence.start, op)
const end = transformPosition(presence.end, op)

if (start && end) {
return { ...presence, start, end }
} else if (start) {
return { ...presence, start, end: start }
} else if (end) {
return { ...presence, start: end, end }
}

return null
}


// ****** Compose

Expand Down
5 changes: 5 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ export type JSONOp = null | JSONOpList

export type Key = number | string
export type Path = Key[]
export type Presence = {
start: Path,
end: Path,
[key: string]: any
}

/**
* JSON documents must be able to round-trip through JSON.stringify /
Expand Down
38 changes: 38 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"mocha": "^7.1.1",
"ot-fuzzer": "1.3",
"ot-simple": "^1.0.0",
"rich-text": "^4.1.0",
"terser": "^4.6.7",
"typescript": "^3.9.5"
},
Expand Down
95 changes: 95 additions & 0 deletions test/presence.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
const assert = require("assert");
const Delta = require("quill-delta");
const { type } = require("../dist/json1.release");

type.registerSubtype(require("rich-text"));

describe("presences", () => {
it("transforms json1 only presences", () => {
const op = ["z", 0, { i: "hello" }];
const presenceBefore = { start: ["z", 0], end: ["z", 1] };
const presenceAfter = { start: ["z", 1], end: ["z", 2] };

assert.deepStrictEqual(
type.transformPresence(presenceBefore, op),
presenceAfter
);
});

it("transforms json1 + text-unicode presences", () => {
const op = ["z", [0, { i: "hello" }], [2, { es: ["hi"] }]];
const presenceBefore = { start: ["z", 1, 1], end: ["z", 2, 0] };
const presenceAfter = { start: ["z", 2, 3], end: ["z", 3, 0] };

assert.deepStrictEqual(
type.transformPresence(presenceBefore, op),
presenceAfter
);
});

it("transforms json1 + rich-text presences", () => {
const op = [
"z",
[0, { i: "hello" }],
[4, { et: "rich-text", e: new Delta([{ insert: "bye" }]) }],
];
const presenceBefore = { start: ["z", 3, 1], end: ["z", 4, 0] };
const presenceAfter = { start: ["z", 4, 4], end: ["z", 5, 0] };

assert.deepStrictEqual(
type.transformPresence(presenceBefore, op),
presenceAfter
);
});

it("does nothing on null presences", () => {
const op = ["z", 0, { i: "hello" }];

assert.deepStrictEqual(type.transformPresence(null, op), null);
});

it("returns null on missing start or end", () => {
const op = ["z", 0, { i: "hello" }];

assert.deepStrictEqual(
type.transformPresence({ start: null, end: [] }, op),
null
);

assert.deepStrictEqual(
type.transformPresence({ start: [], end: null }, op),
null
);
});

it("returns collapsed presence when start container is removed", () => {
const op = ["z", 0, { r: true }];
const presenceBefore = { start: ["z", 0], end: ["z", 1] };
const presenceAfter = { start: ["z", 0], end: ["z", 0] };

assert.deepStrictEqual(
type.transformPresence(presenceBefore, op),
presenceAfter
);
});

it("returns collapsed presence when end container is removed", () => {
const op = ["z", 1, { r: true }];
const presenceBefore = { start: ["z", 0], end: ["z", 1] };
const presenceAfter = { start: ["z", 0], end: ["z", 0] };

assert.deepStrictEqual(
type.transformPresence(presenceBefore, op),
presenceAfter
);
});

it("allows arbitrary data in presences", () => {
const op = ["z", 0, { i: "hello" }];
const data = { user: "John Doe", color: "blue" };
const presenceBefore = { start: ["z", 0], end: ["z", 1], data };
const presenceAfter = type.transformPresence(presenceBefore, op);

assert.ok(presenceBefore.data === presenceAfter.data);
});
});
31 changes: 31 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,11 @@ esprima@^4.0.0:
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==

fast-diff@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==

fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
Expand Down Expand Up @@ -396,6 +401,16 @@ locate-path@^3.0.0:
p-locate "^3.0.0"
path-exists "^3.0.0"

lodash.clonedeep@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=

lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=

lodash@^4.17.15:
version "4.17.19"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
Expand Down Expand Up @@ -569,6 +584,15 @@ picomatch@^2.0.4:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==

quill-delta@^4.2.1:
version "4.2.2"
resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-4.2.2.tgz#015397d046e0a3bed087cd8a51f98c11a1b8f351"
integrity sha512-qjbn82b/yJzOjstBgkhtBjN2TNK+ZHP/BgUQO+j6bRhWQQdmj2lH6hXG7+nwwLF41Xgn//7/83lxs9n2BkTtTg==
dependencies:
fast-diff "1.2.0"
lodash.clonedeep "^4.5.0"
lodash.isequal "^4.5.0"

readdirp@~3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839"
Expand All @@ -586,6 +610,13 @@ require-main-filename@^2.0.0:
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==

rich-text@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/rich-text/-/rich-text-4.1.0.tgz#8b4c0e48a84fbb535dd692998f1e39eaaf0451a8"
integrity sha512-zQg80us6AfopS7s2YPsU+jfLX1RJaOdd6w26Jz4Al1G73AMzqDLTIZSnr0I7HTdCIU1gcGSMDlhq2v4IfoKfbg==
dependencies:
quill-delta "^4.2.1"

seedrandom@^2.4.4:
version "2.4.4"
resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-2.4.4.tgz#b25ea98632c73e45f58b77cfaa931678df01f9ba"
Expand Down