Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@bayou/doc-common

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@bayou/doc-common - npm Package Compare versions

Comparing version 1.0.6 to 1.2.4

CaretId.js

2

BodyChange.js

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -3,0 +3,0 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -14,2 +14,8 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

/**
* {Logger} Logger for this module. **Note:** Just used for some temporary
* debugging stuff.
*/
const log = new Logger('body-delta');
/**
* Always-frozen list of body OT operations. This uses Quill's `Delta` class to

@@ -155,2 +161,7 @@ * implement the "interesting" OT functionality, however this class does _not_

if (wantDocument && !result.isDocument()) {
// **TODO:** Remove this logging once we track down why we're seeing this
// error.
log.event.badComposeOrig(this, other, result);
log.event.badComposeQuill(quillThis.ops, quillOther.ops, quillResult.ops);
throw Errors.badUse('Inappropriate `other` for composition given `wantDocument === true`.');

@@ -198,4 +209,5 @@ }

if ((typeof quillDelta === 'object') && (quillDelta.constructor.name === 'Delta')) {
// The version of `Delta` used by Quill is different than the one we
// specified in our `package.json`. Even though it will often happen
// The version of `Delta` used by Quill is different than the one which
// is specified in the configuration hook
// {@link @bayou/config-common/Text}. Even though it will often happen
// to work if we just let it slide (e.g. by snarfing `ops` out of the

@@ -207,20 +219,21 @@ // object and running with it), we don't want to end up shipping two

// yourself looking at this error, the likely right thing to do is
// look at `package.json` in the version of Quill you are using, and
// update what is returned from
// {@link @bayou/config-common/Text#Delta} to match what Quill has as
// its `quill-delta` dependency.
// look at the lines logged just before the error was thrown, and change
// either how `Text` is configured to match the incoming `Delta` version
// _or_ change where the incoming `Delta` version is used to match what
// got configured for `Text`. Of particular note, if you are using a
// version of `Quill` in distribution form, it bundles within itself a
// version of `Delta`, and you will want to make sure that `Text` uses
// `Quill.import('delta')` to get it hooked up.
// **TODO:** Because reasons, this complaint has been demoted to
// from "actual error" to "stern warning," for the time being. The
// `throw` should be restored at the earliest opportunity, along with
// removing the surrounding bits that let this case fall through.
//throw Errors.badUse('Divergent versions of `quill-delta` package.');
if (!BodyDelta._divergentComplaintMade) {
// NB: This is the only code in this module that uses `see-all`.
BodyDelta._divergentComplaintMade = true;
new Logger('doc-common').warn('Divergent versions of `quill-delta` package!');
}
} else {
throw e;
// This is an attempt to get a stack trace each from both "our" `Delta`
// and the incoming `Delta`, as a possible breadcrumb for folks running
// into this problem.
log.error('Divergent versions of `quill-delta` package.');
BodyDelta._logWhichDelta('ours', Text.Delta);
BodyDelta._logWhichDelta('incoming', quillDelta.constructor);
throw Errors.badUse('Divergent versions of `quill-delta` package.');
}
throw e;
}

@@ -230,2 +243,30 @@

}
/**
* Helper used when making the "divergent" complaint, which aims to log an
* error pointing at the source of one of the versions of `Delta`.
*
* @param {string} label Short label indicating which version this is.
* @param {class} Delta A version of the `Delta` class.
*/
static _logWhichDelta(label, Delta) {
const delta = new Delta([{ insert: 'x' }]);
let trace;
try {
delta.forEach(() => { throw new Error('x'); });
} catch (e) {
trace = e.stack;
}
for (const line of trace.split('\n')) {
const match = line.match(/at Delta[^(]+\(([^:)]+)[:)]/);
if (match) {
log.info(`${label}: ${match[1]}`);
return;
}
}
log.error(`Could not determine \`Delta\` version "${label}" from stack trace!`, trace);
}
}

@@ -1,5 +0,7 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,
// Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>
import GraphemeSplitter from 'grapheme-splitter';
import { BaseOp } from '@bayou/ot-common';

@@ -9,2 +11,7 @@ import { TInt, TObject, TString } from '@bayou/typecheck';

// Instance of the grapheme splitter library
// used to properly count the length of
// special characters, such as emojis.
const splitter = new GraphemeSplitter();
/**

@@ -198,2 +205,36 @@ * Operation on a text document body.

/**
* Returns the length of an Op.
* @param {boolean} [textOnly = false] An optional switch to determine whether
* to only take text into account when determing the length of an Op.
* @returns {integer} The length of the Op. Embeds count as 1.
*/
getLength(textOnly = false) {
const opProps = this.props;
switch (opProps.opName) {
case BodyOp.CODE_embed: {
if (textOnly) {
return 0;
}
return 1;
}
case BodyOp.CODE_text: {
// In order to properly determine the length of
// a given piece of text, a special library was
// needed. This is because special characters,
// such as emojis, often have a "length" of more
// than 1. We are more concerned with "spaces
// in the document".
return splitter.countGraphemes(opProps.text);
}
default: {
return 0;
}
}
}
/**
* Indicates whether or not this instance is an "insert" operation of some

@@ -279,2 +320,13 @@ * sort.

}
/**
* Subclass-specific implementation of {@link #isValidPayload}.
*
* @param {Functor} payload_unused The would-be payload for an instance.
* @returns {boolean} `true` if `payload` is valid, or `false` if not.
*/
static _impl_isValidPayload(payload_unused) {
// **TODO:** Fill this in!
return true;
}
}

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -6,2 +6,3 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

import { BaseSnapshot } from '@bayou/ot-common';
import { Errors } from '@bayou/util-common';

@@ -30,2 +31,15 @@ import BodyChange from './BodyChange';

/**
* {int} The length of this document, where the count is made
* up of text and embeds. An embed has a length of 1.
*/
get length() {
const bodyContentOps = this.contents.ops;
// Assume all contents are `text` or `embed`
const length = bodyContentOps.reduce((sum, op) => sum + op.getLength(), 0);
return length;
}
/**
* Main implementation of {@link #diff}, which produces a delta (not a

@@ -47,2 +61,48 @@ * change).

/**
* Implementation of {@link validateChange}, which
* will perform semantic validation checks on a body change
* in the context of a snapshot.
*
* @param {BodyChange} change The change being validated.
* @throws {Error} A validation error if semantic validation
* performed on a given change fails.
*/
_impl_validateChange(change) {
const ops = change.delta.ops;
for (const op of ops) {
const opProps = op.props;
const { opName } = opProps;
// TODO: validate `delete` in a similar way
switch (opName) {
case 'retain': {
this._validateRetainOp(op, this.length);
break;
}
default: {
break;
}
}
}
}
/**
* Performs semantic validation on a retain OP.
* Checks to make sure that the given retain OP's count
* does not exceed the document body length
* @param {BodyOp} retainOp A retain Op.
* @param {int} bodyLength The maximum length that can be retained.
* @throws {Error} A validation error if any semantic validation fails
* on given `retain` op.
*/
_validateRetainOp(retainOp, bodyLength) {
const { count } = retainOp.props;
if (count > bodyLength) {
throw Errors.badData(`Attempting to retain ${count} when document length is ${bodyLength}`);
}
}
/**
* {class} Class (constructor function) of change objects to be used with

@@ -49,0 +109,0 @@ * instances of this class.

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -10,2 +10,3 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

import CaretDelta from './CaretDelta';
import CaretId from './CaretId';
import CaretOp from './CaretOp';

@@ -17,3 +18,3 @@

*
* **Note:** `sessionId` is not included, because that's separate from the
* **Note:** The caret's ID is not included, because that's separate from the
* caret's "fields" per se.

@@ -26,10 +27,17 @@ *

const CARET_FIELDS = new Map([
['authorId', TString.check.bind(TString)],
['color', ColorUtil.checkCss],
['index', TInt.nonNegative],
['lastActive', Timestamp.check.bind(Timestamp)],
['revNum', RevisionNumber.check],
['index', TInt.nonNegative],
['length', TInt.nonNegative],
['color', ColorUtil.checkCss]
['revNum', RevisionNumber.check]
]);
/**
* {string} Special value for the ID which is only allowed for the default
* caret.
*/
const DEFAULT_ID = '<no_id>';
/**
* {Caret|null} An instance with all default values. Initialized in the static

@@ -46,4 +54,4 @@ * method of the same name.

* names, and the like, is meant to be a synecdochal metaphor for all
* information about a session, including the human driving it. The caret per
* se is merely the most blatant aspect of it.
* information about a session from the user's perspective, including the human
* driving it. The caret per se is merely the most blatant aspect of it.
*/

@@ -54,3 +62,5 @@ export default class Caret extends CommonBase {

if (DEFAULT === null) {
DEFAULT = new Caret('no_session',
// **Note:** There is no default for `authorId`, which is what makes it
// end up getting required when constructing a new instance from scratch.
DEFAULT = new Caret(DEFAULT_ID,
{

@@ -96,24 +106,30 @@ lastActive: Timestamp.now(),

/**
* Constructs an instance. Only the first argument (`sessionIdOrBase`) is
* required, and it is not necessary to specify all the fields in `fields`.
* Fields not listed are derived from the base caret (first argument) if
* specified as such, or from the default value `Caret.DEFAULT` if the first
* argument is a session ID.
* Constructs an instance. Only the first argument (`idOrBase`) is required,
* and it is not necessary to specify all the fields in `fields`. Fields not
* listed are derived from the base caret (first argument) if specified as
* such, or from the default value {@link #DEFAULT} if the first argument is
* an ID.
*
* @param {string|Caret} sessionIdOrBase Session ID that identifies the caret,
* or a base caret instance which provides the session and default values
* for fields.
* **Note:** {@link #DEFAULT} does not bind an `authorId`, which means that
* that field must be specified when creating an instance "from scratch."
*
* @param {string|Caret} idOrBase Caret ID, or a base caret instance which
* provides the ID and default values for fields.
* @param {object} [fields = {}] Fields of the caret, as plain object mapping
* field names to values.
*/
constructor(sessionIdOrBase, fields = {}) {
let sessionId;
constructor(idOrBase, fields = {}) {
let id;
let newFields;
if (sessionIdOrBase instanceof Caret) {
newFields = new Map(sessionIdOrBase._fields);
sessionId = sessionIdOrBase.sessionId;
if (idOrBase instanceof Caret) {
newFields = new Map(idOrBase._fields);
id = idOrBase.id;
} else if (DEFAULT !== null) {
newFields = new Map(DEFAULT._fields);
id = CaretId.check(idOrBase);
} else {
newFields = DEFAULT ? new Map(DEFAULT._fields) : new Map();
sessionId = TString.nonEmpty(sessionIdOrBase);
// If we're here, it means that `DEFAULT` is currently being initialized.
newFields = new Map();
id = TString.check(idOrBase);
}

@@ -125,4 +141,4 @@

/** {string} The session ID. */
this._sessionId = sessionId;
/** {string} The caret ID. */
this._id = id;

@@ -136,3 +152,3 @@ /** {Map<string,*>} Map of all of the caret fields, from name to value. */

if (DEFAULT && (newFields.size !== DEFAULT._fields.size)) {
if (DEFAULT && (newFields.size !== CARET_FIELDS.size)) {
throw Errors.badUse(`Missing field.`);

@@ -145,2 +161,9 @@ }

/**
* {string} ID of the author responsible for this caret.
*/
get authorId() {
return this._fields.get('authorId');
}
/**
* {string} The color to be used when annotating this selection. It is in CSS

@@ -154,2 +177,10 @@ * three-byte hex format (e.g. `'#ffeab9'`).

/**
* {string} ID of the caret. This uniquely identifies this caret within the
* context of a specific document.
*/
get id() {
return this._id;
}
/**
* {Int} The zero-based leading position of this caret / selection.

@@ -162,3 +193,3 @@ */

/**
* {Timestamp} The moment in time when this session was last active.
* {Timestamp} The moment in time when this caret was last active.
*/

@@ -186,13 +217,5 @@ get lastActive() {

/**
* {string} Opaque reference to be used with other APIs to get information
* about the author whose caret this is.
*/
get sessionId() {
return this._sessionId;
}
/**
* Composes the given `delta` on top of this instance, producing a new
* instance. The operations in `delta` must all be `setField` ops for the same
* `sessionId` as this instance.
* `id` as this instance.
*

@@ -212,4 +235,4 @@ * @param {CaretDelta} delta Delta to apply.

throw Errors.badUse(`Invalid operation name: ${props.opName}`);
} else if (props.sessionId !== this.sessionId) {
throw Errors.badUse('Mismatched session ID.');
} else if (props.caretId !== this.id) {
throw Errors.badUse('Mismatched ID.');
}

@@ -235,3 +258,3 @@

return [this._sessionId, fields];
return [this._id, fields];
}

@@ -251,3 +274,3 @@

* @param {Caret} newerCaret Caret to take the difference from. It must have
* the same `sessionId` as this instance.
* the same `id` as this instance.
* @returns {CaretDelta} Delta which represents the difference between

@@ -259,24 +282,24 @@ * `newerCaret` and this instance.

const sessionId = this.sessionId;
const id = this.id;
if (sessionId !== newerCaret.sessionId) {
throw Errors.badUse('Cannot `diff` carets with mismatched `sessionId`.');
if (id !== newerCaret.id) {
throw Errors.badUse('Cannot `diff` carets with mismatched `id`.');
}
return this.diffFields(newerCaret, sessionId);
return this.diffFields(newerCaret, id);
}
/**
* Like `diff()`, except does _not_ check to see if the two instances'
* `sessionId`s match. That is, it only looks at the fields.
* Like `diff()`, except does _not_ check to see if the two instances' `id`s
* match. That is, it only looks at the fields.
*
* @param {Caret} newerCaret Caret to take the difference from.
* @param {string} sessionId Session ID to use for the ops in the result.
* @param {string} id ID to use for the ops in the result.
* @returns {CaretDelta} Delta which represents the difference between
* `newerCaret` and this instance, _not_ including any difference in
* `sessionId`, if any.
* `id`, if any.
*/
diffFields(newerCaret, sessionId) {
diffFields(newerCaret, id) {
Caret.check(newerCaret);
TString.nonEmpty(sessionId);
CaretId.check(id);

@@ -288,3 +311,3 @@ const fields = this._fields;

if (!Caret._equalFields(v, fields.get(k))) {
ops.push(CaretOp.op_setField(sessionId, k, v));
ops.push(CaretOp.op_setField(id, k, v));
}

@@ -310,3 +333,3 @@ }

if (this._sessionId !== other._sessionId) {
if (this._id !== other._id) {
return false;

@@ -313,0 +336,0 @@ }

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -3,0 +3,0 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -16,4 +16,4 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

* **Note:** To be valid as a document delta, the set of operations must (a)
* only consist of `beginSession` ops, and (b) not mention any given session ID
* more than once.
* only consist of `add` ops, and (b) not mention any given caret ID more than
* once.
*

@@ -32,7 +32,7 @@ * Instances of this class are immutable.

_impl_compose(other, wantDocument) {
// Map from each session to an array of ops which apply to it.
const sessions = new Map();
// Map from each caret to an array of ops which apply to it.
const carets = new Map();
// Add / replace the ops, first from `this` and then from `other`, as a
// mapping from the session ID.
// mapping from the caret ID.
for (const op of [...this.ops, ...other.ops]) {

@@ -42,18 +42,18 @@ const opProps = op.props;

switch (opProps.opName) {
case CaretOp.CODE_beginSession: {
// Clear out the session except for this op, because no earlier op
// could possibly affect the result.
sessions.set(opProps.caret.sessionId, [op]);
case CaretOp.CODE_add: {
// Clear out the caret except for this op, because no earlier op could
// possibly affect the result.
carets.set(opProps.caret.id, [op]);
break;
}
case CaretOp.CODE_endSession: {
case CaretOp.CODE_delete: {
if (wantDocument) {
// Document deltas don't remember session deletions.
sessions.delete(opProps.sessionId);
// Document deltas don't remember caret deletions.
carets.delete(opProps.caretId);
} else {
// Clear out the session; same reason as `BEGIN_SESSION` above. We
// _do_ keep the op, because the fact of a deletion needs to be part
// of the final composed result.
sessions.set(opProps.sessionId, [op]);
// Clear out the caret; same reason as `add` above. We _do_ keep the
// op, because the fact of a deletion needs to be part of the final
// composed result.
carets.set(opProps.caretId, [op]);
}

@@ -64,22 +64,21 @@ break;

case CaretOp.CODE_setField: {
const sessionId = opProps.sessionId;
const ops = sessions.get(sessionId);
let handled = false;
const caretId = opProps.caretId;
const ops = carets.get(caretId);
let handled = false;
if (!ops) {
// This is a "naked" set (no corresponding `BEGIN_SESSION` in the
// result. Just start off an array with it.
sessions.set(sessionId, [op]);
// This is a "naked" set (no corresponding `add` in the result).
// Just start off an array with it.
carets.set(caretId, [op]);
handled = true;
} else if (ops.length === 1) {
// We have a single-element array this session. It might be a
// `BEGIN_SESSION` or `END_SESSION`, in which case we can do
// something special.
// We have a single-element array for this caret. It might be a
// `add` or an `delete`, in which case we can do something special.
const op0Props = ops[0].props;
if (op0Props.opName === CaretOp.CODE_beginSession) {
if (op0Props.opName === CaretOp.CODE_add) {
// Integrate the new value into the caret.
const caret = op0Props.caret.compose(new CaretDelta([op]));
ops[0] = CaretOp.op_beginSession(caret);
ops[0] = CaretOp.op_add(caret);
handled = true;
} else if (op0Props.opName === CaretOp.CODE_endSession) {
} else if (op0Props.opName === CaretOp.CODE_delete) {
// We ignore set-after-end. A bit philosophical, but what does

@@ -114,3 +113,3 @@ // it even mean to set a value on a nonexistent thing?

const allOps = [].concat(...sessions.values());
const allOps = [].concat(...carets.values());
return new CaretDelta(allOps);

@@ -132,10 +131,10 @@ }

switch (opProps.opName) {
case CaretOp.CODE_beginSession: {
const sessionId = opProps.caret.sessionId;
case CaretOp.CODE_add: {
const caretId = opProps.caret.id;
if (ids.has(sessionId)) {
if (ids.has(caretId)) {
return false;
}
ids.add(sessionId);
ids.add(caretId);
break;

@@ -142,0 +141,0 @@ }

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -6,6 +6,6 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

import { BaseOp } from '@bayou/ot-common';
import { TString } from '@bayou/typecheck';
import { Errors } from '@bayou/util-common';
import Caret from './Caret';
import CaretId from './CaretId';

@@ -16,10 +16,10 @@ /**

export default class CaretOp extends BaseOp {
/** {string} Opcode constant for "begin session" operations. */
static get CODE_beginSession() {
return 'beginSession';
/** {string} Opcode constant for "add" operations (add a new caret). */
static get CODE_add() {
return 'add';
}
/** {string} Opcode constant for "end session" operations. */
static get CODE_endSession() {
return 'endSession';
/** {string} Opcode constant for "delete" operations (delete a caret). */
static get CODE_delete() {
return 'delete';
}

@@ -33,24 +33,23 @@

/**
* Constructs a new "begin session" operation.
* Constructs a new "add" operation.
*
* @param {Caret} caret The initial caret for the new session (which includes
* a session ID).
* @param {Caret} caret The caret to add.
* @returns {CaretOp} The corresponding operation.
*/
static op_beginSession(caret) {
static op_add(caret) {
Caret.check(caret);
return new CaretOp(CaretOp.CODE_beginSession, caret);
return new CaretOp(CaretOp.CODE_add, caret);
}
/**
* Constructs a new "end session" operation.
* Constructs a new "delete" operation.
*
* @param {string} sessionId ID of the session.
* @param {string} caretId ID of the caret which is to be removed.
* @returns {CaretOp} The corresponding operation.
*/
static op_endSession(sessionId) {
TString.nonEmpty(sessionId);
static op_delete(caretId) {
CaretId.check(caretId);
return new CaretOp(CaretOp.CODE_endSession, sessionId);
return new CaretOp(CaretOp.CODE_delete, caretId);
}

@@ -61,3 +60,3 @@

*
* @param {string} sessionId Session for the caret to update.
* @param {string} caretId ID of the caret to update.
* @param {string} key Name of the field to update.

@@ -68,7 +67,7 @@ * @param {*} value New value for the so-named field. Type restriction on this

*/
static op_setField(sessionId, key, value) {
TString.nonEmpty(sessionId);
static op_setField(caretId, key, value) {
CaretId.check(caretId);
Caret.checkField(key, value);
return new CaretOp(CaretOp.CODE_setField, sessionId, key, value);
return new CaretOp(CaretOp.CODE_setField, caretId, key, value);
}

@@ -87,3 +86,3 @@

switch (opName) {
case CaretOp.CODE_beginSession: {
case CaretOp.CODE_add: {
const [caret] = payload.args;

@@ -93,10 +92,10 @@ return Object.freeze({ opName, caret });

case CaretOp.CODE_endSession: {
const [sessionId] = payload.args;
return Object.freeze({ opName, sessionId });
case CaretOp.CODE_delete: {
const [caretId] = payload.args;
return Object.freeze({ opName, caretId });
}
case CaretOp.CODE_setField: {
const [sessionId, key, value] = payload.args;
return Object.freeze({ opName, sessionId, key, value });
const [caretId, key, value] = payload.args;
return Object.freeze({ opName, caretId, key, value });
}

@@ -109,2 +108,13 @@

}
/**
* Subclass-specific implementation of {@link #isValidPayload}.
*
* @param {Functor} payload_unused The would-be payload for an instance.
* @returns {boolean} `true` if `payload` is valid, or `false` if not.
*/
static _impl_isValidPayload(payload_unused) {
// **TODO:** Fill this in!
return true;
}
}

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -6,3 +6,2 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

import { BaseSnapshot } from '@bayou/ot-common';
import { TString } from '@bayou/typecheck';
import { Errors } from '@bayou/util-common';

@@ -13,2 +12,3 @@

import CaretDelta from './CaretDelta';
import CaretId from './CaretId';
import CaretOp from './CaretOp';

@@ -18,7 +18,7 @@

/**
* Snapshot of information about all active sessions on a particular document.
* Snapshot of information about all active carets on a particular document.
* Instances of this class are always frozen (immutable).
*
* When thought of in terms of a map, instances of this class can be taken to
* be maps from session ID strings to `Caret` values.
* be maps from caret ID strings to `Caret` values.
*/

@@ -38,4 +38,4 @@ export default class CaretSnapshot extends BaseSnapshot {

/**
* {Map<string, CaretOp>} Map of session ID to an `op_beginSession` which
* defines the caret for that session.
* {Map<string, CaretOp>} Map of caret ID to an `op_add` which contains a
* caret with that ID.
*/

@@ -49,4 +49,4 @@ this._carets = new Map();

switch (opProps.opName) {
case CaretOp.CODE_beginSession: {
this._carets.set(opProps.caret.sessionId, op);
case CaretOp.CODE_add: {
this._carets.set(opProps.caret.id, op);
break;

@@ -77,3 +77,3 @@ }

/**
* Gets an iterator over the `[sessionId, caret]` entries that make up the
* Gets an iterator over the `[caretId, caret]` entries that make up the
* snapshot.

@@ -84,3 +84,3 @@ *

*
* @yields {[string, Caret]} Snapshot entries. The keys are the session IDs,
* @yields {[string, Caret]} Snapshot entries. The keys are the caret IDs,
* and the values are the corresponding caret values.

@@ -91,3 +91,3 @@ */

const caret = op.props.caret;
yield [caret.sessionId, caret];
yield [caret.id, caret];
}

@@ -97,4 +97,4 @@ }

/**
* Gets the caret info for the given session. It is an error if this instance
* has no caret for the indicated session.
* Gets the {@link Caret} with the given ID. It is an error if this instance
* has no caret with that ID.
*

@@ -104,7 +104,7 @@ * **Note:** This differs from the semantics of the `Map` method of the same

*
* @param {string} sessionId Session in question.
* @param {string} caretId ID of the caret in question.
* @returns {Caret} Corresponding caret.
*/
get(sessionId) {
const found = this.getOrNull(sessionId);
get(caretId) {
const found = this.getOrNull(caretId);

@@ -115,15 +115,16 @@ if (found) {

throw Errors.badUse(`No such session: ${sessionId}`);
throw Errors.badUse(`No such caret: ${caretId}`);
}
/**
* Gets the caret info for the given session, if any.
* Gets the {@link Caret} with the given ID, if this instance in fact stores
* such a caret.
*
* @param {string} sessionId Session in question.
* @param {string} caretId ID of the caret in question.
* @returns {Caret|null} Corresponding caret, or `null` if there is none.
*/
getOrNull(sessionId) {
TString.nonEmpty(sessionId);
getOrNull(caretId) {
CaretId.check(caretId);
const found = this._carets.get(sessionId);
const found = this._carets.get(caretId);

@@ -155,4 +156,4 @@ return found ? found.props.caret : null;

for (const [sessionId, thisCaret] of thisCarets) {
if (!thisCaret.equals(otherCarets.get(sessionId))) {
for (const [caretId, thisCaret] of thisCarets) {
if (!thisCaret.equals(otherCarets.get(caretId))) {
return false;

@@ -166,3 +167,3 @@ }

/**
* Gets whether or not this instance has a caret for the given session.
* Gets whether or not this instance has a caret with the given ID.
*

@@ -172,11 +173,27 @@ * **Note:** This has identical semantics to the `Map` method of the same

*
* @param {string} sessionId Session in question.
* @returns {boolean} `true` if this instance has a caret for the indicated
* session, or `false` if not.
* @param {string} caretId ID of the caret in question.
* @returns {boolean} `true` if this instance has a caret with the indicated
* ID, or `false` if not.
*/
has(sessionId) {
return this.getOrNull(sessionId) !== null;
has(caretId) {
return this.getOrNull(caretId) !== null;
}
/**
* Returns a randomly-generated ID which is guaranteed not to be used by any
* caret in this instance.
*
* @returns {string} Available caret ID.
*/
randomUnusedId() {
// Loop in case we get _very_ unlucky.
for (;;) {
const result = CaretId.randomInstance();
if (!this.has(result)) {
return result;
}
}
}
/**
* Constructs an instance just like this one, except with an additional or

@@ -193,6 +210,5 @@ * updated reference to the indicated caret. If the given caret (including all

const sessionId = caret.sessionId;
const op = CaretOp.op_beginSession(caret);
const op = CaretOp.op_add(caret);
return op.equals(this._carets.get(sessionId))
return op.equals(this._carets.get(caret.id))
? this

@@ -204,32 +220,22 @@ : this.compose(new CaretChange(this.revNum, [op]));

* Constructs an instance just like this one, except without any reference to
* the session indicated by the given caret. If there is no session for the
* given caret, this method returns `this`.
* a caret with the indicated ID. If there is no such caret, this method
* returns `this`.
*
* @param {Caret} caret The caret whose session should not be represented in
* the result. Only the `sessionId` of the caret is consulted; it doesn't
* matter if other caret fields match.
* @param {string|Caret} idOrCaret The ID of the caret which should not be
* represented in the result, or a {@link Caret} whose ID is used for the
* check. (That is, if given a {@link Caret}, only the `id` is consulted; it
* doesn't matter if other fields match.
* @returns {CaretSnapshot} An appropriately-constructed instance.
*/
withoutCaret(caret) {
Caret.check(caret);
return this.withoutSession(caret.sessionId);
}
withoutCaret(idOrCaret) {
const caretId = (typeof idOrCaret === 'string')
? CaretId.check(idOrCaret)
: Caret.check(idOrCaret).id;
/**
* Constructs an instance just like this one, except without any reference to
* the indicated session. If the session is not represented in this instance,
* this method returns `this`.
*
* @param {string} sessionId ID of the session which should not be represented
* in the result.
* @returns {CaretSnapshot} An appropriately-constructed instance.
*/
withoutSession(sessionId) {
// This type checks `sessionId`, which is why it's not just run when we need
// to call `compose()`.
const op = CaretOp.op_endSession(sessionId);
return this._carets.has(sessionId)
? this.compose(new CaretChange(this.revNum, [op]))
: this;
if (this._carets.has(caretId)) {
const op = CaretOp.op_delete(caretId);
return this.compose(new CaretChange(this.revNum, [op]));
} else {
return this;
}
}

@@ -253,6 +259,6 @@

for (const [sessionId, caretOp] of newerCarets) {
const already = this._carets.get(sessionId);
for (const [caretId, caretOp] of newerCarets) {
const already = this._carets.get(caretId);
if (already) {
// The `sessionId` matches the older snapshot. Indicate an update if the
// The `caretId` matches the older snapshot. Indicate an update if the
// values are different.

@@ -266,3 +272,3 @@ if (!already.equals(caretOp)) {

} else {
// The `sessionId` isn't in the older snapshot, so this is an addition.
// The `caretId` isn't in the older snapshot, so this is an addition.
resultOps.push(caretOp);

@@ -274,5 +280,5 @@ }

for (const sessionId of this._carets.keys()) {
if (!newerCarets.get(sessionId)) {
resultOps.push(CaretOp.op_endSession(sessionId));
for (const caretId of this._carets.keys()) {
if (!newerCarets.get(caretId)) {
resultOps.push(CaretOp.op_delete(caretId));
}

@@ -285,2 +291,7 @@ }

// TODO: implement caret snapshot specific validation
_impl_validateChange() {
return true;
}
/**

@@ -287,0 +298,0 @@ * {class} Class (constructor function) of change objects to be used with

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -13,5 +13,5 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

import CaretDelta from './CaretDelta';
import CaretId from './CaretId';
import CaretOp from './CaretOp';
import CaretSnapshot from './CaretSnapshot';
import DocumentId from './DocumentId';
import Property from './Property';

@@ -22,2 +22,3 @@ import PropertyChange from './PropertyChange';

import PropertySnapshot from './PropertySnapshot';
import SessionInfo from './SessionInfo';
import Timeouts from './Timeouts';

@@ -34,5 +35,5 @@

CaretDelta,
CaretId,
CaretOp,
CaretSnapshot,
DocumentId,
Property,

@@ -43,3 +44,4 @@ PropertyChange,

PropertySnapshot,
SessionInfo,
Timeouts
};

@@ -7,11 +7,13 @@ {

"dependencies": {
"@bayou/codec": "1.0.6",
"@bayou/config-common": "1.0.6",
"@bayou/ot-common": "1.0.6",
"@bayou/see-all": "1.0.6",
"@bayou/typecheck": "1.0.6",
"@bayou/util-common": "1.0.6"
"@bayou/api-common": "1.2.4",
"@bayou/codec": "1.2.4",
"@bayou/config-common": "1.2.4",
"@bayou/ot-common": "1.2.4",
"@bayou/see-all": "1.2.4",
"@bayou/typecheck": "1.2.4",
"@bayou/util-common": "1.2.4",
"grapheme-splitter": "^1.0.2"
},
"name": "@bayou/doc-common",
"version": "1.0.6"
"version": "1.2.4"
}

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -3,0 +3,0 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -3,0 +3,0 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -3,0 +3,0 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -76,2 +76,13 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

}
/**
* Subclass-specific implementation of {@link #isValidPayload}.
*
* @param {Functor} payload_unused The would-be payload for an instance.
* @returns {boolean} `true` if `payload` is valid, or `false` if not.
*/
static _impl_isValidPayload(payload_unused) {
// **TODO:** Fill this in!
return true;
}
}

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -14,3 +14,3 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

/**
* Snapshot of information about all active sessions on a particular document.
* Snapshot of information about all the properties of a particular document.
* Instances of this class are always frozen (immutable).

@@ -237,2 +237,7 @@ *

// TODO: implement property snapshot specific validation
_impl_validateChange() {
return true;
}
/**

@@ -239,0 +244,0 @@ * {class} Class (constructor function) of change objects to be used with

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -3,0 +3,0 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -65,2 +65,25 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

});
it('should reject arguments that are instances of the wrong `Delta` class', () => {
// This test confirms that the code properly detects when there's more
// than one class named `Delta` in the system and the one _not_ used by
// this module is passed in here. Historically speaking, the check has
// caught actual build problems.
// The check doesn't really know that it got a bona fide implementation of
// `quill-delta`, just that it's an instance of a class named `Delta`. We
// define `forEach()` because that's the method that gets used to induce
// an error which helps produce an informative message.
class Delta {
forEach(func) {
func('x');
}
}
const wrongDelta = new Delta();
wrongDelta.ops = [];
assert.throws(() => { BodyDelta.fromQuillForm(wrongDelta); }, /Divergent/);
});
});

@@ -179,3 +202,3 @@

it('should produce the new doc when composing the orig doc with the diff', () => {
it('should produce the new document when composing the orig document with the diff', () => {
const diff = origDoc.diff(newDoc);

@@ -182,0 +205,0 @@ const result = origDoc.compose(diff, true);

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -8,2 +8,3 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

import { inspect } from 'util';
import GraphemeSplitter from 'grapheme-splitter';

@@ -13,2 +14,7 @@ import { BodyOp } from '@bayou/doc-common';

// Instance of the grapheme splitter library
// used to properly count the length of
// special characters, such as emojis.
const splitter = new GraphemeSplitter();
describe('@bayou/doc-common/BodyOp', () => {

@@ -283,2 +289,71 @@ describe('fromQuillForm()', () => {

});
describe('getLength()', () => {
const PLAIN_TEXT = 'plain text';
const PLAIN_TEXT_LENGTH = PLAIN_TEXT.length;
const EMOJI_TEXT = '😀 smile!';
const EMOJI_TEXT_LENGTH = splitter.countGraphemes(EMOJI_TEXT);
const EMBED_OP = BodyOp.op_embed('x', 1);
const PLAIN_TEXT_OP = BodyOp.op_text(PLAIN_TEXT);
const EMOJI_TEXT_OP = BodyOp.op_text(EMOJI_TEXT);
describe('text op', () => {
describe('when `textOnly` is true', () => {
it('should return length of plaintext', () => {
const opLength = PLAIN_TEXT_OP.getLength(true);
assert.strictEqual(opLength, PLAIN_TEXT_LENGTH);
});
});
describe('when `textOnly` is false', () => {
it('should return length of plaintext', () => {
const opLength = PLAIN_TEXT_OP.getLength(false);
assert.strictEqual(opLength, PLAIN_TEXT_LENGTH);
});
});
describe('when `textOnly` is not set', () => {
it('should return length of plaintext', () => {
const opLength = PLAIN_TEXT_OP.getLength();
assert.strictEqual(opLength, PLAIN_TEXT_LENGTH);
});
it('should return length of text with special chars', () => {
const opLength = EMOJI_TEXT_OP.getLength();
assert.strictEqual(opLength, EMOJI_TEXT_LENGTH);
});
});
});
describe('embed op', () => {
describe('when `textOnly` is true', () => {
it('should return 0', () => {
const opLength = EMBED_OP.getLength(true);
assert.strictEqual(opLength, 0);
});
});
describe('when `textOnly` is false', () => {
it('should return 1', () => {
const opLength = EMBED_OP.getLength(false);
assert.strictEqual(opLength, 1);
});
});
describe('when `textOnly` is not set', () => {
it('should return 1', () => {
const opLength = EMBED_OP.getLength();
assert.strictEqual(opLength, 1);
});
});
});
});
});

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -13,15 +13,16 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

*
* @param {string} sessionId Session ID.
* @param {string} id Caret ID.
* @param {Int} index Start caret position.
* @param {Int} length Selection length.
* @param {string} color Highlight color.
* @param {string} authorId Author ID.
* @returns {Caret} Appropriately-constructed caret.
*/
function newCaret(sessionId, index, length, color) {
return new Caret(sessionId, { index, length, color });
function newCaret(id, index, length, color, authorId) {
return new Caret(id, { index, length, color, authorId });
}
const caret1 = newCaret('session-1', 1, 0, '#111111');
const caret2 = newCaret('session-2', 2, 6, '#222222');
const caret3 = newCaret('session-3', 3, 99, '#333333');
const caret1 = newCaret('cr-11111', 1, 0, '#111111', 'author-1');
const caret2 = newCaret('cr-22222', 2, 6, '#222222', 'author-2');
const caret3 = newCaret('cr-33333', 3, 99, '#333333', 'third-author');

@@ -43,4 +44,11 @@ describe('@bayou/doc-common/Caret', () => {

it('should update `authorId` given the appropriate op', () => {
const op = CaretOp.op_setField(caret1.id, 'authorId', 'boop');
const result = caret1.compose(new CaretDelta([op]));
assert.strictEqual(result.authorId, 'boop');
});
it('should update `index` given the appropriate op', () => {
const op = CaretOp.op_setField(caret1.sessionId, 'index', 99999);
const op = CaretOp.op_setField(caret1.id, 'index', 99999);
const result = caret1.compose(new CaretDelta([op]));

@@ -52,3 +60,3 @@

it('should update `length` given the appropriate op', () => {
const op = CaretOp.op_setField(caret1.sessionId, 'length', 99999);
const op = CaretOp.op_setField(caret1.id, 'length', 99999);
const result = caret1.compose(new CaretDelta([op]));

@@ -60,3 +68,3 @@

it('should update `color` given the appropriate op', () => {
const op = CaretOp.op_setField(caret1.sessionId, 'color', '#aabbcc');
const op = CaretOp.op_setField(caret1.id, 'color', '#aabbcc');
const result = caret1.compose(new CaretDelta([op]));

@@ -68,3 +76,3 @@

it('should update `revNum` given the appropriate op', () => {
const op = CaretOp.op_setField(caret1.sessionId, 'revNum', 12345);
const op = CaretOp.op_setField(caret1.id, 'revNum', 12345);
const result = caret1.compose(new CaretDelta([op]));

@@ -75,4 +83,4 @@

it('should refuse to compose if given a non-matching session ID', () => {
const op = CaretOp.op_setField(caret2.sessionId, 'index', 55);
it('should refuse to compose if given a non-matching caret ID', () => {
const op = CaretOp.op_setField(caret2.id, 'index', 55);

@@ -91,3 +99,3 @@ assert.throws(() => { caret1.compose(new CaretDelta([op])); });

it('should refuse to diff if given a non-matching session ID', () => {
it('should refuse to diff if given a non-matching caret ID', () => {
assert.throws(() => { caret1.diff(caret2); });

@@ -98,3 +106,3 @@ });

const older = caret1;
const op = CaretOp.op_setField(older.sessionId, 'index', 99999);
const op = CaretOp.op_setField(older.id, 'index', 99999);
const newer = older.compose(new CaretDelta([op]));

@@ -110,3 +118,3 @@ const diffOps = older.diff(newer).ops;

it('should produce an empty diff when passed itself', () => {
const result = caret1.diffFields(caret1, 'florp');
const result = caret1.diffFields(caret1, 'cr-florp');

@@ -117,4 +125,4 @@ assert.instanceOf(result, CaretDelta);

it('should diff fields even if given a non-matching session ID', () => {
assert.doesNotThrow(() => { caret1.diffFields(caret2, 'florp'); });
it('should diff fields even if given a non-matching caret ID', () => {
assert.doesNotThrow(() => { caret1.diffFields(caret2, 'cr-florp'); });
});

@@ -124,5 +132,5 @@

const older = caret1;
const op = CaretOp.op_setField(older.sessionId, 'index', 99999);
const op = CaretOp.op_setField(older.id, 'index', 99999);
const newer = older.compose(new CaretDelta([op]));
const diffOps = older.diffFields(newer, older.sessionId).ops;
const diffOps = older.diffFields(newer, older.id).ops;

@@ -135,5 +143,5 @@ assert.strictEqual(diffOps.length, 1);

const older = caret1;
const op = CaretOp.op_setField(older.sessionId, 'length', 99999);
const op = CaretOp.op_setField(older.id, 'length', 99999);
const newer = older.compose(new CaretDelta([op]));
const diffOps = older.diffFields(newer, older.sessionId).ops;
const diffOps = older.diffFields(newer, older.id).ops;

@@ -146,5 +154,5 @@ assert.strictEqual(diffOps.length, 1);

const older = caret1;
const op = CaretOp.op_setField(older.sessionId, 'color', '#abcdef');
const op = CaretOp.op_setField(older.id, 'color', '#abcdef');
const newer = older.compose(new CaretDelta([op]));
const diffOps = older.diffFields(newer, older.sessionId).ops;
const diffOps = older.diffFields(newer, older.id).ops;

@@ -169,5 +177,5 @@ assert.strictEqual(diffOps.length, 1);

it('should return `false` when session IDs differ', () => {
const c1 = newCaret('x', 1, 2, '#000011');
const c2 = newCaret('y', 1, 2, '#000011');
it('should return `false` when caret IDs differ', () => {
const c1 = newCaret('cr-xxxxx', 1, 2, '#000011', 'some-author');
const c2 = newCaret('cr-yyyyy', 1, 2, '#000011', 'some-author');
assert.isFalse(c1.equals(c2));

@@ -180,17 +188,21 @@ });

op = CaretOp.op_setField(c1.sessionId, 'index', 99999);
op = CaretOp.op_setField(c1.id, 'index', 99999);
c2 = c1.compose(new CaretDelta([op]));
assert.isFalse(c1.equals(c2));
op = CaretOp.op_setField(c1.sessionId, 'length', 99999);
op = CaretOp.op_setField(c1.id, 'length', 99999);
c2 = c1.compose(new CaretDelta([op]));
assert.isFalse(c1.equals(c2));
op = CaretOp.op_setField(c1.sessionId, 'color', '#999999');
op = CaretOp.op_setField(c1.id, 'color', '#999999');
c2 = c1.compose(new CaretDelta([op]));
assert.isFalse(c1.equals(c2));
op = CaretOp.op_setField(c1.id, 'authorId', 'zagnut');
c2 = c1.compose(new CaretDelta([op]));
assert.isFalse(c1.equals(c2));
});
it('should return `false` when passed a non-caret', () => {
const caret = newCaret('x', 1, 2, '#000011');
const caret = newCaret('cr-florp', 1, 2, '#000011', 'blorp');

@@ -197,0 +209,0 @@ assert.isFalse(caret.equals(undefined));

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -60,8 +60,8 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

it('should not include session ends when `wantDocument` is `true`', () => {
const op1 = CaretOp.op_beginSession(new Caret('aaa'));
const op2 = CaretOp.op_beginSession(new Caret('bbb'));
const op3 = CaretOp.op_beginSession(new Caret('ccc'));
const op4 = CaretOp.op_endSession('bbb');
const op5 = CaretOp.op_endSession('ddd');
it('should not include `delete` ops when `wantDocument` is `true`', () => {
const op1 = CaretOp.op_add(new Caret('cr-aaaaa', { authorId: 'xyz' }));
const op2 = CaretOp.op_add(new Caret('cr-bbbbb', { authorId: 'xyz' }));
const op3 = CaretOp.op_add(new Caret('cr-ccccc', { authorId: 'xyz' }));
const op4 = CaretOp.op_delete('cr-bbbbb');
const op5 = CaretOp.op_delete('cr-ddddd');
const d1 = new CaretDelta([op1, op2]);

@@ -74,5 +74,5 @@ const d2 = new CaretDelta([op3, op4, op5]);

describe('`endSession` preceded by anything for that session', () => {
it('should result in just the `endSession`', () => {
const endOp = CaretOp.op_endSession('session1');
describe('`delete` preceded by anything for that caret', () => {
it('should result in just the `delete`', () => {
const endOp = CaretOp.op_delete('cr-sessi');

@@ -98,3 +98,3 @@ test(

test(
[CaretOp.op_beginSession(new Caret('session1'))],
[CaretOp.op_add(new Caret('cr-sessi', { authorId: 'xyz' }))],
[endOp],

@@ -105,3 +105,3 @@ [endOp]

test(
[CaretOp.op_setField('session1', 'revNum', 5)],
[CaretOp.op_setField('cr-sessi', 'revNum', 5)],
[endOp],

@@ -113,6 +113,6 @@ [endOp]

describe('`setField` after `endSession`', () => {
it('should result in just the `endSession`', () => {
const endOp = CaretOp.op_endSession('session1');
const setOp = CaretOp.op_setField('session1', 'revNum', 123);
describe('`setField` after `delete`', () => {
it('should result in just the `delete`', () => {
const endOp = CaretOp.op_delete('cr-sess1');
const setOp = CaretOp.op_setField('cr-sess1', 'revNum', 123);

@@ -138,3 +138,3 @@ test(

test(
[CaretOp.op_beginSession(new Caret('session1')), endOp],
[CaretOp.op_add(new Caret('cr-sess1', { authorId: 'xyz' })), endOp],
[setOp],

@@ -146,7 +146,7 @@ [endOp]

describe('`setField` after `beginSession`', () => {
it('should result in a modified `beginSession`', () => {
const beginOp = CaretOp.op_beginSession(new Caret('session1'));
const setOp = CaretOp.op_setField('session1', 'revNum', 123);
const resultOp = CaretOp.op_beginSession(new Caret('session1', { revNum: 123 }));
describe('`setField` after `add`', () => {
it('should result in a modified `add`', () => {
const beginOp = CaretOp.op_add(new Caret('cr-sess1', { authorId: 'xyz' }));
const setOp = CaretOp.op_setField('cr-sess1', 'revNum', 123);
const resultOp = CaretOp.op_add(new Caret('cr-sess1', { authorId: 'xyz', revNum: 123 }));

@@ -172,3 +172,3 @@ test(

test(
[beginOp, CaretOp.op_setField('session1', 'revNum', 9999)],
[beginOp, CaretOp.op_setField('cr-sess1', 'revNum', 9999)],
[setOp],

@@ -182,4 +182,4 @@ [resultOp]

it('should drop earlier sets for the same field', () => {
const setOp1 = CaretOp.op_setField('session1', 'revNum', 123);
const setOp2 = CaretOp.op_setField('session1', 'revNum', 999);
const setOp1 = CaretOp.op_setField('cr-sess1', 'revNum', 123);
const setOp2 = CaretOp.op_setField('cr-sess1', 'revNum', 999);

@@ -186,0 +186,0 @@ test(

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -8,3 +8,3 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

import { Caret, CaretChange, CaretDelta, CaretOp, CaretSnapshot } from '@bayou/doc-common';
import { Caret, CaretChange, CaretDelta, CaretId, CaretOp, CaretSnapshot } from '@bayou/doc-common';

@@ -14,33 +14,35 @@ /**

*
* @param {string} sessionId Session ID.
* @param {string} id Caret ID.
* @param {Int} index Start caret position.
* @param {Int} length Selection length.
* @param {string} color Highlight color.
* @param {string} authorId Author ID.
* @returns {Caret} Appropriately-constructed caret.
*/
function newCaret(sessionId, index, length, color) {
return new Caret(sessionId, { index, length, color });
function newCaret(id, index, length, color, authorId) {
return new Caret(id, { index, length, color, authorId });
}
/**
* Convenient `op_beginSession` constructor, which takes positional parameters
* Convenient `op_add` constructor, which takes positional parameters
* for the caret fields.
*
* @param {string} sessionId Session ID.
* @param {string} id Caret ID.
* @param {Int} index Start caret position.
* @param {Int} length Selection length.
* @param {string} color Highlight color.
* @param {string} authorId Author ID.
* @returns {Caret} Appropriately-constructed caret.
*/
function newCaretOp(sessionId, index, length, color) {
return CaretOp.op_beginSession(newCaret(sessionId, index, length, color));
function newCaretOp(id, index, length, color, authorId) {
return CaretOp.op_add(newCaret(id, index, length, color, authorId));
}
const caret1 = newCaret('session_1', 1, 0, '#111111');
const caret2 = newCaret('session_2', 2, 6, '#222222');
const caret3 = newCaret('session_3', 3, 99, '#333333');
const caret1 = newCaret('cr-11111', 1, 0, '#111111', 'aa');
const caret2 = newCaret('cr-22222', 2, 6, '#222222', 'bb');
const caret3 = newCaret('cr-33333', 3, 99, '#333333', 'cc');
const op1 = CaretOp.op_beginSession(caret1);
const op2 = CaretOp.op_beginSession(caret2);
const op3 = CaretOp.op_beginSession(caret3);
const op1 = CaretOp.op_add(caret1);
const op2 = CaretOp.op_add(caret2);
const op3 = CaretOp.op_add(caret3);

@@ -105,4 +107,4 @@ describe('@bayou/doc-common/CaretSnapshot', () => {

test([op1, 'florp', op2]);
test([CaretOp.op_endSession('x')]); // Session ends aren't allowed.
test([CaretOp.op_setField('x', 'revNum', 1)]); // Individual field sets aren't allowed.
test([CaretOp.op_delete('cr-xxxxx')]); // `delete`s aren't allowed.
test([CaretOp.op_setField('cr-xxxxx', 'revNum', 1)]); // Individual field sets aren't allowed.
test([op1, op1]); // Duplicates aren't allowed.

@@ -117,11 +119,11 @@ });

// Session ends aren't allowed.
test([CaretOp.op_endSession('x')]);
test([op1, CaretOp.op_endSession('x')]);
test([op1, CaretOp.op_endSession(caret1.sessionId)]);
// `delete` ops aren't allowed.
test([CaretOp.op_delete('cr-xxxxx')]);
test([op1, CaretOp.op_delete('cr-xxxxx')]);
test([op1, CaretOp.op_delete(caret1.id)]);
// Individual field sets aren't allowed.
test([CaretOp.op_setField('x', 'revNum', 1)]);
test([op1, CaretOp.op_setField('x', 'revNum', 1)]);
test([op1, CaretOp.op_setField(caret1.sessionId, 'revNum', 1)]);
test([CaretOp.op_setField('cr-xxxxx', 'revNum', 1)]);
test([op1, CaretOp.op_setField('cr-xxxxx', 'revNum', 1)]);
test([op1, CaretOp.op_setField(caret1.id, 'revNum', 1)]);

@@ -188,3 +190,3 @@ // Duplicates aren't allowed.

const expected = new CaretSnapshot(1, [op1]);
const change = new CaretChange(1, [CaretOp.op_beginSession(caret1)]);
const change = new CaretChange(1, [CaretOp.op_add(caret1)]);
const result = snap.compose(change);

@@ -197,3 +199,3 @@

const snap = new CaretSnapshot(1, [op1]);
const change = new CaretChange(1, [CaretOp.op_setField('florp', 'index', 1)]);
const change = new CaretChange(1, [CaretOp.op_setField('cr-florp', 'index', 1)]);

@@ -204,7 +206,7 @@ assert.throws(() => { snap.compose(change); });

it('should update a pre-existing caret given an appropriate op', () => {
const c1 = newCaretOp('foo', 1, 2, '#333333');
const c2 = newCaretOp('foo', 3, 2, '#333333');
const c1 = newCaretOp('cr-foooo', 1, 2, '#333333', 'dd');
const c2 = newCaretOp('cr-foooo', 3, 2, '#333333', 'dd');
const snap = new CaretSnapshot(1, [op1, c1]);
const expected = new CaretSnapshot(1, [op1, c2]);
const op = CaretOp.op_setField('foo', 'index', 3);
const op = CaretOp.op_setField('cr-foooo', 'index', 3);
const result = snap.compose(new CaretChange(1, [op]));

@@ -218,3 +220,3 @@

const expected = new CaretSnapshot(1, [op2]);
const result = snap.compose(new CaretChange(1, [CaretOp.op_endSession(caret1.sessionId)]));
const result = snap.compose(new CaretChange(1, [CaretOp.op_delete(caret1.id)]));

@@ -269,5 +271,5 @@ assert.isTrue(result.equals(expected));

it('should result in a caret update if that in fact happens', () => {
const c1 = newCaretOp('florp', 1, 3, '#444444');
const c2 = newCaretOp('florp', 2, 4, '#555555');
const c3 = newCaretOp('florp', 3, 5, '#666666');
const c1 = newCaretOp('cr-florp', 1, 3, '#444444', 'ff');
const c2 = newCaretOp('cr-florp', 2, 4, '#555555', 'gg');
const c3 = newCaretOp('cr-florp', 3, 5, '#666666', 'hh');
const snap1 = new CaretSnapshot(1, [c1]);

@@ -301,9 +303,9 @@ const snap2 = new CaretSnapshot(1, [c2]);

const caret = op.props.caret;
expectMap.set(caret.sessionId, caret);
expectMap.set(caret.id, caret);
}
const snap = new CaretSnapshot(1, ops);
for (const [sessionId, caret] of snap.entries()) {
assert.strictEqual(caret, expectMap.get(sessionId));
expectMap.delete(sessionId);
for (const [caretId, caret] of snap.entries()) {
assert.strictEqual(caret, expectMap.get(caretId));
expectMap.delete(caretId);
}

@@ -358,6 +360,6 @@

it('should return `true` when equal carets are not also `===`', () => {
const c1a = newCaretOp('florp', 2, 3, '#444444');
const c1b = newCaretOp('florp', 2, 3, '#444444');
const c2a = newCaretOp('like', 3, 0, '#dbdbdb');
const c2b = newCaretOp('like', 3, 0, '#dbdbdb');
const c1a = newCaretOp('cr-florp', 2, 3, '#444444', 'ab');
const c1b = newCaretOp('cr-florp', 2, 3, '#444444', 'ab');
const c2a = newCaretOp('cr-like0', 3, 0, '#dbdbdb', 'cd');
const c2b = newCaretOp('cr-like0', 3, 0, '#dbdbdb', 'cd');

@@ -438,17 +440,17 @@ const snap1 = new CaretSnapshot(1, [c1a, c2a]);

describe('get()', () => {
it('should return the caret associated with an existing session', () => {
it('should return the caret associated with an existing ID', () => {
const snap = new CaretSnapshot(999, [op1, op2, op3]);
assert.strictEqual(snap.get(caret1.sessionId), caret1);
assert.strictEqual(snap.get(caret2.sessionId), caret2);
assert.strictEqual(snap.get(caret3.sessionId), caret3);
assert.strictEqual(snap.get(caret1.id), caret1);
assert.strictEqual(snap.get(caret2.id), caret2);
assert.strictEqual(snap.get(caret3.id), caret3);
});
it('should throw an error when given a session ID that is not in the snapshot', () => {
it('should throw an error when given an ID that is not in the snapshot', () => {
const snap = new CaretSnapshot(999, [op1, op3]);
assert.throws(() => { snap.get(caret2.sessionId); });
assert.throws(() => { snap.get(caret2.id); });
});
it('should throw an error if given an invalid session ID', () => {
it('should throw an error if given an invalid ID', () => {
const snap = new CaretSnapshot(999, []);

@@ -463,17 +465,17 @@

describe('getOrNull()', () => {
it('should return the caret associated with an existing session', () => {
it('should return the caret associated with an existing ID', () => {
const snap = new CaretSnapshot(999, [op1, op2, op3]);
assert.strictEqual(snap.getOrNull(caret1.sessionId), caret1);
assert.strictEqual(snap.getOrNull(caret2.sessionId), caret2);
assert.strictEqual(snap.getOrNull(caret3.sessionId), caret3);
assert.strictEqual(snap.getOrNull(caret1.id), caret1);
assert.strictEqual(snap.getOrNull(caret2.id), caret2);
assert.strictEqual(snap.getOrNull(caret3.id), caret3);
});
it('should return `null` when given a session ID that is not in the snapshot', () => {
it('should return `null` when given an ID that is not in the snapshot', () => {
const snap = new CaretSnapshot(999, [op1, op3]);
assert.isNull(snap.getOrNull(caret2.sessionId));
assert.isNull(snap.getOrNull(caret2.id));
});
it('should throw an error if given an invalid session ID', () => {
it('should throw an error if given an invalid ID', () => {
const snap = new CaretSnapshot(999, []);

@@ -488,17 +490,17 @@

describe('has()', () => {
it('should return `true` when given a session ID for an existing session', () => {
it('should return `true` when given an ID for an existing caret', () => {
const snap = new CaretSnapshot(999, [op1, op2, op3]);
assert.isTrue(snap.has(caret1.sessionId));
assert.isTrue(snap.has(caret2.sessionId));
assert.isTrue(snap.has(caret3.sessionId));
assert.isTrue(snap.has(caret1.id));
assert.isTrue(snap.has(caret2.id));
assert.isTrue(snap.has(caret3.id));
});
it('should return `false` when given a session ID that is not in the snapshot', () => {
it('should return `false` when given an ID that is not in the snapshot', () => {
const snap = new CaretSnapshot(999, [op1, op3]);
assert.isFalse(snap.has(caret2.sessionId));
assert.isFalse(snap.has(caret2.id));
});
it('should throw an error if given an invalid session ID', () => {
it('should throw an error if given an invalid ID', () => {
const snap = new CaretSnapshot(999, []);

@@ -512,2 +514,35 @@

describe('randomUnusedId()', () => {
it('should return a string for which `CaretId.isInstance()` is `true`', () => {
const snap = new CaretSnapshot(999, [op1, op2, op3]);
const id = snap.randomUnusedId();
assert.isTrue(CaretId.isInstance(id));
});
it('should return an ID that is not used', () => {
// What we're doing here is mocking out `CaretSnapshot.has()` to lie about
// the IDs in the instance N times, so that we can infer that the method
// under test actually retries.
const snap = new CaretSnapshot(999, []);
let retries = 10;
let gotId = null;
const mocked = Object.create(snap);
mocked.has = (id) => {
if (retries === 0) {
gotId = id;
return false;
} else {
retries--;
return true;
}
};
const result = mocked.randomUnusedId();
assert.strictEqual(result, gotId);
assert.strictEqual(retries, 0);
});
});
describe('withCaret()', () => {

@@ -532,3 +567,3 @@ it('should return `this` if the exact caret is already in the snapshot', () => {

const modCaret = new Caret(caret1, { index: 321 });
const modOp = CaretOp.op_beginSession(modCaret);
const modOp = CaretOp.op_add(modCaret);
const snap = new CaretSnapshot(1, [op1, op2]);

@@ -588,39 +623,57 @@ const expected = new CaretSnapshot(1, [modOp, op2]);

describe('withoutCaret()', () => {
it('should return `this` if there is no matching session', () => {
const snap = new CaretSnapshot(1, [op1]);
describe('valid `Caret` argument', () => {
it('should return `this` if there is no matching caret', () => {
const snap = new CaretSnapshot(1, [op1]);
assert.strictEqual(snap.withoutCaret(caret2), snap);
assert.strictEqual(snap.withoutCaret(caret3), snap);
});
assert.strictEqual(snap.withoutCaret(caret2), snap);
assert.strictEqual(snap.withoutCaret(caret3), snap);
});
it('should return an appropriately-constructed instance if there is a matching session', () => {
const snap = new CaretSnapshot(1, [op1, op2]);
const expected = new CaretSnapshot(1, [op2]);
it('should return an appropriately-constructed instance if there is a matching caret', () => {
const snap = new CaretSnapshot(1, [op1, op2]);
const expected = new CaretSnapshot(1, [op2]);
assert.isTrue(snap.withoutCaret(caret1).equals(expected));
});
assert.isTrue(snap.withoutCaret(caret1).equals(expected));
});
it('should only pay attention to the session ID of the given caret', () => {
const snap = new CaretSnapshot(1, [op1, op2]);
const expected = new CaretSnapshot(1, [op2]);
const modCaret = new Caret(caret1, { revNum: 999999, index: 99 });
it('should only pay attention to the ID of the given caret', () => {
const snap = new CaretSnapshot(1, [op1, op2]);
const expected = new CaretSnapshot(1, [op2]);
const modCaret = new Caret(caret1, { revNum: 999999, index: 99 });
assert.isTrue(snap.withoutCaret(modCaret).equals(expected));
assert.isTrue(snap.withoutCaret(modCaret).equals(expected));
});
});
});
describe('withoutSession()', () => {
it('should return `this` if there is no matching session', () => {
const snap = new CaretSnapshot(1, [op1]);
describe('valid ID argument', () => {
it('should return `this` if there is no matching caret', () => {
const snap = new CaretSnapshot(1, [op1]);
assert.strictEqual(snap.withoutSession('blort_is_not_a_session'), snap);
assert.strictEqual(snap.withoutCaret('cr-not00'), snap);
});
it('should return an appropriately-constructed instance if there is a matching caret', () => {
const snap = new CaretSnapshot(1, [op1, op2]);
const expected = new CaretSnapshot(1, [op2]);
assert.isTrue(snap.withoutCaret(caret1.id).equals(expected));
});
});
it('should return an appropriately-constructed instance if there is a matching session', () => {
const snap = new CaretSnapshot(1, [op1, op2]);
const expected = new CaretSnapshot(1, [op2]);
describe('invalid argument', () => {
it('should reject invalid ID strings', () => {
const snap = new CaretSnapshot(1, [op1]);
assert.throws(() => snap.withoutCaret(''));
assert.throws(() => snap.withoutCaret('ZORCH_SPLAT'));
});
assert.isTrue(snap.withoutSession(caret1.sessionId).equals(expected));
it('should reject arguments that are neither strings nor `Caret`s', () => {
const snap = new CaretSnapshot(1, [op1]);
assert.throws(() => snap.withoutCaret(undefined));
assert.throws(() => snap.withoutCaret(null));
assert.throws(() => snap.withoutCaret(123));
assert.throws(() => snap.withoutCaret([]));
});
});
});
});

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -3,0 +3,0 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -3,0 +3,0 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -3,0 +3,0 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -22,2 +22,3 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

import PropertySnapshot from './PropertySnapshot';
import SessionInfo from './SessionInfo';

@@ -40,3 +41,3 @@ /**

static get SCHEMA_VERSION() {
return '2018-001';
return '2018-004';
}

@@ -66,3 +67,4 @@

registry.registerClass(PropertySnapshot);
registry.registerClass(SessionInfo);
}
}

@@ -1,2 +0,2 @@

// Copyright 2016-2018 the Bayou Authors (Dan Bornstein et alia).
// Copyright 2016-2019 the Bayou Authors (Dan Bornstein et alia).
// Licensed AS IS and WITHOUT WARRANTY under the Apache License,

@@ -3,0 +3,0 @@ // Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc