summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/models/note.js177
-rw-r--r--lib/realtime.js101
2 files changed, 177 insertions, 101 deletions
diff --git a/lib/models/note.js b/lib/models/note.js
index d1c073e9..17988f74 100644
--- a/lib/models/note.js
+++ b/lib/models/note.js
@@ -11,11 +11,16 @@ var shortId = require('shortid');
var Sequelize = require("sequelize");
var async = require('async');
var moment = require('moment');
+var DiffMatchPatch = require('diff-match-patch');
+var dmp = new DiffMatchPatch();
// core
var config = require("../config.js");
var logger = require("../logger.js");
+//ot
+var ot = require("../ot/index.js");
+
// permission types
var permissionTypes = ["freely", "editable", "locked", "private"];
@@ -115,6 +120,7 @@ module.exports = function (sequelize, DataTypes) {
var fsModifiedTime = moment(fs.statSync(filePath).mtime);
var dbModifiedTime = moment(note.lastchangeAt || note.createdAt);
var body = fs.readFileSync(filePath, 'utf8');
+ var contentLength = body.length;
var title = Note.parseNoteTitle(body);
body = LZString.compressToBase64(body);
title = LZString.compressToBase64(title);
@@ -126,7 +132,20 @@ module.exports = function (sequelize, DataTypes) {
}).then(function (note) {
sequelize.models.Revision.saveNoteRevision(note, function (err, revision) {
if (err) return _callback(err, null);
- return callback(null, note.id);
+ // update authorship on after making revision of docs
+ var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch));
+ var operations = Note.transformPatchToOperations(patch, contentLength);
+ var authorship = note.authorship ? JSON.parse(LZString.decompressFromBase64(note.authorship)) : [];
+ for (var i = 0; i < operations.length; i++) {
+ authorship = Note.updateAuthorshipByOperation(operations[i], null, authorship);
+ }
+ note.update({
+ authorship: authorship
+ }).then(function (note) {
+ return callback(null, note.id);
+ }).catch(function (err) {
+ return _callback(err, null);
+ });
});
}).catch(function (err) {
return _callback(err, null);
@@ -247,6 +266,162 @@ module.exports = function (sequelize, DataTypes) {
_meta.slideOptions = meta.slideOptions;
}
return _meta;
+ },
+ updateAuthorshipByOperation: function (operation, userId, authorships) {
+ var index = 0;
+ 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;
+ }
+ }
+ return authorships;
+ },
+ transformPatchToOperations: function (patch, contentLength) {
+ var operations = [];
+ if (patch.length > 0) {
+ // calculate original content length
+ for (var j = patch.length - 1; j >= 0; j--) {
+ var p = patch[j];
+ for (var i = 0; i < p.diffs.length; i++) {
+ var diff = p.diffs[i];
+ switch(diff[0]) {
+ case 1: // insert
+ contentLength -= diff[1].length;
+ break;
+ case -1: // delete
+ contentLength += diff[1].length;
+ break;
+ }
+ }
+ }
+ // generate operations
+ var bias = 0;
+ var lengthBias = 0;
+ for (var j = 0; j < patch.length; j++) {
+ var operation = [];
+ var p = patch[j];
+ var currIndex = p.start1;
+ var currLength = contentLength - bias;
+ for (var i = 0; i < p.diffs.length; i++) {
+ var diff = p.diffs[i];
+ switch(diff[0]) {
+ case 0: // retain
+ if (i == 0) // first
+ operation.push(currIndex + diff[1].length);
+ else if (i != p.diffs.length - 1) // mid
+ operation.push(diff[1].length);
+ else // last
+ operation.push(currLength + lengthBias - currIndex);
+ currIndex += diff[1].length;
+ break;
+ case 1: // insert
+ operation.push(diff[1]);
+ lengthBias += diff[1].length;
+ currIndex += diff[1].length;
+ break;
+ case -1: // delete
+ operation.push(-diff[1].length);
+ bias += diff[1].length;
+ currIndex += diff[1].length;
+ break;
+ }
+ }
+ operations.push(operation);
+ }
+ }
+ return operations;
}
},
hooks: {
diff --git a/lib/realtime.js b/lib/realtime.js
index 0c68a256..9c3d5b2e 100644
--- a/lib/realtime.js
+++ b/lib/realtime.js
@@ -642,106 +642,7 @@ function operationCallback(socket, operation) {
}
}
// 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;
+ note.authorship = models.Note.updateAuthorshipByOperation(operation, userId, note.authorship);
}
function connection(socket) {