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

206 lines
7.2 KiB
TypeScript
Raw Normal View History

// @ts-nocheck
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.
*/
import AttributeMap from './AttributeMap';
import AttributePool from './AttributePool';
import {applyToAText, checkRep, cloneAText, compose, deserializeOps, follow, identity, isIdentity, makeAText, moveOpsToNewPool, newLen, pack, prepareForWire, unpack} from './Changeset';
import {MergingOpAssembler} from "./MergingOpAssembler";
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
let baseAText = 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
let userChangeset = 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) => {
self.setBaseAttributedText(makeAText(text), null);
2011-03-26 13:10:41 +00:00
},
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 = cloneAText(atext);
2020-11-23 13:24:19 -05:00
if (apoolJsonObj) {
const wireApool = (new AttributePool()).fromJsonable(apoolJsonObj);
baseAText.attribs = moveOpsToNewPool(baseAText.attribs, wireApool, apool);
2011-03-26 13:10:41 +00:00
}
submittedChangeset = null;
userChangeset = identity(atext.text.length);
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.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 (isIdentity(c)) return;
userChangeset = compose(userChangeset, c, apool);
2011-03-26 13:10:41 +00:00
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);
c = moveOpsToNewPool(c, wireApool, apool);
2011-03-26 13:10:41 +00:00
}
baseAText = applyToAText(c, baseAText, apool);
2011-03-26 13:10:41 +00:00
2020-11-23 13:24:19 -05:00
let c2 = c;
if (submittedChangeset) {
const oldSubmittedChangeset = submittedChangeset;
submittedChangeset = follow(c, oldSubmittedChangeset, false, apool);
c2 = 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;
userChangeset = follow(
2021-02-17 16:29:33 +00:00
c2, oldUserChangeset, preferInsertingAfterUserChanges, apool);
const postChange = follow(
2021-02-17 16:29:33 +00:00
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 = 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 = unpack(userChangeset);
const assem = new MergingOpAssembler();
for (const op of 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 = pack(cs.oldLen, cs.newLen, assem.toString(), cs.charBank);
checkRep(userChangeset);
if (isIdentity(userChangeset)) toSubmit = null;
2011-07-07 18:59:34 +01:00
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 = identity(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 = 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));
baseAText = applyToAText(submittedChangeset, baseAText, apool);
2011-03-26 13:10:41 +00:00
submittedChangeset = null;
},
2021-02-17 16:28:33 +00:00
setUserChangeNotificationCallback: (callback) => {
2011-03-26 13:10:41 +00:00
changeCallback = callback;
},
hasUncommittedChanges: () => !!(submittedChangeset || (!isIdentity(userChangeset))),
2011-03-26 13:10:41 +00:00
};
2021-02-17 16:28:33 +00:00
};
exports.makeChangesetTracker = makeChangesetTracker;