2016-02-23 15:41:22 +01:00

530 lines
19 KiB
JavaScript

(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, _content) {
super(os, _model, _content)
this.instances = []
}
_destroy () {
for (var i = this.instances.length - 1; i >= 0; i--) {
this.unbindQuill(this.instances[i].editor)
}
super._destroy()
}
get length () {
/*
TODO: I must not use observe to compute the length.
But since I inherit from Y.Array, I can't set observe
the changes at the right momet (for that I would require direct access to EventHandler).
This is the most elegant solution, for now.
But at some time you should re-write Y.Richtext more elegantly!!
*/
return this.toString().length
}
toString () {
return this._content.map(function (v) {
if (typeof v.val === 'string') {
return v.val
}
}).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._content.length; i++) {
let v = this._content[i].val
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._content.length; i++) {
if (curPos === pos) {
break
}
var v = this._content[i].val
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 _content
var delEnd // ..
var v, i // helper variable for elements of _content
for (delStart = 0; curPos < pos && delStart < this._content.length; delStart++) {
v = this._content[delStart].val
if (typeof v === 'string') {
curPos++
} else if (v.constructor === Array) {
curSel[v[0]] = v[1]
}
}
for (delEnd = delStart; curPos < endPos && delEnd < this._content.length; delEnd++) {
v = this._content[delEnd].val
if (typeof v === 'string') {
curPos++
}
}
if (delEnd === this._content.length) {
// yay, you can delete everything without checking
for (i = delEnd - 1; i >= delStart; i--) {
v = this._content[i].val
super.delete(i, 1)
}
} else {
if (typeof v === 'string') {
delEnd--
}
var rightSel = {}
for (i = delEnd; i >= delStart; i--) {
v = this._content[i].val
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._content.length; i++) {
let v = this._content[i].val
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._content.length; i++) {
let v = this._content[i].val
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 < _content.length)
// 2. when a similar attrName already exists between i and the next character
if (antiAttrs[1] !== attrValue && i < this._content.length) { // check 1.
var performStep4 = true
var v
for (j = i; j < this._content.length; j++) {
v = this._content[j].val
if (v.constructor !== Array) {
break
}
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; j >= 0; j--) {
v = this._content[j].val
if (v.constructor !== Array) {
break
}
if (v[0] === attrName) {
super.delete(j, 1)
}
}
}
}
}
bind () {
this.bindQuill.apply(this, arguments)
}
unbindQuill (quill) {
var i = this.instances.findIndex(function (binding) {
return binding.editor === quill
})
if (i >= 0) {
var binding = this.instances[i]
this.unobserve(binding.yCallback)
binding.editor.off('text-change', binding.quillCallback)
this.instances.splice(i, 1)
}
}
bindQuill (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())
function quillCallback (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) {
let additionalContent = quill.getText(self.length)
quill.insertText(self.length, additionalContent)
// quill.deleteText(self.length + additionalContent.length, quill.getLength())
for (name in op.attributes) {
quill.formatText(self.length + additionalContent.length, self.length + additionalContent.length * 2, name, null)
// quill.deleteText(self.length, self.length + op.retain)
}
self.insert(self.length, additionalContent)
// op.attributes = null
}
for (name in op.attributes) {
self.select(pos, pos + op.retain, name, op.attributes[name])
quill.formatText(pos, pos + op.retain, name, op.attributes[name])
}
pos = afterRetain
}
}
})
}
quill.on('text-change', quillCallback)
function yCallback (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._content[l].val
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._content[j].val
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._content[j].val
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._content.length; k++) {
v = self._content[k].val
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._content[u].val
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._content[i].val.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._content[x].val
if (v.constructor === Array) {
if (v[0] === event.value[0]) {
curSel = v[1]
break
}
} else if (typeof v === 'string') {
from++
}
}
for (; x >= 0; x--) {
v = self._content[x].val
if (typeof v === 'string') {
from++
}
}
var to = from
for (x = event.index; x < self._content.length; x++) {
v = self._content[x].val
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()
})
}
this.observe(yCallback)
this.instances.push({
editor: quill,
yCallback: yCallback,
quillCallback: quillCallback
})
}
* _changed () {
this.instances.forEach(function (instance) {
instance.editor.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 _content = yield* Y.Struct.List.map.call(this, model, function (c) {
return {
id: JSON.stringify(c.id),
val: c.content
}
})
return new YRichtext(os, model.id, _content)
}
}))
})
}
module.exports = extend
if (typeof Y !== 'undefined') {
extend(Y)
}
},{}]},{},[1])