|
|
|
|
define([
|
|
|
|
|
'jquery',
|
|
|
|
|
'/common/common-interface.js',
|
|
|
|
|
'/common/common-util.js',
|
|
|
|
|
'/common/hyperscript.js',
|
|
|
|
|
'/customize/messages.js',
|
|
|
|
|
'/bower_components/nthen/index.js',
|
|
|
|
|
//'/bower_components/chainpad-json-validator/json-ot.js',
|
|
|
|
|
|
|
|
|
|
'/bower_components/chainpad/chainpad.dist.js',
|
|
|
|
|
], function ($, UI, Util, h, Messages, nThen, ChainPad /* JsonOT */) {
|
|
|
|
|
//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..."); }
|
|
|
|
|
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');
|
|
|
|
|
$hist.addClass('cp-history-init');
|
|
|
|
|
|
|
|
|
|
if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) {
|
|
|
|
|
throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
initialState: '',
|
|
|
|
|
logLevel: config.debug ? 2 : 0,
|
|
|
|
|
noPrune: true
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var fillChainPad = function (realtime, messages) {
|
|
|
|
|
messages.forEach(function (m) {
|
|
|
|
|
realtime.message(m);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var realtime;
|
|
|
|
|
var states = [];
|
|
|
|
|
var patchWidth = 0;
|
|
|
|
|
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
|
|
|
|
|
var refreshBar = function (snapshotsOnly) {
|
|
|
|
|
var $pos = $hist.find('.cp-history-timeline-pos');
|
|
|
|
|
var $bar = $(bar);
|
|
|
|
|
var users = {
|
|
|
|
|
list: [],
|
|
|
|
|
author: '',
|
|
|
|
|
el: undefined,
|
|
|
|
|
i: 0
|
|
|
|
|
};
|
|
|
|
|
var user = {
|
|
|
|
|
list: [],
|
|
|
|
|
author: '',
|
|
|
|
|
el: undefined,
|
|
|
|
|
i: 0
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var snapshotsData = {};
|
|
|
|
|
var snapshots = [];
|
|
|
|
|
if (config.getLastMetadata) {
|
|
|
|
|
try {
|
|
|
|
|
var md = config.getLastMetadata();
|
|
|
|
|
if (md.snapshots) {
|
|
|
|
|
snapshotsData = md.snapshots;
|
|
|
|
|
snapshots = Object.keys(md.snapshots);
|
|
|
|
|
}
|
|
|
|
|
} catch (e) { console.error(e); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var max = states.length - 1;
|
|
|
|
|
var snapshotsEl = [];
|
|
|
|
|
patchWidth = 100 / max;
|
|
|
|
|
|
|
|
|
|
// Check if we need a new block on the index i for the "obj" type (user or users)
|
|
|
|
|
var check = function (obj, author, i) {
|
|
|
|
|
if (snapshotsOnly) { return; }
|
|
|
|
|
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 hash;
|
|
|
|
|
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')));
|
|
|
|
|
}
|
|
|
|
|
if (config.drive) {
|
|
|
|
|
// Display only one bar, split by patch
|
|
|
|
|
check(user, i, i);
|
|
|
|
|
} else {
|
|
|
|
|
// Display two bars, split by author(s)
|
|
|
|
|
check(user, getAuthor(i, 1), i);
|
|
|
|
|
check(users, getAuthor(i, 2), i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (snapshotsOnly) {
|
|
|
|
|
// We only want to redraw the snapshots
|
|
|
|
|
$bar.find('.cp-history-snapshots').html('').append([
|
|
|
|
|
$pos,
|
|
|
|
|
snapshotsEl
|
|
|
|
|
]);
|
|
|
|
|
} else {
|
|
|
|
|
$(user.el).css('width', (100*(max + 1 - user.i)/max)+'%');
|
|
|
|
|
if (!config.drive) {
|
|
|
|
|
$(users.el).css('width', (100*(max + 1 - users.i)/max)+'%');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$bar.html('').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
|
|
|
|
|
]),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onResize();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// We're supposed to receive 2 checkpoints. If the result is only ONE message
|
|
|
|
|
// and this message is a checkpoint, it means it's the last message of the history
|
|
|
|
|
// (and this is a trimmed history)
|
|
|
|
|
if (messages.length === 1) {
|
|
|
|
|
var parsed = JSON.parse(messages[0].msg);
|
|
|
|
|
if (parsed[0] === 4) {
|
|
|
|
|
isComplete = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (config.debug) { console.log(data.messages); }
|
|
|
|
|
Array.prototype.unshift.apply(allMessages, messages); // Destructive concat
|
|
|
|
|
fillChainPad(realtime, allMessages);
|
|
|
|
|
cb (null, realtime, data.isFull);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var onRevert = function () {
|
|
|
|
|
// Before we can restore the current version, we need to update metadataMgr
|
|
|
|
|
// so that it will uses the snapshots from the realtime version!
|
|
|
|
|
// Restoring the snapshots to their old version would go against the
|
|
|
|
|
// goal of having snapshots
|
|
|
|
|
if (config.getLastMetadata) {
|
|
|
|
|
var metadataMgr = common.getMetadataMgr();
|
|
|
|
|
var lastMd = config.getLastMetadata();
|
|
|
|
|
var _snapshots = lastMd.snapshots;
|
|
|
|
|
var _users = lastMd.users;
|
|
|
|
|
var md = Util.clone(metadataMgr.getMetadata());
|
|
|
|
|
md.snapshots = _snapshots;
|
|
|
|
|
md.users = _users;
|
|
|
|
|
metadataMgr.updateMetadata(md);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// And now we can properly restore the content
|
|
|
|
|
var closed = config.setHistory(false, false);
|
|
|
|
|
if (!closed) {
|
|
|
|
|
return void UI.alert(Messages.history_cantRestore);
|
|
|
|
|
}
|
|
|
|
|
config.onLocal();
|
|
|
|
|
config.onRemote();
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
config.setHistory(true);
|
|
|
|
|
|
|
|
|
|
var $bottom = $toolbar.find('.cp-toolbar-bottom');
|
|
|
|
|
var $cke = $toolbar.find('.cke_toolbox_main');
|
|
|
|
|
|
|
|
|
|
$hist.html('').css('display', 'flex');
|
|
|
|
|
$bottom.hide();
|
|
|
|
|
$cke.hide();
|
|
|
|
|
|
|
|
|
|
UI.spinner($hist).get().show();
|
|
|
|
|
|
|
|
|
|
var update = function (newRt) {
|
|
|
|
|
realtime = newRt;
|
|
|
|
|
if (!realtime) { return []; }
|
|
|
|
|
states = getStates(realtime);
|
|
|
|
|
refreshBar();
|
|
|
|
|
return states;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var $loadMore, $time, get;
|
|
|
|
|
|
|
|
|
|
// Get the content of the selected version, and change the version number
|
|
|
|
|
var loading = false;
|
|
|
|
|
var loadMore = function (cb) {
|
|
|
|
|
if (loading) { return; }
|
|
|
|
|
loading = true;
|
|
|
|
|
$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);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
loading = false;
|
|
|
|
|
if (err) { return void console.error(err); }
|
|
|
|
|
update(newRt);
|
|
|
|
|
$loadMore.find('.fa-ellipsis-h').show();
|
|
|
|
|
$loadMore.find('.fa-refresh').hide();
|
|
|
|
|
get(c);
|
|
|
|
|
if (isFull) {
|
|
|
|
|
$loadMore.off('click').hide();
|
|
|
|
|
}
|
|
|
|
|
if (cb) { cb(); }
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// semantic === 1 : group by user
|
|
|
|
|
// semantic === 2 : group by "group of users"
|
|
|
|
|
get = function (i, blockOnly, semantic) {
|
|
|
|
|
i = parseInt(i);
|
|
|
|
|
if (isNaN(i)) { return; }
|
|
|
|
|
if (i > 0) { i = 0; }
|
|
|
|
|
if (i < -(states.length - 2)) { i = -(states.length - 2); }
|
|
|
|
|
|
|
|
|
|
var idx = getIndex(i);
|
|
|
|
|
if (semantic && i !== c) {
|
|
|
|
|
// If semantic is true, jump to the next patch from a different netflux ID
|
|
|
|
|
var author = getAuthor(idx, semantic);
|
|
|
|
|
var forward = i > c;
|
|
|
|
|
for (var j = idx; (j > 0 && j < states.length ); (forward ? j++ : j--)) {
|
|
|
|
|
if (author !== getAuthor(j, semantic)) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
idx = j;
|
|
|
|
|
i = getRank(idx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (i <= -(states.length - 11)) {
|
|
|
|
|
loadMore();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (blockOnly) { return states[idx]; }
|
|
|
|
|
|
|
|
|
|
var val = states[idx].getContent().doc;
|
|
|
|
|
c = i;
|
|
|
|
|
$hist.find('.cp-toolbar-history-next, .cp-toolbar-history-previous')
|
|
|
|
|
.prop('disabled', '');
|
|
|
|
|
if (c === -(states.length-1)) {
|
|
|
|
|
$hist.find('.cp-toolbar-history-previous').prop('disabled', 'disabled');
|
|
|
|
|
}
|
|
|
|
|
if (c === 0) {
|
|
|
|
|
$hist.find('.cp-toolbar-history-next').prop('disabled', 'disabled');
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
var time = states[idx].time;
|
|
|
|
|
if (time) {
|
|
|
|
|
$time.text(new Date(time).toLocaleString());
|
|
|
|
|
} else { $time.text(''); }
|
|
|
|
|
|
|
|
|
|
if (config.debug) {
|
|
|
|
|
console.log(states[idx]);
|
|
|
|
|
var ops = states[idx] && states[idx].getPatch() && states[idx].getPatch().operations;
|
|
|
|
|
if (Array.isArray(ops)) {
|
|
|
|
|
ops.forEach(function (op) { console.log(op); });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return val || '';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
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);
|
|
|
|
|
};
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
var makeSnapshot = function (title, $input) {
|
|
|
|
|
var idx = getIndex(c);
|
|
|
|
|
if (!config.getLastMetadata || !config.setLastMetadata) { return; }
|
|
|
|
|
try {
|
|
|
|
|
var block = states[idx];
|
|
|
|
|
var hash = block.serverHash;
|
|
|
|
|
var md = config.getLastMetadata();
|
|
|
|
|
md.snapshots = md.snapshots || {};
|
|
|
|
|
if (md.snapshots[hash]) { return; }
|
|
|
|
|
md.snapshots[hash] = {
|
|
|
|
|
title: title,
|
|
|
|
|
time: block.time ? (+new Date(block.time)) : +new Date()
|
|
|
|
|
};
|
|
|
|
|
var sent = config.setLastMetadata(md);
|
|
|
|
|
if (!sent) { return void UI.alert(Messages.snapshots_cantMake); }
|
|
|
|
|
$input.val('');
|
|
|
|
|
refreshBar();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Create the history toolbar
|
|
|
|
|
var display = function () {
|
|
|
|
|
$hist.html('');
|
|
|
|
|
$hist.removeClass('cp-history-init');
|
|
|
|
|
|
|
|
|
|
var fastPrev = h('button.cp-toolbar-history-previous', { title: Messages.history_fastPrev }, [
|
|
|
|
|
h('i.fa.fa-step-backward'),
|
|
|
|
|
h('i.fa.fa-users')
|
|
|
|
|
]);
|
|
|
|
|
var userPrev = h('button.cp-toolbar-history-previous', { title: Messages.history_userPrev }, [
|
|
|
|
|
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_fastNext }, [
|
|
|
|
|
h('i.fa.fa-users'),
|
|
|
|
|
h('i.fa.fa-step-forward'),
|
|
|
|
|
]);
|
|
|
|
|
var userNext = h('button.cp-toolbar-history-next', { title: Messages.history_userNext }, [
|
|
|
|
|
h('i.fa.fa-user'),
|
|
|
|
|
h('i.fa.fa-step-forward'),
|
|
|
|
|
]);
|
|
|
|
|
var next = h('button.cp-toolbar-history-next', { title: Messages.history_next }, [
|
|
|
|
|
h('i.fa.fa-step-forward')
|
|
|
|
|
]);
|
|
|
|
|
if (config.drive) {
|
|
|
|
|
fastNext = h('button.cp-toolbar-history-next', { title: Messages.history_next }, [
|
|
|
|
|
h('i.fa.fa-fast-forward'),
|
|
|
|
|
]);
|
|
|
|
|
fastPrev = h('button.cp-toolbar-history-previous', {title: Messages.history_prev}, [
|
|
|
|
|
h('i.fa.fa-fast-backward'),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.fa.fa-caret-down');
|
|
|
|
|
var time = h('div.cp-history-timeline-time');
|
|
|
|
|
$time = $(time);
|
|
|
|
|
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,
|
|
|
|
|
config.drive ? undefined : userPrev,
|
|
|
|
|
prev,
|
|
|
|
|
]),
|
|
|
|
|
time,
|
|
|
|
|
h('span.cp-history-timeline-next', [
|
|
|
|
|
next,
|
|
|
|
|
config.drive ? undefined : userNext,
|
|
|
|
|
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 restoreTitle = config.drive ? Messages.history_restoreDriveTitle
|
|
|
|
|
: Messages.history_restoreTitle;
|
|
|
|
|
var restore = h('button', {
|
|
|
|
|
title: 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;
|
|
|
|
|
}
|
|
|
|
|
if (config.drive) {
|
|
|
|
|
$hist.addClass('cp-history-drive');
|
|
|
|
|
$(snapshot).hide();
|
|
|
|
|
$(share).hide();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$hist.append([timeline, actions]);
|
|
|
|
|
onResize();
|
|
|
|
|
$(window).on('resize', onResize);
|
|
|
|
|
|
|
|
|
|
var $bar = $(bar);
|
|
|
|
|
$bar.find('.cp-history-snapshots').append(pos);
|
|
|
|
|
$bar.click(function (e) {
|
|
|
|
|
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));
|
|
|
|
|
render(get(v));
|
|
|
|
|
});
|
|
|
|
|
$loadMore = $(_loadMore).click(function () {
|
|
|
|
|
loadMore(function () {
|
|
|
|
|
get(c);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var onKeyDown, onKeyUp;
|
|
|
|
|
var closeUI = function () {
|
|
|
|
|
History.state = false;
|
|
|
|
|
$hist.hide();
|
|
|
|
|
$bottom.show();
|
|
|
|
|
$cke.show();
|
|
|
|
|
$(window).trigger('resize');
|
|
|
|
|
$(window).off('keydown', onKeyDown);
|
|
|
|
|
$(window).off('keyup', onKeyUp);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Version buttons
|
|
|
|
|
$prev.click(function () { render(get(c - 1)); });
|
|
|
|
|
$next.click(function () { render(get(c + 1)); });
|
|
|
|
|
if (config.drive) {
|
|
|
|
|
$fastPrev.click(function () { render(get(c - 10)); });
|
|
|
|
|
$fastNext.click(function () { render(get(c + 10)); });
|
|
|
|
|
$userPrev.click(function () { render(get(c - 10)); });
|
|
|
|
|
$userNext.click(function () { render(get(c + 10)); });
|
|
|
|
|
} else {
|
|
|
|
|
$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) {
|
|
|
|
|
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
|
|
|
|
|
if (e.which === 27) { p(); $(close).click(); }
|
|
|
|
|
};
|
|
|
|
|
onKeyUp = function (e) { e.stopPropagation(); };
|
|
|
|
|
$(window).on('keydown', onKeyDown).on('keyup', onKeyUp).focus();
|
|
|
|
|
|
|
|
|
|
// Snapshots
|
|
|
|
|
$(snapshot).click(function () {
|
|
|
|
|
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; }
|
|
|
|
|
makeSnapshot(val, $input);
|
|
|
|
|
},
|
|
|
|
|
keys: [],
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
UI.openCustomModal(UI.dialog.customModal(content, {buttons: buttons }));
|
|
|
|
|
setTimeout(function () {
|
|
|
|
|
$input.focus();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Share
|
|
|
|
|
$(share).click(function () {
|
|
|
|
|
var block = get(c, true);
|
|
|
|
|
common.getSframeChannel().event('EV_SHARE_OPEN', {
|
|
|
|
|
versionHash: block.serverHash,
|
|
|
|
|
//title: title
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Close & restore buttons
|
|
|
|
|
$(close).click(function () {
|
|
|
|
|
states = [];
|
|
|
|
|
onClose();
|
|
|
|
|
closeUI();
|
|
|
|
|
});
|
|
|
|
|
$(restore).click(function () {
|
|
|
|
|
var restorePrompt = config.drive ? Messages.history_restoreDrivePrompt
|
|
|
|
|
: Messages.history_restorePrompt;
|
|
|
|
|
UI.confirm(restorePrompt, function (yes) {
|
|
|
|
|
if (!yes) { return; }
|
|
|
|
|
var done = onRevert();
|
|
|
|
|
if (done) {
|
|
|
|
|
closeUI();
|
|
|
|
|
var restoreDone = config.drive ? Messages.history_restoreDriveDone
|
|
|
|
|
: Messages.history_restoreDone;
|
|
|
|
|
UI.log(restoreDone);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Display the latest content
|
|
|
|
|
render(get(c));
|
|
|
|
|
$(window).trigger('resize');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (config.onOpen) {
|
|
|
|
|
config.onOpen();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load all the history messages into a new chainpad object
|
|
|
|
|
loadMoreHistory(config, common, function (err, newRt, isFull) {
|
|
|
|
|
History.readOnly = common.getMetadataMgr().getPrivateData().readOnly;
|
|
|
|
|
History.loading = false;
|
|
|
|
|
if (err) { throw new Error(err); }
|
|
|
|
|
update(newRt);
|
|
|
|
|
display();
|
|
|
|
|
if (isFull) {
|
|
|
|
|
$loadMore.off('click').hide();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return History;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|