adding all weblabels from weblabels.fsf.org
[weblabels.fsf.org.git] / etherpad.fsf.org / 20120516 / files / pad_utils.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 Security = require('/security');
24
25/**
26 * Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
27 */
28
29function randomString(len)
30{
31 var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
32 var randomstring = '';
33 len = len || 20
34 for (var i = 0; i < len; i++)
35 {
36 var rnum = Math.floor(Math.random() * chars.length);
37 randomstring += chars.substring(rnum, rnum + 1);
38 }
39 return randomstring;
40}
41
42function createCookie(name, value, days, path)
43{
44 if (days)
45 {
46 var date = new Date();
47 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
48 var expires = "; expires=" + date.toGMTString();
49 }
50 else var expires = "";
51
52 if(!path)
53 path = "/";
54
55 document.cookie = name + "=" + value + expires + "; path=" + path;
56}
57
58function readCookie(name)
59{
60 var nameEQ = name + "=";
61 var ca = document.cookie.split(';');
62 for (var i = 0; i < ca.length; i++)
63 {
64 var c = ca[i];
65 while (c.charAt(0) == ' ') c = c.substring(1, c.length);
66 if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
67 }
68 return null;
69}
70
71var padutils = {
72 escapeHtml: function(x)
73 {
74 return Security.escapeHTML(String(x));
75 },
76 uniqueId: function()
77 {
78 var pad = require('/pad').pad; // Sidestep circular dependency
79 function encodeNum(n, width)
80 {
81 // returns string that is exactly 'width' chars, padding with zeros
82 // and taking rightmost digits
83 return (Array(width + 1).join('0') + Number(n).toString(35)).slice(-width);
84 }
85 return [pad.getClientIp(), encodeNum(+new Date, 7), encodeNum(Math.floor(Math.random() * 1e9), 4)].join('.');
86 },
87 uaDisplay: function(ua)
88 {
89 var m;
90
91 function clean(a)
92 {
93 var maxlen = 16;
94 a = a.replace(/[^a-zA-Z0-9\.]/g, '');
95 if (a.length > maxlen)
96 {
97 a = a.substr(0, maxlen);
98 }
99 return a;
100 }
101
102 function checkver(name)
103 {
104 var m = ua.match(RegExp(name + '\\/([\\d\\.]+)'));
105 if (m && m.length > 1)
106 {
107 return clean(name + m[1]);
108 }
109 return null;
110 }
111
112 // firefox
113 if (checkver('Firefox'))
114 {
115 return checkver('Firefox');
116 }
117
118 // misc browsers, including IE
119 m = ua.match(/compatible; ([^;]+);/);
120 if (m && m.length > 1)
121 {
122 return clean(m[1]);
123 }
124
125 // iphone
126 if (ua.match(/\(iPhone;/))
127 {
128 return 'iPhone';
129 }
130
131 // chrome
132 if (checkver('Chrome'))
133 {
134 return checkver('Chrome');
135 }
136
137 // safari
138 m = ua.match(/Safari\/[\d\.]+/);
139 if (m)
140 {
141 var v = '?';
142 m = ua.match(/Version\/([\d\.]+)/);
143 if (m && m.length > 1)
144 {
145 v = m[1];
146 }
147 return clean('Safari' + v);
148 }
149
150 // everything else
151 var x = ua.split(' ')[0];
152 return clean(x);
153 },
154 // e.g. "Thu Jun 18 2009 13:09"
155 simpleDateTime: function(date)
156 {
157 var d = new Date(+date); // accept either number or date
158 var dayOfWeek = (['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'])[d.getDay()];
159 var month = (['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])[d.getMonth()];
160 var dayOfMonth = d.getDate();
161 var year = d.getFullYear();
162 var hourmin = d.getHours() + ":" + ("0" + d.getMinutes()).slice(-2);
163 return dayOfWeek + ' ' + month + ' ' + dayOfMonth + ' ' + year + ' ' + hourmin;
164 },
165 findURLs: function(text)
166 {
167 // copied from ACE
168 var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
169 var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')');
170 var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g');
171
172 // returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]
173
174
175 function _findURLs(text)
176 {
177 _REGEX_URL.lastIndex = 0;
178 var urls = null;
179 var execResult;
180 while ((execResult = _REGEX_URL.exec(text)))
181 {
182 urls = (urls || []);
183 var startIndex = execResult.index;
184 var url = execResult[0];
185 urls.push([startIndex, url]);
186 }
187
188 return urls;
189 }
190
191 return _findURLs(text);
192 },
193 escapeHtmlWithClickableLinks: function(text, target)
194 {
195 var idx = 0;
196 var pieces = [];
197 var urls = padutils.findURLs(text);
198
199 function advanceTo(i)
200 {
201 if (i > idx)
202 {
203 pieces.push(Security.escapeHTML(text.substring(idx, i)));
204 idx = i;
205 }
206 }
207 if (urls)
208 {
209 for (var j = 0; j < urls.length; j++)
210 {
211 var startIndex = urls[j][0];
212 var href = urls[j][1];
213 advanceTo(startIndex);
214 pieces.push('<a ', (target ? 'target="' + Security.escapeHTMLAttribute(target) + '" ' : ''), 'href="', Security.escapeHTMLAttribute(href), '">');
215 advanceTo(startIndex + href.length);
216 pieces.push('</a>');
217 }
218 }
219 advanceTo(text.length);
220 return pieces.join('');
221 },
222 bindEnterAndEscape: function(node, onEnter, onEscape)
223 {
224
225 // Use keypress instead of keyup in bindEnterAndEscape
226 // Keyup event is fired on enter in IME (Input Method Editor), But
227 // keypress is not. So, I changed to use keypress instead of keyup.
228 // It is work on Windows (IE8, Chrome 6.0.472), CentOs (Firefox 3.0) and Mac OSX (Firefox 3.6.10, Chrome 6.0.472, Safari 5.0).
229 if (onEnter)
230 {
231 node.keypress(function(evt)
232 {
233 if (evt.which == 13)
234 {
235 onEnter(evt);
236 }
237 });
238 }
239
240 if (onEscape)
241 {
242 node.keydown(function(evt)
243 {
244 if (evt.which == 27)
245 {
246 onEscape(evt);
247 }
248 });
249 }
250 },
251 timediff: function(d)
252 {
253 var pad = require('/pad').pad; // Sidestep circular dependency
254 function format(n, word)
255 {
256 n = Math.round(n);
257 return ('' + n + ' ' + word + (n != 1 ? 's' : '') + ' ago');
258 }
259 d = Math.max(0, (+(new Date) - (+d) - pad.clientTimeOffset) / 1000);
260 if (d < 60)
261 {
262 return format(d, 'second');
263 }
264 d /= 60;
265 if (d < 60)
266 {
267 return format(d, 'minute');
268 }
269 d /= 60;
270 if (d < 24)
271 {
272 return format(d, 'hour');
273 }
274 d /= 24;
275 return format(d, 'day');
276 },
277 makeAnimationScheduler: function(funcToAnimateOneStep, stepTime, stepsAtOnce)
278 {
279 if (stepsAtOnce === undefined)
280 {
281 stepsAtOnce = 1;
282 }
283
284 var animationTimer = null;
285
286 function scheduleAnimation()
287 {
288 if (!animationTimer)
289 {
290 animationTimer = window.setTimeout(function()
291 {
292 animationTimer = null;
293 var n = stepsAtOnce;
294 var moreToDo = true;
295 while (moreToDo && n > 0)
296 {
297 moreToDo = funcToAnimateOneStep();
298 n--;
299 }
300 if (moreToDo)
301 {
302 // more to do
303 scheduleAnimation();
304 }
305 }, stepTime * stepsAtOnce);
306 }
307 }
308 return {
309 scheduleAnimation: scheduleAnimation
310 };
311 },
312 makeShowHideAnimator: function(funcToArriveAtState, initiallyShown, fps, totalMs)
313 {
314 var animationState = (initiallyShown ? 0 : -2); // -2 hidden, -1 to 0 fade in, 0 to 1 fade out
315 var animationFrameDelay = 1000 / fps;
316 var animationStep = animationFrameDelay / totalMs;
317
318 var scheduleAnimation = padutils.makeAnimationScheduler(animateOneStep, animationFrameDelay).scheduleAnimation;
319
320 function doShow()
321 {
322 animationState = -1;
323 funcToArriveAtState(animationState);
324 scheduleAnimation();
325 }
326
327 function doQuickShow()
328 { // start showing without losing any fade-in progress
329 if (animationState < -1)
330 {
331 animationState = -1;
332 }
333 else if (animationState <= 0)
334 {
335 animationState = animationState;
336 }
337 else
338 {
339 animationState = Math.max(-1, Math.min(0, -animationState));
340 }
341 funcToArriveAtState(animationState);
342 scheduleAnimation();
343 }
344
345 function doHide()
346 {
347 if (animationState >= -1 && animationState <= 0)
348 {
349 animationState = 1e-6;
350 scheduleAnimation();
351 }
352 }
353
354 function animateOneStep()
355 {
356 if (animationState < -1 || animationState == 0)
357 {
358 return false;
359 }
360 else if (animationState < 0)
361 {
362 // animate show
363 animationState += animationStep;
364 if (animationState >= 0)
365 {
366 animationState = 0;
367 funcToArriveAtState(animationState);
368 return false;
369 }
370 else
371 {
372 funcToArriveAtState(animationState);
373 return true;
374 }
375 }
376 else if (animationState > 0)
377 {
378 // animate hide
379 animationState += animationStep;
380 if (animationState >= 1)
381 {
382 animationState = 1;
383 funcToArriveAtState(animationState);
384 animationState = -2;
385 return false;
386 }
387 else
388 {
389 funcToArriveAtState(animationState);
390 return true;
391 }
392 }
393 }
394
395 return {
396 show: doShow,
397 hide: doHide,
398 quickShow: doQuickShow
399 };
400 },
401 _nextActionId: 1,
402 uncanceledActions: {},
403 getCancellableAction: function(actionType, actionFunc)
404 {
405 var o = padutils.uncanceledActions[actionType];
406 if (!o)
407 {
408 o = {};
409 padutils.uncanceledActions[actionType] = o;
410 }
411 var actionId = (padutils._nextActionId++);
412 o[actionId] = true;
413 return function()
414 {
415 var p = padutils.uncanceledActions[actionType];
416 if (p && p[actionId])
417 {
418 actionFunc();
419 }
420 };
421 },
422 cancelActions: function(actionType)
423 {
424 var o = padutils.uncanceledActions[actionType];
425 if (o)
426 {
427 // clear it
428 delete padutils.uncanceledActions[actionType];
429 }
430 },
431 makeFieldLabeledWhenEmpty: function(field, labelText)
432 {
433 field = $(field);
434
435 function clear()
436 {
437 field.addClass('editempty');
438 field.val(labelText);
439 }
440 field.focus(function()
441 {
442 if (field.hasClass('editempty'))
443 {
444 field.val('');
445 }
446 field.removeClass('editempty');
447 });
448 field.blur(function()
449 {
450 if (!field.val())
451 {
452 clear();
453 }
454 });
455 return {
456 clear: clear
457 };
458 },
459 getCheckbox: function(node)
460 {
461 return $(node).is(':checked');
462 },
463 setCheckbox: function(node, value)
464 {
465 if (value)
466 {
467 $(node).attr('checked', 'checked');
468 }
469 else
470 {
471 $(node).removeAttr('checked');
472 }
473 },
474 bindCheckboxChange: function(node, func)
475 {
476 $(node).bind("click change", func);
477 },
478 encodeUserId: function(userId)
479 {
480 return userId.replace(/[^a-y0-9]/g, function(c)
481 {
482 if (c == ".") return "-";
483 return 'z' + c.charCodeAt(0) + 'z';
484 });
485 },
486 decodeUserId: function(encodedUserId)
487 {
488 return encodedUserId.replace(/[a-y0-9]+|-|z.+?z/g, function(cc)
489 {
490 if (cc == '-') return '.';
491 else if (cc.charAt(0) == 'z')
492 {
493 return String.fromCharCode(Number(cc.slice(1, -1)));
494 }
495 else
496 {
497 return cc;
498 }
499 });
500 }
501};
502
503var globalExceptionHandler = undefined;
504function setupGlobalExceptionHandler() {
505 if (!globalExceptionHandler) {
506 globalExceptionHandler = function test (msg, url, linenumber)
507 {
508 var errorId = randomString(20);
509 if ($("#editorloadingbox").attr("display") != "none"){
510 //show javascript errors to the user
511 $("#editorloadingbox").css("padding", "10px");
512 $("#editorloadingbox").css("padding-top", "45px");
513 $("#editorloadingbox").html("<div style='text-align:left;color:red;font-size:16px;'><b>An error occured</b><br>The error was reported with the following id: '" + errorId + "'<br><br><span style='color:black;font-weight:bold;font-size:16px'>Please send this error message to us: </span><div style='color:black;font-size:14px'>'"
514 + "ErrorId: " + errorId + "<br>UserAgent: " + navigator.userAgent + "<br>" + msg + " in " + url + " at line " + linenumber + "'</div></div>");
515 }
516
517 //send javascript errors to the server
518 var errObj = {errorInfo: JSON.stringify({errorId: errorId, msg: msg, url: url, linenumber: linenumber, userAgent: navigator.userAgent})};
519 var loc = document.location;
520 var url = loc.protocol + "//" + loc.hostname + ":" + loc.port + "/" + loc.pathname.substr(1, loc.pathname.indexOf("/p/")) + "jserror";
521
522 $.post(url, errObj);
523
524 return false;
525 };
526 window.onerror = globalExceptionHandler;
527 }
528}
529
530padutils.setupGlobalExceptionHandler = setupGlobalExceptionHandler;
531
532padutils.binarySearch = require('/ace2_common').binarySearch;
533
534exports.randomString = randomString;
535exports.createCookie = createCookie;
536exports.readCookie = readCookie;
537exports.padutils = padutils;