summaryrefslogtreecommitdiff
path: root/lib/realtime.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/realtime.js')
-rw-r--r--lib/realtime.js156
1 files changed, 154 insertions, 2 deletions
diff --git a/lib/realtime.js b/lib/realtime.js
index 0e9af740..68089570 100644
--- a/lib/realtime.js
+++ b/lib/realtime.js
@@ -151,6 +151,7 @@ function finishUpdateNote(note, _note, callback) {
var values = {
title: title,
content: body,
+ authorship: LZString.compressToBase64(JSON.stringify(note.authorship)),
lastchangeuserId: note.lastchangeuser,
lastchangeAt: Date.now()
};
@@ -404,6 +405,13 @@ function startConnection(socket) {
}, {
model: models.User,
as: "lastchangeuser"
+ }, {
+ model: models.Author,
+ as: "authors",
+ include: [{
+ model: models.User,
+ as: "user"
+ }]
}];
models.Note.findOne({
@@ -424,7 +432,19 @@ function startConnection(socket) {
var body = LZString.decompressFromBase64(note.content);
var createtime = note.createdAt;
var updatetime = note.lastchangeAt;
- var server = new ot.EditorSocketIOServer(body, [], noteId, ifMayEdit);
+ var server = new ot.EditorSocketIOServer(body, [], noteId, ifMayEdit, operationCallback);
+
+ var authors = {};
+ for (var i = 0; i < note.authors.length; i++) {
+ var author = note.authors[i];
+ var profile = models.User.parseProfile(author.user.profile);
+ authors[author.userId] = {
+ userid: author.userId,
+ color: author.color,
+ photo: profile.photo,
+ name: profile.name
+ };
+ }
notes[noteId] = {
id: noteId,
@@ -437,7 +457,9 @@ function startConnection(socket) {
users: {},
createtime: moment(createtime).valueOf(),
updatetime: moment(updatetime).valueOf(),
- server: server
+ server: server,
+ authors: authors,
+ authorship: note.authorship ? JSON.parse(LZString.decompressFromBase64(note.authorship)) : []
};
return finishConnection(socket, notes[noteId], users[socket.id]);
@@ -581,6 +603,136 @@ function ifMayEdit(socket, callback) {
return callback(mayEdit);
}
+function operationCallback(socket, operation) {
+ var noteId = socket.noteId;
+ if (!noteId || !notes[noteId]) return;
+ var note = notes[noteId];
+ var userId = null;
+ // save authors
+ if (socket.request.user && socket.request.user.logged_in) {
+ var socketId = socket.id;
+ var user = users[socketId];
+ userId = socket.request.user.id;
+ if (!note.authors[userId]) {
+ models.Author.create({
+ noteId: noteId,
+ userId: userId,
+ color: users[socketId].color
+ }).then(function (author) {
+ note.authors[author.userId] = {
+ userid: author.userId,
+ color: author.color,
+ photo: user.photo,
+ name: user.name
+ };
+ }).catch(function (err) {
+ return logger.error('operation callback failed: ' + err);
+ });
+ }
+ }
+ // save authorship
+ var index = 0;
+ var authorships = note.authorship;
+ var timestamp = Date.now();
+ for (var i = 0; i < operation.length; i++) {
+ var op = operation[i];
+ if (ot.TextOperation.isRetain(op)) {
+ index += op;
+ } else if (ot.TextOperation.isInsert(op)) {
+ var opStart = index;
+ var opEnd = index + op.length;
+ var inserted = false;
+ // authorship format: [userId, startPos, endPos, createdAt, updatedAt]
+ if (authorships.length <= 0) authorships.push([userId, opStart, opEnd, timestamp, timestamp]);
+ else {
+ for (var j = 0; j < authorships.length; j++) {
+ var authorship = authorships[j];
+ if (!inserted) {
+ var nextAuthorship = authorships[j + 1] || -1;
+ if (nextAuthorship != -1 && nextAuthorship[1] >= opEnd || j >= authorships.length - 1) {
+ if (authorship[1] < opStart && authorship[2] > opStart) {
+ // divide
+ var postLength = authorship[2] - opStart;
+ authorship[2] = opStart;
+ authorship[4] = timestamp;
+ authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]);
+ authorships.splice(j + 2, 0, [authorship[0], opEnd, opEnd + postLength, authorship[3], timestamp]);
+ j += 2;
+ inserted = true;
+ } else if (authorship[1] >= opStart) {
+ authorships.splice(j, 0, [userId, opStart, opEnd, timestamp, timestamp]);
+ j += 1;
+ inserted = true;
+ } else if (authorship[2] <= opStart) {
+ authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]);
+ j += 1;
+ inserted = true;
+ }
+ }
+ }
+ if (authorship[1] >= opStart) {
+ authorship[1] += op.length;
+ authorship[2] += op.length;
+ }
+ }
+ }
+ index += op.length;
+ } else if (ot.TextOperation.isDelete(op)) {
+ var opStart = index;
+ var opEnd = index - op;
+ if (operation.length == 1) {
+ authorships = [];
+ } else if (authorships.length > 0) {
+ for (var j = 0; j < authorships.length; j++) {
+ var authorship = authorships[j];
+ if (authorship[1] >= opStart && authorship[1] <= opEnd && authorship[2] >= opStart && authorship[2] <= opEnd) {
+ authorships.splice(j, 1);
+ j -= 1;
+ } else if (authorship[1] < opStart && authorship[1] < opEnd && authorship[2] > opStart && authorship[2] > opEnd) {
+ authorship[2] += op;
+ authorship[4] = timestamp;
+ } else if (authorship[2] >= opStart && authorship[2] <= opEnd) {
+ authorship[2] = opStart;
+ authorship[4] = timestamp;
+ } else if (authorship[1] >= opStart && authorship[1] <= opEnd) {
+ authorship[1] = opEnd;
+ authorship[4] = timestamp;
+ }
+ if (authorship[1] >= opEnd) {
+ authorship[1] += op;
+ authorship[2] += op;
+ }
+ }
+ }
+ index += op;
+ }
+ }
+ // merge
+ for (var j = 0; j < authorships.length; j++) {
+ var authorship = authorships[j];
+ for (var k = j + 1; k < authorships.length; k++) {
+ var nextAuthorship = authorships[k];
+ if (nextAuthorship && authorship[0] === nextAuthorship[0] && authorship[2] === nextAuthorship[1]) {
+ var minTimestamp = Math.min(authorship[3], nextAuthorship[3]);
+ var maxTimestamp = Math.max(authorship[3], nextAuthorship[3]);
+ authorships.splice(j, 1, [authorship[0], authorship[1], nextAuthorship[2], minTimestamp, maxTimestamp]);
+ authorships.splice(k, 1);
+ j -= 1;
+ break;
+ }
+ }
+ }
+ // clear
+ for (var j = 0; j < authorships.length; j++) {
+ var authorship = authorships[j];
+ if (!authorship[0]) {
+ authorships.splice(j, 1);
+ j -= 1;
+ }
+ }
+ note.authorship = authorships;
+}
+
function connection(socket) {
if (config.maintenance) return;
parseNoteIdFromSocket(socket, function (err, noteId) {