etherpad-lite/static/js/pad_savedrevs.js
Chad Weider fa2a6e9ee6 Implement require of dependencies for all pad_* modules.
Create a lazily-defined local reference for pad on initialization in each pad module in order to avoid circular dependency. At some point in the future this dependency should instead be injected into each module on initialization.
2012-01-22 09:49:13 -08:00

528 lines
14 KiB
JavaScript

/**
* 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 = $('<div class="srouterbox">' + '<div class="srinnerbox">' + '<a href="javascript:void(0)" class="srname"><!-- --></a>' + '<div class="sractions"><a class="srview" href="javascript:void(0)" target="_blank">view</a> | <a class="srrestore" href="javascript:void(0)">restore</a></div>' + '<div class="srtime"><!-- --></div>' + '<div class="srauthor"><!-- --></div>' + '<img class="srtwirly" src="static/img/misc/status-ball.gif">' + '</div></div>');
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 = $('<input type="text" class="srnameedit"/>');
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 = require('/pad2').pad; // Sidestep circular dependency (should be injected).
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;
}());
if (typeof exports !== 'undefined') {
exports.padsavedrevs = padsavedrevs;
}