Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9eac8d9398 | ||
|
|
0b14e90585 | ||
|
|
4726c71dd0 | ||
|
|
59d859b38b | ||
|
|
81c8504462 | ||
|
|
c926ce09f5 | ||
|
|
653a436b88 | ||
|
|
5ab60028ce | ||
|
|
428d825f41 | ||
|
|
b9f9c762eb | ||
|
|
80ab682b0a | ||
|
|
3f60690880 | ||
|
|
e2f93af86e | ||
|
|
76ebd3043d | ||
|
|
b958b72f1d | ||
|
|
89a920df68 |
19
Examples/Drawing/index.html
Normal file
19
Examples/Drawing/index.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<style>
|
||||||
|
path {
|
||||||
|
fill: none;
|
||||||
|
stroke: blue;
|
||||||
|
stroke-width: 1px;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
stroke-linecap: round;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<button type="button" id="clearDrawingCanvas">Clear Drawing</button>
|
||||||
|
<svg id="drawingCanvas" viewbox="0 0 100 100" width="100%"></svg>
|
||||||
|
<script src="../bower_components/yjs/y.js"></script>
|
||||||
|
<script src="../bower_components/d3/d3.js"></script>
|
||||||
|
<script src="./index.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
84
Examples/Drawing/index.js
Normal file
84
Examples/Drawing/index.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/* globals Y, d3 */
|
||||||
|
'strict mode'
|
||||||
|
|
||||||
|
Y({
|
||||||
|
db: {
|
||||||
|
name: 'memory'
|
||||||
|
},
|
||||||
|
connector: {
|
||||||
|
name: 'websockets-client',
|
||||||
|
room: 'drawing-example'
|
||||||
|
// url: 'localhost:1234'
|
||||||
|
},
|
||||||
|
sourceDir: '/bower_components',
|
||||||
|
share: {
|
||||||
|
drawing: 'Array'
|
||||||
|
}
|
||||||
|
}).then(function (y) {
|
||||||
|
window.yDrawing = y
|
||||||
|
var drawing = y.share.drawing
|
||||||
|
var renderPath = d3.svg.line()
|
||||||
|
.x(function (d) { return d[0] })
|
||||||
|
.y(function (d) { return d[1] })
|
||||||
|
.interpolate('basis')
|
||||||
|
|
||||||
|
var svg = d3.select('#drawingCanvas')
|
||||||
|
.call(d3.behavior.drag()
|
||||||
|
.on('dragstart', dragstart)
|
||||||
|
.on('drag', drag)
|
||||||
|
.on('dragend', dragend))
|
||||||
|
|
||||||
|
// create line from a shared array object and update the line when the array changes
|
||||||
|
function drawLine (yarray) {
|
||||||
|
var line = svg.append('path').datum(yarray.toArray())
|
||||||
|
line.attr('d', renderPath)
|
||||||
|
yarray.observe(function (event) {
|
||||||
|
// we only implement insert events that are appended to the end of the array
|
||||||
|
event.values.forEach(function (value) {
|
||||||
|
line.datum().push(value)
|
||||||
|
})
|
||||||
|
line.attr('d', renderPath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// call drawLine every time an array is appended
|
||||||
|
y.share.drawing.observe(function (event) {
|
||||||
|
if (event.type === 'insert') {
|
||||||
|
event.values.forEach(drawLine)
|
||||||
|
} else {
|
||||||
|
// just remove all elements (thats what we do anyway)
|
||||||
|
svg.selectAll('path').remove()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// draw all existing content
|
||||||
|
for (var i = 0; i < drawing.length; i++) {
|
||||||
|
drawLine(drawing.get(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear canvas on request
|
||||||
|
document.querySelector('#clearDrawingCanvas').onclick = function () {
|
||||||
|
drawing.delete(0, drawing.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sharedLine = null
|
||||||
|
function dragstart () {
|
||||||
|
drawing.insert(drawing.length, [Y.Array])
|
||||||
|
sharedLine = drawing.get(drawing.length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// After one dragged event is recognized, we ignore them for 33ms.
|
||||||
|
var ignoreDrag = null
|
||||||
|
function drag () {
|
||||||
|
if (sharedLine != null && ignoreDrag == null) {
|
||||||
|
ignoreDrag = window.setTimeout(function () {
|
||||||
|
ignoreDrag = null
|
||||||
|
}, 33)
|
||||||
|
sharedLine.push([d3.mouse(this)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragend () {
|
||||||
|
sharedLine = null
|
||||||
|
window.clearTimeout(ignoreDrag)
|
||||||
|
ignoreDrag = null
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,144 +1,30 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="../bower_components/quill/dist/quill.snow.css" />
|
<!-- quill does not include dist files! We are using the hosted version instead -->
|
||||||
|
<!--link rel="stylesheet" href="../bower_components/quill/dist/quill.snow.css" /-->
|
||||||
|
<link href="https://cdn.quilljs.com/1.0.4/quill.snow.css" rel="stylesheet">
|
||||||
|
<link href="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css" rel="stylesheet">
|
||||||
|
<link href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/styles/monokai-sublime.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
#quill {
|
#quill-container {
|
||||||
border: 1px solid gray;
|
border: 1px solid gray;
|
||||||
box-shadow: 0px 0px 10px gray;
|
box-shadow: 0px 0px 10px gray;
|
||||||
}
|
}
|
||||||
#toolbar {
|
|
||||||
border-bottom: 1px solid gray;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="quill">
|
<div id="quill-container">
|
||||||
<!-- Create the toolbar container -->
|
<div id="quill">
|
||||||
<div id="toolbar" class="toolbar">
|
|
||||||
<span class="ql-format-group">
|
|
||||||
<select title="Font" class="ql-font">
|
|
||||||
<option value="sans-serif" selected="">Sans Serif</option>
|
|
||||||
<option value="serif">Serif</option>
|
|
||||||
<option value="monospace">Monospace</option>
|
|
||||||
</select>
|
|
||||||
<select title="Size" class="ql-size">
|
|
||||||
<option value="10px">Small</option>
|
|
||||||
<option value="13px" selected="">Normal</option>
|
|
||||||
<option value="18px">Large</option>
|
|
||||||
<option value="32px">Huge</option>
|
|
||||||
</select>
|
|
||||||
</span>
|
|
||||||
<span class="ql-format-group">
|
|
||||||
<span title="Bold" class="ql-format-button ql-bold"></span>
|
|
||||||
<span class="ql-format-separator"></span>
|
|
||||||
<span title="Italic" class="ql-format-button ql-italic"></span>
|
|
||||||
<span class="ql-format-separator"></span>
|
|
||||||
<span title="Underline" class="ql-format-button ql-underline"></span>
|
|
||||||
<span class="ql-format-separator"></span>
|
|
||||||
<span title="Strikethrough" class="ql-format-button ql-strike"></span>
|
|
||||||
</span>
|
|
||||||
<span class="ql-format-group">
|
|
||||||
<select title="Text Color" class="ql-color">
|
|
||||||
<option value="rgb(0, 0, 0)" label="rgb(0, 0, 0)" selected=""></option>
|
|
||||||
<option value="rgb(230, 0, 0)" label="rgb(230, 0, 0)"></option>
|
|
||||||
<option value="rgb(255, 153, 0)" label="rgb(255, 153, 0)"></option>
|
|
||||||
<option value="rgb(255, 255, 0)" label="rgb(255, 255, 0)"></option>
|
|
||||||
<option value="rgb(0, 138, 0)" label="rgb(0, 138, 0)"></option>
|
|
||||||
<option value="rgb(0, 102, 204)" label="rgb(0, 102, 204)"></option>
|
|
||||||
<option value="rgb(153, 51, 255)" label="rgb(153, 51, 255)"></option>
|
|
||||||
<option value="rgb(255, 255, 255)" label="rgb(255, 255, 255)"></option>
|
|
||||||
<option value="rgb(250, 204, 204)" label="rgb(250, 204, 204)"></option>
|
|
||||||
<option value="rgb(255, 235, 204)" label="rgb(255, 235, 204)"></option>
|
|
||||||
<option value="rgb(255, 255, 204)" label="rgb(255, 255, 204)"></option>
|
|
||||||
<option value="rgb(204, 232, 204)" label="rgb(204, 232, 204)"></option>
|
|
||||||
<option value="rgb(204, 224, 245)" label="rgb(204, 224, 245)"></option>
|
|
||||||
<option value="rgb(235, 214, 255)" label="rgb(235, 214, 255)"></option>
|
|
||||||
<option value="rgb(187, 187, 187)" label="rgb(187, 187, 187)"></option>
|
|
||||||
<option value="rgb(240, 102, 102)" label="rgb(240, 102, 102)"></option>
|
|
||||||
<option value="rgb(255, 194, 102)" label="rgb(255, 194, 102)"></option>
|
|
||||||
<option value="rgb(255, 255, 102)" label="rgb(255, 255, 102)"></option>
|
|
||||||
<option value="rgb(102, 185, 102)" label="rgb(102, 185, 102)"></option>
|
|
||||||
<option value="rgb(102, 163, 224)" label="rgb(102, 163, 224)"></option>
|
|
||||||
<option value="rgb(194, 133, 255)" label="rgb(194, 133, 255)"></option>
|
|
||||||
<option value="rgb(136, 136, 136)" label="rgb(136, 136, 136)"></option>
|
|
||||||
<option value="rgb(161, 0, 0)" label="rgb(161, 0, 0)"></option>
|
|
||||||
<option value="rgb(178, 107, 0)" label="rgb(178, 107, 0)"></option>
|
|
||||||
<option value="rgb(178, 178, 0)" label="rgb(178, 178, 0)"></option>
|
|
||||||
<option value="rgb(0, 97, 0)" label="rgb(0, 97, 0)"></option>
|
|
||||||
<option value="rgb(0, 71, 178)" label="rgb(0, 71, 178)"></option>
|
|
||||||
<option value="rgb(107, 36, 178)" label="rgb(107, 36, 178)"></option>
|
|
||||||
<option value="rgb(68, 68, 68)" label="rgb(68, 68, 68)"></option>
|
|
||||||
<option value="rgb(92, 0, 0)" label="rgb(92, 0, 0)"></option>
|
|
||||||
<option value="rgb(102, 61, 0)" label="rgb(102, 61, 0)"></option>
|
|
||||||
<option value="rgb(102, 102, 0)" label="rgb(102, 102, 0)"></option>
|
|
||||||
<option value="rgb(0, 55, 0)" label="rgb(0, 55, 0)"></option>
|
|
||||||
<option value="rgb(0, 41, 102)" label="rgb(0, 41, 102)"></option>
|
|
||||||
<option value="rgb(61, 20, 102)" label="rgb(61, 20, 102)"></option>
|
|
||||||
</select>
|
|
||||||
<span class="ql-format-separator"></span>
|
|
||||||
<select title="Background Color" class="ql-background">
|
|
||||||
<option value="rgb(0, 0, 0)" label="rgb(0, 0, 0)"></option>
|
|
||||||
<option value="rgb(230, 0, 0)" label="rgb(230, 0, 0)"></option>
|
|
||||||
<option value="rgb(255, 153, 0)" label="rgb(255, 153, 0)"></option>
|
|
||||||
<option value="rgb(255, 255, 0)" label="rgb(255, 255, 0)"></option>
|
|
||||||
<option value="rgb(0, 138, 0)" label="rgb(0, 138, 0)"></option>
|
|
||||||
<option value="rgb(0, 102, 204)" label="rgb(0, 102, 204)"></option>
|
|
||||||
<option value="rgb(153, 51, 255)" label="rgb(153, 51, 255)"></option>
|
|
||||||
<option value="rgb(255, 255, 255)" label="rgb(255, 255, 255)" selected=""></option>
|
|
||||||
<option value="rgb(250, 204, 204)" label="rgb(250, 204, 204)"></option>
|
|
||||||
<option value="rgb(255, 235, 204)" label="rgb(255, 235, 204)"></option>
|
|
||||||
<option value="rgb(255, 255, 204)" label="rgb(255, 255, 204)"></option>
|
|
||||||
<option value="rgb(204, 232, 204)" label="rgb(204, 232, 204)"></option>
|
|
||||||
<option value="rgb(204, 224, 245)" label="rgb(204, 224, 245)"></option>
|
|
||||||
<option value="rgb(235, 214, 255)" label="rgb(235, 214, 255)"></option>
|
|
||||||
<option value="rgb(187, 187, 187)" label="rgb(187, 187, 187)"></option>
|
|
||||||
<option value="rgb(240, 102, 102)" label="rgb(240, 102, 102)"></option>
|
|
||||||
<option value="rgb(255, 194, 102)" label="rgb(255, 194, 102)"></option>
|
|
||||||
<option value="rgb(255, 255, 102)" label="rgb(255, 255, 102)"></option>
|
|
||||||
<option value="rgb(102, 185, 102)" label="rgb(102, 185, 102)"></option>
|
|
||||||
<option value="rgb(102, 163, 224)" label="rgb(102, 163, 224)"></option>
|
|
||||||
<option value="rgb(194, 133, 255)" label="rgb(194, 133, 255)"></option>
|
|
||||||
<option value="rgb(136, 136, 136)" label="rgb(136, 136, 136)"></option>
|
|
||||||
<option value="rgb(161, 0, 0)" label="rgb(161, 0, 0)"></option>
|
|
||||||
<option value="rgb(178, 107, 0)" label="rgb(178, 107, 0)"></option>
|
|
||||||
<option value="rgb(178, 178, 0)" label="rgb(178, 178, 0)"></option>
|
|
||||||
<option value="rgb(0, 97, 0)" label="rgb(0, 97, 0)"></option>
|
|
||||||
<option value="rgb(0, 71, 178)" label="rgb(0, 71, 178)"></option>
|
|
||||||
<option value="rgb(107, 36, 178)" label="rgb(107, 36, 178)"></option>
|
|
||||||
<option value="rgb(68, 68, 68)" label="rgb(68, 68, 68)"></option>
|
|
||||||
<option value="rgb(92, 0, 0)" label="rgb(92, 0, 0)"></option>
|
|
||||||
<option value="rgb(102, 61, 0)" label="rgb(102, 61, 0)"></option>
|
|
||||||
<option value="rgb(102, 102, 0)" label="rgb(102, 102, 0)"></option>
|
|
||||||
<option value="rgb(0, 55, 0)" label="rgb(0, 55, 0)"></option>
|
|
||||||
<option value="rgb(0, 41, 102)" label="rgb(0, 41, 102)"></option>
|
|
||||||
<option value="rgb(61, 20, 102)" label="rgb(61, 20, 102)"></option>
|
|
||||||
</select>
|
|
||||||
</span>
|
|
||||||
<span class="ql-format-group">
|
|
||||||
<span title="List" class="ql-format-button ql-list"></span>
|
|
||||||
<span class="ql-format-separator"></span>
|
|
||||||
<span title="Bullet" class="ql-format-button ql-bullet"></span>
|
|
||||||
<span class="ql-format-separator"></span>
|
|
||||||
<select title="Text Alignment" class="ql-align">
|
|
||||||
<option value="left" label="Left" selected=""></option>
|
|
||||||
<option value="center" label="Center"></option>
|
|
||||||
<option value="right" label="Right"></option>
|
|
||||||
<option value="justify" label="Justify"></option>
|
|
||||||
</select>
|
|
||||||
</span>
|
|
||||||
<span class="ql-format-group">
|
|
||||||
<span title="Link" class="ql-format-button ql-link"></span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Create the editor container -->
|
|
||||||
<div id="editor">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Include the Quill library -->
|
<script src="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.js" type="text/javascript"></script>
|
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/highlight.min.js" type="text/javascript"></script>
|
||||||
|
<script src="https://cdn.quilljs.com/1.0.4/quill.js"></script>
|
||||||
|
<!-- quill does not include dist files! We are using the hosted version instead (see above)
|
||||||
<script src="../bower_components/quill/dist/quill.js"></script>
|
<script src="../bower_components/quill/dist/quill.js"></script>
|
||||||
|
-->
|
||||||
<script src="../bower_components/yjs/y.es6"></script>
|
<script src="../bower_components/yjs/y.es6"></script>
|
||||||
<script src="./index.js"></script>
|
<script src="./index.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Y({
|
|||||||
},
|
},
|
||||||
connector: {
|
connector: {
|
||||||
name: 'websockets-client',
|
name: 'websockets-client',
|
||||||
room: 'richtext-example'
|
room: 'richtext-example-quill-1.0'
|
||||||
},
|
},
|
||||||
sourceDir: '/bower_components',
|
sourceDir: '/bower_components',
|
||||||
share: {
|
share: {
|
||||||
@@ -18,13 +18,22 @@ Y({
|
|||||||
window.yQuill = y
|
window.yQuill = y
|
||||||
|
|
||||||
// create quill element
|
// create quill element
|
||||||
window.quill = new Quill('#editor', {
|
window.quill = new Quill('#quill', {
|
||||||
modules: {
|
modules: {
|
||||||
'toolbar': { container: '#toolbar' },
|
formula: true,
|
||||||
'link-tooltip': true
|
syntax: true,
|
||||||
|
toolbar: [
|
||||||
|
[{ size: ['small', false, 'large', 'huge'] }],
|
||||||
|
['bold', 'italic', 'underline'],
|
||||||
|
[{ color: [] }, { background: [] }], // Snow theme fills in values
|
||||||
|
[{ script: 'sub' }, { script: 'super' }],
|
||||||
|
['link', 'image'],
|
||||||
|
['link', 'code-block'],
|
||||||
|
[{list: 'ordered' }]
|
||||||
|
]
|
||||||
},
|
},
|
||||||
theme: 'snow'
|
theme: 'snow'
|
||||||
})
|
});
|
||||||
// bind quill to richtext type
|
// bind quill to richtext type
|
||||||
y.share.richtext.bind(window.quill)
|
y.share.richtext.bind(window.quill)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ Y({
|
|||||||
}).then(function (y) {
|
}).then(function (y) {
|
||||||
window.yXml = y
|
window.yXml = y
|
||||||
// bind xml type to a dom, and put it in body
|
// bind xml type to a dom, and put it in body
|
||||||
y.share.xml.getDom().then(function (dom) {
|
window.sharedDom = y.share.xml.getDom()
|
||||||
window.sharedDom = dom
|
document.body.appendChild(window.sharedDom)
|
||||||
document.body.appendChild(dom)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,10 +18,11 @@
|
|||||||
"y-websockets-client": "latest",
|
"y-websockets-client": "latest",
|
||||||
"y-text": "latest",
|
"y-text": "latest",
|
||||||
"y-indexeddb": "latest",
|
"y-indexeddb": "latest",
|
||||||
"y-xml": "latest",
|
"y-xml": "latest",
|
||||||
"quill": "~0.20.1",
|
"quill": "^1.0.0-rc.2",
|
||||||
"ace": "~1.2.3",
|
"ace": "~1.2.3",
|
||||||
"ace-builds": "~1.2.3",
|
"ace-builds": "~1.2.3",
|
||||||
"jquery": "~2.2.2"
|
"jquery": "~2.2.2",
|
||||||
|
"d3": "^3.5.16"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
189
README.md
189
README.md
@@ -1,98 +1,124 @@
|
|||||||
|
|
||||||
# 
|
# 
|
||||||
|
|
||||||
Yjs is a framework for optimistic concurrency control and automatic conflict resolution on shared data.
|
Yjs is a framework for p2p shared editing on structured data like (rich-)text, json, and XML.
|
||||||
The framework provides similar functionality as [ShareJs] and [OpenCoweb], but supports peer-to-peer
|
It is similar to [ShareJs] and [OpenCoweb], but easy to use.
|
||||||
communication protocols by default. Yjs was designed to handle concurrent actions on arbitrary data
|
For additional information, demos, and tutorials visit [y-js.org](http://y-js.org/).
|
||||||
like Text, Json, and XML. We also provide support for storing and manipulating your shared data offline.
|
|
||||||
For more information and demo applications visit our [homepage](http://y-js.org/).
|
|
||||||
|
|
||||||
You can create you own shared types easily.
|
### Extensions
|
||||||
Therefore, you can design the structure of your custom type,
|
Yjs only knows how to resolve conflicts on shared data. You have to choose a ..
|
||||||
and ensure data validity, while Yjs ensures data consistency (everyone will eventually end up with the same data).
|
* *Connector* - a communication protocol that propagates changes to the clients
|
||||||
We already provide abstract data types for
|
* *Database* - a database to store your changes
|
||||||
|
* one or more *Types* - that represent the shared data
|
||||||
|
|
||||||
|
Connectors, Databases, and Types are available as modules that extend Yjs. Here is a list of the modules we know of:
|
||||||
|
|
||||||
|
##### Connectors
|
||||||
|
|
||||||
|
|Name | Description |
|
||||||
|
|----------------|-----------------------------------|
|
||||||
|
|[webrtc](https://github.com/y-js/y-webrtc) | Propagate updates Browser2Browser via WebRTC|
|
||||||
|
|[websockets](https://github.com/y-js/y-websockets-client) | Set up [a central server](https://github.com/y-js/y-websockets-client), and connect to it via websockets |
|
||||||
|
|[xmpp](https://github.com/y-js/y-xmpp) | Propagate updates in a XMPP multi-user-chat room ([XEP-0045](http://xmpp.org/extensions/xep-0045.html))|
|
||||||
|
|[test](https://github.com/y-js/y-test) | A Connector for testing purposes. It is designed to simulate delays that happen in worst case scenarios|
|
||||||
|
|
||||||
|
##### Database adapters
|
||||||
|
|
||||||
|
|Name | Description |
|
||||||
|
|----------------|-----------------------------------|
|
||||||
|
|[memory](https://github.com/y-js/y-memory) | In-memory storage. |
|
||||||
|
|[indexeddb](https://github.com/y-js/y-indexeddb) | Offline storage for the browser |
|
||||||
|
|[leveldb](https://github.com/y-js/y-leveldb) | Persistent storage for node apps |
|
||||||
|
|
||||||
|
|
||||||
|
##### Types
|
||||||
|
|
||||||
| Name | Description |
|
| Name | Description |
|
||||||
|----------|-------------------|
|
|----------|-------------------|
|
||||||
|[map](https://github.com/y-js/y-map) | A shared Map implementation. Maps from text to any stringify-able object |
|
|[map](https://github.com/y-js/y-map) | A shared Map implementation. Maps from text to any stringify-able object |
|
||||||
|[array](https://github.com/y-js/y-array) | A shared Array implementation |
|
|[array](https://github.com/y-js/y-array) | A shared Array implementation |
|
||||||
|[xml](https://github.com/y-js/y-xml) | An implementation of the DOM. You can create a two way binding to Browser DOM objects |
|
|[xml](https://github.com/y-js/y-xml) | An implementation of the DOM. You can create a two way binding to Browser DOM objects |
|
||||||
|[text](https://github.com/y-js/y-text) | Collaborate on text. Supports two way binding to textareas, input elements, or HTML elements (e.g. <*h1*>, or <*p*>). Also supports the [Ace Editor](https://ace.c9.io) |
|
|[text](https://github.com/y-js/y-text) | Collaborate on text. Supports two way binding to the [Ace Editor](https://ace.c9.io), textareas, input elements, and HTML elements (e.g. <*h1*>, or <*p*>) |
|
||||||
|[richtext](https://github.com/y-js/y-richtext) | Collaborate on rich text. Supports two way binding to the [Quill Rich Text Editor](http://quilljs.com/)|
|
|[richtext](https://github.com/y-js/y-richtext) | Collaborate on rich text. Supports two way binding to the [Quill Rich Text Editor](http://quilljs.com/)|
|
||||||
|
|
||||||
Yjs supports P2P message propagation, and is not bound to a specific communication protocol. Therefore, Yjs is extremely scalable and can be used in a wide range of application scenarios.
|
## Use it!
|
||||||
|
Install Yjs, and its modules with [bower](http://bower.io/), or [npm](https://www.npmjs.org/package/yjs).
|
||||||
We support several communication protocols as so called *Connectors*.
|
|
||||||
You can create your own connector too - read [this wiki page](https://github.com/y-js/yjs/wiki/Custom-Connectors).
|
|
||||||
Currently, we support the following communication protocols:
|
|
||||||
|
|
||||||
|Name | Description |
|
|
||||||
|----------------|-----------------------------------|
|
|
||||||
|[xmpp](https://github.com/y-js/y-xmpp) | Propagate updates in a XMPP multi-user-chat room ([XEP-0045](http://xmpp.org/extensions/xep-0045.html))|
|
|
||||||
|[webrtc](https://github.com/y-js/y-webrtc) | Propagate updates Browser2Browser via WebRTC|
|
|
||||||
|[websockets](https://github.com/y-js/y-websockets-client) | Exchange updates efficiently in the classical client-server model |
|
|
||||||
|[test](https://github.com/y-js/y-test) | A Connector for testing purposes. It is designed to simulate delays that happen in worst case scenarios|
|
|
||||||
|
|
||||||
You are not limited to use a specific database to store the shared data. We provide the following database adapters:
|
|
||||||
|
|
||||||
|Name | Description |
|
|
||||||
|----------------|-----------------------------------|
|
|
||||||
|[memory](https://github.com/y-js/y-memory) | In-memory storage. |
|
|
||||||
|[indexeddb](https://github.com/y-js/y-indexeddb) | Offline storage for the browser |
|
|
||||||
|
|
||||||
The advantages over similar frameworks are support for
|
|
||||||
* .. P2P message propagation and arbitrary communication protocols
|
|
||||||
* .. share any type of data. The types provide a convenient interface
|
|
||||||
* .. offline support: Changes are stored persistently and only relevant changes are propagated on rejoin
|
|
||||||
* .. Intention Preservation: When working on Text, the intention of your changes are preserved. This is particularily important when working offline. Every type has a notion on how we define Intention Preservation on it.
|
|
||||||
|
|
||||||
## Use it!
|
|
||||||
Install yjs and its modules with [bower](http://bower.io/), or with [npm](https://www.npmjs.org/package/yjs).
|
|
||||||
|
|
||||||
### Bower
|
### Bower
|
||||||
```
|
```
|
||||||
bower install yjs --save
|
bower install --save yjs y-array % add all y-* modules you want to use
|
||||||
```
|
```
|
||||||
Then you include the libraries directly from the installation folder.
|
You only need to include the `y.js` file. Yjs is able to automatically require missing modules.
|
||||||
```
|
```
|
||||||
<script src="./bower_components/yjs/y.js"></script>
|
<script src="./bower_components/yjs/y.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Npm
|
### Npm
|
||||||
```
|
```
|
||||||
npm install yjs --save
|
npm install --save yjs % add all y-* modules you want to use
|
||||||
```
|
```
|
||||||
|
|
||||||
And use it like this with *npm*:
|
If you don't include via script tag, you have to explicitly include all modules! (Same goes for other module systems)
|
||||||
```
|
```
|
||||||
Y = require("yjs");
|
var Y = require('yjs')
|
||||||
|
require('y-array')(Y) // add the y-array type to Yjs
|
||||||
|
require('y-websockets-client')(Y)
|
||||||
|
require('y-memory')(Y)
|
||||||
|
require('y-array')(Y)
|
||||||
|
require('y-map')(Y)
|
||||||
|
require('y-text')(Y)
|
||||||
|
// ..
|
||||||
|
// do the same for all modules you want to use
|
||||||
```
|
```
|
||||||
|
|
||||||
# Text editing example
|
# Text editing example
|
||||||
|
Install dependencies
|
||||||
```
|
```
|
||||||
Y({
|
bower i yjs y-memory y-webrtc y-array y-text
|
||||||
db: {
|
|
||||||
name: 'memory' // store in memory.
|
|
||||||
// name: 'indexeddb'
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'websockets-client', // choose the websockets connector
|
|
||||||
// name: 'webrtc'
|
|
||||||
// name: 'xmpp'
|
|
||||||
room: 'Textarea-example-dev'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components', // location of the y-* modules
|
|
||||||
share: {
|
|
||||||
textarea: 'Text' // y.share.textarea is of type Y.Text
|
|
||||||
}
|
|
||||||
// types: ['Richtext', 'Array'] // optional list of types you want to import
|
|
||||||
}).then(function (y) {
|
|
||||||
// bind the textarea to a shared text element
|
|
||||||
y.share.textarea.bind(document.getElementById('textfield'))
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Api
|
Here is a simple example of a shared textarea
|
||||||
|
```
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script src="./bower_components/yjs/y.js"></script>
|
||||||
|
<script>
|
||||||
|
Y({
|
||||||
|
db: {
|
||||||
|
name: 'memory' // use memory database adapter.
|
||||||
|
// name: 'indexeddb'
|
||||||
|
// name: 'leveldb'
|
||||||
|
},
|
||||||
|
connector: {
|
||||||
|
name: 'webrtc', // use webrtc connector
|
||||||
|
// name: 'websockets-client'
|
||||||
|
// name: 'xmpp'
|
||||||
|
room: 'my-room' // clients connecting to the same room share data
|
||||||
|
},
|
||||||
|
sourceDir: '/bower_components', // location of the y-* modules (browser only)
|
||||||
|
share: {
|
||||||
|
textarea: 'Text' // y.share.textarea is of type y-text
|
||||||
|
}
|
||||||
|
}).then(function (y) {
|
||||||
|
// The Yjs instance `y` is available
|
||||||
|
// y.share.* contains the shared types
|
||||||
|
|
||||||
|
// Bind the textarea to y.share.textarea
|
||||||
|
y.share.textarea.bind(document.querySelector('textarea'))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<textarea></textarea>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Get Help & Give Help
|
||||||
|
There are some friendly people on [](https://gitter.im/y-js/yjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) who are eager to help, and answer questions. Please join!
|
||||||
|
|
||||||
|
Report _any_ issues to the [Github issue page](https://github.com/y-js/yjs/issues)! I try to fix them very soon, if possible.
|
||||||
|
|
||||||
|
# API
|
||||||
|
|
||||||
### Y(options)
|
### Y(options)
|
||||||
* options.db
|
* options.db
|
||||||
@@ -104,14 +130,14 @@ Y({
|
|||||||
* All of our connectors specify an `url` property that defines the connection endpoint of the used connector.
|
* All of our connectors specify an `url` property that defines the connection endpoint of the used connector.
|
||||||
* All of our connectors also have a default connection endpoint that you can use for development.
|
* All of our connectors also have a default connection endpoint that you can use for development.
|
||||||
* Have a look at the used connector repository to see all available options.
|
* Have a look at the used connector repository to see all available options.
|
||||||
* options.sourceDir
|
* options.sourceDir (browser only)
|
||||||
* Path where all y-* modules are stored.
|
* Path where all y-* modules are stored
|
||||||
* Defaults to `/bower_components`
|
* Defaults to `/bower_components`
|
||||||
* Not required when running on `nodejs` / `iojs`
|
* Not required when running on `nodejs` / `iojs`
|
||||||
* When using browserify you can specify all used modules like this:
|
* When using nodejs you need to manually extend Yjs:
|
||||||
```
|
```
|
||||||
var Y = require('yjs')
|
var Y = require('yjs')
|
||||||
// you need to require the db, connector, and *all* types you use!
|
// you have to require a db, connector, and *all* types you use!
|
||||||
require('y-memory')(Y)
|
require('y-memory')(Y)
|
||||||
require('y-webrtc')(Y)
|
require('y-webrtc')(Y)
|
||||||
require('y-map')(Y)
|
require('y-map')(Y)
|
||||||
@@ -119,13 +145,13 @@ require('y-map')(Y)
|
|||||||
```
|
```
|
||||||
* options.share
|
* options.share
|
||||||
* Specify on `options.share[arbitraryName]` types that are shared among all users.
|
* Specify on `options.share[arbitraryName]` types that are shared among all users.
|
||||||
* E.g. Specify `options.share[arbitraryName] = 'Array'` to require y-array and create an Y.Array type on `y.share[arbitraryName]`.
|
* E.g. Specify `options.share[arbitraryName] = 'Array'` to require y-array and create an y-array type on `y.share[arbitraryName]`.
|
||||||
* If userA doesn't specify `options.share[arbitraryName]`, it won't be available for userA.
|
* If userA doesn't specify `options.share[arbitraryName]`, it won't be available for userA.
|
||||||
* If userB specifies `options.share[arbitraryName]`, it still won't be available for userA. But all the updates are send from userB to userA.
|
* If userB specifies `options.share[arbitraryName]`, it still won't be available for userA. But all the updates are send from userB to userA.
|
||||||
* In contrast to Y.Map, types on `y.share.*` cannot be overwritten or deleted. Instead, they are merged among all users. This feature is only available on `y.share.*`
|
* In contrast to y-map, types on `y.share.*` cannot be overwritten or deleted. Instead, they are merged among all users. This feature is only available on `y.share.*`
|
||||||
* Weird behavior: It is supported that two users specify different types with the same property name.
|
* Weird behavior: It is supported that two users specify different types with the same property name.
|
||||||
E.g. userA specifies `options.share.x = 'Array'`, and userB specifies `options.share.x = 'Text'`. But they'll only share data if they specified the same type with the same property name
|
E.g. userA specifies `options.share.x = 'Array'`, and userB specifies `options.share.x = 'Text'`. But they only share data if they specified the same type with the same property name
|
||||||
* options.type
|
* options.type (browser only)
|
||||||
* Array of modules that Yjs needs to require, before instantiating a shared type.
|
* Array of modules that Yjs needs to require, before instantiating a shared type.
|
||||||
* By default Yjs requires the specified database adapter, the specified connector, and all modules that are used in `options.share.*`
|
* By default Yjs requires the specified database adapter, the specified connector, and all modules that are used in `options.share.*`
|
||||||
* Put all types here that you intend to use, but are not used in y.share.*
|
* Put all types here that you intend to use, but are not used in y.share.*
|
||||||
@@ -170,18 +196,19 @@ The promise returns an instance of Y. We denote it with a lower case `y`.
|
|||||||
* y.db.userId :: String
|
* y.db.userId :: String
|
||||||
* The used user id for this client. **Never overwrite this**
|
* The used user id for this client. **Never overwrite this**
|
||||||
|
|
||||||
## Get help
|
|
||||||
There are some friendly people on [](https://gitter.im/y-js/yjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) who may help you with your problem, and answer your questions.
|
|
||||||
|
|
||||||
Please report _any_ issues to the [Github issue page](https://github.com/y-js/yjs/issues)! I try to fix them very soon, if possible.
|
|
||||||
If you want to see an issue fixed, please subscribe to the thread (or remind me via gitter).
|
|
||||||
|
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### 12.0.0
|
||||||
|
* **Types are synchronous and never return a promise (except explicitly stated)**
|
||||||
|
* `y.share.map.get('map type') // => returns a y-map instead of a promise`
|
||||||
|
* The event property `oldValues`, and `values` contain a list of values (without wrapper)
|
||||||
|
* Support for the [y-leveldb](https://github.com/y-js/y-leveldb) database adapter
|
||||||
|
* [y-richtext](https://github.com/y-js/y-richtext) supports Quill@1.0.0-rc.2
|
||||||
|
* Only the types are affected by this release. You have to upgrade y-array@10.0.0, y-map@10.0.0, y-richtext@9.0.0, and y-xml@10.0.0
|
||||||
|
|
||||||
### 11.0.0
|
### 11.0.0
|
||||||
|
|
||||||
* **All types now return a single event instead of list of events**
|
* **All types return a single event instead of list of events**
|
||||||
* Insert events contain a list of values
|
* Insert events contain a list of values
|
||||||
* Improved performance for large insertions & deletions
|
* Improved performance for large insertions & deletions
|
||||||
* Several bugfixes (offline editing related)
|
* Several bugfixes (offline editing related)
|
||||||
@@ -195,7 +222,7 @@ If you want to see an issue fixed, please subscribe to the thread (or remind me
|
|||||||
### 9.0.0
|
### 9.0.0
|
||||||
There were several rolling updates from 0.6 to 0.8. We consider Yjs stable since a long time,
|
There were several rolling updates from 0.6 to 0.8. We consider Yjs stable since a long time,
|
||||||
and intend to continue stable releases. From this release forward y-* modules will implement peer-dependencies for npm, and dependencies for bower.
|
and intend to continue stable releases. From this release forward y-* modules will implement peer-dependencies for npm, and dependencies for bower.
|
||||||
Furthermore, incompatible yjs instances will now throw errors when syncing - this feature was influenced by #48. The versioning jump was influenced by react (see [here](https://facebook.github.io/react/blog/2016/02/19/new-versioning-scheme.html))
|
Furthermore, incompatible yjs instances throw errors now when syncing - this feature was influenced by #48. The versioning jump was influenced by react (see [here](https://facebook.github.io/react/blog/2016/02/19/new-versioning-scheme.html))
|
||||||
|
|
||||||
|
|
||||||
### 0.6.0
|
### 0.6.0
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "11.0.0",
|
"version": "12.0.0",
|
||||||
"homepage": "y-js.org",
|
"homepage": "y-js.org",
|
||||||
"authors": [
|
"authors": [
|
||||||
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
|
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
|
||||||
|
|||||||
637
y.es6
637
y.es6
@@ -54,7 +54,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
this.broadcastedHB = false
|
this.broadcastedHB = false
|
||||||
this.syncStep2 = Promise.resolve()
|
this.syncStep2 = Promise.resolve()
|
||||||
this.broadcastOpBuffer = []
|
this.broadcastOpBuffer = []
|
||||||
this.protocolVersion = 10
|
this.protocolVersion = 11
|
||||||
}
|
}
|
||||||
reconnect () {
|
reconnect () {
|
||||||
}
|
}
|
||||||
@@ -421,7 +421,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
module.exports = function (Y) {
|
module.exports = function (Y) {
|
||||||
var globalRoom = {
|
var globalRoom = {
|
||||||
users: {},
|
users: {},
|
||||||
buffers: {}, // TODO: reimplement this idea. This does not cover all cases!! Here, you have a queue which is unrealistic (i.e. think about multiple incoming connections)
|
buffers: {},
|
||||||
removeUser: function (user) {
|
removeUser: function (user) {
|
||||||
for (var i in this.users) {
|
for (var i in this.users) {
|
||||||
this.users[i].userLeft(user)
|
this.users[i].userLeft(user)
|
||||||
@@ -498,7 +498,7 @@ module.exports = function (Y) {
|
|||||||
} else {
|
} else {
|
||||||
resolve()
|
resolve()
|
||||||
}
|
}
|
||||||
}, 10)
|
}, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
globalRoom.whenTransactionsFinished().then(nextFlush)
|
globalRoom.whenTransactionsFinished().then(nextFlush)
|
||||||
@@ -659,7 +659,7 @@ module.exports = function (Y /* :any */) {
|
|||||||
}
|
}
|
||||||
this.gc1 = [] // first stage
|
this.gc1 = [] // first stage
|
||||||
this.gc2 = [] // second stage -> after that, remove the op
|
this.gc2 = [] // second stage -> after that, remove the op
|
||||||
this.gcTimeout = !opts.gcTimeout ? 50000 : opts.gcTimeoutś
|
this.gcTimeout = !opts.gcTimeout ? 50000 : opts.gcTimeouts
|
||||||
function garbageCollect () {
|
function garbageCollect () {
|
||||||
return os.whenTransactionsFinished().then(function () {
|
return os.whenTransactionsFinished().then(function () {
|
||||||
if (os.gc1.length > 0 || os.gc2.length > 0) {
|
if (os.gc1.length > 0 || os.gc2.length > 0) {
|
||||||
@@ -951,68 +951,89 @@ module.exports = function (Y /* :any */) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (defined == null) {
|
if (defined == null) {
|
||||||
var isGarbageCollected = yield* this.isGarbageCollected(op.id)
|
var opid = op.id
|
||||||
|
var isGarbageCollected = yield* this.isGarbageCollected(opid)
|
||||||
if (!isGarbageCollected) {
|
if (!isGarbageCollected) {
|
||||||
|
// TODO: reduce number of get / put calls for op ..
|
||||||
yield* Y.Struct[op.struct].execute.call(this, op)
|
yield* Y.Struct[op.struct].execute.call(this, op)
|
||||||
yield* this.addOperation(op)
|
yield* this.addOperation(op)
|
||||||
yield* this.store.operationAdded(this, op)
|
yield* this.store.operationAdded(this, op)
|
||||||
|
// operationAdded can change op..
|
||||||
|
op = yield* this.getOperation(opid)
|
||||||
// if insertion, try to combine with left
|
// if insertion, try to combine with left
|
||||||
yield* this.tryCombineWithLeft(op)
|
yield* this.tryCombineWithLeft(op)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// called by a transaction when an operation is added
|
/*
|
||||||
|
* Called by a transaction when an operation is added.
|
||||||
|
* This function is especially important for y-indexeddb, where several instances may share a single database.
|
||||||
|
* Every time an operation is created by one instance, it is send to all other instances and operationAdded is called
|
||||||
|
*
|
||||||
|
* If it's not a Delete operation:
|
||||||
|
* * Checks if another operation is executable (listenersById)
|
||||||
|
* * Update state, if possible
|
||||||
|
*
|
||||||
|
* Always:
|
||||||
|
* * Call type
|
||||||
|
*/
|
||||||
* operationAdded (transaction, op) {
|
* operationAdded (transaction, op) {
|
||||||
// increase SS
|
if (op.struct === 'Delete') {
|
||||||
yield* transaction.updateState(op.id[0])
|
var target = yield* transaction.getInsertion(op.target)
|
||||||
|
var type = this.initializedTypes[JSON.stringify(target.parent)]
|
||||||
var opLen = op.content != null ? op.content.length : 1
|
if (type != null) {
|
||||||
for (let i = 0; i < opLen; i++) {
|
yield* type._changed(transaction, op)
|
||||||
// notify whenOperation listeners (by id)
|
}
|
||||||
var sid = JSON.stringify([op.id[0], op.id[1] + i])
|
} else {
|
||||||
var l = this.listenersById[sid]
|
// increase SS
|
||||||
delete this.listenersById[sid]
|
yield* transaction.updateState(op.id[0])
|
||||||
|
var opLen = op.content != null ? op.content.length : 1
|
||||||
if (l != null) {
|
for (let i = 0; i < opLen; i++) {
|
||||||
for (var key in l) {
|
// notify whenOperation listeners (by id)
|
||||||
var listener = l[key]
|
var sid = JSON.stringify([op.id[0], op.id[1] + i])
|
||||||
if (--listener.missing === 0) {
|
var l = this.listenersById[sid]
|
||||||
this.whenOperationsExist([], listener.op)
|
delete this.listenersById[sid]
|
||||||
|
if (l != null) {
|
||||||
|
for (var key in l) {
|
||||||
|
var listener = l[key]
|
||||||
|
if (--listener.missing === 0) {
|
||||||
|
this.whenOperationsExist([], listener.op)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
var t = this.initializedTypes[JSON.stringify(op.parent)]
|
||||||
var t = this.initializedTypes[JSON.stringify(op.parent)]
|
|
||||||
|
|
||||||
// if parent is deleted, mark as gc'd and return
|
// if parent is deleted, mark as gc'd and return
|
||||||
if (op.parent != null) {
|
if (op.parent != null) {
|
||||||
var parentIsDeleted = yield* transaction.isDeleted(op.parent)
|
var parentIsDeleted = yield* transaction.isDeleted(op.parent)
|
||||||
if (parentIsDeleted) {
|
if (parentIsDeleted) {
|
||||||
yield* transaction.deleteList(op.id)
|
yield* transaction.deleteList(op.id)
|
||||||
return
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// notify parent, if it was instanciated as a custom type
|
// notify parent, if it was instanciated as a custom type
|
||||||
if (t != null) {
|
if (t != null) {
|
||||||
let o = Y.utils.copyObject(op)
|
let o = Y.utils.copyOperation(op)
|
||||||
yield* t._changed(transaction, o)
|
yield* t._changed(transaction, o)
|
||||||
}
|
}
|
||||||
if (!op.deleted) {
|
if (!op.deleted) {
|
||||||
// Delete if DS says this is actually deleted
|
// Delete if DS says this is actually deleted
|
||||||
var len = op.content != null ? op.content.length : 1
|
var len = op.content != null ? op.content.length : 1
|
||||||
var startId = op.id // You must not use op.id in the following loop, because op will change when deleted
|
var startId = op.id // You must not use op.id in the following loop, because op will change when deleted
|
||||||
for (let i = 0; i < len; i++) {
|
// TODO: !! console.log('TODO: change this before commiting')
|
||||||
var id = [startId[0], startId[1] + i]
|
for (let i = 0; i < len; i++) {
|
||||||
var opIsDeleted = yield* transaction.isDeleted(id)
|
var id = [startId[0], startId[1] + i]
|
||||||
if (opIsDeleted) {
|
var opIsDeleted = yield* transaction.isDeleted(id)
|
||||||
var delop = {
|
if (opIsDeleted) {
|
||||||
struct: 'Delete',
|
var delop = {
|
||||||
target: id
|
struct: 'Delete',
|
||||||
|
target: id
|
||||||
|
}
|
||||||
|
yield* this.tryExecute.call(transaction, delop)
|
||||||
}
|
}
|
||||||
yield* this.tryExecute.call(transaction, delop)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1063,15 +1084,52 @@ module.exports = function (Y /* :any */) {
|
|||||||
this.waitingTransactions.push(makeGen)
|
this.waitingTransactions.push(makeGen)
|
||||||
if (!this.transactionInProgress) {
|
if (!this.transactionInProgress) {
|
||||||
this.transactionInProgress = true
|
this.transactionInProgress = true
|
||||||
if (false || callImmediately) { // TODO: decide whether this is ok or not..
|
setTimeout(() => {
|
||||||
this.transact(this.getNextRequest())
|
this.transact(this.getNextRequest())
|
||||||
} else {
|
}, 0)
|
||||||
var self = this
|
}
|
||||||
setTimeout(function () {
|
}
|
||||||
self.transact(self.getNextRequest())
|
/*
|
||||||
}, 0)
|
Get a created/initialized type.
|
||||||
|
*/
|
||||||
|
getType (id) {
|
||||||
|
return this.initializedTypes[JSON.stringify(id)]
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Init type. This is called when a remote operation is retrieved, and transformed to a type
|
||||||
|
TODO: delete type from store.initializedTypes[id] when corresponding id was deleted!
|
||||||
|
*/
|
||||||
|
* initType (id, args) {
|
||||||
|
var sid = JSON.stringify(id)
|
||||||
|
var t = this.store.initializedTypes[sid]
|
||||||
|
if (t == null) {
|
||||||
|
var op/* :MapStruct | ListStruct */ = yield* this.getOperation(id)
|
||||||
|
if (op != null) {
|
||||||
|
t = yield* Y[op.type].typeDefinition.initType.call(this, this.store, op, args)
|
||||||
|
this.store.initializedTypes[sid] = t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Create type. This is called when the local user creates a type (which is a synchronous action)
|
||||||
|
*/
|
||||||
|
createType (typedefinition, id) {
|
||||||
|
var structname = typedefinition[0].struct
|
||||||
|
id = id || this.getNextOpId(1)
|
||||||
|
var op = Y.Struct[structname].create(id)
|
||||||
|
op.type = typedefinition[0].name
|
||||||
|
|
||||||
|
this.requestTransaction(function * () {
|
||||||
|
if (op.id[0] === '_') {
|
||||||
|
yield* this.setOperation(op)
|
||||||
|
} else {
|
||||||
|
yield* this.applyCreatedOperations([op])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
var t = Y[op.type].typeDefinition.createType(this, op, typedefinition[1])
|
||||||
|
this.initializedTypes[JSON.stringify(op.id)] = t
|
||||||
|
return t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Y.AbstractDatabase = AbstractDatabase
|
Y.AbstractDatabase = AbstractDatabase
|
||||||
@@ -1574,57 +1632,6 @@ module.exports = function (Y/* :any */) {
|
|||||||
os: Store;
|
os: Store;
|
||||||
ss: Store;
|
ss: Store;
|
||||||
*/
|
*/
|
||||||
/*
|
|
||||||
Get a type based on the id of its model.
|
|
||||||
If it does not exist yes, create it.
|
|
||||||
TODO: delete type from store.initializedTypes[id] when corresponding id was deleted!
|
|
||||||
*/
|
|
||||||
* getType (id, args) {
|
|
||||||
var sid = JSON.stringify(id)
|
|
||||||
var t = this.store.initializedTypes[sid]
|
|
||||||
if (t == null) {
|
|
||||||
var op/* :MapStruct | ListStruct */ = yield* this.getOperation(id)
|
|
||||||
if (op != null) {
|
|
||||||
t = yield* Y[op.type].typeDefinition.initType.call(this, this.store, op, args)
|
|
||||||
this.store.initializedTypes[sid] = t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
* createType (typedefinition, id) {
|
|
||||||
var structname = typedefinition[0].struct
|
|
||||||
id = id || this.store.getNextOpId(1)
|
|
||||||
var op
|
|
||||||
if (id[0] === '_') {
|
|
||||||
op = yield* this.getOperation(id)
|
|
||||||
} else {
|
|
||||||
op = Y.Struct[structname].create(id)
|
|
||||||
op.type = typedefinition[0].name
|
|
||||||
}
|
|
||||||
if (typedefinition[0].appendAdditionalInfo != null) {
|
|
||||||
yield* typedefinition[0].appendAdditionalInfo.call(this, op, typedefinition[1])
|
|
||||||
}
|
|
||||||
if (op[0] === '_') {
|
|
||||||
yield* this.setOperation(op)
|
|
||||||
} else {
|
|
||||||
yield* this.applyCreatedOperations([op])
|
|
||||||
}
|
|
||||||
return yield* this.getType(id, typedefinition[1])
|
|
||||||
}
|
|
||||||
/* createType (typedefinition, id) {
|
|
||||||
var structname = typedefinition[0].struct
|
|
||||||
id = id || this.store.getNextOpId(1)
|
|
||||||
var op = Y.Struct[structname].create(id)
|
|
||||||
op.type = typedefinition[0].name
|
|
||||||
if (typedefinition[0].appendAdditionalInfo != null) {
|
|
||||||
yield* typedefinition[0].appendAdditionalInfo.call(this, op, typedefinition[1])
|
|
||||||
}
|
|
||||||
// yield* this.applyCreatedOperations([op])
|
|
||||||
yield* Y.Struct[op.struct].execute.call(this, op)
|
|
||||||
yield* this.addOperation(op)
|
|
||||||
yield* this.store.operationAdded(this, op)
|
|
||||||
return yield* this.getType(id, typedefinition[1])
|
|
||||||
}*/
|
|
||||||
/*
|
/*
|
||||||
Apply operations that this user created (no remote ones!)
|
Apply operations that this user created (no remote ones!)
|
||||||
* does not check for Struct.*.requiredOps()
|
* does not check for Struct.*.requiredOps()
|
||||||
@@ -1746,14 +1753,11 @@ module.exports = function (Y/* :any */) {
|
|||||||
right = null
|
right = null
|
||||||
}
|
}
|
||||||
if (callType && !preventCallType) {
|
if (callType && !preventCallType) {
|
||||||
var type = this.store.initializedTypes[JSON.stringify(target.parent)]
|
yield* this.store.operationAdded(this, {
|
||||||
if (type != null) {
|
struct: 'Delete',
|
||||||
yield* type._changed(this, {
|
target: target.id,
|
||||||
struct: 'Delete',
|
length: targetLength
|
||||||
target: target.id,
|
})
|
||||||
length: targetLength
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// need to gc in the end!
|
// need to gc in the end!
|
||||||
yield* this.store.addToGarbageCollector.call(this, target, left)
|
yield* this.store.addToGarbageCollector.call(this, target, left)
|
||||||
@@ -1997,6 +2001,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
if (o.right != null) {
|
if (o.right != null) {
|
||||||
var right = yield* this.getOperation(o.right)
|
var right = yield* this.getOperation(o.right)
|
||||||
right.left = o.left
|
right.left = o.left
|
||||||
|
yield* this.setOperation(right)
|
||||||
|
|
||||||
if (o.originOf != null && o.originOf.length > 0) {
|
if (o.originOf != null && o.originOf.length > 0) {
|
||||||
// find new origin of right ops
|
// find new origin of right ops
|
||||||
@@ -2063,10 +2068,6 @@ module.exports = function (Y/* :any */) {
|
|||||||
}
|
}
|
||||||
// we don't need to set right here, because
|
// we don't need to set right here, because
|
||||||
// right should be in o.originOf => it is set it the previous for loop
|
// right should be in o.originOf => it is set it the previous for loop
|
||||||
} else {
|
|
||||||
// we didn't need to reset the origin of right
|
|
||||||
// so we have to set right here
|
|
||||||
yield* this.setOperation(right)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// o may originate in another operation.
|
// o may originate in another operation.
|
||||||
@@ -2495,6 +2496,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
if (firstMissing != null) {
|
if (firstMissing != null) {
|
||||||
// update startPos
|
// update startPos
|
||||||
startPos = firstMissing.id[1]
|
startPos = firstMissing.id[1]
|
||||||
|
startSS[user] = startPos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
yield* this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function * (op) {
|
yield* this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function * (op) {
|
||||||
@@ -2560,7 +2562,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
/* this is what we used before.. use this as a reference..
|
/* this is what we used before.. use this as a reference..
|
||||||
* makeOperationReady (startSS, op) {
|
* makeOperationReady (startSS, op) {
|
||||||
op = Y.Struct[op.struct].encode(op)
|
op = Y.Struct[op.struct].encode(op)
|
||||||
op = Y.utils.copyObject(op)
|
op = Y.utils.copyObject(op) -- use copyoperation instead now!
|
||||||
var o = op
|
var o = op
|
||||||
var ids = [op.id]
|
var ids = [op.id]
|
||||||
// search for the new op.right
|
// search for the new op.right
|
||||||
@@ -2686,6 +2688,159 @@ module.exports = function (Y /* : any*/) {
|
|||||||
receivedOp (op) {
|
receivedOp (op) {
|
||||||
if (this.awaiting <= 0) {
|
if (this.awaiting <= 0) {
|
||||||
this.onevent(op)
|
this.onevent(op)
|
||||||
|
} else if (op.struct === 'Delete') {
|
||||||
|
var self = this
|
||||||
|
var checkDelete = function checkDelete (d) {
|
||||||
|
if (d.length == null) {
|
||||||
|
throw new Error('This shouldn\'t happen! d.length must be defined!')
|
||||||
|
}
|
||||||
|
// we check if o deletes something in self.waiting
|
||||||
|
// if so, we remove the deleted operation
|
||||||
|
for (var w = 0; w < self.waiting.length; w++) {
|
||||||
|
var i = self.waiting[w]
|
||||||
|
if (i.struct === 'Insert' && i.id[0] === d.target[0]) {
|
||||||
|
var iLength = i.hasOwnProperty('content') ? i.content.length : 1
|
||||||
|
var dStart = d.target[1]
|
||||||
|
var dEnd = d.target[1] + (d.length || 1)
|
||||||
|
var iStart = i.id[1]
|
||||||
|
var iEnd = i.id[1] + iLength
|
||||||
|
// Check if they don't overlap
|
||||||
|
if (iEnd <= dStart || dEnd <= iStart) {
|
||||||
|
// no overlapping
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// we check all overlapping cases. All cases:
|
||||||
|
/*
|
||||||
|
1) iiiii
|
||||||
|
ddddd
|
||||||
|
--> modify i and d
|
||||||
|
2) iiiiiii
|
||||||
|
ddddd
|
||||||
|
--> modify i, remove d
|
||||||
|
3) iiiiiii
|
||||||
|
ddd
|
||||||
|
--> remove d, modify i, and create another i (for the right hand side)
|
||||||
|
4) iiiii
|
||||||
|
ddddddd
|
||||||
|
--> remove i, modify d
|
||||||
|
5) iiiiiii
|
||||||
|
ddddddd
|
||||||
|
--> remove both i and d (**)
|
||||||
|
6) iiiiiii
|
||||||
|
ddddd
|
||||||
|
--> modify i, remove d
|
||||||
|
7) iii
|
||||||
|
ddddddd
|
||||||
|
--> remove i, create and apply two d with checkDelete(d) (**)
|
||||||
|
8) iiiii
|
||||||
|
ddddddd
|
||||||
|
--> remove i, modify d (**)
|
||||||
|
9) iiiii
|
||||||
|
ddddd
|
||||||
|
--> modify i and d
|
||||||
|
(**) (also check if i contains content or type)
|
||||||
|
*/
|
||||||
|
// TODO: I left some debugger statements, because I want to debug all cases once in production. REMEMBER END TODO
|
||||||
|
if (iStart < dStart) {
|
||||||
|
if (dStart < iEnd) {
|
||||||
|
if (iEnd < dEnd) {
|
||||||
|
// Case 1
|
||||||
|
// remove the right part of i's content
|
||||||
|
i.content.splice(dStart - iStart)
|
||||||
|
// remove the start of d's deletion
|
||||||
|
d.length = dEnd - iEnd
|
||||||
|
d.target = [d.target[0], iEnd]
|
||||||
|
continue
|
||||||
|
} else if (iEnd === dEnd) {
|
||||||
|
// Case 2
|
||||||
|
i.content.splice(dStart - iStart)
|
||||||
|
// remove d, we do that by simply ending this function
|
||||||
|
return
|
||||||
|
} else { // (dEnd < iEnd)
|
||||||
|
// Case 3
|
||||||
|
var newI = {
|
||||||
|
id: [i.id[0], dEnd],
|
||||||
|
content: i.content.slice(dEnd - iStart),
|
||||||
|
struct: 'Insert'
|
||||||
|
}
|
||||||
|
self.waiting.push(newI)
|
||||||
|
i.content.splice(dStart - iStart)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (dStart === iStart) {
|
||||||
|
if (iEnd < dEnd) {
|
||||||
|
// Case 4
|
||||||
|
d.length = dEnd - iEnd
|
||||||
|
d.target = [d.target[0], iEnd]
|
||||||
|
i.content = []
|
||||||
|
continue
|
||||||
|
} else if (iEnd === dEnd) {
|
||||||
|
// Case 5
|
||||||
|
self.waiting.splice(w, 1)
|
||||||
|
return
|
||||||
|
} else { // (dEnd < iEnd)
|
||||||
|
// Case 6
|
||||||
|
i.content = i.content.slice(dEnd - iStart)
|
||||||
|
i.id = [i.id[0], dEnd]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else { // (dStart < iStart)
|
||||||
|
if (iStart < dEnd) {
|
||||||
|
// they overlap
|
||||||
|
/*
|
||||||
|
7) iii
|
||||||
|
ddddddd
|
||||||
|
--> remove i, create and apply two d with checkDelete(d) (**)
|
||||||
|
8) iiiii
|
||||||
|
ddddddd
|
||||||
|
--> remove i, modify d (**)
|
||||||
|
9) iiiii
|
||||||
|
ddddd
|
||||||
|
--> modify i and d
|
||||||
|
*/
|
||||||
|
if (iEnd < dEnd) {
|
||||||
|
// Case 7
|
||||||
|
// debugger // TODO: You did not test this case yet!!!! (add the debugger here)
|
||||||
|
self.waiting.splice(w, 1)
|
||||||
|
checkDelete({
|
||||||
|
target: [d.target[0], dStart],
|
||||||
|
length: iStart - dStart,
|
||||||
|
struct: 'Delete'
|
||||||
|
})
|
||||||
|
checkDelete({
|
||||||
|
target: [d.target[0], iEnd],
|
||||||
|
length: iEnd - dEnd,
|
||||||
|
struct: 'Delete'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
} else if (iEnd === dEnd) {
|
||||||
|
// Case 8
|
||||||
|
self.waiting.splice(w, 1)
|
||||||
|
w--
|
||||||
|
d.length -= iLength
|
||||||
|
continue
|
||||||
|
} else { // dEnd < iEnd
|
||||||
|
// Case 9
|
||||||
|
d.length = iStart - dStart
|
||||||
|
i.content.splice(0, dEnd - iStart)
|
||||||
|
i.id = [i.id[0], dEnd]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// finished with remaining operations
|
||||||
|
self.waiting.push(d)
|
||||||
|
}
|
||||||
|
if (op.key == null) {
|
||||||
|
// deletes in list
|
||||||
|
checkDelete(op)
|
||||||
|
} else {
|
||||||
|
// deletes in map
|
||||||
|
this.waiting.push(op)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.waiting.push(op)
|
this.waiting.push(op)
|
||||||
}
|
}
|
||||||
@@ -2696,9 +2851,110 @@ module.exports = function (Y /* : any*/) {
|
|||||||
prematurely called operations are executed
|
prematurely called operations are executed
|
||||||
*/
|
*/
|
||||||
awaitAndPrematurelyCall (ops) {
|
awaitAndPrematurelyCall (ops) {
|
||||||
this.awaiting += ops.length
|
this.awaiting++
|
||||||
ops.forEach(this.onevent)
|
ops.map(Y.utils.copyOperation).forEach(this.onevent)
|
||||||
}
|
}
|
||||||
|
* awaitOps (transaction, f, args) {
|
||||||
|
function notSoSmartSort (array) {
|
||||||
|
// this function sorts insertions in a executable order
|
||||||
|
var result = []
|
||||||
|
while (array.length > 0) {
|
||||||
|
for (var i = 0; i < array.length; i++) {
|
||||||
|
var independent = true
|
||||||
|
for (var j = 0; j < array.length; j++) {
|
||||||
|
if (Y.utils.matchesId(array[j], array[i].left)) {
|
||||||
|
// array[i] depends on array[j]
|
||||||
|
independent = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (independent) {
|
||||||
|
result.push(array.splice(i, 1)[0])
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
var before = this.waiting.length
|
||||||
|
// somehow create new operations
|
||||||
|
yield* f.apply(transaction, args)
|
||||||
|
// remove all appended ops / awaited ops
|
||||||
|
this.waiting.splice(before)
|
||||||
|
if (this.awaiting > 0) this.awaiting--
|
||||||
|
// if there are no awaited ops anymore, we can update all waiting ops, and send execute them (if there are still no awaited ops)
|
||||||
|
if (this.awaiting === 0 && this.waiting.length > 0) {
|
||||||
|
// update all waiting ops
|
||||||
|
for (let i = 0; i < this.waiting.length; i++) {
|
||||||
|
var o = this.waiting[i]
|
||||||
|
if (o.struct === 'Insert') {
|
||||||
|
var _o = yield* transaction.getInsertion(o.id)
|
||||||
|
if (_o.parentSub != null && _o.left != null) {
|
||||||
|
// if o is an insertion of a map struc (parentSub is defined), then it shouldn't be necessary to compute left
|
||||||
|
this.waiting.splice(i, 1)
|
||||||
|
i-- // update index
|
||||||
|
} else if (!Y.utils.compareIds(_o.id, o.id)) {
|
||||||
|
// o got extended
|
||||||
|
o.left = [o.id[0], o.id[1] - 1]
|
||||||
|
} else if (_o.left == null) {
|
||||||
|
o.left = null
|
||||||
|
} else {
|
||||||
|
// find next undeleted op
|
||||||
|
var left = yield* transaction.getInsertion(_o.left)
|
||||||
|
while (left.deleted != null) {
|
||||||
|
if (left.left != null) {
|
||||||
|
left = yield* transaction.getInsertion(left.left)
|
||||||
|
} else {
|
||||||
|
left = null
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o.left = left != null ? Y.utils.getLastId(left) : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the previous stuff was async, so we have to check again!
|
||||||
|
// We also pull changes from the bindings, if there exists such a method, this could increase awaiting too
|
||||||
|
if (this._pullChanges != null) {
|
||||||
|
this._pullChanges()
|
||||||
|
}
|
||||||
|
if (this.awaiting === 0) {
|
||||||
|
// sort by type, execute inserts first
|
||||||
|
var ins = []
|
||||||
|
var dels = []
|
||||||
|
this.waiting.forEach(function (o) {
|
||||||
|
if (o.struct === 'Delete') {
|
||||||
|
dels.push(o)
|
||||||
|
} else {
|
||||||
|
ins.push(o)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.waiting = []
|
||||||
|
// put in executable order
|
||||||
|
ins = notSoSmartSort(ins)
|
||||||
|
// this.onevent can trigger the creation of another operation
|
||||||
|
// -> check if this.awaiting increased & stop computation if it does
|
||||||
|
for (var i = 0; i < ins.length; i++) {
|
||||||
|
if (this.awaiting === 0) {
|
||||||
|
this.onevent(ins[i])
|
||||||
|
} else {
|
||||||
|
this.waiting = this.waiting.concat(ins.slice(i))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i = 0; i < dels.length; i++) {
|
||||||
|
if (this.awaiting === 0) {
|
||||||
|
this.onevent(dels[i])
|
||||||
|
} else {
|
||||||
|
this.waiting = this.waiting.concat(dels.slice(i))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: Remove awaitedInserts and awaitedDeletes in favor of awaitedOps, as they are deprecated and do not always work
|
||||||
|
// Do this in one of the coming releases that are breaking anyway
|
||||||
/*
|
/*
|
||||||
Call this when you successfully awaited the execution of n Insert operations
|
Call this when you successfully awaited the execution of n Insert operations
|
||||||
*/
|
*/
|
||||||
@@ -2709,15 +2965,17 @@ module.exports = function (Y /* : any*/) {
|
|||||||
if (op.struct === 'Insert') {
|
if (op.struct === 'Insert') {
|
||||||
for (var i = this.waiting.length - 1; i >= 0; i--) {
|
for (var i = this.waiting.length - 1; i >= 0; i--) {
|
||||||
let w = this.waiting[i]
|
let w = this.waiting[i]
|
||||||
|
// TODO: do I handle split operations correctly here? Super unlikely, but yeah..
|
||||||
|
// Also: can this case happen? Can op be inserted in the middle of a larger op that is in $waiting?
|
||||||
if (w.struct === 'Insert') {
|
if (w.struct === 'Insert') {
|
||||||
if (Y.utils.compareIds(op.left, w.id)) {
|
if (Y.utils.matchesId(w, op.left)) {
|
||||||
// include the effect of op in w
|
// include the effect of op in w
|
||||||
w.right = op.id
|
w.right = op.id
|
||||||
// exclude the effect of w in op
|
// exclude the effect of w in op
|
||||||
op.left = w.left
|
op.left = w.left
|
||||||
} else if (Y.utils.compareIds(op.right, w.id)) {
|
} else if (Y.utils.compareIds(w.id, op.right)) {
|
||||||
// similar..
|
// similar..
|
||||||
w.left = op.id
|
w.left = Y.utils.getLastId(op)
|
||||||
op.right = w.right
|
op.right = w.right
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2754,17 +3012,55 @@ module.exports = function (Y /* : any*/) {
|
|||||||
/* (private)
|
/* (private)
|
||||||
Try to execute the events for the waiting operations
|
Try to execute the events for the waiting operations
|
||||||
*/
|
*/
|
||||||
_tryCallEvents (n) {
|
_tryCallEvents () {
|
||||||
this.awaiting -= n
|
function notSoSmartSort (array) {
|
||||||
|
var result = []
|
||||||
|
while (array.length > 0) {
|
||||||
|
for (var i = 0; i < array.length; i++) {
|
||||||
|
var independent = true
|
||||||
|
for (var j = 0; j < array.length; j++) {
|
||||||
|
if (Y.utils.matchesId(array[j], array[i].left)) {
|
||||||
|
// array[i] depends on array[j]
|
||||||
|
independent = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (independent) {
|
||||||
|
result.push(array.splice(i, 1)[0])
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
if (this.awaiting > 0) this.awaiting--
|
||||||
if (this.awaiting === 0 && this.waiting.length > 0) {
|
if (this.awaiting === 0 && this.waiting.length > 0) {
|
||||||
var ops = this.waiting
|
var ins = []
|
||||||
|
var dels = []
|
||||||
|
this.waiting.forEach(function (o) {
|
||||||
|
if (o.struct === 'Delete') {
|
||||||
|
dels.push(o)
|
||||||
|
} else {
|
||||||
|
ins.push(o)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
ins = notSoSmartSort(ins)
|
||||||
|
ins.forEach(this.onevent)
|
||||||
|
dels.forEach(this.onevent)
|
||||||
this.waiting = []
|
this.waiting = []
|
||||||
ops.forEach(this.onevent)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Y.utils.EventHandler = EventHandler
|
Y.utils.EventHandler = EventHandler
|
||||||
|
|
||||||
|
/*
|
||||||
|
Default class of custom types!
|
||||||
|
*/
|
||||||
|
class CustomType {
|
||||||
|
|
||||||
|
}
|
||||||
|
Y.utils.CustomType = CustomType
|
||||||
|
|
||||||
/*
|
/*
|
||||||
A wrapper for the definition of a custom type.
|
A wrapper for the definition of a custom type.
|
||||||
Every custom type must have three properties:
|
Every custom type must have three properties:
|
||||||
@@ -2776,7 +3072,7 @@ module.exports = function (Y /* : any*/) {
|
|||||||
* class
|
* class
|
||||||
- the constructor of the custom type (e.g. in order to inherit from a type)
|
- the constructor of the custom type (e.g. in order to inherit from a type)
|
||||||
*/
|
*/
|
||||||
class CustomType { // eslint-disable-line
|
class CustomTypeDefinition { // eslint-disable-line
|
||||||
/* ::
|
/* ::
|
||||||
struct: any;
|
struct: any;
|
||||||
initType: any;
|
initType: any;
|
||||||
@@ -2787,12 +3083,14 @@ module.exports = function (Y /* : any*/) {
|
|||||||
if (def.struct == null ||
|
if (def.struct == null ||
|
||||||
def.initType == null ||
|
def.initType == null ||
|
||||||
def.class == null ||
|
def.class == null ||
|
||||||
def.name == null
|
def.name == null ||
|
||||||
|
def.createType == null
|
||||||
) {
|
) {
|
||||||
throw new Error('Custom type was not initialized correctly!')
|
throw new Error('Custom type was not initialized correctly!')
|
||||||
}
|
}
|
||||||
this.struct = def.struct
|
this.struct = def.struct
|
||||||
this.initType = def.initType
|
this.initType = def.initType
|
||||||
|
this.createType = def.createType
|
||||||
this.class = def.class
|
this.class = def.class
|
||||||
this.name = def.name
|
this.name = def.name
|
||||||
if (def.appendAdditionalInfo != null) {
|
if (def.appendAdditionalInfo != null) {
|
||||||
@@ -2804,13 +3102,13 @@ module.exports = function (Y /* : any*/) {
|
|||||||
this.parseArguments.typeDefinition = this
|
this.parseArguments.typeDefinition = this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Y.utils.CustomType = CustomType
|
Y.utils.CustomTypeDefinition = CustomTypeDefinition
|
||||||
|
|
||||||
Y.utils.isTypeDefinition = function isTypeDefinition (v) {
|
Y.utils.isTypeDefinition = function isTypeDefinition (v) {
|
||||||
if (v != null) {
|
if (v != null) {
|
||||||
if (v instanceof Y.utils.CustomType) return [v]
|
if (v instanceof Y.utils.CustomTypeDefinition) return [v]
|
||||||
else if (v.constructor === Array && v[0] instanceof Y.utils.CustomType) return v
|
else if (v.constructor === Array && v[0] instanceof Y.utils.CustomTypeDefinition) return v
|
||||||
else if (v instanceof Function && v.typeDefinition instanceof Y.utils.CustomType) return [v.typeDefinition]
|
else if (v instanceof Function && v.typeDefinition instanceof Y.utils.CustomTypeDefinition) return [v.typeDefinition]
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -2828,6 +3126,20 @@ module.exports = function (Y /* : any*/) {
|
|||||||
}
|
}
|
||||||
Y.utils.copyObject = copyObject
|
Y.utils.copyObject = copyObject
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copy an operation, so that it can be manipulated.
|
||||||
|
Note: You must not change subproperties (except o.content)!
|
||||||
|
*/
|
||||||
|
function copyOperation (o) {
|
||||||
|
o = copyObject(o)
|
||||||
|
if (o.content != null) {
|
||||||
|
o.content = o.content.map(function (c) { return c })
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
Y.utils.copyOperation = copyOperation
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Defines a smaller relation on Id's
|
Defines a smaller relation on Id's
|
||||||
*/
|
*/
|
||||||
@@ -2836,6 +3148,11 @@ module.exports = function (Y /* : any*/) {
|
|||||||
}
|
}
|
||||||
Y.utils.smaller = smaller
|
Y.utils.smaller = smaller
|
||||||
|
|
||||||
|
function inDeletionRange (del, ins) {
|
||||||
|
return del.target[0] === ins[0] && del.target[1] <= ins[1] && ins[1] < del.target[1] + (del.length || 1)
|
||||||
|
}
|
||||||
|
Y.utils.inDeletionRange = inDeletionRange
|
||||||
|
|
||||||
function compareIds (id1, id2) {
|
function compareIds (id1, id2) {
|
||||||
if (id1 == null || id2 == null) {
|
if (id1 == null || id2 == null) {
|
||||||
return id1 === id2
|
return id1 === id2
|
||||||
@@ -2892,9 +3209,9 @@ module.exports = function (Y /* : any*/) {
|
|||||||
I tried to optimize this for performance, therefore no highlevel operations.
|
I tried to optimize this for performance, therefore no highlevel operations.
|
||||||
*/
|
*/
|
||||||
class SmallLookupBuffer extends Store {
|
class SmallLookupBuffer extends Store {
|
||||||
constructor (arg) {
|
constructor (arg1, arg2) {
|
||||||
// super(...arguments) -- do this when this is supported by stable nodejs
|
// super(...arguments) -- do this when this is supported by stable nodejs
|
||||||
super(arg)
|
super(arg1, arg2)
|
||||||
this.writeBuffer = createEmptyOpsArray(5)
|
this.writeBuffer = createEmptyOpsArray(5)
|
||||||
this.readBuffer = createEmptyOpsArray(10)
|
this.readBuffer = createEmptyOpsArray(10)
|
||||||
}
|
}
|
||||||
@@ -3051,7 +3368,7 @@ module.exports = Y
|
|||||||
Y.requiringModules = requiringModules
|
Y.requiringModules = requiringModules
|
||||||
|
|
||||||
Y.extend = function (name, value) {
|
Y.extend = function (name, value) {
|
||||||
if (value instanceof Y.utils.CustomType) {
|
if (value instanceof Y.utils.CustomTypeDefinition) {
|
||||||
Y[name] = value.parseArguments
|
Y[name] = value.parseArguments
|
||||||
} else {
|
} else {
|
||||||
Y[name] = value
|
Y[name] = value
|
||||||
@@ -3087,6 +3404,7 @@ function requestModules (modules) {
|
|||||||
})
|
})
|
||||||
promises.push(requireModule.promise)
|
promises.push(requireModule.promise)
|
||||||
} else {
|
} else {
|
||||||
|
console.info('YJS: Please do not depend on automatic requiring of modules anymore! Extend modules as follows `require(\'y-modulename\')(Y)`')
|
||||||
require(modulename)(Y)
|
require(modulename)(Y)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -3133,23 +3451,25 @@ function Y (opts/* :YOptions */) /* :Promise<YConfig> */ {
|
|||||||
modules.push(opts.share[name])
|
modules.push(opts.share[name])
|
||||||
}
|
}
|
||||||
Y.sourceDir = opts.sourceDir
|
Y.sourceDir = opts.sourceDir
|
||||||
return Y.requestModules(modules).then(function () {
|
return new Promise(function (resolve, reject) {
|
||||||
return new Promise(function (resolve, reject) {
|
setTimeout(function () {
|
||||||
if (opts == null) reject('An options object is expected! ')
|
Y.requestModules(modules).then(function () {
|
||||||
else if (opts.connector == null) reject('You must specify a connector! (missing connector property)')
|
if (opts == null) reject('An options object is expected! ')
|
||||||
else if (opts.connector.name == null) reject('You must specify connector name! (missing connector.name property)')
|
else if (opts.connector == null) reject('You must specify a connector! (missing connector property)')
|
||||||
else if (opts.db == null) reject('You must specify a database! (missing db property)')
|
else if (opts.connector.name == null) reject('You must specify connector name! (missing connector.name property)')
|
||||||
else if (opts.connector.name == null) reject('You must specify db name! (missing db.name property)')
|
else if (opts.db == null) reject('You must specify a database! (missing db property)')
|
||||||
else if (opts.share == null) reject('You must specify a set of shared types!')
|
else if (opts.connector.name == null) reject('You must specify db name! (missing db.name property)')
|
||||||
else {
|
else if (opts.share == null) reject('You must specify a set of shared types!')
|
||||||
var yconfig = new YConfig(opts)
|
else {
|
||||||
yconfig.db.whenUserIdSet(function () {
|
var yconfig = new YConfig(opts)
|
||||||
yconfig.init(function () {
|
yconfig.db.whenUserIdSet(function () {
|
||||||
resolve(yconfig)
|
yconfig.init(function () {
|
||||||
|
resolve(yconfig)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
}
|
}).catch(reject)
|
||||||
})
|
}, 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3174,6 +3494,9 @@ class YConfig {
|
|||||||
for (var propertyname in opts.share) {
|
for (var propertyname in opts.share) {
|
||||||
var typeConstructor = opts.share[propertyname].split('(')
|
var typeConstructor = opts.share[propertyname].split('(')
|
||||||
var typeName = typeConstructor.splice(0, 1)
|
var typeName = typeConstructor.splice(0, 1)
|
||||||
|
var type = Y[typeName]
|
||||||
|
var typedef = type.typeDefinition
|
||||||
|
var id = ['_', typedef.struct + '_' + typeName + '_' + propertyname + '_' + typeConstructor]
|
||||||
var args = []
|
var args = []
|
||||||
if (typeConstructor.length === 1) {
|
if (typeConstructor.length === 1) {
|
||||||
try {
|
try {
|
||||||
@@ -3181,11 +3504,13 @@ class YConfig {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error('Was not able to parse type definition! (share.' + propertyname + ')')
|
throw new Error('Was not able to parse type definition! (share.' + propertyname + ')')
|
||||||
}
|
}
|
||||||
|
if (type.typeDefinition.parseArguments == null) {
|
||||||
|
throw new Error(typeName + ' does not expect arguments!')
|
||||||
|
} else {
|
||||||
|
args = typedef.parseArguments(args[0])[1]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var type = Y[typeName]
|
share[propertyname] = yield* this.store.initType.call(this, id, args)
|
||||||
var typedef = type.typeDefinition
|
|
||||||
var id = ['_', typedef.struct + '_' + typeName + '_' + propertyname + '_' + typeConstructor]
|
|
||||||
share[propertyname] = yield* this.createType(type.apply(typedef, args), id)
|
|
||||||
}
|
}
|
||||||
this.store.whenTransactionsFinished()
|
this.store.whenTransactionsFinished()
|
||||||
.then(callback)
|
.then(callback)
|
||||||
|
|||||||
Reference in New Issue
Block a user