2011-12-04 16:33:56 +01:00
/ * *
* 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 .
* /
2012-01-18 12:58:13 +01:00
var Security = require ( '/security' ) ;
2012-01-29 02:38:23 +01:00
/ * *
* 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 ;
}
2012-01-27 07:42:58 +01:00
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 )
{
2012-01-18 12:58:13 +01:00
return Security . escapeHTML ( String ( x ) ) ;
2011-03-26 14:10:41 +01:00
} ,
2011-07-07 19:59:34 +02:00
uniqueId : function ( )
{
2012-01-27 05:37:18 +01:00
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 )
{
2012-01-18 12:58:13 +01:00
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 ) ;
2012-01-18 12:58:13 +01:00
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 )
{
2012-01-27 05:37:18 +01:00
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 ;
}
} ) ;
}
} ;
2011-08-17 20:24:44 +02:00
2012-01-29 03:12:01 +01:00
var globalExceptionHandler = undefined ;
function setupGlobalExceptionHandler ( ) {
if ( ! globalExceptionHandler ) {
globalExceptionHandler = function test ( msg , url , linenumber )
{
2012-02-28 14:38:29 +01:00
var errorId = randomString ( 20 ) ;
if ( $ ( "#editorloadingbox" ) . attr ( "display" ) != "none" ) {
//show javascript errors to the user
$ ( "#editorloadingbox" ) . css ( "padding" , "10px" ) ;
$ ( "#editorloadingbox" ) . css ( "padding-top" , "45px" ) ;
$ ( "#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'>'"
+ "ErrorId: " + errorId + "<br>UserAgent: " + navigator . userAgent + "<br>" + msg + " in " + url + " at line " + linenumber + "'</div></div>" ) ;
}
//send javascript errors to the server
var errObj = { errorInfo : JSON . stringify ( { errorId : errorId , 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" ;
2011-08-17 20:24:44 +02:00
2012-02-28 14:38:29 +01:00
$ . post ( url , errObj ) ;
2011-08-17 20:24:44 +02:00
2012-02-28 14:38:29 +01:00
return false ;
2012-01-29 03:12:01 +01:00
} ;
window . onerror = globalExceptionHandler ;
}
}
padutils . setupGlobalExceptionHandler = setupGlobalExceptionHandler ;
2012-01-16 02:23:48 +01:00
2012-01-26 17:22:44 +01:00
padutils . binarySearch = require ( '/ace2_common' ) . binarySearch ;
2012-01-29 02:38:23 +01:00
exports . randomString = randomString ;
2012-01-27 07:42:58 +01:00
exports . createCookie = createCookie ;
exports . readCookie = readCookie ;
2012-01-16 02:23:48 +01:00
exports . padutils = padutils ;