Skip to content

Commit d23ced1

Browse files
committed
Update to move authorship calculation code to note model and support update authorship after making revision of docs
1 parent a5e6b5d commit d23ced1

2 files changed

Lines changed: 177 additions & 101 deletions

File tree

lib/models/note.js

Lines changed: 176 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@ var shortId = require('shortid');
1111
var Sequelize = require("sequelize");
1212
var async = require('async');
1313
var moment = require('moment');
14+
var DiffMatchPatch = require('diff-match-patch');
15+
var dmp = new DiffMatchPatch();
1416

1517
// core
1618
var config = require("../config.js");
1719
var logger = require("../logger.js");
1820

21+
//ot
22+
var ot = require("../ot/index.js");
23+
1924
// permission types
2025
var permissionTypes = ["freely", "editable", "locked", "private"];
2126

@@ -115,6 +120,7 @@ module.exports = function (sequelize, DataTypes) {
115120
var fsModifiedTime = moment(fs.statSync(filePath).mtime);
116121
var dbModifiedTime = moment(note.lastchangeAt || note.createdAt);
117122
var body = fs.readFileSync(filePath, 'utf8');
123+
var contentLength = body.length;
118124
var title = Note.parseNoteTitle(body);
119125
body = LZString.compressToBase64(body);
120126
title = LZString.compressToBase64(title);
@@ -126,7 +132,20 @@ module.exports = function (sequelize, DataTypes) {
126132
}).then(function (note) {
127133
sequelize.models.Revision.saveNoteRevision(note, function (err, revision) {
128134
if (err) return _callback(err, null);
129-
return callback(null, note.id);
135+
// update authorship on after making revision of docs
136+
var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch));
137+
var operations = Note.transformPatchToOperations(patch, contentLength);
138+
var authorship = note.authorship ? JSON.parse(LZString.decompressFromBase64(note.authorship)) : [];
139+
for (var i = 0; i < operations.length; i++) {
140+
authorship = Note.updateAuthorshipByOperation(operations[i], null, authorship);
141+
}
142+
note.update({
143+
authorship: authorship
144+
}).then(function (note) {
145+
return callback(null, note.id);
146+
}).catch(function (err) {
147+
return _callback(err, null);
148+
});
130149
});
131150
}).catch(function (err) {
132151
return _callback(err, null);
@@ -247,6 +266,162 @@ module.exports = function (sequelize, DataTypes) {
247266
_meta.slideOptions = meta.slideOptions;
248267
}
249268
return _meta;
269+
},
270+
updateAuthorshipByOperation: function (operation, userId, authorships) {
271+
var index = 0;
272+
var timestamp = Date.now();
273+
for (var i = 0; i < operation.length; i++) {
274+
var op = operation[i];
275+
if (ot.TextOperation.isRetain(op)) {
276+
index += op;
277+
} else if (ot.TextOperation.isInsert(op)) {
278+
var opStart = index;
279+
var opEnd = index + op.length;
280+
var inserted = false;
281+
// authorship format: [userId, startPos, endPos, createdAt, updatedAt]
282+
if (authorships.length <= 0) authorships.push([userId, opStart, opEnd, timestamp, timestamp]);
283+
else {
284+
for (var j = 0; j < authorships.length; j++) {
285+
var authorship = authorships[j];
286+
if (!inserted) {
287+
var nextAuthorship = authorships[j + 1] || -1;
288+
if (nextAuthorship != -1 && nextAuthorship[1] >= opEnd || j >= authorships.length - 1) {
289+
if (authorship[1] < opStart && authorship[2] > opStart) {
290+
// divide
291+
var postLength = authorship[2] - opStart;
292+
authorship[2] = opStart;
293+
authorship[4] = timestamp;
294+
authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]);
295+
authorships.splice(j + 2, 0, [authorship[0], opEnd, opEnd + postLength, authorship[3], timestamp]);
296+
j += 2;
297+
inserted = true;
298+
} else if (authorship[1] >= opStart) {
299+
authorships.splice(j, 0, [userId, opStart, opEnd, timestamp, timestamp]);
300+
j += 1;
301+
inserted = true;
302+
} else if (authorship[2] <= opStart) {
303+
authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]);
304+
j += 1;
305+
inserted = true;
306+
}
307+
}
308+
}
309+
if (authorship[1] >= opStart) {
310+
authorship[1] += op.length;
311+
authorship[2] += op.length;
312+
}
313+
}
314+
}
315+
index += op.length;
316+
} else if (ot.TextOperation.isDelete(op)) {
317+
var opStart = index;
318+
var opEnd = index - op;
319+
if (operation.length == 1) {
320+
authorships = [];
321+
} else if (authorships.length > 0) {
322+
for (var j = 0; j < authorships.length; j++) {
323+
var authorship = authorships[j];
324+
if (authorship[1] >= opStart && authorship[1] <= opEnd && authorship[2] >= opStart && authorship[2] <= opEnd) {
325+
authorships.splice(j, 1);
326+
j -= 1;
327+
} else if (authorship[1] < opStart && authorship[1] < opEnd && authorship[2] > opStart && authorship[2] > opEnd) {
328+
authorship[2] += op;
329+
authorship[4] = timestamp;
330+
} else if (authorship[2] >= opStart && authorship[2] <= opEnd) {
331+
authorship[2] = opStart;
332+
authorship[4] = timestamp;
333+
} else if (authorship[1] >= opStart && authorship[1] <= opEnd) {
334+
authorship[1] = opEnd;
335+
authorship[4] = timestamp;
336+
}
337+
if (authorship[1] >= opEnd) {
338+
authorship[1] += op;
339+
authorship[2] += op;
340+
}
341+
}
342+
}
343+
index += op;
344+
}
345+
}
346+
// merge
347+
for (var j = 0; j < authorships.length; j++) {
348+
var authorship = authorships[j];
349+
for (var k = j + 1; k < authorships.length; k++) {
350+
var nextAuthorship = authorships[k];
351+
if (nextAuthorship && authorship[0] === nextAuthorship[0] && authorship[2] === nextAuthorship[1]) {
352+
var minTimestamp = Math.min(authorship[3], nextAuthorship[3]);
353+
var maxTimestamp = Math.max(authorship[3], nextAuthorship[3]);
354+
authorships.splice(j, 1, [authorship[0], authorship[1], nextAuthorship[2], minTimestamp, maxTimestamp]);
355+
authorships.splice(k, 1);
356+
j -= 1;
357+
break;
358+
}
359+
}
360+
}
361+
// clear
362+
for (var j = 0; j < authorships.length; j++) {
363+
var authorship = authorships[j];
364+
if (!authorship[0]) {
365+
authorships.splice(j, 1);
366+
j -= 1;
367+
}
368+
}
369+
return authorships;
370+
},
371+
transformPatchToOperations: function (patch, contentLength) {
372+
var operations = [];
373+
if (patch.length > 0) {
374+
// calculate original content length
375+
for (var j = patch.length - 1; j >= 0; j--) {
376+
var p = patch[j];
377+
for (var i = 0; i < p.diffs.length; i++) {
378+
var diff = p.diffs[i];
379+
switch(diff[0]) {
380+
case 1: // insert
381+
contentLength -= diff[1].length;
382+
break;
383+
case -1: // delete
384+
contentLength += diff[1].length;
385+
break;
386+
}
387+
}
388+
}
389+
// generate operations
390+
var bias = 0;
391+
var lengthBias = 0;
392+
for (var j = 0; j < patch.length; j++) {
393+
var operation = [];
394+
var p = patch[j];
395+
var currIndex = p.start1;
396+
var currLength = contentLength - bias;
397+
for (var i = 0; i < p.diffs.length; i++) {
398+
var diff = p.diffs[i];
399+
switch(diff[0]) {
400+
case 0: // retain
401+
if (i == 0) // first
402+
operation.push(currIndex + diff[1].length);
403+
else if (i != p.diffs.length - 1) // mid
404+
operation.push(diff[1].length);
405+
else // last
406+
operation.push(currLength + lengthBias - currIndex);
407+
currIndex += diff[1].length;
408+
break;
409+
case 1: // insert
410+
operation.push(diff[1]);
411+
lengthBias += diff[1].length;
412+
currIndex += diff[1].length;
413+
break;
414+
case -1: // delete
415+
operation.push(-diff[1].length);
416+
bias += diff[1].length;
417+
currIndex += diff[1].length;
418+
break;
419+
}
420+
}
421+
operations.push(operation);
422+
}
423+
}
424+
return operations;
250425
}
251426
},
252427
hooks: {

lib/realtime.js

Lines changed: 1 addition & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -642,106 +642,7 @@ function operationCallback(socket, operation) {
642642
}
643643
}
644644
// save authorship
645-
var index = 0;
646-
var authorships = note.authorship;
647-
var timestamp = Date.now();
648-
for (var i = 0; i < operation.length; i++) {
649-
var op = operation[i];
650-
if (ot.TextOperation.isRetain(op)) {
651-
index += op;
652-
} else if (ot.TextOperation.isInsert(op)) {
653-
var opStart = index;
654-
var opEnd = index + op.length;
655-
var inserted = false;
656-
// authorship format: [userId, startPos, endPos, createdAt, updatedAt]
657-
if (authorships.length <= 0) authorships.push([userId, opStart, opEnd, timestamp, timestamp]);
658-
else {
659-
for (var j = 0; j < authorships.length; j++) {
660-
var authorship = authorships[j];
661-
if (!inserted) {
662-
var nextAuthorship = authorships[j + 1] || -1;
663-
if (nextAuthorship != -1 && nextAuthorship[1] >= opEnd || j >= authorships.length - 1) {
664-
if (authorship[1] < opStart && authorship[2] > opStart) {
665-
// divide
666-
var postLength = authorship[2] - opStart;
667-
authorship[2] = opStart;
668-
authorship[4] = timestamp;
669-
authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]);
670-
authorships.splice(j + 2, 0, [authorship[0], opEnd, opEnd + postLength, authorship[3], timestamp]);
671-
j += 2;
672-
inserted = true;
673-
} else if (authorship[1] >= opStart) {
674-
authorships.splice(j, 0, [userId, opStart, opEnd, timestamp, timestamp]);
675-
j += 1;
676-
inserted = true;
677-
} else if (authorship[2] <= opStart) {
678-
authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]);
679-
j += 1;
680-
inserted = true;
681-
}
682-
}
683-
}
684-
if (authorship[1] >= opStart) {
685-
authorship[1] += op.length;
686-
authorship[2] += op.length;
687-
}
688-
}
689-
}
690-
index += op.length;
691-
} else if (ot.TextOperation.isDelete(op)) {
692-
var opStart = index;
693-
var opEnd = index - op;
694-
if (operation.length == 1) {
695-
authorships = [];
696-
} else if (authorships.length > 0) {
697-
for (var j = 0; j < authorships.length; j++) {
698-
var authorship = authorships[j];
699-
if (authorship[1] >= opStart && authorship[1] <= opEnd && authorship[2] >= opStart && authorship[2] <= opEnd) {
700-
authorships.splice(j, 1);
701-
j -= 1;
702-
} else if (authorship[1] < opStart && authorship[1] < opEnd && authorship[2] > opStart && authorship[2] > opEnd) {
703-
authorship[2] += op;
704-
authorship[4] = timestamp;
705-
} else if (authorship[2] >= opStart && authorship[2] <= opEnd) {
706-
authorship[2] = opStart;
707-
authorship[4] = timestamp;
708-
} else if (authorship[1] >= opStart && authorship[1] <= opEnd) {
709-
authorship[1] = opEnd;
710-
authorship[4] = timestamp;
711-
}
712-
if (authorship[1] >= opEnd) {
713-
authorship[1] += op;
714-
authorship[2] += op;
715-
}
716-
}
717-
}
718-
index += op;
719-
}
720-
}
721-
// merge
722-
for (var j = 0; j < authorships.length; j++) {
723-
var authorship = authorships[j];
724-
for (var k = j + 1; k < authorships.length; k++) {
725-
var nextAuthorship = authorships[k];
726-
if (nextAuthorship && authorship[0] === nextAuthorship[0] && authorship[2] === nextAuthorship[1]) {
727-
var minTimestamp = Math.min(authorship[3], nextAuthorship[3]);
728-
var maxTimestamp = Math.max(authorship[3], nextAuthorship[3]);
729-
authorships.splice(j, 1, [authorship[0], authorship[1], nextAuthorship[2], minTimestamp, maxTimestamp]);
730-
authorships.splice(k, 1);
731-
j -= 1;
732-
break;
733-
}
734-
}
735-
}
736-
// clear
737-
for (var j = 0; j < authorships.length; j++) {
738-
var authorship = authorships[j];
739-
if (!authorship[0]) {
740-
authorships.splice(j, 1);
741-
j -= 1;
742-
}
743-
}
744-
note.authorship = authorships;
645+
note.authorship = models.Note.updateAuthorshipByOperation(operation, userId, note.authorship);
745646
}
746647

747648
function connection(socket) {

0 commit comments

Comments
 (0)