define([ 'jquery', '/common/common-interface.js', '/bower_components/nthen/index.js', //'/bower_components/chainpad-json-validator/json-ot.js', '/bower_components/chainpad/chainpad.dist.js', ], function ($, UI, 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..."); } History.loading = true; var $toolbar = config.$toolbar; 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 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) { if (!config.debug) { return obj.msg; } 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); }); }; // 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 () { config.setHistory(false, false); config.onLocal(); config.onRemote(); }; config.setHistory(true); var Messages = common.Messages; var realtime; var states = []; var c = 0;//states.length - 1; var $hist = $toolbar.find('.cp-toolbar-history'); 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 onUpdate; var update = function (newRt) { realtime = newRt; if (!realtime) { return []; } states = getStates(realtime); if (typeof onUpdate === "function") { onUpdate(); } return states; }; var $loadMore, $version, 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.removeClass('fa fa-ellipsis-h') .append($('<span>', {'class': 'fa fa-refresh fa-spin fa-3x fa-fw'})); 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); $loadMore.addClass('fa fa-ellipsis-h').html(''); get(c); if (isFull) { $loadMore.off('click').hide(); $version.show(); } if (cb) { cb(); } }); }; get = function (i) { 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 = states.length - 1 + i; var val = states[idx].getContent().doc; c = i; if (typeof onUpdate === "function") { onUpdate(); } $hist.find('.cp-toolbar-history-next, .cp-toolbar-history-previous, ' + '.cp-toolbar-history-fast-next, .cp-toolbar-history-fast-previous') .css('visibility', ''); if (c === -(states.length-1)) { $hist.find('.cp-toolbar-history-previous').css('visibility', 'hidden'); $hist.find('.cp-toolbar-history-fast-previous').css('visibility', 'hidden'); } if (c === 0) { $hist.find('.cp-toolbar-history-next').css('visibility', 'hidden'); $hist.find('.cp-toolbar-history-fast-next').css('visibility', 'hidden'); } var $pos = $hist.find('.cp-toolbar-history-pos'); var p = 100 * (1 - (-c / (states.length-2))); $pos.css('margin-left', p+'%'); // 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)); 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); }; // Create the history toolbar var display = function () { $hist.html(''); 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 $prev =$('<button>', { 'class': 'cp-toolbar-history-previous fa fa-step-backward buttonPrimary', title: Messages.history_prev }).appendTo($hist); var $nav = $('<div>', {'class': 'cp-toolbar-history-goto'}).appendTo($hist); var $next = $('<button>', { 'class': 'cp-toolbar-history-next fa fa-step-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); $('<span>', {'class': 'cp-history-filler'}).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); // Load a version when clicking on the bar $container.click(function (e) { 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)); }); onUpdate = function () { // Called when a new version is loaded }; var onKeyDown, onKeyUp; var close = function () { $hist.hide(); $bottom.show(); $cke.show(); $(window).trigger('resize'); $(window).off('keydown', onKeyDown); $(window).off('keyup', onKeyUp); }; // Version buttons $prev.click(function () { render(getPrevious()); }); $next.click(function () { render(getNext()); }); $fastPrev.click(function () { render(getPrevious(10)); }); $fastNext.click(function () { render(getNext(10)); }); onKeyDown = function (e) { var p = function () { e.preventDefault(); }; 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(); } }; onKeyUp = function (e) { e.stopPropagation(); }; $(window).on('keydown', onKeyDown).on('keyup', onKeyUp).focus(); // Close & restore buttons $close.click(function () { states = []; close(); onClose(); }); $rev.click(function () { UI.confirm(Messages.history_restorePrompt, function (yes) { if (!yes) { return; } close(); onRevert(); UI.log(Messages.history_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); c = states.length - 1; display(); if (isFull) { $loadMore.off('click').hide(); $version.show(); } }); }; return History; });