etherpad-lite/static/js/pad_utils.js

529 lines
13 KiB
JavaScript
Raw Normal View History

/**
* 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
*/
2011-03-26 14:10:41 +01:00
/**
* Copyright 2009 Google Inc.
2011-07-07 19:59:34 +02:00
*
2011-03-26 14:10:41 +01:00
* 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
2011-07-07 19:59:34 +02:00
*
2011-03-26 14:10:41 +01:00
* http://www.apache.org/licenses/LICENSE-2.0
2011-07-07 19:59:34 +02:00
*
2011-03-26 14:10:41 +01:00
* 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 Security = require('/security');
/**
* Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
*/
function randomString(len)
{
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var randomstring = '';
len = len || 20
for (var i = 0; i < len; i++)
{
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1);
}
return randomstring;
}
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;
}
2011-03-26 14:10:41 +01:00
var padutils = {
2011-07-07 19:59:34 +02:00
escapeHtml: function(x)
{
return Security.escapeHTML(String(x));
2011-03-26 14:10:41 +01:00
},
2011-07-07 19:59:34 +02:00
uniqueId: function()
{
var pad = require('/pad').pad; // Sidestep circular dependency
2011-07-07 19:59:34 +02:00
function encodeNum(n, width)
{
2011-03-26 14:10:41 +01:00
// returns string that is exactly 'width' chars, padding with zeros
// and taking rightmost digits
2011-07-07 19:59:34 +02:00
return (Array(width + 1).join('0') + Number(n).toString(35)).slice(-width);
2011-03-26 14:10:41 +01:00
}
2011-07-07 19:59:34 +02:00
return [pad.getClientIp(), encodeNum(+new Date, 7), encodeNum(Math.floor(Math.random() * 1e9), 4)].join('.');
2011-03-26 14:10:41 +01:00
},
2011-07-07 19:59:34 +02:00
uaDisplay: function(ua)
{
2011-03-26 14:10:41 +01:00
var m;
2011-07-07 19:59:34 +02:00
function clean(a)
{
2011-03-26 14:10:41 +01:00
var maxlen = 16;
a = a.replace(/[^a-zA-Z0-9\.]/g, '');
2011-07-07 19:59:34 +02:00
if (a.length > maxlen)
{
a = a.substr(0, maxlen);
2011-03-26 14:10:41 +01:00
}
return a;
}
2011-07-07 19:59:34 +02:00
function checkver(name)
{
2011-03-26 14:10:41 +01:00
var m = ua.match(RegExp(name + '\\/([\\d\\.]+)'));
2011-07-07 19:59:34 +02:00
if (m && m.length > 1)
{
return clean(name + m[1]);
2011-03-26 14:10:41 +01:00
}
return null;
}
// firefox
2011-07-07 19:59:34 +02:00
if (checkver('Firefox'))
{
return checkver('Firefox');
}
2011-03-26 14:10:41 +01:00
// misc browsers, including IE
m = ua.match(/compatible; ([^;]+);/);
2011-07-07 19:59:34 +02:00
if (m && m.length > 1)
{
2011-03-26 14:10:41 +01:00
return clean(m[1]);
}
// iphone
2011-07-07 19:59:34 +02:00
if (ua.match(/\(iPhone;/))
{
2011-03-26 14:10:41 +01:00
return 'iPhone';
}
// chrome
2011-07-07 19:59:34 +02:00
if (checkver('Chrome'))
{
return checkver('Chrome');
}
2011-03-26 14:10:41 +01:00
// safari
m = ua.match(/Safari\/[\d\.]+/);
2011-07-07 19:59:34 +02:00
if (m)
{
2011-03-26 14:10:41 +01:00
var v = '?';
m = ua.match(/Version\/([\d\.]+)/);
2011-07-07 19:59:34 +02:00
if (m && m.length > 1)
{
2011-03-26 14:10:41 +01:00
v = m[1];
}
2011-07-07 19:59:34 +02:00
return clean('Safari' + v);
2011-03-26 14:10:41 +01:00
}
// everything else
var x = ua.split(' ')[0];
return clean(x);
},
// e.g. "Thu Jun 18 2009 13:09"
2011-07-07 19:59:34 +02:00
simpleDateTime: function(date)
{
2011-03-26 14:10:41 +01:00
var d = new Date(+date); // accept either number or date
2011-07-07 19:59:34 +02:00
var dayOfWeek = (['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'])[d.getDay()];
var month = (['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])[d.getMonth()];
2011-03-26 14:10:41 +01:00
var dayOfMonth = d.getDate();
var year = d.getFullYear();
2011-07-07 19:59:34 +02:00
var hourmin = d.getHours() + ":" + ("0" + d.getMinutes()).slice(-2);
return dayOfWeek + ' ' + month + ' ' + dayOfMonth + ' ' + year + ' ' + hourmin;
2011-03-26 14:10:41 +01:00
},
2011-07-07 19:59:34 +02:00
findURLs: function(text)
{
2011-03-26 14:10:41 +01:00
// copied from ACE
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]/;
2011-07-07 19:59:34 +02:00
var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')');
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');
2011-03-26 14:10:41 +01:00
// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]
2011-07-07 19:59:34 +02:00
function _findURLs(text)
{
2011-03-26 14:10:41 +01:00
_REGEX_URL.lastIndex = 0;
var urls = null;
var execResult;
2011-07-07 19:59:34 +02:00
while ((execResult = _REGEX_URL.exec(text)))
{
2011-03-26 14:10:41 +01:00
urls = (urls || []);
var startIndex = execResult.index;
var url = execResult[0];
urls.push([startIndex, url]);
}
return urls;
}
return _findURLs(text);
},
2011-07-07 19:59:34 +02:00
escapeHtmlWithClickableLinks: function(text, target)
{
2011-03-26 14:10:41 +01:00
var idx = 0;
var pieces = [];
var urls = padutils.findURLs(text);
2011-07-07 19:59:34 +02:00
function advanceTo(i)
{
if (i > idx)
{
pieces.push(Security.escapeHTML(text.substring(idx, i)));
2011-03-26 14:10:41 +01:00
idx = i;
}
}
2011-07-07 19:59:34 +02:00
if (urls)
{
for (var j = 0; j < urls.length; j++)
{
2011-03-26 14:10:41 +01:00
var startIndex = urls[j][0];
var href = urls[j][1];
advanceTo(startIndex);
pieces.push('<a ', (target ? 'target="' + Security.escapeHTMLAttribute(target) + '" ' : ''), 'href="', Security.escapeHTMLAttribute(href), '">');
2011-03-26 14:10:41 +01:00
advanceTo(startIndex + href.length);
pieces.push('</a>');
}
}
advanceTo(text.length);
return pieces.join('');
},
2011-07-07 19:59:34 +02:00
bindEnterAndEscape: function(node, onEnter, onEscape)
{
2011-03-26 14:10:41 +01:00
// Use keypress instead of keyup in bindEnterAndEscape
// Keyup event is fired on enter in IME (Input Method Editor), But
// keypress is not. So, I changed to use keypress instead of keyup.
// 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).
2011-07-07 19:59:34 +02:00
if (onEnter)
{
node.keypress(function(evt)
{
if (evt.which == 13)
{
2011-03-26 14:10:41 +01:00
onEnter(evt);
}
});
}
2011-07-07 19:59:34 +02:00
if (onEscape)
{
node.keydown(function(evt)
{
if (evt.which == 27)
{
2011-03-26 14:10:41 +01:00
onEscape(evt);
}
});
}
},
2011-07-07 19:59:34 +02:00
timediff: function(d)
{
var pad = require('/pad').pad; // Sidestep circular dependency
2011-07-07 19:59:34 +02:00
function format(n, word)
{
2011-03-26 14:10:41 +01:00
n = Math.round(n);
return ('' + n + ' ' + word + (n != 1 ? 's' : '') + ' ago');
}
d = Math.max(0, (+(new Date) - (+d) - pad.clientTimeOffset) / 1000);
2011-07-07 19:59:34 +02:00
if (d < 60)
{
return format(d, 'second');
}
2011-03-26 14:10:41 +01:00
d /= 60;
2011-07-07 19:59:34 +02:00
if (d < 60)
{
return format(d, 'minute');
}
2011-03-26 14:10:41 +01:00
d /= 60;
2011-07-07 19:59:34 +02:00
if (d < 24)
{
return format(d, 'hour');
}
2011-03-26 14:10:41 +01:00
d /= 24;
return format(d, 'day');
},
2011-07-07 19:59:34 +02:00
makeAnimationScheduler: function(funcToAnimateOneStep, stepTime, stepsAtOnce)
{
if (stepsAtOnce === undefined)
{
2011-03-26 14:10:41 +01:00
stepsAtOnce = 1;
}
var animationTimer = null;
2011-07-07 19:59:34 +02:00
function scheduleAnimation()
{
if (!animationTimer)
{
animationTimer = window.setTimeout(function()
{
2011-03-26 14:10:41 +01:00
animationTimer = null;
var n = stepsAtOnce;
var moreToDo = true;
2011-07-07 19:59:34 +02:00
while (moreToDo && n > 0)
{
2011-03-26 14:10:41 +01:00
moreToDo = funcToAnimateOneStep();
n--;
}
2011-07-07 19:59:34 +02:00
if (moreToDo)
{
2011-03-26 14:10:41 +01:00
// more to do
scheduleAnimation();
}
2011-07-07 19:59:34 +02:00
}, stepTime * stepsAtOnce);
2011-03-26 14:10:41 +01:00
}
}
2011-07-07 19:59:34 +02:00
return {
scheduleAnimation: scheduleAnimation
};
2011-03-26 14:10:41 +01:00
},
2011-07-07 19:59:34 +02:00
makeShowHideAnimator: function(funcToArriveAtState, initiallyShown, fps, totalMs)
{
2011-03-26 14:10:41 +01:00
var animationState = (initiallyShown ? 0 : -2); // -2 hidden, -1 to 0 fade in, 0 to 1 fade out
var animationFrameDelay = 1000 / fps;
var animationStep = animationFrameDelay / totalMs;
2011-07-07 19:59:34 +02:00
var scheduleAnimation = padutils.makeAnimationScheduler(animateOneStep, animationFrameDelay).scheduleAnimation;
2011-03-26 14:10:41 +01:00
2011-07-07 19:59:34 +02:00
function doShow()
{
2011-03-26 14:10:41 +01:00
animationState = -1;
funcToArriveAtState(animationState);
scheduleAnimation();
}
2011-07-07 19:59:34 +02:00
function doQuickShow()
{ // start showing without losing any fade-in progress
if (animationState < -1)
{
2011-03-26 14:10:41 +01:00
animationState = -1;
}
2011-07-07 19:59:34 +02:00
else if (animationState <= 0)
{
2011-03-26 14:10:41 +01:00
animationState = animationState;
}
2011-07-07 19:59:34 +02:00
else
{
animationState = Math.max(-1, Math.min(0, -animationState));
2011-03-26 14:10:41 +01:00
}
funcToArriveAtState(animationState);
scheduleAnimation();
}
2011-07-07 19:59:34 +02:00
function doHide()
{
if (animationState >= -1 && animationState <= 0)
{
2011-03-26 14:10:41 +01:00
animationState = 1e-6;
scheduleAnimation();
}
}
2011-07-07 19:59:34 +02:00
function animateOneStep()
{
if (animationState < -1 || animationState == 0)
{
2011-03-26 14:10:41 +01:00
return false;
}
2011-07-07 19:59:34 +02:00
else if (animationState < 0)
{
2011-03-26 14:10:41 +01:00
// animate show
animationState += animationStep;
2011-07-07 19:59:34 +02:00
if (animationState >= 0)
{
2011-03-26 14:10:41 +01:00
animationState = 0;
funcToArriveAtState(animationState);
return false;
}
2011-07-07 19:59:34 +02:00
else
{
2011-03-26 14:10:41 +01:00
funcToArriveAtState(animationState);
return true;
}
}
2011-07-07 19:59:34 +02:00
else if (animationState > 0)
{
2011-03-26 14:10:41 +01:00
// animate hide
animationState += animationStep;
2011-07-07 19:59:34 +02:00
if (animationState >= 1)
{
2011-03-26 14:10:41 +01:00
animationState = 1;
funcToArriveAtState(animationState);
animationState = -2;
return false;
}
2011-07-07 19:59:34 +02:00
else
{
2011-03-26 14:10:41 +01:00
funcToArriveAtState(animationState);
return true;
}
}
}
2011-07-07 19:59:34 +02:00
return {
show: doShow,
hide: doHide,
quickShow: doQuickShow
};
2011-03-26 14:10:41 +01:00
},
_nextActionId: 1,
uncanceledActions: {},
2011-07-07 19:59:34 +02:00
getCancellableAction: function(actionType, actionFunc)
{
2011-03-26 14:10:41 +01:00
var o = padutils.uncanceledActions[actionType];
2011-07-07 19:59:34 +02:00
if (!o)
{
2011-03-26 14:10:41 +01:00
o = {};
padutils.uncanceledActions[actionType] = o;
}
var actionId = (padutils._nextActionId++);
o[actionId] = true;
2011-07-07 19:59:34 +02:00
return function()
{
2011-03-26 14:10:41 +01:00
var p = padutils.uncanceledActions[actionType];
2011-07-07 19:59:34 +02:00
if (p && p[actionId])
{
2011-03-26 14:10:41 +01:00
actionFunc();
}
};
},
2011-07-07 19:59:34 +02:00
cancelActions: function(actionType)
{
2011-03-26 14:10:41 +01:00
var o = padutils.uncanceledActions[actionType];
2011-07-07 19:59:34 +02:00
if (o)
{
2011-03-26 14:10:41 +01:00
// clear it
delete padutils.uncanceledActions[actionType];
}
},
2011-07-07 19:59:34 +02:00
makeFieldLabeledWhenEmpty: function(field, labelText)
{
2011-03-26 14:10:41 +01:00
field = $(field);
2011-07-07 19:59:34 +02:00
function clear()
{
2011-03-26 14:10:41 +01:00
field.addClass('editempty');
field.val(labelText);
}
2011-07-07 19:59:34 +02:00
field.focus(function()
{
if (field.hasClass('editempty'))
{
2011-03-26 14:10:41 +01:00
field.val('');
}
field.removeClass('editempty');
});
2011-07-07 19:59:34 +02:00
field.blur(function()
{
if (!field.val())
{
2011-03-26 14:10:41 +01:00
clear();
}
});
2011-07-07 19:59:34 +02:00
return {
clear: clear
};
2011-03-26 14:10:41 +01:00
},
2011-07-07 19:59:34 +02:00
getCheckbox: function(node)
{
2011-03-26 14:10:41 +01:00
return $(node).is(':checked');
},
2011-07-07 19:59:34 +02:00
setCheckbox: function(node, value)
{
if (value)
{
2011-03-26 14:10:41 +01:00
$(node).attr('checked', 'checked');
}
2011-07-07 19:59:34 +02:00
else
{
2011-03-26 14:10:41 +01:00
$(node).removeAttr('checked');
}
},
2011-07-07 19:59:34 +02:00
bindCheckboxChange: function(node, func)
{
2011-03-26 14:10:41 +01:00
$(node).bind("click change", func);
},
2011-07-07 19:59:34 +02:00
encodeUserId: function(userId)
{
return userId.replace(/[^a-y0-9]/g, function(c)
{
2011-03-26 14:10:41 +01:00
if (c == ".") return "-";
2011-07-07 19:59:34 +02:00
return 'z' + c.charCodeAt(0) + 'z';
2011-03-26 14:10:41 +01:00
});
},
2011-07-07 19:59:34 +02:00
decodeUserId: function(encodedUserId)
{
return encodedUserId.replace(/[a-y0-9]+|-|z.+?z/g, function(cc)
{
2011-03-26 14:10:41 +01:00
if (cc == '-') return '.';
2011-07-07 19:59:34 +02:00
else if (cc.charAt(0) == 'z')
{
return String.fromCharCode(Number(cc.slice(1, -1)));
2011-03-26 14:10:41 +01:00
}
2011-07-07 19:59:34 +02:00
else
{
2011-03-26 14:10:41 +01:00
return cc;
}
});
}
};
var globalExceptionHandler = undefined;
function setupGlobalExceptionHandler() {
//send javascript errors to the server
if (!globalExceptionHandler) {
globalExceptionHandler = function test (msg, url, linenumber)
{
var errObj = {errorInfo: JSON.stringify({msg: msg, url: url, linenumber: linenumber, userAgent: navigator.userAgent})};
var loc = document.location;
var url = loc.protocol + "//" + loc.hostname + ":" + loc.port + "/" + loc.pathname.substr(1, loc.pathname.indexOf("/p/")) + "jserror";
$.post(url, errObj);
return false;
};
window.onerror = globalExceptionHandler;
}
}
padutils.setupGlobalExceptionHandler = setupGlobalExceptionHandler;
padutils.binarySearch = require('/ace2_common').binarySearch;
exports.randomString = randomString;
exports.createCookie = createCookie;
exports.readCookie = readCookie;
exports.padutils = padutils;