Valid HTML test
[weblabels.fsf.org.git] / etherpad.fsf.org / 20120516 / files / pad_savedrevs.js
CommitLineData
5a920362 1/**
2 * This code is mostly from the old Etherpad. Please help us to comment this code.
3 * This helps other people to understand this code better and helps them to improve it.
4 * TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
5 */
6
7/**
8 * Copyright 2009 Google Inc.
9 *
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
13 *
14 * http://www.apache.org/licenses/LICENSE-2.0
15 *
16 * Unless required by applicable law or agreed to in writing, software
17 * distributed under the License is distributed on an "AS-IS" BASIS,
18 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 * See the License for the specific language governing permissions and
20 * limitations under the License.
21 */
22
23var padutils = require('/pad_utils').padutils;
24var paddocbar = require('/pad_docbar').paddocbar;
25
26var padsavedrevs = (function()
27{
28
29 function reversedCopy(L)
30 {
31 var L2 = L.slice();
32 L2.reverse();
33 return L2;
34 }
35
36 function makeRevisionBox(revisionInfo, rnum)
37 {
38 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>');
39 setBoxLabel(box, revisionInfo.label);
40 setBoxTimestamp(box, revisionInfo.timestamp);
41 box.find(".srauthor").html("by " + padutils.escapeHtml(revisionInfo.savedBy));
42 var viewLink = '/ep/pad/view/' + pad.getPadId() + '/' + revisionInfo.id;
43 box.find(".srview").attr('href', viewLink);
44 var restoreLink = 'javascript:void(require('+JSON.stringify(module.id)+').padsavedrevs.restoreRevision(' + JSON.stringify(rnum) + ');';
45 box.find(".srrestore").attr('href', restoreLink);
46 box.find(".srname").click(function(evt)
47 {
48 editRevisionLabel(rnum, box);
49 });
50 return box;
51 }
52
53 function setBoxLabel(box, label)
54 {
55 box.find(".srname").html(padutils.escapeHtml(label)).attr('title', label);
56 }
57
58 function setBoxTimestamp(box, timestamp)
59 {
60 box.find(".srtime").html(padutils.escapeHtml(
61 padutils.timediff(new Date(timestamp))));
62 }
63
64 function getNthBox(n)
65 {
66 return $("#savedrevisions .srouterbox").eq(n);
67 }
68
69 function editRevisionLabel(rnum, box)
70 {
71 var input = $('<input type="text" class="srnameedit"/>');
72 box.find(".srnameedit").remove(); // just in case
73 var label = box.find(".srname");
74 input.width(label.width());
75 input.height(label.height());
76 input.css('top', label.position().top);
77 input.css('left', label.position().left);
78 label.after(input);
79 label.css('opacity', 0);
80
81 function endEdit()
82 {
83 input.remove();
84 label.css('opacity', 1);
85 }
86 var rev = currentRevisionList[rnum];
87 var oldLabel = rev.label;
88 input.blur(function()
89 {
90 var newLabel = input.val();
91 if (newLabel && newLabel != oldLabel)
92 {
93 relabelRevision(rnum, newLabel);
94 }
95 endEdit();
96 });
97 input.val(rev.label).focus().select();
98 padutils.bindEnterAndEscape(input, function onEnter()
99 {
100 input.blur();
101 }, function onEscape()
102 {
103 input.val('').blur();
104 });
105 }
106
107 function relabelRevision(rnum, newLabel)
108 {
109 var rev = currentRevisionList[rnum];
110 $.ajax(
111 {
112 type: 'post',
113 url: '/ep/pad/saverevisionlabel',
114 data: {
115 userId: pad.getUserId(),
116 padId: pad.getPadId(),
117 revId: rev.id,
118 newLabel: newLabel
119 },
120 success: success,
121 error: error
122 });
123
124 function success(text)
125 {
126 var newRevisionList = JSON.parse(text);
127 self.newRevisionList(newRevisionList);
128 pad.sendClientMessage(
129 {
130 type: 'revisionLabel',
131 revisionList: reversedCopy(currentRevisionList),
132 savedBy: pad.getUserName(),
133 newLabel: newLabel
134 });
135 }
136
137 function error(e)
138 {
139 alert("Oops! There was an error saving that revision label. Please try again later.");
140 }
141 }
142
143 var currentRevisionList = [];
144
145 function setRevisionList(newRevisionList, noAnimation)
146 {
147 // deals with changed labels and new added revisions
148 for (var i = 0; i < currentRevisionList.length; i++)
149 {
150 var a = currentRevisionList[i];
151 var b = newRevisionList[i];
152 if (b.label != a.label)
153 {
154 setBoxLabel(getNthBox(i), b.label);
155 }
156 }
157 for (var j = currentRevisionList.length; j < newRevisionList.length; j++)
158 {
159 var newBox = makeRevisionBox(newRevisionList[j], j);
160 $("#savedrevs-scrollinner").append(newBox);
161 newBox.css('left', j * REVISION_BOX_WIDTH);
162 }
163 var newOnes = (newRevisionList.length > currentRevisionList.length);
164 currentRevisionList = newRevisionList;
165 if (newOnes)
166 {
167 setDesiredScroll(getMaxScroll());
168 if (noAnimation)
169 {
170 setScroll(desiredScroll);
171 }
172
173 if (!noAnimation)
174 {
175 var nameOfLast = currentRevisionList[currentRevisionList.length - 1].label;
176 displaySavedTip(nameOfLast);
177 }
178 }
179 }
180
181 function refreshRevisionList()
182 {
183 for (var i = 0; i < currentRevisionList.length; i++)
184 {
185 var r = currentRevisionList[i];
186 var box = getNthBox(i);
187 setBoxTimestamp(box, r.timestamp);
188 }
189 }
190
191 var savedTipAnimator = padutils.makeShowHideAnimator(function(state)
192 {
193 if (state == -1)
194 {
195 $("#revision-notifier").css('opacity', 0).css('display', 'block');
196 }
197 else if (state == 0)
198 {
199 $("#revision-notifier").css('opacity', 1);
200 }
201 else if (state == 1)
202 {
203 $("#revision-notifier").css('opacity', 0).css('display', 'none');
204 }
205 else if (state < 0)
206 {
207 $("#revision-notifier").css('opacity', 1);
208 }
209 else if (state > 0)
210 {
211 $("#revision-notifier").css('opacity', 1 - state);
212 }
213 }, false, 25, 300);
214
215 function displaySavedTip(text)
216 {
217 $("#revision-notifier .name").html(padutils.escapeHtml(text));
218 savedTipAnimator.show();
219 padutils.cancelActions("hide-revision-notifier");
220 var hideLater = padutils.getCancellableAction("hide-revision-notifier", function()
221 {
222 savedTipAnimator.hide();
223 });
224 window.setTimeout(hideLater, 3000);
225 }
226
227 var REVISION_BOX_WIDTH = 120;
228 var curScroll = 0; // distance between left of revisions and right of view
229 var desiredScroll = 0;
230
231 function getScrollWidth()
232 {
233 return REVISION_BOX_WIDTH * currentRevisionList.length;
234 }
235
236 function getViewportWidth()
237 {
238 return $("#savedrevs-scrollouter").width();
239 }
240
241 function getMinScroll()
242 {
243 return Math.min(getViewportWidth(), getScrollWidth());
244 }
245
246 function getMaxScroll()
247 {
248 return getScrollWidth();
249 }
250
251 function setScroll(newScroll)
252 {
253 curScroll = newScroll;
254 $("#savedrevs-scrollinner").css('right', newScroll);
255 updateScrollArrows();
256 }
257
258 function setDesiredScroll(newDesiredScroll, dontUpdate)
259 {
260 desiredScroll = Math.min(getMaxScroll(), Math.max(getMinScroll(), newDesiredScroll));
261 if (!dontUpdate)
262 {
263 updateScroll();
264 }
265 }
266
267 function updateScroll()
268 {
269 updateScrollArrows();
270 scrollAnimator.scheduleAnimation();
271 }
272
273 function updateScrollArrows()
274 {
275 $("#savedrevs-scrollleft").toggleClass("disabledscrollleft", desiredScroll <= getMinScroll());
276 $("#savedrevs-scrollright").toggleClass("disabledscrollright", desiredScroll >= getMaxScroll());
277 }
278 var scrollAnimator = padutils.makeAnimationScheduler(function()
279 {
280 setDesiredScroll(desiredScroll, true); // re-clamp
281 if (Math.abs(desiredScroll - curScroll) < 1)
282 {
283 setScroll(desiredScroll);
284 return false;
285 }
286 else
287 {
288 setScroll(curScroll + (desiredScroll - curScroll) * 0.5);
289 return true;
290 }
291 }, 50, 2);
292
293 var isSaving = false;
294
295 function setIsSaving(v)
296 {
297 isSaving = v;
298 rerenderButton();
299 }
300
301 function haveReachedRevLimit()
302 {
303 var mv = pad.getPrivilege('maxRevisions');
304 return (!(mv < 0 || mv > currentRevisionList.length));
305 }
306
307 function rerenderButton()
308 {
309 if (isSaving || (!pad.isFullyConnected()) || haveReachedRevLimit())
310 {
311 $("#savedrevs-savenow").css('opacity', 0.75);
312 }
313 else
314 {
315 $("#savedrevs-savenow").css('opacity', 1);
316 }
317 }
318
319 var scrollRepeatTimer = null;
320 var scrollStartTime = 0;
321
322 function setScrollRepeatTimer(dir)
323 {
324 clearScrollRepeatTimer();
325 scrollStartTime = +new Date;
326 scrollRepeatTimer = window.setTimeout(function f()
327 {
328 if (!scrollRepeatTimer)
329 {
330 return;
331 }
332 self.scroll(dir);
333 var scrollTime = (+new Date) - scrollStartTime;
334 var delay = (scrollTime > 2000 ? 50 : 300);
335 scrollRepeatTimer = window.setTimeout(f, delay);
336 }, 300);
337 $(document).bind('mouseup', clearScrollRepeatTimer);
338 }
339
340 function clearScrollRepeatTimer()
341 {
342 if (scrollRepeatTimer)
343 {
344 window.clearTimeout(scrollRepeatTimer);
345 scrollRepeatTimer = null;
346 }
347 $(document).unbind('mouseup', clearScrollRepeatTimer);
348 }
349
350 var pad = undefined;
351 var self = {
352 init: function(initialRevisions, _pad)
353 {
354 pad = _pad;
355 self.newRevisionList(initialRevisions, true);
356
357 $("#savedrevs-savenow").click(function()
358 {
359 self.saveNow();
360 });
361 $("#savedrevs-scrollleft").mousedown(function()
362 {
363 self.scroll('left');
364 setScrollRepeatTimer('left');
365 });
366 $("#savedrevs-scrollright").mousedown(function()
367 {
368 self.scroll('right');
369 setScrollRepeatTimer('right');
370 });
371 $("#savedrevs-close").click(function()
372 {
373 paddocbar.setShownPanel(null);
374 });
375
376 // update "saved n minutes ago" times
377 window.setInterval(function()
378 {
379 refreshRevisionList();
380 }, 60 * 1000);
381 },
382 restoreRevision: function(rnum)
383 {
384 var rev = currentRevisionList[rnum];
385 var warning = ("Restoring this revision will overwrite the current" + " text of the pad. " + "Are you sure you want to continue?");
386 var hidePanel = paddocbar.hideLaterIfNoOtherInteraction();
387 var box = getNthBox(rnum);
388 if (confirm(warning))
389 {
390 box.find(".srtwirly").show();
391 $.ajax(
392 {
393 type: 'get',
394 url: '/ep/pad/getrevisionatext',
395 data: {
396 padId: pad.getPadId(),
397 revId: rev.id
398 },
399 success: success,
400 error: error
401 });
402 }
403
404 function success(resultJson)
405 {
406 untwirl();
407 var result = JSON.parse(resultJson);
408 padeditor.restoreRevisionText(result);
409 window.setTimeout(function()
410 {
411 hidePanel();
412 }, 0);
413 }
414
415 function error(e)
416 {
417 untwirl();
418 alert("Oops! There was an error retreiving the text (revNum= " + rev.revNum + "; padId=" + pad.getPadId());
419 }
420
421 function untwirl()
422 {
423 box.find(".srtwirly").hide();
424 }
425 },
426 showReachedLimit: function()
427 {
428 alert("Sorry, you do not have privileges to save more than " + pad.getPrivilege('maxRevisions') + " revisions.");
429 },
430 newRevisionList: function(lst, noAnimation)
431 {
432 // server gives us list with newest first;
433 // we want chronological order
434 var L = reversedCopy(lst);
435 setRevisionList(L, noAnimation);
436 rerenderButton();
437 },
438 saveNow: function()
439 {
440 if (isSaving)
441 {
442 return;
443 }
444 if (!pad.isFullyConnected())
445 {
446 return;
447 }
448 if (haveReachedRevLimit())
449 {
450 self.showReachedLimit();
451 return;
452 }
453 setIsSaving(true);
454 var savedBy = pad.getUserName() || "unnamed";
455 pad.callWhenNotCommitting(submitSave);
456
457 function submitSave()
458 {
459 $.ajax(
460 {
461 type: 'post',
462 url: '/ep/pad/saverevision',
463 data: {
464 padId: pad.getPadId(),
465 savedBy: savedBy,
466 savedById: pad.getUserId(),
467 revNum: pad.getCollabRevisionNumber()
468 },
469 success: success,
470 error: error
471 });
472 }
473
474 function success(text)
475 {
476 setIsSaving(false);
477 var newRevisionList = JSON.parse(text);
478 self.newRevisionList(newRevisionList);
479 pad.sendClientMessage(
480 {
481 type: 'newRevisionList',
482 revisionList: newRevisionList,
483 savedBy: savedBy
484 });
485 }
486
487 function error(e)
488 {
489 setIsSaving(false);
490 alert("Oops! The server failed to save the revision. Please try again later.");
491 }
492 },
493 handleResizePage: function()
494 {
495 updateScrollArrows();
496 },
497 handleIsFullyConnected: function(isConnected)
498 {
499 rerenderButton();
500 },
501 scroll: function(dir)
502 {
503 var minScroll = getMinScroll();
504 var maxScroll = getMaxScroll();
505 if (dir == 'left')
506 {
507 if (desiredScroll > minScroll)
508 {
509 var n = Math.floor((desiredScroll - 1 - minScroll) / REVISION_BOX_WIDTH);
510 setDesiredScroll(Math.max(0, n) * REVISION_BOX_WIDTH + minScroll);
511 }
512 }
513 else if (dir == 'right')
514 {
515 if (desiredScroll < maxScroll)
516 {
517 var n = Math.floor((maxScroll - desiredScroll - 1) / REVISION_BOX_WIDTH);
518 setDesiredScroll(maxScroll - Math.max(0, n) * REVISION_BOX_WIDTH);
519 }
520 }
521 }
522 };
523 return self;
524}());
525
526exports.padsavedrevs = padsavedrevs;