@cocreate/crdt 1.18.12 → 1.18.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/FUNDING.yml +3 -3
- package/.github/workflows/automated.yml +95 -96
- package/.github/workflows/manual.yml +44 -44
- package/CHANGELOG.md +1623 -1616
- package/CONTRIBUTING.md +96 -96
- package/CoCreate.config.js +26 -26
- package/LICENSE +682 -682
- package/README.md +97 -97
- package/demo/index.html +38 -38
- package/docs/index.html +273 -273
- package/package.json +49 -49
- package/release.config.js +21 -21
- package/src/index.js +471 -471
- package/webpack.config.js +84 -84
package/src/index.js
CHANGED
|
@@ -1,471 +1,471 @@
|
|
|
1
|
-
/*globals config, atob, btoa, localStorage, CustomEvent*/
|
|
2
|
-
import crud from '@cocreate/crud-client';
|
|
3
|
-
import message from '@cocreate/message-client';
|
|
4
|
-
import uuid from '@cocreate/uuid';
|
|
5
|
-
import localStorage from '@cocreate/local-storage';
|
|
6
|
-
|
|
7
|
-
const docs = new Map();
|
|
8
|
-
const clientId = crud.socket.clientId || uuid.generate(12);
|
|
9
|
-
const checkedDb = new Map();
|
|
10
|
-
const isInit = new Map();
|
|
11
|
-
|
|
12
|
-
function init(data){
|
|
13
|
-
getText(data).then(value => {
|
|
14
|
-
data.value = value;
|
|
15
|
-
data.start = 0;
|
|
16
|
-
data['clientId'] = clientId;
|
|
17
|
-
localChange(data);
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function getDoc(data) {
|
|
22
|
-
try {
|
|
23
|
-
if (['_id', 'organization_id'].includes(data.name))
|
|
24
|
-
return
|
|
25
|
-
let docName = getDocName(data);
|
|
26
|
-
let doc = docs.get(docName);
|
|
27
|
-
|
|
28
|
-
if (!doc) {
|
|
29
|
-
docs.set(docName, new Map());
|
|
30
|
-
doc = docs.get(docName);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (!doc.has('changeLog')) {
|
|
34
|
-
let changeLog = [];
|
|
35
|
-
doc.set('undoLog', new Map())
|
|
36
|
-
doc.set('redoLog', new Map())
|
|
37
|
-
|
|
38
|
-
if (data.read != 'false') {
|
|
39
|
-
if (!data.newDocument) {
|
|
40
|
-
let response = await crud.readDocument({
|
|
41
|
-
collection: "crdt-transactions",
|
|
42
|
-
filter: {
|
|
43
|
-
query: [{
|
|
44
|
-
name: 'docName',
|
|
45
|
-
operator: "$eq",
|
|
46
|
-
value: docName
|
|
47
|
-
}]
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
if (response.document && response.document[0] && response.document[0].changeLog) {
|
|
51
|
-
changeLog = response.document[0].changeLog;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
doc.set('changeLog', changeLog);
|
|
55
|
-
await generateText(data, true);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
else if (!doc.has('text')){
|
|
59
|
-
await generateText(data, false);
|
|
60
|
-
}
|
|
61
|
-
return doc;
|
|
62
|
-
}
|
|
63
|
-
catch (e) {
|
|
64
|
-
console.log('Invalid param', e);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
String.prototype.customSplice = function (index, absIndex, string) {
|
|
69
|
-
return this.slice(0, index) + string+ this.slice(index + Math.abs(absIndex));
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
async function generateText(data, flag) {
|
|
73
|
-
try {
|
|
74
|
-
let doc = docs.get(getDocName(data))
|
|
75
|
-
|
|
76
|
-
let string = '';
|
|
77
|
-
let changeLog = doc.get('changeLog');
|
|
78
|
-
for (let change of changeLog) {
|
|
79
|
-
if (change || change !== null ) {
|
|
80
|
-
string = string.customSplice(change.start, change.length, change.value);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
if (string === '' && data.read !== 'false') {
|
|
84
|
-
string = await checkDb(data, flag);
|
|
85
|
-
}
|
|
86
|
-
doc.set('text', string);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
catch (e) {
|
|
90
|
-
console.error(e);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async function checkDb(data, flag) {
|
|
95
|
-
let { collection, document_id, name } = data;
|
|
96
|
-
if (checkedDb.get(`${collection}${document_id}${name}`)) return;
|
|
97
|
-
checkedDb.set(`${collection}${document_id}${name}`, true);
|
|
98
|
-
|
|
99
|
-
let string = ''
|
|
100
|
-
if (data.newDocument)
|
|
101
|
-
string = data.newDocument
|
|
102
|
-
else {
|
|
103
|
-
let response = await crud.readDocument({ collection, document: {_id: document_id, name}});
|
|
104
|
-
string = crud.getValueFromObject(response.document[0], name);
|
|
105
|
-
}
|
|
106
|
-
if (string && typeof string !== 'string')
|
|
107
|
-
string = ""
|
|
108
|
-
if (string && typeof string === 'string' && flag != false) {
|
|
109
|
-
data.value = string;
|
|
110
|
-
data.start = 0;
|
|
111
|
-
data.clientId = clientId;
|
|
112
|
-
insertChange(data);
|
|
113
|
-
}
|
|
114
|
-
return string || '';
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function insertChange(data, flag) {
|
|
118
|
-
let docName = getDocName(data);
|
|
119
|
-
let doc = docs.get(docName);
|
|
120
|
-
let changeLog = doc.get('changeLog');
|
|
121
|
-
if (!changeLog)
|
|
122
|
-
return
|
|
123
|
-
|
|
124
|
-
let type = 'insert';
|
|
125
|
-
if (data.start == undefined) return;
|
|
126
|
-
if (!data.value)
|
|
127
|
-
type = 'delete';
|
|
128
|
-
|
|
129
|
-
let change = {
|
|
130
|
-
datetime: data.datetime || new Date().toISOString(),
|
|
131
|
-
value: data.value || '',
|
|
132
|
-
start: data.start,
|
|
133
|
-
end: data.end,
|
|
134
|
-
length: data.length || 0,
|
|
135
|
-
clientId: data.clientId || clientId,
|
|
136
|
-
user_id: data.user_id || localStorage.getItem("user_id"),
|
|
137
|
-
type
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
let lastChange = changeLog[changeLog.length - 1];
|
|
141
|
-
if (lastChange && change.datetime && lastChange.datetime){
|
|
142
|
-
if (change.datetime < lastChange.datetime){
|
|
143
|
-
// TODO: insert change at index position and update start and end postions for every element after insert
|
|
144
|
-
console.log('requires changeLog rebuild');
|
|
145
|
-
}
|
|
146
|
-
if (flag != 'replace') {
|
|
147
|
-
if (lastChange && change.start == lastChange.start) {
|
|
148
|
-
let date1 = new Date(lastChange.datetime);
|
|
149
|
-
let date2 = new Date(change.datetime);
|
|
150
|
-
let diff = date2.getTime() - date1.getTime();
|
|
151
|
-
if (diff < 500) {
|
|
152
|
-
if (change.value.length == 1) {
|
|
153
|
-
change.start = lastChange.start + lastChange.value.length;
|
|
154
|
-
data.start = change.start;
|
|
155
|
-
}
|
|
156
|
-
if (change.length == 1) {
|
|
157
|
-
change.start = lastChange.start - lastChange.length;
|
|
158
|
-
data.start = change.start;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
if (!change || change == null ) {
|
|
165
|
-
console.log('null change')
|
|
166
|
-
return
|
|
167
|
-
}
|
|
168
|
-
let string = doc.get('text') || '';
|
|
169
|
-
if (change.length > 0)
|
|
170
|
-
change.removedValue = string.substring(change.start, change.length);
|
|
171
|
-
|
|
172
|
-
if (flag == 'replace') {
|
|
173
|
-
// TODO get current string and create new changeLog array then push new change
|
|
174
|
-
changeLog = [change];
|
|
175
|
-
doc.set('changeLog', changeLog)
|
|
176
|
-
} else {
|
|
177
|
-
changeLog.push(change);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
doc.set('text', string.customSplice(change.start, change.length, change.value));
|
|
181
|
-
string = doc.get('text');
|
|
182
|
-
|
|
183
|
-
if (!data.clientId){
|
|
184
|
-
data['datetime'] = change.datetime;
|
|
185
|
-
data['clientId'] = change.clientId;
|
|
186
|
-
|
|
187
|
-
broadcastChange(data);
|
|
188
|
-
localChange(data, string);
|
|
189
|
-
}
|
|
190
|
-
else
|
|
191
|
-
localChange(data, string);
|
|
192
|
-
|
|
193
|
-
if (data.clientId == clientId && data.save != "false")
|
|
194
|
-
persistChange(data);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function broadcastChange(data){
|
|
198
|
-
message.send({
|
|
199
|
-
room: "",
|
|
200
|
-
broadcastSender: 'false',
|
|
201
|
-
broadcastBrowser: 'once',
|
|
202
|
-
message: "crdt",
|
|
203
|
-
data
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function localChange(data, string) {
|
|
208
|
-
const localChange = new CustomEvent('cocreate-crdt-update', {
|
|
209
|
-
detail: { ...data, string },
|
|
210
|
-
});
|
|
211
|
-
window.dispatchEvent(localChange);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function persistChange(data) {
|
|
215
|
-
let docName = getDocName(data);
|
|
216
|
-
let doc = docs.get(docName);
|
|
217
|
-
let changeLog = doc.get('changeLog');
|
|
218
|
-
let text = doc.get('text');
|
|
219
|
-
let Data = {
|
|
220
|
-
collection: 'crdt-transactions',
|
|
221
|
-
document: {
|
|
222
|
-
_id: data.document_id,
|
|
223
|
-
docName,
|
|
224
|
-
changeLog,
|
|
225
|
-
text,
|
|
226
|
-
crud: {
|
|
227
|
-
collection: data.collection,
|
|
228
|
-
document_id: data.document_id,
|
|
229
|
-
name: data.name
|
|
230
|
-
}
|
|
231
|
-
},
|
|
232
|
-
upsert: true,
|
|
233
|
-
namespace: data.namespace,
|
|
234
|
-
room: data.room,
|
|
235
|
-
broadcast: data.broadcast,
|
|
236
|
-
broadcastSender: data.broadcastSender,
|
|
237
|
-
metadata: 'crdt-change'
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
crud.updateDocument(Data);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
message.listen('crdt', function(response) {
|
|
244
|
-
let data = response.data
|
|
245
|
-
let docName = getDocName(data);
|
|
246
|
-
let doc = docs.get(docName);
|
|
247
|
-
|
|
248
|
-
if (doc){
|
|
249
|
-
if (data.clientId !== clientId){
|
|
250
|
-
insertChange(data);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
crud.listen('sync', function(data) {
|
|
256
|
-
if (data.collection.includes('crdt-transactions')) {
|
|
257
|
-
if (data.document && data.document[0]) {
|
|
258
|
-
let Data = data.document[0];
|
|
259
|
-
let docName = Data.docName;
|
|
260
|
-
let doc = docs.get(docName);
|
|
261
|
-
if (doc && Data.crud) {
|
|
262
|
-
Data.crud.value = Data.text
|
|
263
|
-
Data.crud.start = 0
|
|
264
|
-
Data.crud.length = doc.get('text').length
|
|
265
|
-
|
|
266
|
-
doc.set('changeLog', Data.changeLog)
|
|
267
|
-
doc.set('text', Data.text)
|
|
268
|
-
// TODO: compare modified dates to check if arrays need to merged and orderd by date or if we just use server
|
|
269
|
-
localChange(Data.crud, Data.text)
|
|
270
|
-
console.log('crdtSync')
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
/*
|
|
278
|
-
crdt.getText({
|
|
279
|
-
collection: 'modules',
|
|
280
|
-
document_id: '5e4802ce3ed96d38e71fc7e5',
|
|
281
|
-
name: 'name'
|
|
282
|
-
})
|
|
283
|
-
*/
|
|
284
|
-
async function getText(data) {
|
|
285
|
-
try {
|
|
286
|
-
let doc = await getDoc(data);
|
|
287
|
-
if (doc) {
|
|
288
|
-
let value = doc.get('text')
|
|
289
|
-
return value;
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
console.log('undefined')
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
catch (e) {
|
|
296
|
-
console.error(e);
|
|
297
|
-
return "";
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
/*
|
|
303
|
-
crdt.replaceText({
|
|
304
|
-
collection: "module",
|
|
305
|
-
document_id: "",
|
|
306
|
-
name: "",
|
|
307
|
-
value: "",
|
|
308
|
-
crud: true | false,
|
|
309
|
-
element: dom_object,
|
|
310
|
-
metadata: "xxxx"
|
|
311
|
-
})
|
|
312
|
-
*/
|
|
313
|
-
async function replaceText(data) {
|
|
314
|
-
try {
|
|
315
|
-
let doc = await getDoc(data);
|
|
316
|
-
if (doc) {
|
|
317
|
-
let oldValue = await getText(data);
|
|
318
|
-
if (oldValue)
|
|
319
|
-
data.length = oldValue.length;
|
|
320
|
-
else
|
|
321
|
-
data.length = 0;
|
|
322
|
-
|
|
323
|
-
data.start = 0;
|
|
324
|
-
updateText(data, 'replace');
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
catch (e) {
|
|
328
|
-
console.error(e);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/*
|
|
333
|
-
crdt.updateText({
|
|
334
|
-
collection: 'module_activities',
|
|
335
|
-
document_id: '5e4802ce3ed96d38e71fc7e5',
|
|
336
|
-
name: 'name',
|
|
337
|
-
value: 'T',
|
|
338
|
-
start: '8',
|
|
339
|
-
attributes: {bold: true}
|
|
340
|
-
length: 2, // length is used to define charcters that will be deleted
|
|
341
|
-
})
|
|
342
|
-
*/
|
|
343
|
-
async function updateText(data, flag) {
|
|
344
|
-
let doc = await getDoc(data);
|
|
345
|
-
if (doc) {
|
|
346
|
-
|
|
347
|
-
insertChange(data, flag);
|
|
348
|
-
|
|
349
|
-
if (data.crud != 'false' && data.save != 'false') {
|
|
350
|
-
let wholestring = await getText(data);
|
|
351
|
-
crud.updateDocument({
|
|
352
|
-
collection: data.collection,
|
|
353
|
-
document: {
|
|
354
|
-
_id: data.document_id,
|
|
355
|
-
[data.name]: wholestring
|
|
356
|
-
},
|
|
357
|
-
upsert: data.upsert,
|
|
358
|
-
namespace: data.namespace,
|
|
359
|
-
room: data.room,
|
|
360
|
-
broadcast: data.broadcast,
|
|
361
|
-
broadcastSender: data.broadcastSender,
|
|
362
|
-
metadata: 'crdt-updateDocument'
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function createChange(data, change){
|
|
369
|
-
if (change.value && change.length == 0) {
|
|
370
|
-
change.length = change.value.length
|
|
371
|
-
change.removedValue = change.value
|
|
372
|
-
change.value = '';
|
|
373
|
-
change.type = 'delete';
|
|
374
|
-
} else {
|
|
375
|
-
change.value = change.removedValue
|
|
376
|
-
change.length = 0;
|
|
377
|
-
change.type = 'insert';
|
|
378
|
-
}
|
|
379
|
-
data = {...data, ...change};
|
|
380
|
-
delete data.clientId
|
|
381
|
-
delete data.datetime
|
|
382
|
-
delete change.clientId
|
|
383
|
-
delete change.datetime
|
|
384
|
-
return {data, change}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function undoText(data){
|
|
388
|
-
let docName = getDocName(data);
|
|
389
|
-
let doc = docs.get(docName);
|
|
390
|
-
let changeLog = doc.get('changeLog');
|
|
391
|
-
let undoLog = doc.get('undoLog')
|
|
392
|
-
|
|
393
|
-
for (let index = changeLog.length - 1; index >= 0; index--) {
|
|
394
|
-
let change = Object.assign({}, changeLog[index]);
|
|
395
|
-
if (change && change.clientId == clientId){
|
|
396
|
-
let log = undoLog.get(index)
|
|
397
|
-
if (!log) {
|
|
398
|
-
if (log != 'undo') {
|
|
399
|
-
undoLog.set(index, 'undo')
|
|
400
|
-
change.index = changeLog.length += 1;
|
|
401
|
-
let updated = createChange(data, change);
|
|
402
|
-
undoLog.set(updated.change.index, updated.change)
|
|
403
|
-
updateText(updated.data)
|
|
404
|
-
return
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function redoText(data){
|
|
412
|
-
let docName = getDocName(data);
|
|
413
|
-
let doc = docs.get(docName);
|
|
414
|
-
let redoLog = doc.get('redoLog')
|
|
415
|
-
let undoLog = Array.from(doc.get('undoLog').values());
|
|
416
|
-
|
|
417
|
-
for (let index = undoLog.length - 1; index >= 0; index--) {
|
|
418
|
-
let change = Object.assign({}, undoLog[index]);
|
|
419
|
-
if (change && change != 'undo') {
|
|
420
|
-
let log = redoLog.get(change.index)
|
|
421
|
-
|
|
422
|
-
if (!log) {
|
|
423
|
-
if (log != 'redo') {
|
|
424
|
-
let updated = createChange(data, change);
|
|
425
|
-
redoLog.set(updated.change.index, updated.change)
|
|
426
|
-
updateText(updated.data)
|
|
427
|
-
return
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
async function viewVersion(data) {
|
|
435
|
-
try {
|
|
436
|
-
let docName = getDocName(data);
|
|
437
|
-
let doc = docs.get(docName);
|
|
438
|
-
let changeLog = doc.get('changeLog');
|
|
439
|
-
let string = '';
|
|
440
|
-
|
|
441
|
-
let log = changeLog.slice(0, data.version)
|
|
442
|
-
for (let change of log) {
|
|
443
|
-
if (change || change !== null ) {
|
|
444
|
-
string = string.customSplice(change.start, change.length, change.value);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
return { ...data, ...string }
|
|
448
|
-
}
|
|
449
|
-
catch (e) {
|
|
450
|
-
console.error(e);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
// function deleteDoc(docName) {
|
|
457
|
-
// if (this.docs[docName]) {
|
|
458
|
-
// delete this.docs[docName];
|
|
459
|
-
// }
|
|
460
|
-
// }
|
|
461
|
-
|
|
462
|
-
// function destroyObserver(docName, typeName) {
|
|
463
|
-
// this.docs[docName].doc.getText(typeName).unobserve((event) => {});
|
|
464
|
-
// this.docs[docName].socket.awareness.off('change', this._awarenessListener);
|
|
465
|
-
// }
|
|
466
|
-
|
|
467
|
-
function getDocName(data) {
|
|
468
|
-
return `${data.collection}${data.document_id}${data.name}`;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
export default { init, getText, updateText, replaceText, undoText, redoText, viewVersion };
|
|
1
|
+
/*globals config, atob, btoa, localStorage, CustomEvent*/
|
|
2
|
+
import crud from '@cocreate/crud-client';
|
|
3
|
+
import message from '@cocreate/message-client';
|
|
4
|
+
import uuid from '@cocreate/uuid';
|
|
5
|
+
import localStorage from '@cocreate/local-storage';
|
|
6
|
+
|
|
7
|
+
const docs = new Map();
|
|
8
|
+
const clientId = crud.socket.clientId || uuid.generate(12);
|
|
9
|
+
const checkedDb = new Map();
|
|
10
|
+
const isInit = new Map();
|
|
11
|
+
|
|
12
|
+
function init(data){
|
|
13
|
+
getText(data).then(value => {
|
|
14
|
+
data.value = value;
|
|
15
|
+
data.start = 0;
|
|
16
|
+
data['clientId'] = clientId;
|
|
17
|
+
localChange(data);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function getDoc(data) {
|
|
22
|
+
try {
|
|
23
|
+
if (['_id', 'organization_id'].includes(data.name))
|
|
24
|
+
return
|
|
25
|
+
let docName = getDocName(data);
|
|
26
|
+
let doc = docs.get(docName);
|
|
27
|
+
|
|
28
|
+
if (!doc) {
|
|
29
|
+
docs.set(docName, new Map());
|
|
30
|
+
doc = docs.get(docName);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!doc.has('changeLog')) {
|
|
34
|
+
let changeLog = [];
|
|
35
|
+
doc.set('undoLog', new Map())
|
|
36
|
+
doc.set('redoLog', new Map())
|
|
37
|
+
|
|
38
|
+
if (data.read != 'false') {
|
|
39
|
+
if (!data.newDocument) {
|
|
40
|
+
let response = await crud.readDocument({
|
|
41
|
+
collection: "crdt-transactions",
|
|
42
|
+
filter: {
|
|
43
|
+
query: [{
|
|
44
|
+
name: 'docName',
|
|
45
|
+
operator: "$eq",
|
|
46
|
+
value: docName
|
|
47
|
+
}]
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
if (response.document && response.document[0] && response.document[0].changeLog) {
|
|
51
|
+
changeLog = response.document[0].changeLog;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
doc.set('changeLog', changeLog);
|
|
55
|
+
await generateText(data, true);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else if (!doc.has('text')){
|
|
59
|
+
await generateText(data, false);
|
|
60
|
+
}
|
|
61
|
+
return doc;
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
console.log('Invalid param', e);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
String.prototype.customSplice = function (index, absIndex, string) {
|
|
69
|
+
return this.slice(0, index) + string+ this.slice(index + Math.abs(absIndex));
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
async function generateText(data, flag) {
|
|
73
|
+
try {
|
|
74
|
+
let doc = docs.get(getDocName(data))
|
|
75
|
+
|
|
76
|
+
let string = '';
|
|
77
|
+
let changeLog = doc.get('changeLog');
|
|
78
|
+
for (let change of changeLog) {
|
|
79
|
+
if (change || change !== null ) {
|
|
80
|
+
string = string.customSplice(change.start, change.length, change.value);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (string === '' && data.read !== 'false') {
|
|
84
|
+
string = await checkDb(data, flag);
|
|
85
|
+
}
|
|
86
|
+
doc.set('text', string);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
console.error(e);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function checkDb(data, flag) {
|
|
95
|
+
let { collection, document_id, name } = data;
|
|
96
|
+
if (checkedDb.get(`${collection}${document_id}${name}`)) return;
|
|
97
|
+
checkedDb.set(`${collection}${document_id}${name}`, true);
|
|
98
|
+
|
|
99
|
+
let string = ''
|
|
100
|
+
if (data.newDocument)
|
|
101
|
+
string = data.newDocument
|
|
102
|
+
else {
|
|
103
|
+
let response = await crud.readDocument({ collection, document: {_id: document_id, name}});
|
|
104
|
+
string = crud.getValueFromObject(response.document[0], name);
|
|
105
|
+
}
|
|
106
|
+
if (string && typeof string !== 'string')
|
|
107
|
+
string = ""
|
|
108
|
+
if (string && typeof string === 'string' && flag != false) {
|
|
109
|
+
data.value = string;
|
|
110
|
+
data.start = 0;
|
|
111
|
+
data.clientId = clientId;
|
|
112
|
+
insertChange(data);
|
|
113
|
+
}
|
|
114
|
+
return string || '';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function insertChange(data, flag) {
|
|
118
|
+
let docName = getDocName(data);
|
|
119
|
+
let doc = docs.get(docName);
|
|
120
|
+
let changeLog = doc.get('changeLog');
|
|
121
|
+
if (!changeLog)
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
let type = 'insert';
|
|
125
|
+
if (data.start == undefined) return;
|
|
126
|
+
if (!data.value)
|
|
127
|
+
type = 'delete';
|
|
128
|
+
|
|
129
|
+
let change = {
|
|
130
|
+
datetime: data.datetime || new Date().toISOString(),
|
|
131
|
+
value: data.value || '',
|
|
132
|
+
start: data.start,
|
|
133
|
+
end: data.end,
|
|
134
|
+
length: data.length || 0,
|
|
135
|
+
clientId: data.clientId || clientId,
|
|
136
|
+
user_id: data.user_id || localStorage.getItem("user_id"),
|
|
137
|
+
type
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
let lastChange = changeLog[changeLog.length - 1];
|
|
141
|
+
if (lastChange && change.datetime && lastChange.datetime){
|
|
142
|
+
if (change.datetime < lastChange.datetime){
|
|
143
|
+
// TODO: insert change at index position and update start and end postions for every element after insert
|
|
144
|
+
console.log('requires changeLog rebuild');
|
|
145
|
+
}
|
|
146
|
+
if (flag != 'replace') {
|
|
147
|
+
if (lastChange && change.start == lastChange.start) {
|
|
148
|
+
let date1 = new Date(lastChange.datetime);
|
|
149
|
+
let date2 = new Date(change.datetime);
|
|
150
|
+
let diff = date2.getTime() - date1.getTime();
|
|
151
|
+
if (diff < 500) {
|
|
152
|
+
if (change.value.length == 1) {
|
|
153
|
+
change.start = lastChange.start + lastChange.value.length;
|
|
154
|
+
data.start = change.start;
|
|
155
|
+
}
|
|
156
|
+
if (change.length == 1) {
|
|
157
|
+
change.start = lastChange.start - lastChange.length;
|
|
158
|
+
data.start = change.start;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (!change || change == null ) {
|
|
165
|
+
console.log('null change')
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
let string = doc.get('text') || '';
|
|
169
|
+
if (change.length > 0)
|
|
170
|
+
change.removedValue = string.substring(change.start, change.length);
|
|
171
|
+
|
|
172
|
+
if (flag == 'replace') {
|
|
173
|
+
// TODO get current string and create new changeLog array then push new change
|
|
174
|
+
changeLog = [change];
|
|
175
|
+
doc.set('changeLog', changeLog)
|
|
176
|
+
} else {
|
|
177
|
+
changeLog.push(change);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
doc.set('text', string.customSplice(change.start, change.length, change.value));
|
|
181
|
+
string = doc.get('text');
|
|
182
|
+
|
|
183
|
+
if (!data.clientId){
|
|
184
|
+
data['datetime'] = change.datetime;
|
|
185
|
+
data['clientId'] = change.clientId;
|
|
186
|
+
|
|
187
|
+
broadcastChange(data);
|
|
188
|
+
localChange(data, string);
|
|
189
|
+
}
|
|
190
|
+
else
|
|
191
|
+
localChange(data, string);
|
|
192
|
+
|
|
193
|
+
if (data.clientId == clientId && data.save != "false")
|
|
194
|
+
persistChange(data);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function broadcastChange(data){
|
|
198
|
+
message.send({
|
|
199
|
+
room: "",
|
|
200
|
+
broadcastSender: 'false',
|
|
201
|
+
broadcastBrowser: 'once',
|
|
202
|
+
message: "crdt",
|
|
203
|
+
data
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function localChange(data, string) {
|
|
208
|
+
const localChange = new CustomEvent('cocreate-crdt-update', {
|
|
209
|
+
detail: { ...data, string },
|
|
210
|
+
});
|
|
211
|
+
window.dispatchEvent(localChange);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function persistChange(data) {
|
|
215
|
+
let docName = getDocName(data);
|
|
216
|
+
let doc = docs.get(docName);
|
|
217
|
+
let changeLog = doc.get('changeLog');
|
|
218
|
+
let text = doc.get('text');
|
|
219
|
+
let Data = {
|
|
220
|
+
collection: 'crdt-transactions',
|
|
221
|
+
document: {
|
|
222
|
+
_id: data.document_id,
|
|
223
|
+
docName,
|
|
224
|
+
changeLog,
|
|
225
|
+
text,
|
|
226
|
+
crud: {
|
|
227
|
+
collection: data.collection,
|
|
228
|
+
document_id: data.document_id,
|
|
229
|
+
name: data.name
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
upsert: true,
|
|
233
|
+
namespace: data.namespace,
|
|
234
|
+
room: data.room,
|
|
235
|
+
broadcast: data.broadcast,
|
|
236
|
+
broadcastSender: data.broadcastSender,
|
|
237
|
+
metadata: 'crdt-change'
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
crud.updateDocument(Data);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
message.listen('crdt', function(response) {
|
|
244
|
+
let data = response.data
|
|
245
|
+
let docName = getDocName(data);
|
|
246
|
+
let doc = docs.get(docName);
|
|
247
|
+
|
|
248
|
+
if (doc){
|
|
249
|
+
if (data.clientId !== clientId){
|
|
250
|
+
insertChange(data);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
crud.listen('sync', function(data) {
|
|
256
|
+
if (data.collection.includes('crdt-transactions')) {
|
|
257
|
+
if (data.document && data.document[0]) {
|
|
258
|
+
let Data = data.document[0];
|
|
259
|
+
let docName = Data.docName;
|
|
260
|
+
let doc = docs.get(docName);
|
|
261
|
+
if (doc && Data.crud) {
|
|
262
|
+
Data.crud.value = Data.text
|
|
263
|
+
Data.crud.start = 0
|
|
264
|
+
Data.crud.length = doc.get('text').length
|
|
265
|
+
|
|
266
|
+
doc.set('changeLog', Data.changeLog)
|
|
267
|
+
doc.set('text', Data.text)
|
|
268
|
+
// TODO: compare modified dates to check if arrays need to merged and orderd by date or if we just use server
|
|
269
|
+
localChange(Data.crud, Data.text)
|
|
270
|
+
console.log('crdtSync')
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
/*
|
|
278
|
+
crdt.getText({
|
|
279
|
+
collection: 'modules',
|
|
280
|
+
document_id: '5e4802ce3ed96d38e71fc7e5',
|
|
281
|
+
name: 'name'
|
|
282
|
+
})
|
|
283
|
+
*/
|
|
284
|
+
async function getText(data) {
|
|
285
|
+
try {
|
|
286
|
+
let doc = await getDoc(data);
|
|
287
|
+
if (doc) {
|
|
288
|
+
let value = doc.get('text')
|
|
289
|
+
return value;
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
console.log('undefined')
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
catch (e) {
|
|
296
|
+
console.error(e);
|
|
297
|
+
return "";
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
/*
|
|
303
|
+
crdt.replaceText({
|
|
304
|
+
collection: "module",
|
|
305
|
+
document_id: "",
|
|
306
|
+
name: "",
|
|
307
|
+
value: "",
|
|
308
|
+
crud: true | false,
|
|
309
|
+
element: dom_object,
|
|
310
|
+
metadata: "xxxx"
|
|
311
|
+
})
|
|
312
|
+
*/
|
|
313
|
+
async function replaceText(data) {
|
|
314
|
+
try {
|
|
315
|
+
let doc = await getDoc(data);
|
|
316
|
+
if (doc) {
|
|
317
|
+
let oldValue = await getText(data);
|
|
318
|
+
if (oldValue)
|
|
319
|
+
data.length = oldValue.length;
|
|
320
|
+
else
|
|
321
|
+
data.length = 0;
|
|
322
|
+
|
|
323
|
+
data.start = 0;
|
|
324
|
+
updateText(data, 'replace');
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch (e) {
|
|
328
|
+
console.error(e);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/*
|
|
333
|
+
crdt.updateText({
|
|
334
|
+
collection: 'module_activities',
|
|
335
|
+
document_id: '5e4802ce3ed96d38e71fc7e5',
|
|
336
|
+
name: 'name',
|
|
337
|
+
value: 'T',
|
|
338
|
+
start: '8',
|
|
339
|
+
attributes: {bold: true}
|
|
340
|
+
length: 2, // length is used to define charcters that will be deleted
|
|
341
|
+
})
|
|
342
|
+
*/
|
|
343
|
+
async function updateText(data, flag) {
|
|
344
|
+
let doc = await getDoc(data);
|
|
345
|
+
if (doc) {
|
|
346
|
+
|
|
347
|
+
insertChange(data, flag);
|
|
348
|
+
|
|
349
|
+
if (data.crud != 'false' && data.save != 'false') {
|
|
350
|
+
let wholestring = await getText(data);
|
|
351
|
+
crud.updateDocument({
|
|
352
|
+
collection: data.collection,
|
|
353
|
+
document: {
|
|
354
|
+
_id: data.document_id,
|
|
355
|
+
[data.name]: wholestring
|
|
356
|
+
},
|
|
357
|
+
upsert: data.upsert,
|
|
358
|
+
namespace: data.namespace,
|
|
359
|
+
room: data.room,
|
|
360
|
+
broadcast: data.broadcast,
|
|
361
|
+
broadcastSender: data.broadcastSender,
|
|
362
|
+
metadata: 'crdt-updateDocument'
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function createChange(data, change){
|
|
369
|
+
if (change.value && change.length == 0) {
|
|
370
|
+
change.length = change.value.length
|
|
371
|
+
change.removedValue = change.value
|
|
372
|
+
change.value = '';
|
|
373
|
+
change.type = 'delete';
|
|
374
|
+
} else {
|
|
375
|
+
change.value = change.removedValue
|
|
376
|
+
change.length = 0;
|
|
377
|
+
change.type = 'insert';
|
|
378
|
+
}
|
|
379
|
+
data = {...data, ...change};
|
|
380
|
+
delete data.clientId
|
|
381
|
+
delete data.datetime
|
|
382
|
+
delete change.clientId
|
|
383
|
+
delete change.datetime
|
|
384
|
+
return {data, change}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function undoText(data){
|
|
388
|
+
let docName = getDocName(data);
|
|
389
|
+
let doc = docs.get(docName);
|
|
390
|
+
let changeLog = doc.get('changeLog');
|
|
391
|
+
let undoLog = doc.get('undoLog')
|
|
392
|
+
|
|
393
|
+
for (let index = changeLog.length - 1; index >= 0; index--) {
|
|
394
|
+
let change = Object.assign({}, changeLog[index]);
|
|
395
|
+
if (change && change.clientId == clientId){
|
|
396
|
+
let log = undoLog.get(index)
|
|
397
|
+
if (!log) {
|
|
398
|
+
if (log != 'undo') {
|
|
399
|
+
undoLog.set(index, 'undo')
|
|
400
|
+
change.index = changeLog.length += 1;
|
|
401
|
+
let updated = createChange(data, change);
|
|
402
|
+
undoLog.set(updated.change.index, updated.change)
|
|
403
|
+
updateText(updated.data)
|
|
404
|
+
return
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function redoText(data){
|
|
412
|
+
let docName = getDocName(data);
|
|
413
|
+
let doc = docs.get(docName);
|
|
414
|
+
let redoLog = doc.get('redoLog')
|
|
415
|
+
let undoLog = Array.from(doc.get('undoLog').values());
|
|
416
|
+
|
|
417
|
+
for (let index = undoLog.length - 1; index >= 0; index--) {
|
|
418
|
+
let change = Object.assign({}, undoLog[index]);
|
|
419
|
+
if (change && change != 'undo') {
|
|
420
|
+
let log = redoLog.get(change.index)
|
|
421
|
+
|
|
422
|
+
if (!log) {
|
|
423
|
+
if (log != 'redo') {
|
|
424
|
+
let updated = createChange(data, change);
|
|
425
|
+
redoLog.set(updated.change.index, updated.change)
|
|
426
|
+
updateText(updated.data)
|
|
427
|
+
return
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async function viewVersion(data) {
|
|
435
|
+
try {
|
|
436
|
+
let docName = getDocName(data);
|
|
437
|
+
let doc = docs.get(docName);
|
|
438
|
+
let changeLog = doc.get('changeLog');
|
|
439
|
+
let string = '';
|
|
440
|
+
|
|
441
|
+
let log = changeLog.slice(0, data.version)
|
|
442
|
+
for (let change of log) {
|
|
443
|
+
if (change || change !== null ) {
|
|
444
|
+
string = string.customSplice(change.start, change.length, change.value);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return { ...data, ...string }
|
|
448
|
+
}
|
|
449
|
+
catch (e) {
|
|
450
|
+
console.error(e);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
// function deleteDoc(docName) {
|
|
457
|
+
// if (this.docs[docName]) {
|
|
458
|
+
// delete this.docs[docName];
|
|
459
|
+
// }
|
|
460
|
+
// }
|
|
461
|
+
|
|
462
|
+
// function destroyObserver(docName, typeName) {
|
|
463
|
+
// this.docs[docName].doc.getText(typeName).unobserve((event) => {});
|
|
464
|
+
// this.docs[docName].socket.awareness.off('change', this._awarenessListener);
|
|
465
|
+
// }
|
|
466
|
+
|
|
467
|
+
function getDocName(data) {
|
|
468
|
+
return `${data.collection}${data.document_id}${data.name}`;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export default { init, getText, updateText, replaceText, undoText, redoText, viewVersion };
|