View and restore the history of a pad

This commit is contained in:
yflory 2017-04-21 17:31:47 +02:00
parent 43c045721c
commit 92ea03d7d9
9 changed files with 314 additions and 71 deletions

View File

@ -422,6 +422,17 @@
display: inline-block;
input { width: 50px; }
}
.gotoInput {
vertical-align: middle;
}
}
.cke_toolbox .cryptpad-toolbar-history {
input.gotoInput {
background: white;
height: 20px;
padding: 3px 3px;
border-radius: 5px;
}
}
.cryptpad-spinner {
height: 16px;

View File

@ -488,6 +488,15 @@
.cryptpad-toolbar-history .goto input {
width: 50px;
}
.cryptpad-toolbar-history .gotoInput {
vertical-align: middle;
}
.cke_toolbox .cryptpad-toolbar-history input.gotoInput {
background: white;
height: 20px;
padding: 3px 3px;
border-radius: 5px;
}
.cryptpad-spinner {
height: 16px;
width: 16px;

View File

@ -115,6 +115,17 @@ define(function () {
out.cancel = "Annuler";
out.cancelButton = 'Annuler (Echap)';
out.historyButton = "Afficher l'historique du document";
out.history_next = "Voir la version suivante";
out.history_prev = "Voir la version précédente";
out.history_goTo = "Voir la version sélectionnée";
out.history_close = "Retour";
out.history_closeTitle = "Fermer l'historique";
out.history_restore = "Restaurer";
out.history_restoreTitle = "Restaurer la version du document sélectionnée";
out.history_restorePrompt = "Êtes-vous sûr de vouloir remplacer la version actuelle du document par la version affichée ?";
out.history_restoreDone = "Document restauré";
// Polls
out.poll_title = "Sélecteur de date Zero Knowledge";

View File

@ -117,6 +117,17 @@ define(function () {
out.cancel = "Cancel";
out.cancelButton = 'Cancel (esc)';
out.historyButton = "Display the document history";
out.history_next = "Go to the next version";
out.history_prev = "Go to the previous version";
out.history_goTo = "Go to the selected version";
out.history_close = "Back";
out.history_closeTitle = "Close the history";
out.history_restore = "Restore";
out.history_restoreTitle = "Restore the selected version of the document";
out.history_restorePrompt = "Are you sure you want to replace the current version of the document by the displayed one?";
out.history_restoreDone = "Document restored";
// Polls
out.poll_title = "Zero Knowledge Date Picker";

View File

@ -54,6 +54,8 @@ define([
var defaultName = Cryptpad.getDefaultName(parsedHash);
var initialState = Messages.codeInitialState;
var isHistoryMode = false;
var editor = module.editor = CMeditor.fromTextArea($textarea[0], {
lineNumbers: true,
lineWrapping: true,
@ -164,6 +166,14 @@ define([
var canonicalize = function (t) { return t.replace(/\r\n/g, '\n'); };
var setHistory = function (bool, update) {
isHistoryMode = bool;
setEditable(!bool);
if (!bool && update) {
config.onRemote();
}
};
var isDefaultTitle = function () {
var parsed = Cryptpad.parsePadUrl(window.location.href);
return Cryptpad.isDefaultName(parsed, document.title);
@ -191,6 +201,7 @@ define([
var onLocal = config.onLocal = function () {
if (initializing) { return; }
if (isHistoryMode) { return; }
if (readOnly) { return; }
editor.save();
@ -372,7 +383,7 @@ define([
var onInit = config.onInit = function (info) {
userList = info.userList;
var config = {
var configTb = {
displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'],
userData: userData,
readOnly: readOnly,
@ -388,8 +399,7 @@ define([
},
common: Cryptpad
};
if (readOnly) {delete config.changeNameID; }
toolbar = module.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.getLag, userList, config);
toolbar = module.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.getLag, userList, configTb);
var $rightside = $bar.find('.' + Toolbar.constants.rightside);
var $userBlock = $bar.find('.' + Toolbar.constants.username);
@ -402,19 +412,36 @@ define([
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
}
/* add an export button */
var $hist = Cryptpad.createButton();
var historyRender = function () {};
var historyClose = function () {
// TODO: enable onlocal, onremote... (or at least the display part)
/* add a history button */
var histConfig = {};
histConfig.onRender = function (val) {
if (typeof val === "undefined") { return; }
try {
var hjson = JSON.parse(val || '{}');
var remoteDoc = hjson.content;
editor.setValue(remoteDoc || '');
editor.save();
} catch (e) {
// Probably a parse error
console.error(e);
}
};
var historyTodo = function (hist) {
hist.display($bar, historyRender, historyClose);
histConfig.onClose = function () {
// Close button clicked
setHistory(false, true);
};
$hist.removeClass('fa-question').addClass('fa-history').click(function () {
// TODO: disable onlocal, onremote...
Cryptpad.getHistory(historyTodo);
});
histConfig.onRevert = function () {
// Revert button clicked
setHistory(false, false);
config.onLocal();
config.onRemote();
};
histConfig.onReady = function () {
// Called when the history is loaded and the UI displayed
setHistory(true);
};
histConfig.$toolbar = $bar;
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
$rightside.append($hist);
/* save as template */
@ -663,6 +690,7 @@ define([
var onRemote = config.onRemote = function () {
if (initializing) { return; }
if (isHistoryMode) { return; }
var scroll = editor.getScrollInfo();
var oldDoc = canonicalize($textarea.val());

View File

@ -9,6 +9,17 @@ define([
var History = {};
var getStates = function (rt) {
var states = [];
var b = rt.getAuthBlock();
if (b) { states.unshift(b.getContent().doc); }
while (b.getParent()) {
b = b.getParent();
states.unshift(b.getContent().doc);
}
return states;
};
/* TODO
* Implement GET_FULL_HISTORY serverside
* All the history messages should be ['FULL_HISTORY', wc.id, msg]
@ -23,22 +34,22 @@ define([
var wcId = common.hrefToHexChannelId(window.location.href);
var createRealtime = function(chan) {
console.log(ChainPad);
return ChainPad.create({
userName: 'history',
initialState: '',
transformFunction: JsonOT.validate,
logLevel: 0
logLevel: 1,
noPrune: true
});
};
var realtime = createRealtime();
var secret = Cryptpad.getSecrets();
var secret = common.getSecrets();
var crypto = Crypto.createEncryptor(secret.keys);
var to = window.setTimeout(function () {
cb('[GET_FULL_HISTORY_TIMEOUT]');
}, 3000);
}, 30000);
var parse = function (msg) {
try {
@ -50,104 +61,169 @@ define([
var onMsg = function (msg) {
var parsed = parse(msg);
if (parsed[0] === 'FULL_HISTORY_END') {
console.log('END');
window.clearTimeout(to);
cb(null, realtime);
return;
}
if (parsed[0] !== 'FULL_HISTORY') { return; }
var msg = parsed[1];
var decryptedMsg = crypto.decrypt(msg, secret.keys.validateKey);
realtime.message(decryptedMsg);
msg = parsed[1][4];
if (msg) {
msg = msg.replace(/^cp\|/, '');
var decryptedMsg = crypto.decrypt(msg, secret.keys.validateKey);
realtime.message(decryptedMsg);
}
};
network.on('message', function (msg, sender) {
onMsg(msg);
});
network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', wcId]));
network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', wcId, secret.keys.validateKey]));
};
var create = History.create = function (common, cb) {
var exp = {};
var create = History.create = function (common, config) {
if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");}
var $toolbar = config.$toolbar;
var noFunc = function () {};
var render = config.onRender || noFunc;
var onClose = config.onClose || noFunc;
var onRevert = config.onRevert || noFunc;
var onReady = config.onReady || noFunc;
var states = exp.states = ['a', 'b', 'c'];
var c = exp.current = states.length - 1;
console.log(c);
var Messages = common.Messages;
var realtime;
var states = []; //getStates(rt); //['a', 'b', 'c'];
var c = states.length - 1;
var $hist = $toolbar.find('.cryptpad-toolbar-history');
var $left = $toolbar.find('.cryptpad-toolbar-leftside');
var $right = $toolbar.find('.cryptpad-toolbar-rightside');
var onUpdate;
var update = exp.update = function () {
states = [];
var update = function () {
if (!realtime) { return []; }
states = getStates(realtime);
if (typeof onUpdate === "function") { onUpdate(); }
return states;
};
var get = exp.get = function (i) {
// Get the content of the selected version, and change the version number
var get = function (i) {
i = parseInt(i);
console.log('getting', i);
if (typeof(i) !== "number" || i < 0 || i > states.length - 1) { return; }
var hash = states[i];
if (isNaN(i)) { return; }
if (i < 0) { i = 0; }
if (i > states.length - 1) { i = states.length - 1; }
var val = states[i];
c = i;
if (typeof onUpdate === "function") { onUpdate(); }
return '';
$hist.find('.next, .previous').show();
if (c === states.length - 1) { $hist.find('.next').hide(); }
if (c === 0) { $hist.find('.previous').hide(); }
return val || '';
};
var getNext = exp.getNext = function () {
if (c < states.length - 1) { return get(++c); }
var getNext = function (step) {
return typeof step === "number" ? get(c + step) : get(c + 1);
};
var getPrevious = exp.getPrevious = function () {
if (c > 0) { return get(--c); }
var getPrevious = function (step) {
return typeof step === "number" ? get(c - step) : get(c - 1);
};
var display = exp.display = function ($toolbar, render, onClose) {
var $hist = $toolbar.find('.cryptpad-toolbar-history').html('').show();
var $left = $toolbar.find('.cryptpad-toolbar-leftside').hide();
var $right = $toolbar.find('.cryptpad-toolbar-rightside').hide();
var $prev =$('<button>', {'class': 'previous'}).text('<<').appendTo($hist);
var $next = $('<button>', {'class': 'next'}).text('>>').appendTo($hist);
// Create the history toolbar
var display = function () {
$hist.html('').show();
$left.hide();
$right.hide();
var $prev =$('<button>', {
'class': 'previous fa fa-step-backward',
title: Messages.history_prev
}).appendTo($hist);
var $next = $('<button>', {
'class': 'next fa fa-step-forward',
title: Messages.history_next
}).appendTo($hist);
var $nav = $('<div>', {'class': 'goto'}).appendTo($hist);
var $cur = $('<input>', {
'class' : 'gotoInput',
'type' : 'number',
'min' : '1',
'max' : states.length
}).val(c + 1).appendTo($nav);
var $label = $('<label>').text(' / '+ states.length).appendTo($nav);
var $goTo = $('<button>').text('V').appendTo($nav);
var $goTo = $('<button>', {
'class': 'fa fa-check',
'title': Messages.history_goTo
}).appendTo($nav);
$('<br>').appendTo($nav);
var $rev = $('<button>', {'class':'revertHistory'}).text('TODO: revert').appendTo($nav);
var $close = $('<button>', {'class':'closeHistory'}).text('TODO: close').appendTo($nav);
var $rev = $('<button>', {
'class':'revertHistory',
title: Messages.history_restoreTitle
}).text(Messages.history_restore).appendTo($nav);
var $close = $('<button>', {
'class':'closeHistory',
title: Messages.history_closeTitle
}).text(Messages.history_close).appendTo($nav);
onUpdate = function () {
$cur.attr('max', exp.states.length);
$cur.attr('max', states.length);
$cur.val(c+1);
$label.text(' / ' + states.length);
};
var toRender = function (getter) {
return function () { render(getter()) };
};
$prev.click(toRender(getPrevious));
$next.click(toRender(getNext));
$goTo.click(function () {
render( get($cur.val() - 1) )
});
$close.click(function () {
var close = function () {
$hist.hide();
$left.show();
$right.show();
};
// Buttons actions
$prev.click(function () { render(getPrevious()); });
$next.click(function () { render(getNext()); });
$goTo.click(function () { render( get($cur.val() - 1) ); });
$cur.keydown(function (e) {
var p = function () { e.preventDefault(); };
if (e.which === 13) { p(); return render( get($cur.val() - 1) ); } // Enter
if ([37, 40].indexOf(e.which) >= 0) { p(); return render(getPrevious()); } // Left
if ([38, 39].indexOf(e.which) >= 0) { p(); return render(getNext()); } // Right
if (e.which === 33) { p(); return render(getNext(10)); } // PageUp
if (e.which === 34) { p(); return render(getPrevious(10)); } // PageUp
if (e.which === 27) { p(); $close.click(); }
}).focus();
$cur.on('change', function () {
$goTo.click();
});
$close.click(function () {
states = [];
close();
onClose();
});
$rev.click(function () {
common.confirm(Messages.history_restorePrompt, function (yes) {
if (!yes) { return; }
close();
onRevert();
common.log(Messages.history_restoreDone);
});
});
// Display the latest content
render(get(c));
};
// Load all the history messages into a new chainpad object
loadHistory(common, function (err, newRt) {
if (err) { throw new Error(err); }
realtime = exp.realtime = newRt;
cb(exp);
realtime = newRt;
update();
c = states.length - 1;
display();
onReady();
});
};

View File

@ -83,9 +83,7 @@ define([
common.findStronger = Hash.findStronger;
// History
common.getHistory = function (cb) {
return History.create(common, cb);
};
common.getHistory = function (config) { return History.create(common, config); };
var getStore = common.getStore = function () {
if (store) { return store; }
@ -808,6 +806,18 @@ define([
style: 'font:'+size+' FontAwesome'
});
break;
case 'history':
button = $('<button>', {
title: Messages.historyButton,
'class': "fa fa-history",
style: 'font:'+size+' FontAwesome'
});
if (data.histConfig) {
button.click(function () {
common.getHistory(data.histConfig);
});
};
break;
default:
button = $('<button>', {
'class': "fa fa-question",

View File

@ -110,6 +110,8 @@ define([
var parsedHash = Cryptpad.parsePadUrl(window.location.href);
var defaultName = Cryptpad.getDefaultName(parsedHash);
var isHistoryMode = false;
if (readOnly) {
$('#pad-iframe')[0].contentWindow.$('#cke_1_toolbox > .cke_toolbox_main').hide();
}
@ -413,6 +415,14 @@ define([
}
};
var setHistory = function (bool, update) {
isHistoryMode = bool;
setEditable(!bool);
if (!bool && update) {
realtimeOptions.onRemote();
}
};
var updateTitle = function (newTitle) {
if (newTitle === document.title) { return; }
// Change the title now, and set it back to the old value if there is an error
@ -477,6 +487,7 @@ define([
var onRemote = realtimeOptions.onRemote = function () {
if (initializing) { return; }
if (isHistoryMode) { return; }
var oldShjson = stringifyDOM(inner);
@ -568,7 +579,7 @@ define([
var onInit = realtimeOptions.onInit = function (info) {
userList = info.userList;
var config = {
var configTb = {
displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'],
userData: userData,
readOnly: readOnly,
@ -584,8 +595,7 @@ define([
},
common: Cryptpad
};
if (readOnly) {delete config.changeNameID; }
toolbar = info.realtime.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.getLag, userList, config);
toolbar = info.realtime.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.getLag, userList, configTb);
var $rightside = $bar.find('.' + Toolbar.constants.rightside);
var $userBlock = $bar.find('.' + Toolbar.constants.username);
@ -617,6 +627,39 @@ define([
$rightside.append($collapse);
}
/* add a history button */
var histConfig = {};
histConfig.onRender = function (val) {
if (typeof val === "undefined") { return; }
try {
applyHjson(val || '');
/*var hjson = JSON.parse(val || '{}'); // TODO
var remoteDoc = hjson.content;
editor.setValue(remoteDoc || ''); // TODO
editor.save(); // TODO*/
} catch (e) {
// Probably a parse error
console.error(e);
}
};
histConfig.onClose = function () {
// Close button clicked
setHistory(false, true);
};
histConfig.onRevert = function () {
// Revert button clicked
setHistory(false, false);
realtimeOptions.onLocal();
realtimeOptions.onRemote();
};
histConfig.onReady = function () {
// Called when the history is loaded and the UI displayed
setHistory(true);
};
histConfig.$toolbar = $bar;
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
$rightside.append($hist);
/* save as template */
if (!Cryptpad.isTemplate(window.location.href)) {
var templateObj = {
@ -767,6 +810,7 @@ define([
var onLocal = realtimeOptions.onLocal = function () {
if (initializing) { return; }
if (isHistoryMode) { return; }
if (readOnly) { return; }
// stringify the json and send it into chainpad

View File

@ -74,6 +74,8 @@ define([
var defaultName = Cryptpad.getDefaultName(parsedHash);
var initialState = Messages.slideInitialState;
var isHistoryMode = false;
var editor = module.editor = CMeditor.fromTextArea($textarea[0], {
lineNumbers: true,
lineWrapping: true,
@ -214,6 +216,14 @@ define([
var canonicalize = function (t) { return t.replace(/\r\n/g, '\n'); };
var setHistory = function (bool, update) {
isHistoryMode = bool;
setEditable(!bool);
if (!bool && update) {
config.onRemote();
}
};
var isDefaultTitle = function () {
var parsed = Cryptpad.parsePadUrl(window.location.href);
return Cryptpad.isDefaultName(parsed, APP.title);
@ -245,6 +255,7 @@ define([
var onLocal = config.onLocal = function () {
if (initializing) { return; }
if (isHistoryMode) { return; }
if (readOnly) { return; }
editor.save();
@ -503,7 +514,7 @@ define([
var onInit = config.onInit = function (info) {
userList = info.userList;
var config = {
var configTb = {
displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'],
userData: userData,
readOnly: readOnly,
@ -519,8 +530,7 @@ define([
},
common: Cryptpad
};
if (readOnly) {delete config.changeNameID; }
toolbar = module.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.getLag, info.userList, config);
toolbar = module.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.getLag, info.userList, configTb);
var $rightside = $bar.find('.' + Toolbar.constants.rightside);
var $userBlock = $bar.find('.' + Toolbar.constants.username);
@ -533,6 +543,38 @@ define([
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
}
/* add a history button */
var histConfig = {};
histConfig.onRender = function (val) {
if (typeof val === "undefined") { return; }
try {
var hjson = JSON.parse(val || '{}');
var remoteDoc = hjson.content;
editor.setValue(remoteDoc || '');
editor.save();
} catch (e) {
// Probably a parse error
console.error(e);
}
};
histConfig.onClose = function () {
// Close button clicked
setHistory(false, true);
};
histConfig.onRevert = function () {
// Revert button clicked
setHistory(false, false);
config.onLocal();
config.onRemote();
};
histConfig.onReady = function () {
// Called when the history is loaded and the UI displayed
setHistory(true);
};
histConfig.$toolbar = $bar;
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
$rightside.append($hist);
/* save as template */
if (!Cryptpad.isTemplate(window.location.href)) {
var templateObj = {
@ -842,6 +884,7 @@ define([
var onRemote = config.onRemote = function () {
if (initializing) { return; }
if (isHistoryMode) { return; }
var scroll = editor.getScrollInfo();
var oldDoc = canonicalize($textarea.val());