You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cryptpad/www/common/sframe-common-history.js

641 lines
24 KiB
JavaScript

7 years ago
define([
'jquery',
'/common/common-interface.js',
'/common/hyperscript.js',
'/customize/messages.js',
'/bower_components/nthen/index.js',
//'/bower_components/chainpad-json-validator/json-ot.js',
7 years ago
'/bower_components/chainpad/chainpad.dist.js',
], function ($, UI, h, Messages, nThen, ChainPad /* JsonOT */) {
7 years ago
//var ChainPad = window.ChainPad;
7 years ago
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..."); }
if (History.state) { return void console.error("Already loaded"); }
History.loading = true;
History.state = true;
var $toolbar = config.$toolbar;
var $hist = $toolbar.find('.cp-toolbar-history');
if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) {
throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory");
7 years ago
}
var getStates = function (rt) {
var states = [];
var b = rt.getAuthBlock();
if (b) { states.unshift(b); }
while (b.getParent()) {
b = b.getParent();
states.unshift(b);
}
return states;
};
var createRealtime = function (config) {
7 years ago
return ChainPad.create({
userName: 'history',
validateContent: function (content) {
try {
JSON.parse(content);
return true;
} catch (e) {
console.log('Failed to parse, rejecting patch');
return false;
}
},
7 years ago
initialState: '',
logLevel: config.debug ? 2 : 0,
7 years ago
noPrune: true
});
};
var fillChainPad = function (realtime, messages) {
messages.forEach(function (m) {
realtime.message(m);
});
};
7 years ago
4 years ago
var realtime;
var states = [];
var patchWidth = 0;
4 years ago
var c = 0;//states.length - 1;
var getIndex = function (i) {
return states.length - 1 + i;
};
var getRank = function (idx) {
return idx - states.length + 1;
};
// Get the author or group of author linked to a state
var getAuthor = function (idx, semantic) {
if (semantic === 1 || !config.extractMetadata) {
return states[idx].author;
}
try {
var val = JSON.parse(states[idx].getContent().doc);
var md = config.extractMetadata(val);
var users = Object.keys(md.users).sort();
return users.join();
} catch (e) {
console.error(e);
return states[idx].author;
}
};
var bar = h('span.cp-history-timeline-bar');
var onResize = function () {
var $bar = $(bar);
if (!$bar.width() || !$bar.length) { return; }
var widthPx = patchWidth * $bar.width() / 100;
$hist.removeClass('cp-smallpatch');
$bar.find('.cp-history-snapshot').css('margin-left', "");
var $pos = $hist.find('.cp-history-timeline-pos');
$pos.css('margin-left', "");
if (widthPx < 18) {
$hist.addClass('cp-smallpatch');
$bar.find('.cp-history-snapshot').css('margin-left', (widthPx/2-2)+"px");
$pos.css('margin-left', (widthPx/2-2)+"px");
}
};
// Refresh the timeline UI with the block states
4 years ago
var refreshBar = function () {
var $pos = $hist.find('.cp-history-timeline-pos');
4 years ago
var $bar = $(bar).html('');
var users = {
list: [],
author: '',
el: undefined,
i: 0
};
var user = {
list: [],
author: '',
el: undefined,
i: 0
};
var snapshotsData = {};
var snapshots = [];
try {
if (config.extractMetadata) {
var idx = states.length - 1;
var val = JSON.parse(states[idx].getContent().doc);
var md = config.extractMetadata(val);
if (md.snapshots) {
snapshotsData = md.snapshots;
snapshots = Object.keys(md.snapshots);
}
}
} catch (e) { console.error(e); }
4 years ago
var max = states.length - 1;
var check = function (obj, author, i) {
if (obj.author !== author) {
obj.author = author;
if (obj.el) {
$(obj.el).css('width', (100*(i - obj.i)/max)+'%');
}
obj.el = h('span.cp-history-bar-el');
obj.list.push(obj.el);
obj.i = i;
}
};
var snapshotsEl = [];
patchWidth = 100 / max;
var hash;
4 years ago
for (var i = 1; i < states.length; i++) {
hash = states[i].serverHash;
if (snapshots.indexOf(hash) !== -1) {
snapshotsEl.push(h('div.cp-history-snapshot', {
style: 'width:'+patchWidth+'%;left:'+(patchWidth * (i-1))+'%;',
title: snapshotsData[hash].title
}, h('i.fa.fa-camera')));
}
4 years ago
check(user, getAuthor(i, 1), i);
check(users, getAuthor(i, 2), i);
}
$(user.el).css('width', (100*(max + 1 - user.i)/max)+'%');
$(users.el).css('width', (100*(max + 1 - users.i)/max)+'%');
$bar.append([
h('span.cp-history-timeline-users', users.list),
h('span.cp-history-timeline-user', user.list),
h('div.cp-history-snapshots', [
$pos[0],
snapshotsEl
]),
4 years ago
]);
onResize();
4 years ago
};
var allMessages = [];
var lastKnownHash;
var isComplete = false;
var loadMoreHistory = function (config, common, cb) {
if (isComplete) { return void cb ('EFULL'); }
var realtime = createRealtime(config);
var sframeChan = common.getSframeChannel();
sframeChan.query('Q_GET_HISTORY_RANGE', {
lastKnownHash: lastKnownHash,
sharedFolder: config.sharedFolder
}, function (err, data) {
if (err) { return void console.error(err); }
if (!Array.isArray(data.messages)) { return void console.error('Not an array!'); }
lastKnownHash = data.lastKnownHash;
isComplete = data.isFull;
var messages = (data.messages || []).map(function (obj) {
return obj;
});
if (config.debug) { console.log(data.messages); }
Array.prototype.unshift.apply(allMessages, messages); // Destructive concat
fillChainPad(realtime, allMessages);
cb (null, realtime, data.isFull);
});
};
7 years ago
// config.setHistory(bool, bool)
// - bool1: history value
// - bool2: reset old content?
var render = function (val) {
if (typeof val === "undefined") { return; }
try {
config.applyVal(val);
} catch (e) {
// Probably a parse error
console.error(e);
}
};
var onClose = function () {
config.setHistory(false, true);
};
Messages.history_cantRestore = "Can't restore now. Disconnected."; // XXX
7 years ago
var onRevert = function () {
var closed = config.setHistory(false, false);
if (!closed) {
return void UI.alert(Messages.history_cantRestore);
}
7 years ago
config.onLocal();
config.onRemote();
return true;
7 years ago
};
config.setHistory(true);
7 years ago
var $bottom = $toolbar.find('.cp-toolbar-bottom');
7 years ago
var $cke = $toolbar.find('.cke_toolbox_main');
$hist.html('').css('display', 'flex');
$bottom.hide();
7 years ago
$cke.hide();
UI.spinner($hist).get().show();
7 years ago
var update = function (newRt) {
realtime = newRt;
7 years ago
if (!realtime) { return []; }
states = getStates(realtime);
4 years ago
refreshBar();
7 years ago
return states;
};
var $loadMore, $version, $time, get;
7 years ago
// Get the content of the selected version, and change the version number
var loading = false;
var loadMore = function (cb) {
if (loading) { return; }
loading = true;
4 years ago
$loadMore.find('.fa-ellipsis-h').hide();
$loadMore.find('.fa-refresh').show();
loadMoreHistory(config, common, function (err, newRt, isFull) {
if (err === 'EFULL') {
$loadMore.off('click').hide();
get(c);
$version.show();
return;
}
loading = false;
if (err) { return void console.error(err); }
update(newRt);
4 years ago
$loadMore.find('.fa-ellipsis-h').show();
$loadMore.find('.fa-refresh').hide();
get(c);
if (isFull) {
$loadMore.off('click').hide();
$version.show();
}
if (cb) { cb(); }
});
};
// semantic === 1 : group by user
// semantic === 2 : group by "group of users"
get = function (i, blockOnly, semantic) {
7 years ago
i = parseInt(i);
if (isNaN(i)) { return; }
if (i > 0) { i = 0; }
if (i < -(states.length - 2)) { i = -(states.length - 2); }
if (i <= -(states.length - 11)) {
loadMore();
}
var idx = getIndex(i);
4 years ago
if (semantic && i !== c) {
// If semantic is truc, jump to the next patch from a different netflux ID
var author = getAuthor(idx, semantic);
4 years ago
var forward = i > c;
for (var j = idx; (j > 0 && j < states.length ); (forward ? j++ : j--)) {
if (author !== getAuthor(j, semantic)) {
break;
}
4 years ago
idx = j;
i = getRank(idx);
}
}
if (blockOnly) { return states[idx]; }
var val = states[idx].getContent().doc;
7 years ago
c = i;
4 years ago
$hist.find('.cp-toolbar-history-next, .cp-toolbar-history-previous')
.prop('disabled', '');
if (c === -(states.length-1)) {
4 years ago
$hist.find('.cp-toolbar-history-previous').prop('disabled', 'disabled');
}
if (c === 0) {
4 years ago
$hist.find('.cp-toolbar-history-next').prop('disabled', 'disabled');
}
4 years ago
var $pos = $hist.find('.cp-history-timeline-pos');
var p = 100 * (1 - (-(c - 1) / (states.length-1)));
$pos.css('left', p+'%');
$pos.css('width', patchWidth+'%');
// Display the version when the full history is loaded
// Note: the first version is always empty and probably can't be displayed, so
// we can consider we have only states.length - 1 versions
$version.text(idx + ' / ' + (states.length-1));
var time = states[idx].time;
if (time) {
$time.text(new Date(time).toLocaleString());
} else { $time.text(''); }
7 years ago
if (config.debug) {
console.log(states[idx]);
var ops = states[idx] && states[idx].getPatch() && states[idx].getPatch().operations;
7 years ago
if (Array.isArray(ops)) {
ops.forEach(function (op) { console.log(op); });
}
}
7 years ago
return val || '';
};
/*
7 years ago
var getNext = function (step) {
return typeof step === "number" ? get(c + step) : get(c + 1);
};
var getPrevious = function (step) {
return typeof step === "number" ? get(c - step) : get(c - 1);
};
*/
7 years ago
Messages.history_fastPrev = "Previous editing session"; // XXX
Messages.history_userPrev = "Previous user"; // XXX
Messages.history_fastNext = "Next editing session"; // XXX
Messages.history_userNext = "Next user"; // XXX
7 years ago
// Create the history toolbar
var display = function () {
$hist.html('');
var fastPrev = h('button.cp-toolbar-history-previous', { title: Messages.history_fastPrev }, [
4 years ago
h('i.fa.fa-step-backward'),
h('i.fa.fa-users')
]);
var userPrev = h('button.cp-toolbar-history-previous', { title: Messages.history_userPrev }, [
4 years ago
h('i.fa.fa-step-backward'),
h('i.fa.fa-user')
]);
var prev = h('button.cp-toolbar-history-previous', { title: Messages.history_prev }, [
h('i.fa.fa-step-backward')
]);
var fastNext = h('button.cp-toolbar-history-next', { title: Messages.history_next }, [
h('i.fa.fa-users'),
h('i.fa.fa-step-forward'),
]);
var userNext = h('button.cp-toolbar-history-next', { title: Messages.history_userNext }, [
4 years ago
h('i.fa.fa-user'),
h('i.fa.fa-step-forward'),
]);
var next = h('button.cp-toolbar-history-next', { title: Messages.history_fastNext }, [
4 years ago
h('i.fa.fa-step-forward')
]);
var $fastPrev = $(fastPrev);
var $userPrev = $(userPrev);
var $prev = $(prev);
var $fastNext = $(fastNext);
var $userNext = $(userNext);
var $next = $(next);
var _loadMore = h('button.cp-toolbar-history-loadmore', { title: Messages.history_loadMore }, [
h('i.fa.fa-ellipsis-h'),
h('i.fa.fa-refresh.fa-spin.fa-3x.fa-fw', { style: 'display: none;' })
]);
var pos = h('span.cp-history-timeline-pos');
var time = h('div.cp-history-timeline-time');
$time = $(time);
$version = $(); // XXX
var timeline = h('div.cp-toolbar-history-timeline', [
h('div.cp-history-timeline-line', [
h('span.cp-history-timeline-legend', [
h('i.fa.fa-users'),
h('i.fa.fa-user')
]),
h('span.cp-history-timeline-loadmore', _loadMore),
h('span.cp-history-timeline-container', [
bar
])
]),
h('div.cp-history-timeline-actions', [
h('span.cp-history-timeline-prev', [
fastPrev,
userPrev,
prev,
]),
time,
h('span.cp-history-timeline-next', [
next,
userNext,
fastNext
])
])
]);
Messages.history_restore = "Restore";// XXX
Messages.history_close = "Close";// XXX
Messages.history_shareTitle = "Share a link to this version"; // XXX
4 years ago
var snapshot = h('button', {
title: Messages.snapshots_new,
4 years ago
disabled: History.readOnly ? 'disabled' : undefined
}, [
h('i.fa.fa-camera')
]);
var share = h('button', { title: Messages.history_shareTitle }, [
4 years ago
h('i.fa.fa-shhare-alt'),
h('span', Messages.shareButton)
]);
var restore = h('button', {
title: Messages.history_restoreTitle,
disabled: History.readOnly ? 'disabled' : undefined
}, [
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
])
]);
$hist.append([timeline, actions]);
onResize();
$(window).on('resize', onResize);
4 years ago
/*
var $rev = $('<button>', {
'class':'cp-toolbar-history-revert buttonSuccess fa fa-check-circle-o',
title: Messages.history_restoreTitle
}).appendTo($hist);//.text(Messages.history_restore);
if (History.readOnly) { $rev.css('visibility', 'hidden'); }
$('<span>', {'class': 'cp-history-filler'}).appendTo($hist);
var $fastPrev = $('<button>', {
'class': 'cp-toolbar-history-fast-previous fa fa-fast-backward buttonPrimary',
title: Messages.history_prev
}).appendTo($hist);
var $userPrev =$('<button>', {
'class': 'cp-toolbar-history-previous fa fa-backward buttonPrimary',
title: Messages.history_prev
}).appendTo($hist);
7 years ago
var $prev =$('<button>', {
'class': 'cp-toolbar-history-previous fa fa-step-backward buttonPrimary',
7 years ago
title: Messages.history_prev
}).appendTo($hist);
var $nav = $('<div>', {'class': 'cp-toolbar-history-goto'}).appendTo($hist);
7 years ago
var $next = $('<button>', {
'class': 'cp-toolbar-history-next fa fa-step-forward buttonPrimary',
7 years ago
title: Messages.history_next
}).appendTo($hist);
var $userNext =$('<button>', {
'class': 'cp-toolbar-history-next fa fa-forward buttonPrimary',
title: Messages.history_next
}).appendTo($hist);
var $fastNext = $('<button>', {
'class': 'cp-toolbar-history-fast-next fa fa-fast-forward buttonPrimary',
title: Messages.history_next
}).appendTo($hist);
var $share = $('<button>', {
'class': 'fa fa-shhare-alt buttonPrimary',
title: Messages.shareButton
}).appendTo($hist);
$('<span>', {'class': 'cp-history-filler'}).appendTo($hist);
$time = $(h('div')).appendTo($hist);
var $close = $('<button>', {
'class':'cp-toolbar-history-close fa fa-window-close',
title: Messages.history_closeTitle
}).appendTo($hist);
var $bar = $('<div>', {'class': 'cp-toolbar-history-bar'}).appendTo($nav);
var $container = $('<div>', {'class':'cp-toolbar-history-pos-container'}).appendTo($bar);
$('<div>', {'class': 'cp-toolbar-history-pos'}).appendTo($container);
$version = $('<span>', {
'class': 'cp-toolbar-history-version'
}).prependTo($bar).hide();
$loadMore = $('<button>', {
'class':'cp-toolbar-history-loadmore fa fa-ellipsis-h',
title: Messages.history_loadMore
}).click(function () {
loadMore(function () {
get(c);
});
}).prependTo($container);
7 years ago
// Load a version when clicking on the bar
$container.click(function (e) {
7 years ago
e.stopPropagation();
if (!$(e.target).is('.cp-toolbar-history-pos-container')) { return; }
var p = e.offsetX / $container.width();
var v = -Math.round((states.length - 1) * (1 - p));
render(get(v));
7 years ago
});
4 years ago
*/
var $bar = $(bar);
$bar.find('.cp-history-snapshots').append(pos);
$bar.click(function (e) {
4 years ago
e.stopPropagation();
var $t = $(e.target);
if ($t.closest('.cp-history-snapshot').length) {
$t = $t.closest('.cp-history-snapshot');
}
var isEl = $t.is('.cp-history-snapshot');
if (!$t.is('.cp-history-snapshots') && !isEl) { return; }
var x = e.offsetX;
if (isEl) {
x += $t.position().left;
}
var p = x / $bar.width();
var v = 1-Math.ceil((states.length - 1) * (1 - p));
4 years ago
render(get(v));
});
$loadMore = $(_loadMore).click(function () {
loadMore(function () {
get(c);
});
});
7 years ago
var onKeyDown, onKeyUp;
4 years ago
var closeUI = function () {
History.state = false;
7 years ago
$hist.hide();
$bottom.show();
7 years ago
$cke.show();
$(window).trigger('resize');
$(window).off('keydown', onKeyDown);
$(window).off('keyup', onKeyUp);
7 years ago
};
// Version buttons
$prev.click(function () { render(get(c - 1)); });
$next.click(function () { render(get(c + 1)); });
$userPrev.click(function () { render(get(c - 1, false, 1)); });
$userNext.click(function () { render(get(c + 1, false, 1)); });
$fastPrev.click(function () { render(get(c - 1, false, 2)); });
$fastNext.click(function () { render(get(c + 1, false, 2)); });
onKeyDown = function (e) {
7 years ago
var p = function () { e.preventDefault(); };
if (e.which === 39) { p(); return $next.click(); } // Right
if (e.which === 37) { p(); return $prev.click(); } // Left
if (e.which === 38) { p(); return $userNext.click(); } // Up
if (e.which === 40) { p(); return $userPrev.click(); } // Down
if (e.which === 33) { p(); return $fastNext.click(); } // PageUp
if (e.which === 34) { p(); return $fastPrev.click(); } // PageUp
4 years ago
if (e.which === 27) { p(); $(close).click(); }
};
onKeyUp = function (e) { e.stopPropagation(); };
$(window).on('keydown', onKeyDown).on('keyup', onKeyUp).focus();
// Share
4 years ago
$(share).click(function () {
var block = get(c, true);
common.getSframeChannel().event('EV_SHARE_OPEN', {
versionHash: block.serverHash,
//title: title
});
});
// Close & restore buttons
4 years ago
$(close).click(function () {
7 years ago
states = [];
onClose();
4 years ago
closeUI();
7 years ago
});
4 years ago
$(restore).click(function () {
UI.confirm(Messages.history_restorePrompt, function (yes) {
7 years ago
if (!yes) { return; }
var done = onRevert();
if (done) {
4 years ago
closeUI();
UI.log(Messages.history_restoreDone);
}
7 years ago
});
});
// Display the latest content
render(get(c));
$(window).trigger('resize');
7 years ago
};
if (config.onOpen) {
config.onOpen();
}
7 years ago
// Load all the history messages into a new chainpad object
loadMoreHistory(config, common, function (err, newRt, isFull) {
History.readOnly = common.getMetadataMgr().getPrivateData().readOnly;
7 years ago
History.loading = false;
if (err) { throw new Error(err); }
update(newRt);
7 years ago
display();
if (isFull) {
$loadMore.off('click').hide();
$version.show();
}
7 years ago
});
};
return History;
});