227 lines
7.2 KiB
JavaScript
227 lines
7.2 KiB
JavaScript
var EventEmitter2, MultiCursor, Quill, dom, _,
|
|
__hasProp = {}.hasOwnProperty,
|
|
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
|
|
|
|
Quill = require('../quill');
|
|
|
|
EventEmitter2 = require('eventemitter2').EventEmitter2;
|
|
|
|
_ = Quill.require('lodash');
|
|
|
|
dom = Quill.require('dom');
|
|
|
|
MultiCursor = (function(_super) {
|
|
__extends(MultiCursor, _super);
|
|
|
|
MultiCursor.DEFAULTS = {
|
|
template: '<span class="cursor-flag"> <span class="cursor-name"></span> </span> <span class="cursor-caret"></span>',
|
|
timeout: 2500
|
|
};
|
|
|
|
MultiCursor.events = {
|
|
CURSOR_ADDED: 'cursor-addded',
|
|
CURSOR_MOVED: 'cursor-moved',
|
|
CURSOR_REMOVED: 'cursor-removed'
|
|
};
|
|
|
|
function MultiCursor(quill, options) {
|
|
this.quill = quill;
|
|
this.options = options;
|
|
this.cursors = {};
|
|
this.container = this.quill.addContainer('cursor-container', true);
|
|
this.quill.addStyles({
|
|
'.cursor-container': {
|
|
'position': 'absolute',
|
|
'left': '0',
|
|
'top': '0',
|
|
'z-index': '1000'
|
|
},
|
|
'.cursor': {
|
|
'margin-left': '-1px',
|
|
'position': 'absolute'
|
|
},
|
|
'.cursor-flag': {
|
|
'bottom': '100%',
|
|
'position': 'absolute',
|
|
'white-space': 'nowrap'
|
|
},
|
|
'.cursor-name': {
|
|
'display': 'inline-block',
|
|
'color': 'white',
|
|
'padding': '2px 8px'
|
|
},
|
|
'.cursor-caret': {
|
|
'height': '100%',
|
|
'position': 'absolute',
|
|
'width': '2px'
|
|
},
|
|
'.cursor.hidden .cursor-flag': {
|
|
'display': 'none'
|
|
},
|
|
'.cursor.top > .cursor-flag': {
|
|
'bottom': 'auto',
|
|
'top': '100%'
|
|
},
|
|
'.cursor.right > .cursor-flag': {
|
|
'right': '-2px'
|
|
}
|
|
});
|
|
this.quill.on(this.quill.constructor.events.TEXT_CHANGE, _.bind(this._applyDelta, this));
|
|
}
|
|
|
|
MultiCursor.prototype.clearCursors = function() {
|
|
_.each(_.keys(this.cursors), _.bind(this.removeCursor, this));
|
|
return this.cursors = {};
|
|
};
|
|
|
|
MultiCursor.prototype.moveCursor = function(userId, index) {
|
|
var cursor;
|
|
cursor = this.cursors[userId];
|
|
cursor.index = index;
|
|
dom(cursor.elem).removeClass('hidden');
|
|
clearTimeout(cursor.timer);
|
|
cursor.timer = setTimeout((function(_this) {
|
|
return function() {
|
|
dom(cursor.elem).addClass('hidden');
|
|
return cursor.timer = null;
|
|
};
|
|
})(this), this.options.timeout);
|
|
this._updateCursor(cursor);
|
|
return cursor;
|
|
};
|
|
|
|
MultiCursor.prototype.removeCursor = function(userId) {
|
|
var cursor;
|
|
cursor = this.cursors[userId];
|
|
this.emit(MultiCursor.events.CURSOR_REMOVED, cursor);
|
|
if (cursor != null) {
|
|
cursor.elem.parentNode.removeChild(cursor.elem);
|
|
}
|
|
return delete this.cursors[userId];
|
|
};
|
|
|
|
MultiCursor.prototype.setCursor = function(userId, index, name, color) {
|
|
var cursor;
|
|
if (this.cursors[userId] == null) {
|
|
this.cursors[userId] = cursor = {
|
|
userId: userId,
|
|
index: index,
|
|
color: color,
|
|
elem: this._buildCursor(name, color)
|
|
};
|
|
this.emit(MultiCursor.events.CURSOR_ADDED, cursor);
|
|
}
|
|
_.defer((function(_this) {
|
|
return function() {
|
|
return _this.moveCursor(userId, index);
|
|
};
|
|
})(this));
|
|
return this.cursors[userId];
|
|
};
|
|
|
|
MultiCursor.prototype.shiftCursors = function(index, length, authorId) {
|
|
if (authorId == null) {
|
|
authorId = null;
|
|
}
|
|
return _.each(this.cursors, (function(_this) {
|
|
return function(cursor, id) {
|
|
if (!(cursor && (cursor.index > index || cursor.userId === authorId))) {
|
|
return;
|
|
}
|
|
return cursor.index += Math.max(length, index - cursor.index);
|
|
};
|
|
})(this));
|
|
};
|
|
|
|
MultiCursor.prototype.update = function() {
|
|
return _.each(this.cursors, (function(_this) {
|
|
return function(cursor, id) {
|
|
if (cursor == null) {
|
|
return;
|
|
}
|
|
_this._updateCursor(cursor);
|
|
return true;
|
|
};
|
|
})(this));
|
|
};
|
|
|
|
MultiCursor.prototype._applyDelta = function(delta) {
|
|
var index;
|
|
index = 0;
|
|
_.each(delta.ops, (function(_this) {
|
|
return function(op) {
|
|
var length, _ref;
|
|
length = 0;
|
|
if (op.insert != null) {
|
|
length = op.insert.length || 1;
|
|
_this.shiftCursors(index, length, (_ref = op.attributes) != null ? _ref['author'] : void 0);
|
|
} else if (op["delete"] != null) {
|
|
_this.shiftCursors(index, -1 * op["delete"], null);
|
|
} else if (op.retain != null) {
|
|
_this.shiftCursors(index, 0, null);
|
|
length = op.retain;
|
|
}
|
|
return index += length;
|
|
};
|
|
})(this));
|
|
return this.update();
|
|
};
|
|
|
|
MultiCursor.prototype._buildCursor = function(name, color) {
|
|
var cursor, cursorCaret, cursorFlag, cursorName;
|
|
cursor = this.container.ownerDocument.createElement('span');
|
|
dom(cursor).addClass('cursor');
|
|
cursor.innerHTML = this.options.template;
|
|
cursorFlag = cursor.querySelector('.cursor-flag');
|
|
cursorName = cursor.querySelector('.cursor-name');
|
|
dom(cursorName).text(name);
|
|
cursorCaret = cursor.querySelector('.cursor-caret');
|
|
cursorCaret.style.backgroundColor = cursorName.style.backgroundColor = color;
|
|
this.container.appendChild(cursor);
|
|
return cursor;
|
|
};
|
|
|
|
MultiCursor.prototype._moveCursor = function(cursor, reference, side) {
|
|
var bounds, flag, win;
|
|
if (side == null) {
|
|
side = 'left';
|
|
}
|
|
win = dom(reference).window();
|
|
bounds = reference.getBoundingClientRect();
|
|
cursor.elem.style.top = bounds.top + win.pageYOffset + 'px';
|
|
cursor.elem.style.left = bounds[side] + 'px';
|
|
cursor.elem.style.height = bounds.height + 'px';
|
|
flag = cursor.elem.querySelector('.cursor-flag');
|
|
dom(cursor.elem).toggleClass('top', parseInt(cursor.elem.style.top) <= flag.offsetHeight).toggleClass('left', parseInt(cursor.elem.style.left) <= flag.offsetWidth).toggleClass('right', this.quill.root.offsetWidth - parseInt(cursor.elem.style.left) <= flag.offsetWidth);
|
|
return this.emit(MultiCursor.events.CURSOR_MOVED, cursor);
|
|
};
|
|
|
|
MultiCursor.prototype._updateCursor = function(cursor) {
|
|
var didSplit, guide, leaf, leftNode, offset, rightNode, _ref, _ref1;
|
|
this.quill.editor.checkUpdate();
|
|
_ref = this.quill.editor.doc.findLeafAt(cursor.index, true), leaf = _ref[0], offset = _ref[1];
|
|
guide = this.container.ownerDocument.createElement('span');
|
|
if (leaf != null) {
|
|
_ref1 = dom(leaf.node).split(offset), leftNode = _ref1[0], rightNode = _ref1[1], didSplit = _ref1[2];
|
|
dom(guide).text(dom.ZERO_WIDTH_NOBREAK_SPACE);
|
|
leaf.node.parentNode.insertBefore(guide, rightNode);
|
|
} else {
|
|
dom(guide).text(dom.NOBREAK_SPACE);
|
|
this.quill.root.appendChild(guide);
|
|
}
|
|
this._moveCursor(cursor, guide);
|
|
dom(guide).remove();
|
|
if (didSplit) {
|
|
dom(leaf.node.parentNode).normalize();
|
|
}
|
|
return this.quill.editor.selection.update(Quill.sources.SILENT);
|
|
};
|
|
|
|
return MultiCursor;
|
|
|
|
})(EventEmitter2);
|
|
|
|
Quill.registerModule('multi-cursor', MultiCursor);
|
|
|
|
module.exports = MultiCursor;
|