(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= 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])