/** * This code is mostly from the old Etherpad. Please help us to comment this code. * This helps other people to understand this code better and helps them to improve it. * TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED */ /** * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS-IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var padutils = require('/pad_utils').padutils; var paddocbar = require('/pad_docbar').paddocbar; var padsavedrevs = (function() { function reversedCopy(L) { var L2 = L.slice(); L2.reverse(); return L2; } function makeRevisionBox(revisionInfo, rnum) { var box = $('
'); setBoxLabel(box, revisionInfo.label); setBoxTimestamp(box, revisionInfo.timestamp); box.find(".srauthor").html("by " + padutils.escapeHtml(revisionInfo.savedBy)); var viewLink = '/ep/pad/view/' + pad.getPadId() + '/' + revisionInfo.id; box.find(".srview").attr('href', viewLink); var restoreLink = 'javascript:void(require('+JSON.stringify(module.id)+').padsavedrevs.restoreRevision(' + JSON.stringify(rnum) + ');'; box.find(".srrestore").attr('href', restoreLink); box.find(".srname").click(function(evt) { editRevisionLabel(rnum, box); }); return box; } function setBoxLabel(box, label) { box.find(".srname").html(padutils.escapeHtml(label)).attr('title', label); } function setBoxTimestamp(box, timestamp) { box.find(".srtime").html(padutils.escapeHtml( padutils.timediff(new Date(timestamp)))); } function getNthBox(n) { return $("#savedrevisions .srouterbox").eq(n); } function editRevisionLabel(rnum, box) { var input = $(''); box.find(".srnameedit").remove(); // just in case var label = box.find(".srname"); input.width(label.width()); input.height(label.height()); input.css('top', label.position().top); input.css('left', label.position().left); label.after(input); label.css('opacity', 0); function endEdit() { input.remove(); label.css('opacity', 1); } var rev = currentRevisionList[rnum]; var oldLabel = rev.label; input.blur(function() { var newLabel = input.val(); if (newLabel && newLabel != oldLabel) { relabelRevision(rnum, newLabel); } endEdit(); }); input.val(rev.label).focus().select(); padutils.bindEnterAndEscape(input, function onEnter() { input.blur(); }, function onEscape() { input.val('').blur(); }); } function relabelRevision(rnum, newLabel) { var rev = currentRevisionList[rnum]; $.ajax( { type: 'post', url: '/ep/pad/saverevisionlabel', data: { userId: pad.getUserId(), padId: pad.getPadId(), revId: rev.id, newLabel: newLabel }, success: success, error: error }); function success(text) { var newRevisionList = JSON.parse(text); self.newRevisionList(newRevisionList); pad.sendClientMessage( { type: 'revisionLabel', revisionList: reversedCopy(currentRevisionList), savedBy: pad.getUserName(), newLabel: newLabel }); } function error(e) { alert("Oops! There was an error saving that revision label. Please try again later."); } } var currentRevisionList = []; function setRevisionList(newRevisionList, noAnimation) { // deals with changed labels and new added revisions for (var i = 0; i < currentRevisionList.length; i++) { var a = currentRevisionList[i]; var b = newRevisionList[i]; if (b.label != a.label) { setBoxLabel(getNthBox(i), b.label); } } for (var j = currentRevisionList.length; j < newRevisionList.length; j++) { var newBox = makeRevisionBox(newRevisionList[j], j); $("#savedrevs-scrollinner").append(newBox); newBox.css('left', j * REVISION_BOX_WIDTH); } var newOnes = (newRevisionList.length > currentRevisionList.length); currentRevisionList = newRevisionList; if (newOnes) { setDesiredScroll(getMaxScroll()); if (noAnimation) { setScroll(desiredScroll); } if (!noAnimation) { var nameOfLast = currentRevisionList[currentRevisionList.length - 1].label; displaySavedTip(nameOfLast); } } } function refreshRevisionList() { for (var i = 0; i < currentRevisionList.length; i++) { var r = currentRevisionList[i]; var box = getNthBox(i); setBoxTimestamp(box, r.timestamp); } } var savedTipAnimator = padutils.makeShowHideAnimator(function(state) { if (state == -1) { $("#revision-notifier").css('opacity', 0).css('display', 'block'); } else if (state == 0) { $("#revision-notifier").css('opacity', 1); } else if (state == 1) { $("#revision-notifier").css('opacity', 0).css('display', 'none'); } else if (state < 0) { $("#revision-notifier").css('opacity', 1); } else if (state > 0) { $("#revision-notifier").css('opacity', 1 - state); } }, false, 25, 300); function displaySavedTip(text) { $("#revision-notifier .name").html(padutils.escapeHtml(text)); savedTipAnimator.show(); padutils.cancelActions("hide-revision-notifier"); var hideLater = padutils.getCancellableAction("hide-revision-notifier", function() { savedTipAnimator.hide(); }); window.setTimeout(hideLater, 3000); } var REVISION_BOX_WIDTH = 120; var curScroll = 0; // distance between left of revisions and right of view var desiredScroll = 0; function getScrollWidth() { return REVISION_BOX_WIDTH * currentRevisionList.length; } function getViewportWidth() { return $("#savedrevs-scrollouter").width(); } function getMinScroll() { return Math.min(getViewportWidth(), getScrollWidth()); } function getMaxScroll() { return getScrollWidth(); } function setScroll(newScroll) { curScroll = newScroll; $("#savedrevs-scrollinner").css('right', newScroll); updateScrollArrows(); } function setDesiredScroll(newDesiredScroll, dontUpdate) { desiredScroll = Math.min(getMaxScroll(), Math.max(getMinScroll(), newDesiredScroll)); if (!dontUpdate) { updateScroll(); } } function updateScroll() { updateScrollArrows(); scrollAnimator.scheduleAnimation(); } function updateScrollArrows() { $("#savedrevs-scrollleft").toggleClass("disabledscrollleft", desiredScroll <= getMinScroll()); $("#savedrevs-scrollright").toggleClass("disabledscrollright", desiredScroll >= getMaxScroll()); } var scrollAnimator = padutils.makeAnimationScheduler(function() { setDesiredScroll(desiredScroll, true); // re-clamp if (Math.abs(desiredScroll - curScroll) < 1) { setScroll(desiredScroll); return false; } else { setScroll(curScroll + (desiredScroll - curScroll) * 0.5); return true; } }, 50, 2); var isSaving = false; function setIsSaving(v) { isSaving = v; rerenderButton(); } function haveReachedRevLimit() { var mv = pad.getPrivilege('maxRevisions'); return (!(mv < 0 || mv > currentRevisionList.length)); } function rerenderButton() { if (isSaving || (!pad.isFullyConnected()) || haveReachedRevLimit()) { $("#savedrevs-savenow").css('opacity', 0.75); } else { $("#savedrevs-savenow").css('opacity', 1); } } var scrollRepeatTimer = null; var scrollStartTime = 0; function setScrollRepeatTimer(dir) { clearScrollRepeatTimer(); scrollStartTime = +new Date; scrollRepeatTimer = window.setTimeout(function f() { if (!scrollRepeatTimer) { return; } self.scroll(dir); var scrollTime = (+new Date) - scrollStartTime; var delay = (scrollTime > 2000 ? 50 : 300); scrollRepeatTimer = window.setTimeout(f, delay); }, 300); $(document).bind('mouseup', clearScrollRepeatTimer); } function clearScrollRepeatTimer() { if (scrollRepeatTimer) { window.clearTimeout(scrollRepeatTimer); scrollRepeatTimer = null; } $(document).unbind('mouseup', clearScrollRepeatTimer); } var pad = undefined; var self = { init: function(initialRevisions, _pad) { pad = _pad; self.newRevisionList(initialRevisions, true); $("#savedrevs-savenow").click(function() { self.saveNow(); }); $("#savedrevs-scrollleft").mousedown(function() { self.scroll('left'); setScrollRepeatTimer('left'); }); $("#savedrevs-scrollright").mousedown(function() { self.scroll('right'); setScrollRepeatTimer('right'); }); $("#savedrevs-close").click(function() { paddocbar.setShownPanel(null); }); // update "saved n minutes ago" times window.setInterval(function() { refreshRevisionList(); }, 60 * 1000); }, restoreRevision: function(rnum) { var rev = currentRevisionList[rnum]; var warning = ("Restoring this revision will overwrite the current" + " text of the pad. " + "Are you sure you want to continue?"); var hidePanel = paddocbar.hideLaterIfNoOtherInteraction(); var box = getNthBox(rnum); if (confirm(warning)) { box.find(".srtwirly").show(); $.ajax( { type: 'get', url: '/ep/pad/getrevisionatext', data: { padId: pad.getPadId(), revId: rev.id }, success: success, error: error }); } function success(resultJson) { untwirl(); var result = JSON.parse(resultJson); padeditor.restoreRevisionText(result); window.setTimeout(function() { hidePanel(); }, 0); } function error(e) { untwirl(); alert("Oops! There was an error retreiving the text (revNum= " + rev.revNum + "; padId=" + pad.getPadId()); } function untwirl() { box.find(".srtwirly").hide(); } }, showReachedLimit: function() { alert("Sorry, you do not have privileges to save more than " + pad.getPrivilege('maxRevisions') + " revisions."); }, newRevisionList: function(lst, noAnimation) { // server gives us list with newest first; // we want chronological order var L = reversedCopy(lst); setRevisionList(L, noAnimation); rerenderButton(); }, saveNow: function() { if (isSaving) { return; } if (!pad.isFullyConnected()) { return; } if (haveReachedRevLimit()) { self.showReachedLimit(); return; } setIsSaving(true); var savedBy = pad.getUserName() || "unnamed"; pad.callWhenNotCommitting(submitSave); function submitSave() { $.ajax( { type: 'post', url: '/ep/pad/saverevision', data: { padId: pad.getPadId(), savedBy: savedBy, savedById: pad.getUserId(), revNum: pad.getCollabRevisionNumber() }, success: success, error: error }); } function success(text) { setIsSaving(false); var newRevisionList = JSON.parse(text); self.newRevisionList(newRevisionList); pad.sendClientMessage( { type: 'newRevisionList', revisionList: newRevisionList, savedBy: savedBy }); } function error(e) { setIsSaving(false); alert("Oops! The server failed to save the revision. Please try again later."); } }, handleResizePage: function() { updateScrollArrows(); }, handleIsFullyConnected: function(isConnected) { rerenderButton(); }, scroll: function(dir) { var minScroll = getMinScroll(); var maxScroll = getMaxScroll(); if (dir == 'left') { if (desiredScroll > minScroll) { var n = Math.floor((desiredScroll - 1 - minScroll) / REVISION_BOX_WIDTH); setDesiredScroll(Math.max(0, n) * REVISION_BOX_WIDTH + minScroll); } } else if (dir == 'right') { if (desiredScroll < maxScroll) { var n = Math.floor((maxScroll - desiredScroll - 1) / REVISION_BOX_WIDTH); setDesiredScroll(maxScroll - Math.max(0, n) * REVISION_BOX_WIDTH); } } } }; return self; }()); exports.padsavedrevs = padsavedrevs;