prosemirror-collab
Advanced tools
Comparing version 1.2.2 to 1.3.0-beta.1
@@ -47,3 +47,3 @@ ## 1.2.2 (2019-11-20) | ||
[`sendableSteps`](http://prosemirror.net/docs/ref/version/0.18.0.html#collab.sendableSteps) now also returns information about the original transactions that produced the steps. | ||
[`sendableSteps`](https://prosemirror.net/docs/ref/version/0.18.0.html#collab.sendableSteps) now also returns information about the original transactions that produced the steps. | ||
@@ -56,4 +56,4 @@ ## 0.11.0 (2016-09-21) | ||
Interface [adjusted](http://prosemirror.net/docs/ref/version/0.11.0.html#collab) to work with the new | ||
[plugin](http://prosemirror.net/docs/ref/version/0.11.0.html#state.Plugin) system. | ||
Interface [adjusted](https://prosemirror.net/docs/ref/version/0.11.0.html#collab) to work with the new | ||
[plugin](https://prosemirror.net/docs/ref/version/0.11.0.html#state.Plugin) system. | ||
@@ -63,6 +63,6 @@ ### New features | ||
When receiving changes, the module now | ||
[generates](http://prosemirror.net/docs/ref/version/0.11.0.html#collab.receiveAction) a regular | ||
[transform action](http://prosemirror.net/docs/ref/version/0.11.0.html#state.TransformAction) instead of hard-setting | ||
[generates](https://prosemirror.net/docs/ref/version/0.11.0.html#collab.receiveAction) a regular | ||
[transform action](https://prosemirror.net/docs/ref/version/0.11.0.html#state.TransformAction) instead of hard-setting | ||
the editor's document. This solves problematic corner cases for code | ||
keeping track of the document by listening to transform actions. | ||
@@ -1,31 +0,30 @@ | ||
'use strict'; | ||
import { PluginKey, Plugin, TextSelection } from 'prosemirror-state'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
var prosemirrorState = require('prosemirror-state'); | ||
var Rebaseable = function Rebaseable(step, inverted, origin) { | ||
this.step = step; | ||
this.inverted = inverted; | ||
this.origin = origin; | ||
}; | ||
// : ([Rebaseable], [Step], Transform) → [Rebaseable] | ||
// Undo a given set of steps, apply a set of other steps, and then | ||
// redo them. | ||
class Rebaseable { | ||
constructor(step, inverted, origin) { | ||
this.step = step; | ||
this.inverted = inverted; | ||
this.origin = origin; | ||
} | ||
} | ||
/** | ||
Undo a given set of steps, apply a set of other steps, and then | ||
redo them @internal | ||
*/ | ||
function rebaseSteps(steps, over, transform) { | ||
for (var i = steps.length - 1; i >= 0; i--) { transform.step(steps[i].inverted); } | ||
for (var i$1 = 0; i$1 < over.length; i$1++) { transform.step(over[i$1]); } | ||
var result = []; | ||
for (var i$2 = 0, mapFrom = steps.length; i$2 < steps.length; i$2++) { | ||
var mapped = steps[i$2].step.map(transform.mapping.slice(mapFrom)); | ||
mapFrom--; | ||
if (mapped && !transform.maybeStep(mapped).failed) { | ||
transform.mapping.setMirror(mapFrom, transform.steps.length - 1); | ||
result.push(new Rebaseable(mapped, mapped.invert(transform.docs[transform.docs.length - 1]), steps[i$2].origin)); | ||
for (let i = steps.length - 1; i >= 0; i--) | ||
transform.step(steps[i].inverted); | ||
for (let i = 0; i < over.length; i++) | ||
transform.step(over[i]); | ||
let result = []; | ||
for (let i = 0, mapFrom = steps.length; i < steps.length; i++) { | ||
let mapped = steps[i].step.map(transform.mapping.slice(mapFrom)); | ||
mapFrom--; | ||
if (mapped && !transform.maybeStep(mapped).failed) { | ||
transform.mapping.setMirror(mapFrom, transform.steps.length - 1); | ||
result.push(new Rebaseable(mapped, mapped.invert(transform.docs[transform.docs.length - 1]), steps[i].origin)); | ||
} | ||
} | ||
} | ||
return result | ||
return result; | ||
} | ||
// This state field accumulates changes that have to be sent to the | ||
@@ -36,154 +35,124 @@ // central authority in the collaborating group and makes it possible | ||
// in the resulting editor state. | ||
var CollabState = function CollabState(version, unconfirmed) { | ||
// : number | ||
// The version number of the last update received from the central | ||
// authority. Starts at 0 or the value of the `version` property | ||
// in the option object, for the editor's value when the option | ||
// was enabled. | ||
this.version = version; | ||
// : [Rebaseable] | ||
// The local steps that havent been successfully sent to the | ||
// server yet. | ||
this.unconfirmed = unconfirmed; | ||
}; | ||
class CollabState { | ||
constructor( | ||
// The version number of the last update received from the central | ||
// authority. Starts at 0 or the value of the `version` property | ||
// in the option object, for the editor's value when the option | ||
// was enabled. | ||
version, | ||
// The local steps that havent been successfully sent to the | ||
// server yet. | ||
unconfirmed) { | ||
this.version = version; | ||
this.unconfirmed = unconfirmed; | ||
} | ||
} | ||
function unconfirmedFrom(transform) { | ||
var result = []; | ||
for (var i = 0; i < transform.steps.length; i++) | ||
{ result.push(new Rebaseable(transform.steps[i], | ||
transform.steps[i].invert(transform.docs[i]), | ||
transform)); } | ||
return result | ||
let result = []; | ||
for (let i = 0; i < transform.steps.length; i++) | ||
result.push(new Rebaseable(transform.steps[i], transform.steps[i].invert(transform.docs[i]), transform)); | ||
return result; | ||
} | ||
var collabKey = new prosemirrorState.PluginKey("collab"); | ||
// :: (?Object) → Plugin | ||
// | ||
// Creates a plugin that enables the collaborative editing framework | ||
// for the editor. | ||
// | ||
// config::- An optional set of options | ||
// | ||
// version:: ?number | ||
// The starting version number of the collaborative editing. | ||
// Defaults to 0. | ||
// | ||
// clientID:: ?union<number, string> | ||
// This client's ID, used to distinguish its changes from those of | ||
// other clients. Defaults to a random 32-bit number. | ||
function collab(config) { | ||
if ( config === void 0 ) config = {}; | ||
config = {version: config.version || 0, | ||
clientID: config.clientID == null ? Math.floor(Math.random() * 0xFFFFFFFF) : config.clientID}; | ||
return new prosemirrorState.Plugin({ | ||
key: collabKey, | ||
state: { | ||
init: function () { return new CollabState(config.version, []); }, | ||
apply: function apply(tr, collab) { | ||
var newState = tr.getMeta(collabKey); | ||
if (newState) | ||
{ return newState } | ||
if (tr.docChanged) | ||
{ return new CollabState(collab.version, collab.unconfirmed.concat(unconfirmedFrom(tr))) } | ||
return collab | ||
} | ||
}, | ||
config: config, | ||
// This is used to notify the history plugin to not merge steps, | ||
// so that the history can be rebased. | ||
historyPreserveItems: true | ||
}) | ||
const collabKey = new PluginKey("collab"); | ||
/** | ||
Creates a plugin that enables the collaborative editing framework | ||
for the editor. | ||
*/ | ||
function collab(config = {}) { | ||
let conf = { | ||
version: config.version || 0, | ||
clientID: config.clientID == null ? Math.floor(Math.random() * 0xFFFFFFFF) : config.clientID | ||
}; | ||
return new Plugin({ | ||
key: collabKey, | ||
state: { | ||
init: () => new CollabState(conf.version, []), | ||
apply(tr, collab) { | ||
let newState = tr.getMeta(collabKey); | ||
if (newState) | ||
return newState; | ||
if (tr.docChanged) | ||
return new CollabState(collab.version, collab.unconfirmed.concat(unconfirmedFrom(tr))); | ||
return collab; | ||
} | ||
}, | ||
// @ts-ignore | ||
config: conf, | ||
// This is used to notify the history plugin to not merge steps, | ||
// so that the history can be rebased. | ||
historyPreserveItems: true | ||
}); | ||
} | ||
// :: (state: EditorState, steps: [Step], clientIDs: [union<number, string>], options: ?Object) → Transaction | ||
// Create a transaction that represents a set of new steps received from | ||
// the authority. Applying this transaction moves the state forward to | ||
// adjust to the authority's view of the document. | ||
// | ||
// options::- Additional options. | ||
// | ||
// mapSelectionBackward:: ?boolean | ||
// When enabled (the default is `false`), if the current selection | ||
// is a [text selection](#state.TextSelection), its sides are | ||
// mapped with a negative bias for this transaction, so that | ||
// content inserted at the cursor ends up after the cursor. Users | ||
// usually prefer this, but it isn't done by default for reasons | ||
// of backwards compatibility. | ||
function receiveTransaction(state, steps, clientIDs, options) { | ||
// Pushes a set of steps (received from the central authority) into | ||
// the editor state (which should have the collab plugin enabled). | ||
// Will recognize its own changes, and confirm unconfirmed steps as | ||
// appropriate. Remaining unconfirmed steps will be rebased over | ||
// remote steps. | ||
var collabState = collabKey.getState(state); | ||
var version = collabState.version + steps.length; | ||
var ourID = collabKey.get(state).spec.config.clientID; | ||
// Find out which prefix of the steps originated with us | ||
var ours = 0; | ||
while (ours < clientIDs.length && clientIDs[ours] == ourID) { ++ours; } | ||
var unconfirmed = collabState.unconfirmed.slice(ours); | ||
steps = ours ? steps.slice(ours) : steps; | ||
// If all steps originated with us, we're done. | ||
if (!steps.length) | ||
{ return state.tr.setMeta(collabKey, new CollabState(version, unconfirmed)) } | ||
var nUnconfirmed = unconfirmed.length; | ||
var tr = state.tr; | ||
if (nUnconfirmed) { | ||
unconfirmed = rebaseSteps(unconfirmed, steps, tr); | ||
} else { | ||
for (var i = 0; i < steps.length; i++) { tr.step(steps[i]); } | ||
unconfirmed = []; | ||
} | ||
var newCollabState = new CollabState(version, unconfirmed); | ||
if (options && options.mapSelectionBackward && state.selection instanceof prosemirrorState.TextSelection) { | ||
tr.setSelection(prosemirrorState.TextSelection.between(tr.doc.resolve(tr.mapping.map(state.selection.anchor, -1)), | ||
tr.doc.resolve(tr.mapping.map(state.selection.head, -1)), -1)); | ||
tr.updated &= ~1; | ||
} | ||
return tr.setMeta("rebased", nUnconfirmed).setMeta("addToHistory", false).setMeta(collabKey, newCollabState) | ||
/** | ||
Create a transaction that represents a set of new steps received from | ||
the authority. Applying this transaction moves the state forward to | ||
adjust to the authority's view of the document. | ||
*/ | ||
function receiveTransaction(state, steps, clientIDs, options = {}) { | ||
// Pushes a set of steps (received from the central authority) into | ||
// the editor state (which should have the collab plugin enabled). | ||
// Will recognize its own changes, and confirm unconfirmed steps as | ||
// appropriate. Remaining unconfirmed steps will be rebased over | ||
// remote steps. | ||
let collabState = collabKey.getState(state); | ||
let version = collabState.version + steps.length; | ||
let ourID = collabKey.get(state).spec.config.clientID; | ||
// Find out which prefix of the steps originated with us | ||
let ours = 0; | ||
while (ours < clientIDs.length && clientIDs[ours] == ourID) | ||
++ours; | ||
let unconfirmed = collabState.unconfirmed.slice(ours); | ||
steps = ours ? steps.slice(ours) : steps; | ||
// If all steps originated with us, we're done. | ||
if (!steps.length) | ||
return state.tr.setMeta(collabKey, new CollabState(version, unconfirmed)); | ||
let nUnconfirmed = unconfirmed.length; | ||
let tr = state.tr; | ||
if (nUnconfirmed) { | ||
unconfirmed = rebaseSteps(unconfirmed, steps, tr); | ||
} | ||
else { | ||
for (let i = 0; i < steps.length; i++) | ||
tr.step(steps[i]); | ||
unconfirmed = []; | ||
} | ||
let newCollabState = new CollabState(version, unconfirmed); | ||
if (options && options.mapSelectionBackward && state.selection instanceof TextSelection) { | ||
tr.setSelection(TextSelection.between(tr.doc.resolve(tr.mapping.map(state.selection.anchor, -1)), tr.doc.resolve(tr.mapping.map(state.selection.head, -1)), -1)); | ||
tr.updated &= ~1; | ||
} | ||
return tr.setMeta("rebased", nUnconfirmed).setMeta("addToHistory", false).setMeta(collabKey, newCollabState); | ||
} | ||
/** | ||
Provides data describing the editor's unconfirmed steps, which need | ||
to be sent to the central authority. Returns null when there is | ||
nothing to send. | ||
// :: (state: EditorState) → ?{version: number, steps: [Step], clientID: union<number, string>, origins: [Transaction]} | ||
// Provides data describing the editor's unconfirmed steps, which need | ||
// to be sent to the central authority. Returns null when there is | ||
// nothing to send. | ||
// | ||
// `origins` holds the _original_ transactions that produced each | ||
// steps. This can be useful for looking up time stamps and other | ||
// metadata for the steps, but note that the steps may have been | ||
// rebased, whereas the origin transactions are still the old, | ||
// unchanged objects. | ||
`origins` holds the _original_ transactions that produced each | ||
steps. This can be useful for looking up time stamps and other | ||
metadata for the steps, but note that the steps may have been | ||
rebased, whereas the origin transactions are still the old, | ||
unchanged objects. | ||
*/ | ||
function sendableSteps(state) { | ||
var collabState = collabKey.getState(state); | ||
if (collabState.unconfirmed.length == 0) { return null } | ||
return { | ||
version: collabState.version, | ||
steps: collabState.unconfirmed.map(function (s) { return s.step; }), | ||
clientID: collabKey.get(state).spec.config.clientID, | ||
get origins() { return this._origins || (this._origins = collabState.unconfirmed.map(function (s) { return s.origin; })) } | ||
} | ||
let collabState = collabKey.getState(state); | ||
if (collabState.unconfirmed.length == 0) | ||
return null; | ||
return { | ||
version: collabState.version, | ||
steps: collabState.unconfirmed.map(s => s.step), | ||
clientID: collabKey.get(state).spec.config.clientID, | ||
get origins() { | ||
return this._origins || (this._origins = collabState.unconfirmed.map(s => s.origin)); | ||
} | ||
}; | ||
} | ||
// :: (EditorState) → number | ||
// Get the version up to which the collab plugin has synced with the | ||
// central authority. | ||
/** | ||
Get the version up to which the collab plugin has synced with the | ||
central authority. | ||
*/ | ||
function getVersion(state) { | ||
return collabKey.getState(state).version | ||
return collabKey.getState(state).version; | ||
} | ||
exports.collab = collab; | ||
exports.getVersion = getVersion; | ||
exports.rebaseSteps = rebaseSteps; | ||
exports.receiveTransaction = receiveTransaction; | ||
exports.sendableSteps = sendableSteps; | ||
//# sourceMappingURL=index.js.map | ||
export { collab, getVersion, rebaseSteps, receiveTransaction, sendableSteps }; |
{ | ||
"name": "prosemirror-collab", | ||
"version": "1.2.2", | ||
"version": "1.3.0-beta.1", | ||
"description": "Collaborative editing for ProseMirror", | ||
"main": "dist/index.js", | ||
"module": "dist/index.es.js", | ||
"type": "module", | ||
"main": "dist/index.cjs", | ||
"module": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"exports": { | ||
"import": "./dist/index.js", | ||
"require": "./dist/index.cjs" | ||
}, | ||
"sideEffects": false, | ||
"license": "MIT", | ||
@@ -23,17 +30,12 @@ "maintainers": [ | ||
"devDependencies": { | ||
"ist": "^1.0.0", | ||
"mocha": "^3.0.2", | ||
"@prosemirror/buildhelper": "^0.1.5", | ||
"prosemirror-history": "^1.0.0", | ||
"prosemirror-model": "^1.0.0", | ||
"prosemirror-test-builder": "^1.0.0", | ||
"prosemirror-transform": "^1.0.0", | ||
"rollup": "^1.26.3", | ||
"@rollup/plugin-buble": "^0.20.0" | ||
"prosemirror-transform": "^1.0.0" | ||
}, | ||
"scripts": { | ||
"test": "mocha test/test-*.js", | ||
"build": "rollup -c", | ||
"watch": "rollup -c -w", | ||
"prepare": "npm run build" | ||
"test": "pm-runtests", | ||
"prepare": "pm-buildhelper src/collab.ts" | ||
} | ||
} |
# prosemirror-collab | ||
[ [**WEBSITE**](http://prosemirror.net) | [**ISSUES**](https://github.com/prosemirror/prosemirror/issues) | [**FORUM**](https://discuss.prosemirror.net) | [**GITTER**](https://gitter.im/ProseMirror/prosemirror) | [**CHANGELOG**](https://github.com/ProseMirror/prosemirror-collab/blob/master/CHANGELOG.md) ] | ||
[ [**WEBSITE**](https://prosemirror.net) | [**ISSUES**](https://github.com/prosemirror/prosemirror/issues) | [**FORUM**](https://discuss.prosemirror.net) | [**GITTER**](https://gitter.im/ProseMirror/prosemirror) | [**CHANGELOG**](https://github.com/ProseMirror/prosemirror-collab/blob/master/CHANGELOG.md) ] | ||
This is a [core module](http://prosemirror.net/docs/ref/#collab) of [ProseMirror](http://prosemirror.net). | ||
This is a [core module](https://prosemirror.net/docs/ref/#collab) of [ProseMirror](https://prosemirror.net). | ||
ProseMirror is a well-behaved rich semantic content editor based on | ||
@@ -10,9 +10,9 @@ contentEditable, with support for collaborative editing and custom | ||
This [module](http://prosemirror.net/docs/ref/#collab) implements a | ||
This [module](https://prosemirror.net/docs/ref/#collab) implements a | ||
plugin that helps track and merge changes for | ||
[collaborative editing](http://prosemirror\.net/docs/guide/#collab). | ||
[collaborative editing](https://prosemirror.net/docs/guide/#collab). | ||
The [project page](http://prosemirror.net) has more information, a | ||
number of [examples](http://prosemirror.net/examples/) and the | ||
[documentation](http://prosemirror.net/docs/). | ||
The [project page](https://prosemirror.net) has more information, a | ||
number of [examples](https://prosemirror.net/examples/) and the | ||
[documentation](https://prosemirror.net/docs/). | ||
@@ -19,0 +19,0 @@ This code is released under an |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
62680
5
14
661
Yes
2
1