diff --git a/src/node/db/Pad.js b/src/node/db/Pad.js index b69780a4..73d61458 100644 --- a/src/node/db/Pad.js +++ b/src/node/db/Pad.js @@ -15,6 +15,11 @@ var padManager = require("./PadManager"); var padMessageHandler = require("../handler/PadMessageHandler"); var readOnlyManager = require("./ReadOnlyManager"); var crypto = require("crypto"); +var randomString = require("../utils/randomstring"); + +//serialization/deserialization attributes +var attributeBlackList = ["id"]; +var jsonableList = ["pool"]; /** * Copied from the Etherpad source code. It converts Windows line breaks to Unix line breaks and convert Tabs to spaces @@ -34,7 +39,7 @@ var Pad = function Pad(id) { this.publicStatus = false; this.passwordHash = null; this.id = id; - + this.savedRevisions = []; }; exports.Pad = Pad; @@ -75,15 +80,28 @@ Pad.prototype.appendRevision = function appendRevision(aChangeset, author) { newRevData.meta.atext = this.atext; } - db.set("pad:"+this.id+":revs:"+newRev, newRevData); - db.set("pad:"+this.id, {atext: this.atext, - pool: this.pool.toJsonable(), - head: this.head, - chatHead: this.chatHead, - publicStatus: this.publicStatus, - passwordHash: this.passwordHash}); + db.set("pad:"+this.id+":revs:"+newRev, newRevData); + this.saveToDatabase(); }; +//save all attributes to the database +Pad.prototype.saveToDatabase = function saveToDatabase(){ + var dbObject = {}; + + for(var attr in this){ + if(typeof this[attr] === "function") continue; + if(attributeBlackList.indexOf(attr) !== -1) continue; + + dbObject[attr] = this[attr]; + + if(jsonableList.indexOf(attr) !== -1){ + dbObject[attr] = dbObject[attr].toJsonable(); + } + } + + db.set("pad:"+this.id, dbObject); +} + Pad.prototype.getRevisionChangeset = function getRevisionChangeset(revNum, callback) { db.getSub("pad:"+this.id+":revs:"+revNum, ["changeset"], callback); }; @@ -200,11 +218,10 @@ Pad.prototype.setText = function setText(newText) { }; Pad.prototype.appendChatMessage = function appendChatMessage(text, userId, time) { - this.chatHead++; - //save the chat entry in the database - db.set("pad:"+this.id+":chat:"+this.chatHead, {"text": text, "userId": userId, "time": time}); - //save the new chat head - db.setSub("pad:"+this.id, ["chatHead"], this.chatHead); + this.chatHead++; + //save the chat entry in the database + db.set("pad:"+this.id+":chat:"+this.chatHead, {"text": text, "userId": userId, "time": time}); + this.saveToDatabase(); }; Pad.prototype.getChatMessage = function getChatMessage(entryNum, callback) { @@ -324,27 +341,14 @@ Pad.prototype.init = function init(text, callback) { //if this pad exists, load it if(value != null) { - _this.head = value.head; - _this.atext = value.atext; - _this.pool = _this.pool.fromJsonable(value.pool); - - //ensure we have a local chatHead variable - if(value.chatHead != null) - _this.chatHead = value.chatHead; - else - _this.chatHead = -1; - - //ensure we have a local publicStatus variable - if(value.publicStatus != null) - _this.publicStatus = value.publicStatus; - else - _this.publicStatus = false; - - //ensure we have a local passwordHash variable - if(value.passwordHash != null) - _this.passwordHash = value.passwordHash; - else - _this.passwordHash = null; + //copy all attr. To a transfrom via fromJsonable if necassary + for(var attr in value){ + if(jsonableList.indexOf(attr) !== -1){ + _this[attr] = _this[attr].fromJsonable(value[attr]); + } else { + _this[attr] = value[attr]; + } + } } //this pad doesn't exist, so create it else @@ -452,12 +456,12 @@ Pad.prototype.remove = function remove(callback) { //set in db Pad.prototype.setPublicStatus = function setPublicStatus(publicStatus) { this.publicStatus = publicStatus; - db.setSub("pad:"+this.id, ["publicStatus"], this.publicStatus); + this.saveToDatabase(); }; Pad.prototype.setPassword = function setPassword(password) { this.passwordHash = password == null ? null : hash(password, generateSalt()); - db.setSub("pad:"+this.id, ["passwordHash"], this.passwordHash); + this.saveToDatabase(); }; Pad.prototype.isCorrectPassword = function isCorrectPassword(password) { @@ -468,6 +472,31 @@ Pad.prototype.isPasswordProtected = function isPasswordProtected() { return this.passwordHash != null; }; +Pad.prototype.addSavedRevision = function addSavedRevision(revNum, savedById, label) { + //if this revision is already saved, return silently + for(var i in this.savedRevisions){ + if(this.savedRevisions.revNum === revNum){ + return; + } + } + + //build the saved revision object + var savedRevision = {}; + savedRevision.revNum = revNum; + savedRevision.savedById = savedById; + savedRevision.label = label || "Revision " + revNum; + savedRevision.timestamp = new Date().getTime(); + savedRevision.id = randomString(10); + + //save this new saved revision + this.savedRevisions.push(savedRevision); + this.saveToDatabase(); +}; + +Pad.prototype.getSavedRevisions = function getSavedRevisions() { + return this.savedRevisions; +}; + /* Crypto helper methods */ function hash(password, salt) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 26748517..e1786b2a 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -190,6 +190,11 @@ exports.handleMessage = function(client, message) { handleChatMessage(client, message); } + else if(message.type == "COLLABROOM" && + message.data.type == "SAVE_REVISION") + { + handleSaveRevisionMessage(client, message); + } else if(message.type == "COLLABROOM" && message.data.type == "CLIENT_MESSAGE" && message.data.payload.type == "suggestUserName") @@ -203,6 +208,23 @@ exports.handleMessage = function(client, message) } } +/** + * Handles a save revision message + * @param client the client that send this message + * @param message the message from the client + */ +function handleSaveRevisionMessage(client, message){ + var padId = session2pad[client.id]; + var userId = sessioninfos[client.id].author; + + padManager.getPad(padId, function(err, pad) + { + if(ERR(err)) return; + + pad.addSavedRevision(pad.head, userId); + }); +} + /** * Handles a Chat Message * @param client the client that send this message diff --git a/src/node/handler/TimesliderMessageHandler.js b/src/node/handler/TimesliderMessageHandler.js index 0081e645..9c063acd 100644 --- a/src/node/handler/TimesliderMessageHandler.js +++ b/src/node/handler/TimesliderMessageHandler.js @@ -166,6 +166,7 @@ function createTimesliderClientVars (padId, callback) hooks: [], initialStyledContents: {} }; + var pad; var initialChangesets = []; @@ -180,6 +181,12 @@ function createTimesliderClientVars (padId, callback) callback(); }); }, + //get all saved revisions and add them + function(callback) + { + clientVars.savedRevisions = pad.getSavedRevisions(); + callback(); + }, //get all authors and add them to function(callback) { diff --git a/src/node/utils/randomstring.js b/src/node/utils/randomstring.js new file mode 100644 index 00000000..4c1bba24 --- /dev/null +++ b/src/node/utils/randomstring.js @@ -0,0 +1,16 @@ +/** + * Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids + */ +var randomString = function randomString(len) +{ + var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + var randomstring = ''; + for (var i = 0; i < len; i++) + { + var rnum = Math.floor(Math.random() * chars.length); + randomstring += chars.substring(rnum, rnum + 1); + } + return randomstring; +}; + +module.exports = randomString; diff --git a/src/static/css/pad.css b/src/static/css/pad.css index 13f4bc93..74da2f74 100644 --- a/src/static/css/pad.css +++ b/src/static/css/pad.css @@ -1036,6 +1036,9 @@ margin-top: 1px; background-position: 0px -183px; display: inline-block; } +.buttonicon-savedRevision { + background-position: 0px -493px +} #usericon { @@ -1269,4 +1272,4 @@ input[type=checkbox] { #online_count { line-height: 24px; } -} \ No newline at end of file +} diff --git a/src/static/css/timeslider.css b/src/static/css/timeslider.css index 926c8012..7dc3ce0e 100644 --- a/src/static/css/timeslider.css +++ b/src/static/css/timeslider.css @@ -71,8 +71,11 @@ #padmain {top:30px;} #editbarright {float:right;} #returnbutton {color:#222; font-size:16px; line-height:29px; margin-top:0; padding-right:6px;} -#importexport {top:118px;} #importexport .popup {width:185px;} +#importexport{ + top:118px; + width:185px; +} /* lists */ .list-bullet2, .list-indent2, .list-number2 {margin-left:3em;} diff --git a/src/static/img/etherpad_lite_icons.png b/src/static/img/etherpad_lite_icons.png index cadf5ed2..27867d42 100644 Binary files a/src/static/img/etherpad_lite_icons.png and b/src/static/img/etherpad_lite_icons.png differ diff --git a/src/static/js/pad.js b/src/static/js/pad.js index c72b1036..176911ac 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -42,7 +42,7 @@ var padeditbar = require('./pad_editbar').padeditbar; var padeditor = require('./pad_editor').padeditor; var padimpexp = require('./pad_impexp').padimpexp; var padmodals = require('./pad_modals').padmodals; -var padsavedrevs = require('./pad_savedrevs').padsavedrevs; +var padsavedrevs = require('./pad_savedrevs'); var paduserlist = require('./pad_userlist').paduserlist; var padutils = require('./pad_utils').padutils; @@ -52,6 +52,48 @@ var randomString = require('./pad_utils').randomString; var hooks = require('./pluginfw/hooks'); +function createCookie(name, value, days, path) +{ + if (days) + { + var date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + var expires = "; expires=" + date.toGMTString(); + } + else var expires = ""; + + if(!path) + path = "/"; + + document.cookie = name + "=" + value + expires + "; path=" + path; +} + +function readCookie(name) +{ + var nameEQ = name + "="; + var ca = document.cookie.split(';'); + for (var i = 0; i < ca.length; i++) + { + var c = ca[i]; + while (c.charAt(0) == ' ') c = c.substring(1, c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); + } + return null; +} + +function randomString() +{ + var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + var string_length = 20; + var randomstring = ''; + for (var i = 0; i < string_length; i++) + { + var rnum = Math.floor(Math.random() * chars.length); + randomstring += chars.substring(rnum, rnum + 1); + } + return "t." + randomstring; +} + function getParams() { var params = getUrlVars() @@ -459,7 +501,7 @@ var pad = { guestPolicy: pad.padOptions.guestPolicy }, this); padimpexp.init(this); - padsavedrevs.init(clientVars.initialRevisionList, this); + padsavedrevs.init(this); padeditor.init(postAceInit, pad.padOptions.view || {}, this); diff --git a/src/static/js/pad_docbar.js b/src/static/js/pad_docbar.js index 08bbb0c4..c5858126 100644 --- a/src/static/js/pad_docbar.js +++ b/src/static/js/pad_docbar.js @@ -449,7 +449,7 @@ var paddocbar = (function() handleResizePage: function() { // Side-step circular reference. This should be injected. - var padsavedrevs = require('./pad_savedrevs').padsavedrevs; + var padsavedrevs = require('./pad_savedrevs'); padsavedrevs.handleResizePage(); }, hideLaterIfNoOtherInteraction: function() diff --git a/src/static/js/pad_editbar.js b/src/static/js/pad_editbar.js index 0cfd1f20..7e8cf96e 100644 --- a/src/static/js/pad_editbar.js +++ b/src/static/js/pad_editbar.js @@ -22,7 +22,7 @@ var padutils = require('./pad_utils').padutils; var padeditor = require('./pad_editor').padeditor; -var padsavedrevs = require('./pad_savedrevs').padsavedrevs; +var padsavedrevs = require('./pad_savedrevs'); function indexOf(array, value) { for (var i = 0, ii = array.length; i < ii; i++) { @@ -131,7 +131,7 @@ var padeditbar = (function() { self.toogleDropDown("importexport"); } - else if (cmd == 'save') + else if (cmd == 'savedRevision') { padsavedrevs.saveNow(); } diff --git a/src/static/js/pad_savedrevs.js b/src/static/js/pad_savedrevs.js index 2a0f4fde..bf4f941c 100644 --- a/src/static/js/pad_savedrevs.js +++ b/src/static/js/pad_savedrevs.js @@ -1,11 +1,5 @@ /** - * 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. + * Copyright 2012 Peter 'Pita' Martischka * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,507 +14,13 @@ * limitations under the License. */ -var padutils = require('./pad_utils').padutils; -var paddocbar = require('./pad_docbar').paddocbar; +var pad; -var padsavedrevs = (function() -{ +exports.saveNow = function(){ + pad.collabClient.sendMessage({"type": "SAVE_REVISION"}); + alert("This revision is now marked as a saved revision"); +} - 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; +exports.init = function(_pad){ + pad = _pad; +} diff --git a/src/templates/pad.html b/src/templates/pad.html index 765eec0c..e589fb55 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -61,6 +61,11 @@