Deploy 0.8.0

This commit is contained in:
Kevin Jahns
2016-01-15 00:01:56 +01:00
parent ff006c92d7
commit 0ec83aa431
97 changed files with 37273 additions and 35 deletions

View File

@@ -0,0 +1,36 @@
{
"name": "y-richtext",
"version": "0.7.5",
"authors": [
"corentin.cadiou@linagora.com",
"kevin.jahns@rwth-aachen.de"
],
"description": "Rich Text type for Yjs",
"keywords": [
"webrtc",
"text",
"edition",
"collaborative",
"rich text"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {},
"homepage": "https://github.com/y-js/y-richtext",
"_release": "0.7.5",
"_resolution": {
"type": "version",
"tag": "v0.7.5",
"commit": "5a6056fa835fab93e6add70537dde4cb67e8428c"
},
"_source": "git://github.com/y-js/y-richtext.git",
"_target": "~0.7.5",
"_originalSource": "y-richtext",
"_direct": true
}

View File

@@ -0,0 +1,25 @@
The MIT License (MIT)
Copyright (c) 2015
- Corentin Cadiou <corentin.cadiou@linagora.com>
- Kevin Jahns <kevin.jahns@rwth-aachen.de>
- Linagora
- Veeting.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,59 @@
# Rich Text type for [Yjs](https://github.com/y-js/richtext)
This type strongly resembles the [rich text](https://github.com/ottypes/rich-text) format for operational transformation. Under the hood, however, several mechanisms ensure that the intentions of your changes are preserved. Furthermore, you can transform the actions on the document in the rich text format back and forth, and, therefore, you can bind this type to any rich text editor that supports the widely used rich text format.
## Use it!
Retrieve this with bower or npm.
##### Bower
```
bower install y-richtext --save
```
and include the js library.
```
<script src="./bower_components/y-richtext/y-richtext.js"></script>
```
##### NPM
```
npm install y-richtext --save
```
and put it on the `Y` object.
```
Y.RichText = require("y-richtext");
```
### RichText Object
##### Reference
* Create
```
var yrichtext = new Y.RichText()
```
* Create
```
var yrichtext = new Y.RichText(ot_delta)
```
* .bind(editor)
* Bind this type to an rich text editor. (Currently, only QuillJs is supported)
# A note on intention preservation
This type has several mechanisms to ensure that the intention of your actions are preserved. For example:
* If two users fix a word concurrently, only one change will prevail. A classical example is that two users want to correct the word "missplled". If two users correct it at the same time (or they merge after they corrected it offline), the result in operation transformation algorithms would be "misspeelled". This type will ensure that the result is "misspelled"
* When a user inserts content *c* after a set of content *C_left*, and before a set of content *C_right*, then *C_left* will be always to the left of c, and *C_right* will be always to the right of *c*. This property will also hold when content is deleted or when a deletion is undone.
## Contribution
We thank [Veeting](https://www.veeting.com/) and [Linagora](https://www.linagora.com/) who sponsored this work, and agreed to publish it as Open Source.
## License
Yjs and the RichText type are licensed under the [MIT License](./LICENSE.txt).
- Corentin Cadiou <corentin.cadiou@linagora.com>
- Kevin Jahns <kevin.jahns@rwth-aachen.de>

View File

@@ -0,0 +1,25 @@
{
"name": "y-richtext",
"version": "0.7.6",
"authors": [
"corentin.cadiou@linagora.com",
"kevin.jahns@rwth-aachen.de"
],
"description": "Rich Text type for Yjs",
"keywords": [
"webrtc",
"text",
"edition",
"collaborative",
"rich text"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {}
}

View File

@@ -0,0 +1,503 @@
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global Y */
'use strict'
function extend (Y) {
Y.requestModules(['Array']).then(function () {
class YRichtext extends Y.Array['class'] {
constructor (os, _model, idArray, valArray) {
super(os, _model, idArray, valArray)
this._length = 0
this.instances = []
for (var i = 0, v = valArray[i]; i < valArray.length; i++) {
if (typeof v === 'string') {
this._length++
}
}
var self = this
this.observe(function (events) {
for (var i = 0, event = events[i]; i < events.length; i++) {
if (event.type === 'insert') {
if (typeof event.value === 'string') {
self._length++
}
} else if (event.type === 'delete') {
if (typeof event.value === 'string') {
self._length--
}
}
}
})
}
get length () {
return this._length
}
toString () {
return this.valArray.map(function (v) {
if (typeof v === 'string') {
return v
}
}).join('')
}
toOTOps () {
var ops = []
var op = {
insert: [],
attributes: {}
}
function createNewOp () {
var attrs = {}
// copy attributes
for (var name in op.attributes) {
attrs[name] = op.attributes[name]
}
op = {
insert: [],
attributes: attrs
}
}
var i = 0
for (; i < this.valArray.length; i++) {
let v = this.valArray[i]
if (v.constructor === Array) {
if (op.insert.length > 0) {
op.insert = op.insert.join('')
ops.push(op)
createNewOp()
}
if (v[1] === null) {
delete op.attributes[v[0]]
} else {
op.attributes[v[0]] = v[1]
}
} else {
op.insert.push(v)
}
}
if (op.insert.length > 0) {
op.insert = op.insert.join('')
ops.push(op)
}
return ops
}
insert (pos, content) {
var curPos = 0
var selection = {}
for (var i = 0; i < this.valArray.length; i++) {
if (curPos === pos) {
break
}
var v = this.valArray[i]
if (typeof v === 'string') {
curPos++
} else if (v.constructor === Array) {
if (v[1] === null) {
delete selection[v[0]]
} else {
selection[v[0]] = v[1]
}
}
}
super.insert(i, content.split(''))
return selection
}
delete (pos, length) {
/*
let x = to be deleted string
let s = some string
let * = some selection
E.g.
sss*s***x*xxxxx***xx*x**ss*s
|---delete-range--|
delStart delEnd
We'll check the following
* is it possible to delete some of the selections?
1. a dominating selection to the right could be the same as the selection (curSel) to delStart
2. a selections could be overwritten by another selection to the right
*/
var curPos = 0
var curSel = {}
var endPos = pos + length
if (length <= 0) return
var delStart // relative to valArray
var delEnd // ..
var v, i // helper variable for elements of valArray
for (delStart = 0, v = this.valArray[delStart]; curPos < pos && delStart < this.valArray.length; v = this.valArray[++delStart]) {
if (typeof v === 'string') {
curPos++
} else if (v.constructor === Array) {
curSel[v[0]] = v[1]
}
}
for (delEnd = delStart, v = this.valArray[delEnd]; curPos < endPos && delEnd < this.valArray.length; v = this.valArray[++delEnd]) {
if (typeof v === 'string') {
curPos++
}
}
if (delEnd === this.valArray.length) {
// yay, you can delete everything without checking
for (i = delEnd - 1, v = this.valArray[i]; i >= delStart; v = this.valArray[--i]) {
super.delete(i, 1)
}
} else {
if (typeof v === 'string') {
delEnd--
}
var rightSel = {}
for (i = delEnd, v = this.valArray[i]; i >= delStart; v = this.valArray[--i]) {
if (v.constructor === Array) {
if (rightSel[v[0]] === undefined) {
if (v[1] === curSel[v[0]]) {
// case 1.
super.delete(i, 1)
}
rightSel[v[0]] = v[1]
} else {
// case 2.
super.delete(i, 1)
}
} else if (typeof v === 'string') {
// always delete the strings
super.delete(i, 1)
}
}
}
}
/*
1. get selection attributes from position $from
(name it antiAttrs, and we'll use it to make sure that selection ends in antiAttrs)
2. Insert selection $attr, if necessary
3. Between from and to, we'll delete all selections that do not match $attr.
Furthermore, we'll update antiAttrs, if necessary
4. In the end well insert a selection that makes sure that selection($to) ends in antiAttrs
*/
select (from, to, attrName, attrValue) {
if (from == null || to == null || attrName == null || attrValue === undefined) {
throw new Error('You must define four parameters')
} else {
var step2i
var step2sel
var antiAttrs = [attrName, null]
var curPos = 0
var i = 0
// 1. compute antiAttrs
for (; i < this.valArray.length; i++) {
let v = this.valArray[i]
if (curPos === from) {
break
}
if (v.constructor === Array) {
if (v[0] === attrName) {
antiAttrs[1] = v[1]
}
} else if (typeof v === 'string') {
curPos++
}
}
// 2. Insert attr
if (antiAttrs[1] !== attrValue) {
// we'll execute this later
step2i = i
step2sel = [attrName, attrValue]
}
// 3. update antiAttrs, modify selection
var deletes = []
for (; i < this.valArray.length; i++) {
let v = this.valArray[i]
if (curPos === to) {
break
}
if (v.constructor === Array) {
if (v[0] === attrName) {
antiAttrs[1] = v[1]
deletes.push(i)
}
} else if (typeof v === 'string') {
curPos++
}
}
// actually delete the found selections
// also.. we have to delete from right to left (so that the positions dont change)
for (var j = deletes.length - 1; j >= 0; j--) {
var del = deletes[j]
super.delete(del, 1)
// update i, rel. to
if (del < i) {
i--
}
if (del < step2i) {
step2i--
}
}
// 4. Update selection to match antiAttrs
// never insert, if not necessary
// 1. when it is the last position ~ i < valArray.length)
// 2. when a similar attrName already exists between i and the next character
if (antiAttrs[1] !== attrValue && i < this.valArray.length) { // check 1.
var performStep4 = true
var v
for (j = i, v = this.valArray[j]; j < this.valArray.length && v.constructor === Array; v = this.valArray[++j]) {
if (v[0] === attrName) {
performStep4 = false // check 2.
if (v[1] === attrValue) {
super.delete(j, 1)
}
break
}
}
if (performStep4) {
var sel = [attrName, antiAttrs[1]]
super.insert(i, [sel])
}
}
if (step2i != null) {
super.insert(step2i, [step2sel])
// if there are some selections to the left of step2sel, delete them if possible
// * have same attribute name
// * no insert between step2sel and selection
for (j = step2i - 1, v = this.valArray[j]; j >= 0 && v.constructor === Array; v = this.valArray[--j]) {
if (v[0] === attrName) {
super.delete(j, 1)
}
}
}
}
}
bind (quill) {
this.instances.push(quill)
var self = this
// this function makes sure that either the
// quill event is executed, or the yjs observer is executed
var token = true
function mutualExcluse (f) {
if (token) {
token = false
try {
f()
} catch (e) {
token = true
throw new Error(e)
}
token = true
}
}
quill.setContents(this.toOTOps())
quill.on('text-change', function (delta) {
mutualExcluse(function () {
var pos = 0
var name // helper variable
for (var i = 0; i < delta.ops.length; i++) {
var op = delta.ops[i]
if (op.insert != null) {
var attrs = self.insert(pos, op.insert)
// create new selection
for (name in op.attributes) {
if (op.attributes[name] !== attrs[name]) {
self.select(pos, pos + op.insert.length, name, op.attributes[name])
}
}
// not-existence of an attribute in op.attributes denotes
// that we have to unselect (set to null)
for (name in attrs) {
if (op.attributes == null || attrs[name] !== op.attributes[name]) {
self.select(pos, pos + op.insert.length, name, null)
}
}
pos += op.insert.length
}
if (op.delete != null) {
self.delete(pos, op.delete)
}
if (op.retain != null) {
var afterRetain = pos + op.retain
if (afterRetain > self.length) {
var diff = afterRetain - self.length
var enters = ''
while (diff !== 0) {
diff--
enters += '\n'
}
for (name in op.attributes) {
quill.formatText(self.length, self.length + op.retain, name, null)
// quill.deleteText(self.length, self.length + op.retain)
}
quill.insertText(self.length, enters, op.attributes)
self.insert(self.length, enters)
}
for (name in op.attributes) {
self.select(pos, pos + op.retain, name, op.attributes[name])
}
pos = afterRetain
}
}
})
})
this.observe(function (events) {
mutualExcluse(function () {
var v // helper variable
var curSel // helper variable (current selection)
for (var i = 0; i < events.length; i++) {
var event = events[i]
if (event.type === 'insert') {
if (typeof event.value === 'string') {
var position = 0
var insertSel = {}
for (var l = event.index - 1; l >= 0; l--) {
v = self.valArray[l]
if (typeof v === 'string') {
position++
} else if (v.constructor === Array && typeof insertSel[v[0]] === 'undefined') {
insertSel[v[0]] = v[1]
}
}
quill.insertText(position, event.value, insertSel)
} else if (event.value.constructor === Array) {
// a new selection is created
// find left selection that matches newSel[0]
curSel = null
var newSel = event.value
// denotes the start position of the selection
// (without the selection objects)
var selectionStart = 0
for (var j = event.index - 1; j >= 0; j--) {
v = self.valArray[j]
if (v.constructor === Array) {
// check if v matches newSel
if (newSel[0] === v[0]) {
// found a selection
// update curSel and go to next step
curSel = v[1]
break
}
} else if (typeof v === 'string') {
selectionStart++
}
}
// make sure to decrement j, so we correctly compute selectionStart
for (; j >= 0; j--) {
v = self.valArray[j]
if (typeof v === 'string') {
selectionStart++
}
}
// either a selection was found {then curSel was updated}, or not (then curSel = null)
if (newSel[1] === curSel) {
// both are the same. not necessary to do anything
return
}
// now find out the range over which newSel has to be created
var selectionEnd = selectionStart
for (var k = event.index + 1; k < self.valArray.length; k++) {
v = self.valArray[k]
if (v.constructor === Array) {
if (v[0] === newSel[0]) {
// found another selection with same attr name
break
}
} else if (typeof v === 'string') {
selectionEnd++
}
}
// create a selection from selectionStart to selectionEnd
if (selectionStart !== selectionEnd) {
quill.formatText(selectionStart, selectionEnd, newSel[0], newSel[1])
}
}
} else if (event.type === 'delete') {
if (typeof event.value === 'string') { // TODO: see button. add || `event.length > 1`
// only if these conditions are true, we have to actually check if we have to delete sth.
// Then we have to check if between pos and pos + event.length are selections:
// delete till pos + (event.length - number of selections)
var pos = 0
for (var u = 0; u < event.index; u++) {
v = self.valArray[u]
if (typeof v === 'string') {
pos++
}
}
var delLength = event.length
/* TODO!!
they do not exist anymore.. so i can't query. you have to query over event.value(s) - but that not yet implemented
for (; i < event.index + event.length; i++) {
if (self.valArray[i].constructor === Array) {
delLength--
}
}*/
quill.deleteText(pos, pos + delLength)
} else if (event.value.constructor === Array) {
curSel = null
var from = 0
var x
for (x = event.index - 1; x >= 0; x--) {
v = self.valArray[x]
if (v.constructor === Array) {
if (v[0] === event.value[0]) {
curSel = v[1]
break
}
} else if (typeof v === 'string') {
from++
}
}
for (; x >= 0; v = self.valArray[--x]) {
if (typeof v === 'string') {
from++
}
}
var to = from
for (x = event.index; x < self.valArray.length; x++) {
v = self.valArray[x]
if (v.constructor === Array) {
if (v[0] === event.value[0]) {
break
}
} else if (typeof v === 'string') {
to++
}
}
if (curSel !== event.value[1] && from !== to) {
quill.formatText(from, to, event.value[0], curSel)
}
}
}
}
quill.editor.checkUpdate()
})
})
}
* _changed () {
this.instances.forEach(function (quill) {
quill.editor.checkUpdate()
})
yield* Y.Array.class.prototype._changed.apply(this, arguments)
}
}
Y.extend('Richtext', new Y.utils.CustomType({
name: 'Richtext',
class: YRichtext,
struct: 'List',
initType: function * YTextInitializer (os, model) {
var valArray = []
var idArray = yield* Y.Struct.List.map.call(this, model, function (c) {
valArray.push(c.content)
return JSON.stringify(c.id)
})
return new YRichtext(os, model.id, idArray, valArray)
}
}))
})
}
module.exports = extend
if (typeof Y !== 'undefined') {
extend(Y)
}
},{}]},{},[1])

File diff suppressed because one or more lines are too long