Merge branch 'historyOO' into staging

This commit is contained in:
yflory 2020-10-05 16:41:23 +02:00
commit e698241ee9
10 changed files with 732 additions and 24 deletions

View File

@ -71,6 +71,10 @@
min-width: 0;
text-align: center;
}
.cp-history-timeline-version {
font-size: 12px;
margin-right: 10px;
}
}
.cp-toolbar-history-actions {
display: flex;
@ -120,6 +124,9 @@
.cp-history-timeline-actions {
margin-left: 21px !important;
}
.cp-toolbar-history-actions {
align-items: baseline !important;
}
}
&.cp-smallpatch {
.cp-history-snapshot {
@ -137,6 +144,17 @@
}
}
}
&.cp-history-oo {
.cp-history-timeline-container {
height: 20px !important;
.cp-history-timeline-pos {
height: 20px !important;
}
}
.cp-history-timeline-actions {
margin-left: 0 !important;
}
}
.cp-history-timeline-line {
display: flex;
.cp-history-timeline-legend {
@ -229,6 +247,12 @@
}
}
}
.cp-history-timeline-version:empty {
display: none;
}
.cp-history-timeline-time:empty {
display: none;
}
.cp-history-timeline-next {
button:last-child {
margin-right: 0;
@ -254,7 +278,6 @@
left: ~"calc(50% - 6px)";
}
}
}
}

View File

@ -662,10 +662,11 @@ const handleGetHistoryRange = function (Env, Server, seq, userId, parsed) {
}
var oldestKnownHash = map.from;
var untilHash = map.to;
var desiredMessages = map.count;
var desiredCheckpoint = map.cpCount;
var txid = map.txid;
if (typeof(desiredMessages) !== 'number' && typeof(desiredCheckpoint) !== 'number') {
if (typeof(desiredMessages) !== 'number' && typeof(desiredCheckpoint) !== 'number' && !untilHash) {
return void Server.send(userId, [seq, 'ERROR', 'UNSPECIFIED_COUNT', HISTORY_KEEPER_ID]);
}
@ -674,7 +675,7 @@ const handleGetHistoryRange = function (Env, Server, seq, userId, parsed) {
}
Server.send(userId, [seq, 'ACK']);
Env.getOlderHistory(channelName, oldestKnownHash, desiredMessages, desiredCheckpoint, function (err, toSend) {
Env.getOlderHistory(channelName, oldestKnownHash, untilHash, desiredMessages, desiredCheckpoint, function (err, toSend) {
if (err && err.code !== 'ENOENT') {
Env.Log.error("HK_GET_OLDER_HISTORY", err);
}

View File

@ -230,6 +230,7 @@ const computeMetadata = function (data, cb) {
const getOlderHistory = function (data, cb) {
const oldestKnownHash = data.hash;
const untilHash = data.toHash;
const channelName = data.channel;
const desiredMessages = data.desiredMessages;
const desiredCheckpoint = data.desiredCheckpoint;
@ -260,6 +261,13 @@ const getOlderHistory = function (data, cb) {
var toSend = [];
if (typeof (desiredMessages) === "number") {
toSend = messages.slice(-desiredMessages);
} else if (untilHash) {
for (var j = messages.length - 1; j >= 0; j--) {
toSend.unshift(messages[j]);
if (Array.isArray(messages[j]) && HK.getHash(messages[j][4]) === untilHash) {
break;
}
}
} else {
let cpCount = 0;
for (var i = messages.length - 1; i >= 0; i--) {

View File

@ -281,12 +281,13 @@ Workers.initialize = function (Env, config, _cb) {
});
};
Env.getOlderHistory = function (channel, oldestKnownHash, desiredMessages, desiredCheckpoint, cb) {
Env.getOlderHistory = function (channel, oldestKnownHash, toHash, desiredMessages, desiredCheckpoint, cb) {
Env.store.getWeakLock(channel, function (next) {
sendCommand({
channel: channel,
command: "GET_OLDER_HISTORY",
hash: oldestKnownHash,
toHash: toHash,
desiredMessages: desiredMessages,
desiredCheckpoint: desiredCheckpoint,
}, Util.both(next, cb));

View File

@ -0,0 +1,384 @@
define([
'jquery',
'/common/common-interface.js',
'/common/hyperscript.js',
], function ($, UI, h) {
//var ChainPad = window.ChainPad;
var History = {};
History.create = function (common, config) {
if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");}
if (History.loading) { return void console.error("History is already being loaded..."); }
History.loading = true;
var $toolbar = config.$toolbar;
var sframeChan = common.getSframeChannel();
History.readOnly = common.getMetadataMgr().getPrivateData().readOnly || !common.isLoggedIn();
if (!config.onlyoffice || !config.setHistory || !config.onCheckpoint || !config.onPatch || !config.makeSnapshot) {
throw new Error("Missing config element");
}
var cpIndex = -1;
var msgIndex = -1;
var ooMessages = {};
var loading = false;
var update = function () {};
var currentTime;
// Get an array of the checkpoint IDs sorted their patch index
var hashes = config.onlyoffice.hashes;
var sortedCp = Object.keys(hashes).map(Number).sort(function (a, b) {
return hashes[a].index - hashes[b].index;
});
var endWithCp = sortedCp.length &&
config.onlyoffice.lastHash === hashes[sortedCp[sortedCp.length - 1]].hash;
var fillOO = function (id, messages) {
if (!id) { return; }
if (ooMessages[id]) { return; }
ooMessages[id] = messages;
update();
};
if (endWithCp) { cpIndex = 0; }
var $version, $time, $share;
var $hist = $toolbar.find('.cp-toolbar-history');
$hist.addClass('cp-smallpatch');
$hist.addClass('cp-history-oo');
var $bottom = $toolbar.find('.cp-toolbar-bottom');
var getVersion = function () {
var major = sortedCp.length - cpIndex;
return major + '.' + (msgIndex+1);
};
var showVersion = function (initial) {
var v = getVersion();
if (initial) {
v = "Latest"; // XXX
}
$version.text("Version: " + v); // XXX
var $pos = $hist.find('.cp-history-timeline-pos');
var cps = sortedCp.length;
var id = sortedCp[cps - cpIndex -1] || -1;
if (!ooMessages[id]) { return; }
var msgs = ooMessages[id];
console.log(ooMessages);
var p = 100*((msgIndex+1) / (msgs.length));
$pos.css('margin-left', p+'%');
var time = currentTime = msgs[msgIndex] && msgs[msgIndex].time;
if (time) { $time.text(new Date(time).toLocaleString()); }
else { $time.text(''); }
};
// We want to load a checkpoint (or initial state)
var loadMoreOOHistory = function () {
if (!Array.isArray(sortedCp)) { return void console.error("Wrong type"); }
var cp = {};
// Get the checkpoint ID
var id = -1;
if (cpIndex < sortedCp.length) {
id = sortedCp[sortedCp.length - 1 - cpIndex];
cp = hashes[id];
}
var nextId = sortedCp[sortedCp.length - cpIndex];
// Get the history between "toHash" and "fromHash". This function is using
// "getOlderHistory", that's why we start from the more recent hash
// and we go back in time to an older hash
// We need to get all the patches between the current cp hash and the next cp hash
// Current cp or initial hash (invalid hash ==> initial hash)
var toHash = cp.hash || 'NONE';
// Next cp or last hash
var fromHash = nextId ? hashes[nextId].hash : config.onlyoffice.lastHash;
msgIndex = -1;
showVersion();
if (ooMessages[id]) {
// Cp already loaded: reload OO
loading = false;
return void config.onCheckpoint(cp);
}
sframeChan.query('Q_GET_HISTORY_RANGE', {
channel: config.onlyoffice.channel,
lastKnownHash: fromHash,
toHash: toHash,
}, function (err, data) {
if (err) { return void console.error(err); }
if (!Array.isArray(data.messages)) { return void console.error('Not an array!'); }
// The first "cp" in history is the empty doc. It doesn't include the first patch
// of the history
var initialCp = cpIndex === sortedCp.length;
var messages = (data.messages || []).slice(initialCp ? 0 : 1);
if (config.debug) { console.log(data.messages); }
fillOO(id, messages);
loading = false;
config.onCheckpoint(cp);
$share.show();
});
};
var onClose = function () { config.setHistory(false); };
var onRevert = function () {
config.onRevert();
};
config.setHistory(true);
var Messages = common.Messages;
$hist.html('').css('display', 'flex');
$bottom.hide();
UI.spinner($hist).get().show();
var $fastPrev, $fastNext, $next;
var getId = function () {
var cps = sortedCp.length;
return sortedCp[cps - cpIndex -1] || -1;
};
update = function () {
var cps = sortedCp.length;
$fastPrev.show();
$next.show();
$fastNext.show();
if (cpIndex >= cps && msgIndex === 0) {
$fastPrev.hide();
}
if (cpIndex === 0) {
$fastNext.hide();
}
var id = getId();
var msgs = (ooMessages[id] || []).length;
if (msgIndex >= (msgs-1)) {
$next.hide();
}
};
var next = function () {
var id = getId();
if (!ooMessages[id]) { loading = false; return; }
var msgs = ooMessages[id];
msgIndex++;
var patch = msgs[msgIndex];
if (!patch) { loading = false; return; }
config.onPatch(patch);
showVersion();
setTimeout(function () {
$('iframe').blur();
loading = false;
}, 200);
};
// Create the history toolbar
var display = function () {
$hist.html('');
var fastPrev = h('button.cp-toolbar-history-previous', { title: Messages.history_prev }, [
h('i.fa.fa-fast-backward'),
]);
var fastNext = h('button.cp-toolbar-history-next', { title: Messages.history_next }, [
h('i.fa.fa-fast-forward'),
]);
var _next = h('button.cp-toolbar-history-next', { title: Messages.history_next }, [
h('i.fa.fa-step-forward')
]);
$fastPrev = $(fastPrev);
$fastNext = $(fastNext).hide();
$next = $(_next).hide();
var pos = h('span.cp-history-timeline-pos.fa.fa-caret-down');
var time = h('div.cp-history-timeline-time');
var version = h('div.cp-history-timeline-version');
$time = $(time);
$version = $(version);
var bar;
var timeline = h('div.cp-toolbar-history-timeline', [
h('div.cp-history-timeline-line', [
bar = h('span.cp-history-timeline-container')
]),
h('div.cp-history-timeline-actions', [
h('span.cp-history-timeline-prev', [
fastPrev,
]),
time,
version,
h('span.cp-history-timeline-next', [
_next,
fastNext
])
])
]);
var snapshot = h('button', {
title: Messages.snapshots_new,
}, [
h('i.fa.fa-camera')
]);
var share = h('button', { title: Messages.history_shareTitle }, [
h('i.fa.fa-shhare-alt'),
h('span', Messages.shareButton)
]);
var restore = h('button', {
title: Messages.history_restoreTitle,
}, [
h('i.fa.fa-check'),
h('span', Messages.history_restore)
]);
var close = h('button', { title: Messages.history_closeTitle }, [
h('i.fa.fa-times'),
h('span', Messages.history_close)
]);
var actions = h('div.cp-toolbar-history-actions', [
h('span.cp-history-actions-first', [
snapshot,
share
]),
h('span.cp-history-actions-last', [
restore,
close
])
]);
if (History.readOnly) {
snapshot.disabled = true;
restore.disabled = true;
}
$share = $(share);
$hist.append([timeline, actions]);
$(bar).append(pos);
var onKeyDown, onKeyUp;
var closeUI = function () {
$hist.hide();
$bottom.show();
$(window).trigger('resize');
$(window).off('keydown', onKeyDown);
$(window).off('keyup', onKeyUp);
};
// Push one patch
$next.click(function () {
if (loading) { return; }
loading = true;
next();
update();
});
// Go to previous checkpoint
$fastNext.click(function () {
if (loading) { return; }
loading = true;
cpIndex--;
loadMoreOOHistory();
update();
});
// Go to next checkpoint
$fastPrev.click(function () {
if (loading) { return; }
loading = true;
if (msgIndex === -1) {
cpIndex++;
}
loadMoreOOHistory();
update();
});
onKeyDown = function (e) {
var p = function () { e.preventDefault(); };
if ([38, 39].indexOf(e.which) >= 0) { p(); return $next.click(); } // Right
if (e.which === 33) { p(); return $fastNext.click(); } // PageUp
if (e.which === 34) { p(); return $fastPrev.click(); } // PageUp
if (e.which === 27) { p(); return $(close).click(); }
};
onKeyUp = function (e) { e.stopPropagation(); };
$(window).on('keydown', onKeyDown).on('keyup', onKeyUp).focus();
// Versioned link
$share.click(function () {
common.getSframeChannel().event('EV_SHARE_OPEN', {
versionHash: getVersion()
});
});
Messages.snapshots_ooPickVersion = "You must select a version before creating a snapshot"; // XXX
$(snapshot).click(function () {
if (cpIndex === -1 && msgIndex === -1) { return void UI.warn(Messages.snapshots_ooPickVersion); }
var input = h('input', {
placeholder: Messages.snapshots_placeholder
});
var $input = $(input);
var content = h('div', [
h('h5', Messages.snapshots_new),
input
]);
var buttons = [{
className: 'cancel',
name: Messages.filePicker_close,
onClick: function () {},
keys: [27],
}, {
className: 'primary',
iconClass: '.fa.fa-camera',
name: Messages.snapshots_new,
onClick: function () {
var val = $input.val();
if (!val) { return true; }
config.makeSnapshot(val, function (err) {
if (err) { return; }
UI.log(Messages.saved);
}, {
hash: getVersion(),
time: currentTime || 0
});
},
keys: [13],
}];
UI.openCustomModal(UI.dialog.customModal(content, {buttons: buttons }));
setTimeout(function () {
$input.focus();
});
});
// Close & restore buttons
$(close).click(function () {
History.loading = false;
onClose();
closeUI();
});
$(restore).click(function () {
UI.confirm(Messages.history_restorePrompt, function (yes) {
if (!yes) { return; }
closeUI();
History.loading = false;
onRevert();
UI.log(Messages.history_restoreDone);
});
});
};
display();
showVersion(true);
//return void loadMoreOOHistory();
};
return History;
});

View File

@ -14,6 +14,7 @@ define([
'/customize/application_config.js',
'/bower_components/chainpad/chainpad.dist.js',
'/file/file-crypto.js',
'/common/onlyoffice/history.js',
'/common/onlyoffice/oocell_base.js',
'/common/onlyoffice/oodoc_base.js',
'/common/onlyoffice/ooslide_base.js',
@ -40,6 +41,7 @@ define([
AppConfig,
ChainPad,
FileCrypto,
History,
EmptyCell,
EmptyDoc,
EmptySlide,
@ -243,8 +245,17 @@ define([
ready: false,
readyCb: undefined,
sendCmd: function (data, cb) {
if (APP.history) { return; }
sframeChan.query('Q_OO_COMMAND', data, cb);
},
getHistory: function (cb) {
rtChannel.sendCmd({
cmd: 'GET_HISTORY',
data: {}
}, function () {
APP.onHistorySynced = cb;
});
},
sendMsg: function (msg, cp, cb) {
rtChannel.sendCmd({
cmd: 'SEND_MESSAGE',
@ -320,10 +331,14 @@ define([
// Get the last cp idx
var all = sortCpIndex(content.hashes || {});
var current = all[all.length - 1] || 0;
// XXX Keep all cp now
// Get the expected cp idx
var _i = Math.floor(ev.index / CHECKPOINT_INTERVAL);
//var _i = Math.floor(ev.index / CHECKPOINT_INTERVAL);
// Take the max of both
var i = Math.max(_i, current);
//var i = Math.max(_i, current);
var i = current + 1;
content.hashes[i] = {
file: data.url,
hash: ev.hash,
@ -396,7 +411,7 @@ define([
}
myUniqueOOId = undefined;
setMyId();
APP.docEditor.destroyEditor(); // Kill the old editor
if (APP.docEditor) { APP.docEditor.destroyEditor(); } // Kill the old editor
$('iframe[name="frameEditor"]').after(h('div#cp-app-oo-placeholder')).remove();
ooLoaded = false;
oldLocks = {};
@ -404,6 +419,7 @@ define([
clearTimeout(pendingChanges[key]);
delete pendingChanges[key];
});
if (APP.stopHistory) { APP.history = false; }
startOO(blob, type, true);
};
@ -413,8 +429,8 @@ define([
var file = getFileType();
blob.name = (metadataMgr.getMetadataLazy().title || file.doc) + '.' + file.type;
var data = {
hash: ooChannel.lastHash,
index: ooChannel.cpIndex
hash: APP.history ? ooChannel.historyLastHash : ooChannel.lastHash,
index: APP.history ? ooChannel.currentIndex : ooChannel.cpIndex
};
fixSheets();
@ -532,6 +548,9 @@ define([
return new Blob([newText], {type: 'text/plain'});
};
var loadLastDocument = function (lastCp, onCpError, cb) {
if (!lastCp || !lastCp.file) {
return void onCpError('EEMPTY');
}
ooChannel.cpIndex = lastCp.index || 0;
ooChannel.lastHash = lastCp.hash;
var parsed = Hash.parsePadUrl(lastCp.file);
@ -597,6 +616,94 @@ define([
});
};
var openVersionHash = function (version) {
readOnly = true;
var hashes = content.hashes || {};
var sortedCp = Object.keys(hashes).map(Number).sort(function (a, b) {
return hashes[a].index - hashes[b].index;
});
var s = version.split('.');
if (s.length !== 2) { return UI.errorLoadingScreen(Messages.error); }
var major = Number(s[0]);
var cpId = sortedCp[major - 1];
var nextCpId = sortedCp[major];
var cp = hashes[cpId] || {};
var minor = Number(s[1]) + 1;
var toHash = cp.hash || 'NONE';
var fromHash = nextCpId ? hashes[nextCpId].hash : 'NONE';
sframeChan.query('Q_GET_HISTORY_RANGE', {
channel: content.channel,
lastKnownHash: fromHash,
toHash: toHash,
}, function (err, data) {
if (err) { console.error(err); return void UI.errorLoadingScreen(Messages.error); }
if (!Array.isArray(data.messages)) {
console.error('Not an array');
return void UI.errorLoadingScreen(Messages.error);
}
// The first "cp" in history is the empty doc. It doesn't include the first patch
// of the history
var initialCp = major === 0;
var messages = (data.messages || []).slice(initialCp ? 0 : 1, minor);
messages.forEach(function (obj) {
try { obj.msg = JSON.parse(obj.msg); } catch (e) { console.error(e); }
});
// The version exists if we have results in the "messages" array
// or if we requested a x.0 version
var exists = !Number(s[1]) || messages.length;
var vHashEl;
Messages.oo_deletedVersion = "This version no longer exists in the history."; // XXX
if (!privateData.embed) {
Messages.infobar_versionHash = "You're currently viewing an old version of this document ({0})."; // XXX (duplicate from history branch)
var vTime = (messages[messages.length - 1] || {}).time;
var vTimeStr = vTime ? new Date(vTime).toLocaleString()
: 'v' + privateData.ooVersionHash;
var vTxt = Messages._getKey('infobar_versionHash',  [vTimeStr]);
// If we expected patched and we don't have any, it means this part
// of the history has been deleted
var vType = "warning";
if (!exists) {
vTxt = Messages.oo_deletedVersion
vType = "danger";
}
vHashEl = h('div.alert.alert-'+vType+'.cp-burn-after-reading', vTxt);
$('#cp-app-oo-editor').prepend(vHashEl);
}
if (!exists) { return void UI.removeLoadingScreen(); }
loadLastDocument(cp, function () {
if (cp.hash && vHashEl) {
// We requested a checkpoint but we can't find it...
UI.removeLoadingScreen();
vHashEl.innerText = Messages.oo_deletedVersion;
$(vHashEl).removeClass('alert-warning').addClass('alert-danger');
return;
}
var file = getFileType();
var type = common.getMetadataMgr().getPrivateData().ooType;
var blob = loadInitDocument(type, true);
ooChannel.queue = messages;
resetData(blob, file);
UI.removeLoadingScreen();
}, function (blob, file) {
ooChannel.queue = messages;
resetData(blob, file);
UI.removeLoadingScreen();
});
});
};
var openRtChannel = function (cb) {
if (rtChannel.ready) { return void cb(); }
var chan = content.channel || Hash.createChannelId();
@ -619,10 +726,15 @@ define([
removeClient(obj.data);
break;
case 'MESSAGE':
if (APP.history) {
ooChannel.historyLastHash = obj.data.hash;
ooChannel.currentIndex++;
return;
}
if (ooChannel.ready) {
// In read-only mode, push the message to the queue and prompt
// the user to refresh OO (without reloading the page)
if (readOnly) {
/*if (readOnly) {
ooChannel.queue.push(obj.data);
if (APP.refreshPopup) { return; }
APP.refreshPopup = true;
@ -631,7 +743,7 @@ define([
// 1 popup every 15s
APP.refreshRoTo = setTimeout(refreshReadOnly, READONLY_REFRESH_TO);
return;
}
}*/
ooChannel.send(obj.data.msg);
ooChannel.lastHash = obj.data.hash;
ooChannel.cpIndex++;
@ -639,6 +751,12 @@ define([
ooChannel.queue.push(obj.data);
}
break;
case 'HISTORY_SYNCED':
if (typeof(APP.onHistorySynced) !== "function") { return; }
APP.onHistorySynced();
delete APP.onHistorySynced;
break;
}
});
};
@ -812,6 +930,8 @@ define([
};
var handleLock = function (obj, send) {
if (APP.history) { return; }
if (content.saveLock) {
if (!isLockedModal.modal) {
isLockedModal.modal = UI.openCustomModal(isLockedModal.content);
@ -842,7 +962,9 @@ define([
if (isLockedModal.modal) {
isLockedModal.modal.closeModal();
delete isLockedModal.modal;
$('#cp-app-oo-editor > iframe')[0].contentWindow.focus();
if (!APP.history) {
$('#cp-app-oo-editor > iframe')[0].contentWindow.focus();
}
}
send({
type: "getLock",
@ -1041,7 +1163,7 @@ define([
startOO = function (blob, file, force) {
if (APP.ooconfig && !force) { return void console.error('already started'); }
var url = URL.createObjectURL(blob);
var lock = readOnly || APP.migrate;
var lock = !APP.history && (APP.migrate);
// Starting from version 3, we can use the view mode again
// defined but never used
@ -1170,6 +1292,10 @@ define([
if (lock) {
getEditor().setViewModeDisconnect();
} else if (readOnly) {
try {
getEditor().asc_setRestriction(true);
} catch (e) {}
} else {
setEditable(true);
}
@ -1177,7 +1303,15 @@ define([
if (isLockedModal.modal && force) {
isLockedModal.modal.closeModal();
delete isLockedModal.modal;
$('#cp-app-oo-editor > iframe')[0].contentWindow.focus();
if (!APP.history) {
$('#cp-app-oo-editor > iframe')[0].contentWindow.focus();
}
}
if (APP.history) {
try {
getEditor().asc_setRestriction(true);
} catch (e) {}
}
if (APP.migrate && !readOnly) {
@ -1733,6 +1867,125 @@ define([
});
$save.appendTo(toolbar.$bottomM);
}
if (!privateData.ooVersionHash) {
(function () {
/* add a history button */
var commit = function () {
// Wait for the checkpoint to be uploaded before leaving history mode
// (race condition). We use "stopHistory" to remove the history
// flag only when the checkpoint is ready.
APP.stopHistory = true;
makeCheckpoint(true);
};
var loadCp = function (cp, keepQueue) {
loadLastDocument(cp, function () {
var file = getFileType();
var type = common.getMetadataMgr().getPrivateData().ooType;
var blob = loadInitDocument(type, true);
if (!keepQueue) { ooChannel.queue = []; }
resetData(blob, file);
}, function (blob, file) {
if (!keepQueue) { ooChannel.queue = []; }
resetData(blob, file);
});
};
var onPatch = function (patch) {
// Patch on the current cp
ooChannel.send(JSON.parse(patch.msg));
};
var onCheckpoint = function (cp) {
// We want to load a checkpoint:
loadCp(cp);
};
var setHistoryMode = function (bool) {
if (bool) {
APP.history = true;
getEditor().setViewModeDisconnect();
return;
}
// Cancel button: redraw from lastCp
APP.history = false;
ooChannel.queue = [];
ooChannel.ready = false;
// Fill the queue and then load the last CP
rtChannel.getHistory(function () {
var lastCp = getLastCp();
loadCp(lastCp, true);
});
};
var deleteSnapshot = function (hash) {
var md = Util.clone(cpNfInner.metadataMgr.getMetadata());
var snapshots = md.snapshots = md.snapshots || {};
delete snapshots[hash];
metadataMgr.updateMetadata(md);
APP.onLocal();
};
var makeSnapshot = function (title, cb, obj) {
var hash, time;
if (obj && obj.hash && obj.time) {
hash = obj.hash;
time = obj.time
} else {
var major = Object.keys(content.hashes).length;
var cpIndex = getLastCp().index || 0;
var minor = ooChannel.cpIndex - cpIndex;
hash = major+'.'+minor;
time = +new Date();
}
var md = Util.clone(metadataMgr.getMetadata());
var snapshots = md.snapshots = md.snapshots || {};
if (snapshots[hash]) { cb('EEXISTS'); return void UI.warn(Messages.error); } // XXX
snapshots[hash] = {
title: title,
time: time
};
metadataMgr.updateMetadata(md);
APP.onLocal();
APP.realtime.onSettle(cb);
};
var loadSnapshot = function (hash) {
sframeChan.event('EV_OO_OPENVERSION', {
hash: hash
});
};
common.createButton('', true, {
name: 'history',
icon: 'fa-history',
text: Messages.historyText,
tippy: Messages.historyButton
}).click(function () {
ooChannel.historyLastHash = ooChannel.lastHash;
ooChannel.currentIndex = ooChannel.cpIndex;
//Feedback.send('OO_HISTORY'); // XXX pull Feedback from require
var histConfig = {
onPatch: onPatch,
onCheckpoint: onCheckpoint,
onRevert: commit,
setHistory: setHistoryMode,
makeSnapshot: makeSnapshot,
onlyoffice: {
hashes: content.hashes || {},
channel: content.channel,
lastHash: ooChannel.lastHash
},
$toolbar: $('.cp-toolbar-container')
};
History.create(common, histConfig);
}).appendTo(toolbar.$drawer);
// Snapshots
var $snapshot = common.createButton('snapshots', true, {
remove: deleteSnapshot,
make: makeSnapshot,
load: loadSnapshot
});
toolbar.$drawer.append($snapshot);
})();
}
if (window.CP_DEV_MODE || DISPLAY_RESTORE_BUTTON) {
common.createButton('', true, {
name: 'delete',
@ -1879,6 +2132,11 @@ define([
sframeChan.event('EV_BURN_PAD', content.channel);
}
if (privateData.ooVersionHash) {
return void openVersionHash(privateData.ooVersionHash);
}
var useNewDefault = content.version && content.version >= 2;
openRtChannel(function () {
setMyId();

View File

@ -4,12 +4,13 @@ define([
'/api/config',
'/common/dom-ready.js',
'/common/requireconfig.js',
'/common/common-hash.js',
'/common/sframe-common-outer.js'
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
], function (nThen, ApiConfig, DomReady, RequireConfig, Hash, SFCommonO) {
var requireConfig = RequireConfig();
// Loaded in load #2
var hash, href;
var hash, href, version;
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
@ -27,6 +28,13 @@ define([
if (window.history && window.history.replaceState && hash) {
window.history.replaceState({}, window.document.title, '#');
}
var parsed = Hash.parsePadUrl(href);
if (parsed && parsed.hashData) {
var opts = parsed.getOptions();
version = opts.versionHash;
}
document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + window.location.pathname + 'inner.html?' +
requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req)));
@ -46,6 +54,7 @@ define([
}).nThen(function (/*waitFor*/) {
var addData = function (obj) {
obj.ooType = window.location.pathname.replace(/^\//, '').replace(/\/$/, '');
obj.ooVersionHash = version;
obj.ooForceVersion = localStorage.CryptPad_ooVersion || sessionStorage.CryptPad_ooVersion || "";
};
var addRpc = function (sframeChan, Cryptpad, Utils) {
@ -135,6 +144,13 @@ define([
}
Cryptpad.onlyoffice.execCommand(obj, cb);
});
sframeChan.on('EV_OO_OPENVERSION', function (obj, cb) {
if (!obj || !obj.hash) { return; }
var parsed = Hash.parsePadUrl(window.location.href);
var opts = parsed.getOptions();
opts.versionHash = obj.hash;
window.open(parsed.getUrl(opts));
});
Cryptpad.onlyoffice.onEvent.reg(function (obj) {
if (obj.ev === 'MESSAGE' && !/^cp\|/.test(obj.data)) {
try {

View File

@ -2090,7 +2090,7 @@ define([
if (first) {
// If the first message if not a checkpoint, it means it is the first
// message of the pad, so we have the full history!
if (!/^cp\|/.test(msg)) { fullHistory = true; }
if (!/^cp\|/.test(msg) && !data.toHash) { fullHistory = true; }
lastKnownHash = msg.slice(0,64);
first = false;
}
@ -2107,7 +2107,8 @@ define([
network.on('message', onMsg);
network.sendto(hk, JSON.stringify(['GET_HISTORY_RANGE', data.channel, {
from: data.lastKnownHash,
cpCount: data.cpCount || 2,
to: data.toHash,
cpCount: data.cpCount || 2, // Ignored if "to" is provided
txid: txid
}]));
};

View File

@ -2,6 +2,21 @@ define([
], function () {
var OO = {};
var getHistory = function (ctx, client, cb) {
var c = ctx.clients[client];
if (!c) { return void cb({error: 'ENOENT'}); }
var chan = ctx.channels[c.channel];
if (!chan) { return void cb({error: 'ENOCHAN'}); }
cb();
chan.history.forEach(function (msg) {
ctx.emit('MESSAGE', {
msg: msg,
validateKey: chan.validateKey
}, [client]);
});
ctx.emit('HISTORY_SYNCED', {}, [client]);
};
var openChannel = function (ctx, obj, client, cb) {
var channel = obj.channel;
var padChan = obj.padChan;
@ -24,11 +39,8 @@ define([
// ==> Use our netflux ID to create our client ID
if (!c.id) { c.id = chan.wc.myID + '-' + client; }
chan.history.forEach(function (msg) {
ctx.emit('MESSAGE', {
msg: msg,
validateKey: chan.validateKey
}, [client]);
getHistory(ctx, client, function () {
ctx.emit('READY', '', [client]);
});
// ==> And push the new tab to the list
@ -318,6 +330,9 @@ define([
if (cmd === 'OPEN_CHANNEL') {
return void openChannel(ctx, data, clientId, cb);
}
if (cmd === 'GET_HISTORY') {
return void getHistory(ctx, clientId, cb);
}
if (cmd === 'REENCRYPT') {
return void reencrypt(ctx, data, clientId, cb);
}

View File

@ -1106,8 +1106,9 @@ define([
var validate = nSecret.keys.validateKey;
var crypto = Crypto.createEncryptor(nSecret.keys);
Cryptpad.getHistoryRange({
channel: channel,
channel: data.channel || channel,
validateKey: validate,
toHash: data.toHash,
lastKnownHash: data.lastKnownHash
}, function (data) {
cb({