etherpad-lite/src/static/js/changesettracker.js

204 lines
7.3 KiB
JavaScript
Raw Normal View History

2021-02-17 16:28:33 +00:00
'use strict';
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
2011-03-26 13:10:41 +00:00
/**
* Copyright 2009 Google Inc.
2011-07-07 18:59:34 +01:00
*
2011-03-26 13:10:41 +00:00
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
2011-07-07 18:59:34 +01:00
*
2011-03-26 13:10:41 +00:00
* http://www.apache.org/licenses/LICENSE-2.0
2011-07-07 18:59:34 +01:00
*
2011-03-26 13:10:41 +00:00
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const AttributeMap = require('./AttributeMap');
2020-11-23 13:24:19 -05:00
const AttributePool = require('./AttributePool');
const Changeset = require('./Changeset');
2011-03-26 13:10:41 +00:00
2021-02-17 16:28:33 +00:00
const makeChangesetTracker = (scheduler, apool, aceCallbacksProvider) => {
2011-03-26 13:10:41 +00:00
// latest official text from server
2020-11-23 13:24:19 -05:00
let baseAText = Changeset.makeAText('\n');
2011-03-26 13:10:41 +00:00
// changes applied to baseText that have been submitted
2020-11-23 13:24:19 -05:00
let submittedChangeset = null;
2011-03-26 13:10:41 +00:00
// changes applied to submittedChangeset since it was prepared
2020-11-23 13:24:19 -05:00
let userChangeset = Changeset.identity(1);
2011-03-26 13:10:41 +00:00
// is the changesetTracker enabled
2020-11-23 13:24:19 -05:00
let tracking = false;
2011-03-26 13:10:41 +00:00
// stack state flag so that when we change the rep we don't
// handle the notification recursively. When setting, always
// unset in a "finally" block. When set to true, the setter
// takes change of userChangeset.
2020-11-23 13:24:19 -05:00
let applyingNonUserChanges = false;
2011-03-26 13:10:41 +00:00
2020-11-23 13:24:19 -05:00
let changeCallback = null;
2011-03-26 13:10:41 +00:00
2020-11-23 13:24:19 -05:00
let changeCallbackTimeout = null;
2011-07-07 18:59:34 +01:00
2021-02-17 16:28:33 +00:00
const setChangeCallbackTimeout = () => {
2011-03-26 13:10:41 +00:00
// can call this multiple times per call-stack, because
// we only schedule a call to changeCallback if it exists
// and if there isn't a timeout already scheduled.
if (changeCallback && changeCallbackTimeout == null) {
2020-11-23 13:24:19 -05:00
changeCallbackTimeout = scheduler.setTimeout(() => {
try {
2011-07-07 18:59:34 +01:00
changeCallback();
} catch (pseudoError) {
// as empty as my soul
} finally {
2011-07-07 18:59:34 +01:00
changeCallbackTimeout = null;
}
2011-03-26 13:10:41 +00:00
}, 0);
}
2021-02-17 16:28:33 +00:00
};
2011-03-26 13:10:41 +00:00
2020-11-23 13:24:19 -05:00
let self;
2011-03-26 13:10:41 +00:00
return self = {
2021-02-17 16:28:33 +00:00
isTracking: () => tracking,
setBaseText: (text) => {
2011-03-26 13:10:41 +00:00
self.setBaseAttributedText(Changeset.makeAText(text), null);
},
2021-02-17 16:28:33 +00:00
setBaseAttributedText: (atext, apoolJsonObj) => {
2020-11-23 13:24:19 -05:00
aceCallbacksProvider.withCallbacks('setBaseText', (callbacks) => {
2011-03-26 13:10:41 +00:00
tracking = true;
baseAText = Changeset.cloneAText(atext);
2020-11-23 13:24:19 -05:00
if (apoolJsonObj) {
const wireApool = (new AttributePool()).fromJsonable(apoolJsonObj);
2011-07-07 18:59:34 +01:00
baseAText.attribs = Changeset.moveOpsToNewPool(baseAText.attribs, wireApool, apool);
2011-03-26 13:10:41 +00:00
}
submittedChangeset = null;
userChangeset = Changeset.identity(atext.text.length);
applyingNonUserChanges = true;
2020-11-23 13:24:19 -05:00
try {
2011-03-26 13:10:41 +00:00
callbacks.setDocumentAttributedText(atext);
2020-11-23 13:24:19 -05:00
} finally {
2011-07-07 18:59:34 +01:00
applyingNonUserChanges = false;
2011-03-26 13:10:41 +00:00
}
});
},
2021-02-17 16:28:33 +00:00
composeUserChangeset: (c) => {
2011-07-07 18:59:34 +01:00
if (!tracking) return;
2011-03-26 13:10:41 +00:00
if (applyingNonUserChanges) return;
if (Changeset.isIdentity(c)) return;
userChangeset = Changeset.compose(userChangeset, c, apool);
setChangeCallbackTimeout();
},
2021-02-17 16:28:33 +00:00
applyChangesToBase: (c, optAuthor, apoolJsonObj) => {
2011-07-07 18:59:34 +01:00
if (!tracking) return;
2011-03-26 13:10:41 +00:00
2020-11-23 13:24:19 -05:00
aceCallbacksProvider.withCallbacks('applyChangesToBase', (callbacks) => {
if (apoolJsonObj) {
const wireApool = (new AttributePool()).fromJsonable(apoolJsonObj);
2011-07-07 18:59:34 +01:00
c = Changeset.moveOpsToNewPool(c, wireApool, apool);
2011-03-26 13:10:41 +00:00
}
baseAText = Changeset.applyToAText(c, baseAText, apool);
2020-11-23 13:24:19 -05:00
let c2 = c;
if (submittedChangeset) {
const oldSubmittedChangeset = submittedChangeset;
2011-07-07 18:59:34 +01:00
submittedChangeset = Changeset.follow(c, oldSubmittedChangeset, false, apool);
c2 = Changeset.follow(oldSubmittedChangeset, c, true, apool);
2011-03-26 13:10:41 +00:00
}
2020-11-23 13:24:19 -05:00
const preferInsertingAfterUserChanges = true;
const oldUserChangeset = userChangeset;
2021-02-17 16:29:33 +00:00
userChangeset = Changeset.follow(
c2, oldUserChangeset, preferInsertingAfterUserChanges, apool);
const postChange = Changeset.follow(
oldUserChangeset, c2, !preferInsertingAfterUserChanges, apool);
2011-03-26 13:10:41 +00:00
2020-11-23 13:24:19 -05:00
const preferInsertionAfterCaret = true; // (optAuthor && optAuthor > thisAuthor);
2011-03-26 13:10:41 +00:00
applyingNonUserChanges = true;
2020-11-23 13:24:19 -05:00
try {
2011-03-26 13:10:41 +00:00
callbacks.applyChangesetToDocument(postChange, preferInsertionAfterCaret);
2020-11-23 13:24:19 -05:00
} finally {
2011-07-07 18:59:34 +01:00
applyingNonUserChanges = false;
2011-03-26 13:10:41 +00:00
}
});
},
2021-02-17 16:28:33 +00:00
prepareUserChangeset: () => {
2011-03-26 13:10:41 +00:00
// If there are user changes to submit, 'changeset' will be the
// changeset, else it will be null.
2020-11-23 13:24:19 -05:00
let toSubmit;
if (submittedChangeset) {
2011-07-07 18:59:34 +01:00
// submission must have been canceled, prepare new changeset
// that includes old submittedChangeset
toSubmit = Changeset.compose(submittedChangeset, userChangeset, apool);
2020-11-23 13:24:19 -05:00
} else {
// Get my authorID
2020-11-23 13:24:19 -05:00
const authorId = parent.parent.pad.myUserInfo.userId;
// Sanitize authorship: Replace all author attributes with this user's author ID in case the
// text was copied from another author.
const cs = Changeset.unpack(userChangeset);
const assem = Changeset.mergingOpAssembler();
for (const op of Changeset.deserializeOps(cs.ops)) {
if (op.opcode === '+') {
const attribs = AttributeMap.fromString(op.attribs, apool);
const oldAuthorId = attribs.get('author');
if (oldAuthorId != null && oldAuthorId !== authorId) {
attribs.set('author', authorId);
op.attribs = attribs.toString();
}
}
assem.append(op);
}
assem.endDocument();
userChangeset = Changeset.pack(cs.oldLen, cs.newLen, assem.toString(), cs.charBank);
Changeset.checkRep(userChangeset);
2011-07-07 18:59:34 +01:00
if (Changeset.isIdentity(userChangeset)) toSubmit = null;
else toSubmit = userChangeset;
2011-03-26 13:10:41 +00:00
}
let cs = null;
2020-11-23 13:24:19 -05:00
if (toSubmit) {
2011-07-07 18:59:34 +01:00
submittedChangeset = toSubmit;
userChangeset = Changeset.identity(Changeset.newLen(toSubmit));
2011-03-26 13:10:41 +00:00
2011-07-07 18:59:34 +01:00
cs = toSubmit;
2011-03-26 13:10:41 +00:00
}
2020-11-23 13:24:19 -05:00
let wireApool = null;
if (cs) {
const forWire = Changeset.prepareForWire(cs, apool);
2011-07-07 18:59:34 +01:00
wireApool = forWire.pool.toJsonable();
cs = forWire.translated;
2011-03-26 13:10:41 +00:00
}
2020-11-23 13:24:19 -05:00
const data = {
2011-07-07 18:59:34 +01:00
changeset: cs,
2020-11-23 13:24:19 -05:00
apool: wireApool,
2011-07-07 18:59:34 +01:00
};
2011-03-26 13:10:41 +00:00
return data;
},
2021-02-17 16:28:33 +00:00
applyPreparedChangesetToBase: () => {
2020-11-23 13:24:19 -05:00
if (!submittedChangeset) {
2011-07-07 18:59:34 +01:00
// violation of protocol; use prepareUserChangeset first
2020-11-23 13:24:19 -05:00
throw new Error('applySubmittedChangesToBase: no submitted changes to apply');
2011-03-26 13:10:41 +00:00
}
2020-11-23 13:24:19 -05:00
// bumpDebug("applying committed changeset: "+submittedChangeset.encodeToString(false));
2011-03-26 13:10:41 +00:00
baseAText = Changeset.applyToAText(submittedChangeset, baseAText, apool);
submittedChangeset = null;
},
2021-02-17 16:28:33 +00:00
setUserChangeNotificationCallback: (callback) => {
2011-03-26 13:10:41 +00:00
changeCallback = callback;
},
2021-02-17 16:28:33 +00:00
hasUncommittedChanges: () => !!(submittedChangeset || (!Changeset.isIdentity(userChangeset))),
2011-03-26 13:10:41 +00:00
};
2021-02-17 16:28:33 +00:00
};
exports.makeChangesetTracker = makeChangesetTracker;