web/hanna-arendt/script.js
changeset 836 9de4ed65f00c
child 849 973a136cfbd0
equal deleted inserted replaced
835:556f5c8e2883 836:9de4ed65f00c
       
     1 var tracking_keywords = [ "#AVPHannaArendt" ];
       
     2 
       
     3 function rejectUser(username) {
       
     4     return (/^[A-Z][a-z]{2,8}[0-9]{4,6}$/.test(username))
       
     5 }
       
     6 
       
     7 if (typeof annotations == "undefined" || !annotations) {
       
     8     var annotations = {
       
     9         "default" : {
       
    10             "colors" : {
       
    11                 "h" : 0,
       
    12                 "s" : 0
       
    13             }
       
    14         },
       
    15         "positive" : {
       
    16             "display_name" : "++ | accord",
       
    17             "keywords" : [ /\+\+/ ],
       
    18             "colors" : {
       
    19                 "h" : .3,
       
    20                 "s" : .65
       
    21             }
       
    22         },
       
    23         "negative" : {
       
    24             "display_name" : "-- | désaccord",
       
    25             "keywords" : [ /\-\-/ ],
       
    26             "colors" : {
       
    27                 "h" : 0,
       
    28                 "s" : .8
       
    29             }
       
    30         },
       
    31         "reference" : {
       
    32             "display_name" : "== | référence",
       
    33             "keywords" : [ /\=\=/ ],
       
    34             "colors" : {
       
    35                 "h" : .16,
       
    36                 "s" : .8
       
    37             }
       
    38         },
       
    39         "question" : {
       
    40             "display_name" : "?? | question",
       
    41             "keywords" : [ /\?\?/ ],
       
    42             "colors" : {
       
    43                 "h" : .6,
       
    44                 "s" : .8
       
    45             }
       
    46         }
       
    47     }    
       
    48 }
       
    49 
       
    50 if (typeof l10n == "undefined") {
       
    51     l10n = { "rechercher" : "Rechercher" }
       
    52 }
       
    53 
       
    54 if (typeof suggested_keywords == "undefined") {
       
    55     suggested_keywords = [ ];
       
    56 }
       
    57 
       
    58 if (typeof max_pages == "undefined" || !max_pages) {
       
    59     max_pages = 5;
       
    60 }
       
    61 
       
    62 tracking_keywords = _(tracking_keywords).map(function(_w) {
       
    63     return _w.toLowerCase();
       
    64 });
       
    65 
       
    66 var twCx = {
       
    67         tlPaper : null,
       
    68         followLast : true,
       
    69         position : "0",
       
    70         date_levels : [
       
    71             3600 * 1000,
       
    72             15 * 60 * 1000,
       
    73             5 * 60 * 1000,
       
    74             60 * 1000
       
    75         ],
       
    76         timeLevel : 1,
       
    77         deltaX : 40,
       
    78         tlWidth : 150,
       
    79         tlHeight : 480,
       
    80         globalWords : {},
       
    81         suggestCount : _(suggested_keywords).map(function(_w) {
       
    82             return {
       
    83                 "word" : _w,
       
    84                 "rgxp" : new RegExp(_w.replace(/(\W)/g, '\\$1'), "im"),
       
    85                 "freq" : 0,
       
    86                 "annotations" : {}
       
    87             }
       
    88         }),
       
    89         refMouse : { x : 0, y : 0},
       
    90         refPosTl : { x : 0, y : 0},
       
    91         tlMouseMoved : false,
       
    92         tlMouseClicked : false,
       
    93         filtre : null,
       
    94         tlBuffer : '',
       
    95         relHover : [],
       
    96         wheelDelta : 0,
       
    97         scrollEnabled : false,
       
    98         scrollExtent : 8000 - 480,
       
    99         lastScrollPos : 0,
       
   100         urlRegExp : /https?:\/\/[0-9a-zA-Z\.%\/-_]+/g,
       
   101         wordRegExp : /[^ \.&;,'"!\?\d\(\)\+\[\]\\\…\-«»:\/]{3,}/g,
       
   102         stopWords : [
       
   103             'aussi', 'and', 'avec', 'aux', 'bien', 'car', 'cette', 'comme', 'dans', 'donc', 'des', 'elle', 'encore', 'est',
       
   104             'être', 'eux', 'faire', 'fait', 'http', 'ici', 'ils', 'les', 'leur', 'leurs', 'mais', 'mes', 'même', 'mon', 'notre',
       
   105             'non', 'nos', 'nous', 'ont', 'oui', 'par', 'pas', 'peu', 'peut', 'plus', 'pour', 'que', 'qui', 'ses' ,'son', 'sont', 'sur',
       
   106             'tes', 'très', 'the', 'ton', 'tous', 'tout', 'une', 'votre', 'vos', 'vous'
       
   107             ]
       
   108     }
       
   109 
       
   110 function getTweets(options) {
       
   111     
       
   112     function getTweetUrl(url) {
       
   113         $.getJSON(url, function(data) {
       
   114             options.tweets = options.tweets.concat(data);
       
   115             options.currentPage++;
       
   116             if (options.cbData) {
       
   117                 options.cbData();
       
   118             }
       
   119             var _isLast = true;
       
   120             if (data.results && data.results.length) {
       
   121                 var _oldestTweetId = data.results[data.results.length - 1].id_str,
       
   122                     _maxId = _oldestTweetId;
       
   123                 if (options.currentPage < options.pages) {
       
   124                     _isLast = false;
       
   125                     getTweetUrl(baseurl + firstparams + '&max_id=' + _maxId + lastparams);
       
   126                 }
       
   127             }
       
   128             
       
   129             if (_isLast) {
       
   130                     options.tweets.sort(function(a,b) {
       
   131                        return a.id - b.id; 
       
   132                     });
       
   133                     if (options.cbEnd) {
       
   134                         options.cbEnd();
       
   135                     }
       
   136             }
       
   137         });
       
   138     }
       
   139     
       
   140     options.tweets = [];
       
   141     options.pages || (options.pages = 1);
       
   142     options.rpp || (options.rpp = 100);
       
   143     options.currentPage = 0;
       
   144     var baseurl = "search_tweets.php",
       
   145         firstparams = "?endpoint=favorites/list" + (typeof favUser === "string" ? ("&screen_name=" + encodeURIComponent(favUser)) : "")
       
   146         	+ "&count=" + options.rpp + "&include_entities=true",
       
   147         lastparams = (options.since_id ? "&since_id=" + options.since_id : '' ) + "&callback=?",
       
   148         jsonurl = baseurl + firstparams + lastparams;
       
   149     getTweetUrl(jsonurl);
       
   150 }
       
   151 
       
   152 function getColor(annotation, lum) {
       
   153     return Raphael.hsl2rgb(annotations[annotation].colors.h, annotations[annotation].colors.s, lum);
       
   154 }
       
   155 
       
   156 function tweetPopup(url) {
       
   157     var popW = 550,
       
   158         popH = 350,
       
   159         scrW = screen.width,
       
   160         scrH = screen.height,
       
   161         posX = Math.round((scrW/2)-(popW/2)),
       
   162         posY = (scrH > popH ? Math.round((scrH/2)-(popH/2)) : 0);
       
   163     window.open(url,
       
   164         '',
       
   165         'left=' + posX + ',top=' + posY + ',width=' + popW + ',height=' + popH + ',personalbar=0,toolbar=0,scrollbars=1,resizable=1');
       
   166 }
       
   167 
       
   168 function arc(source, target) {
       
   169     var x3 = .3 * target.y - .3 * source.y + .8 * source.x + .2 * target.x;
       
   170     var y3 = .8 * source.y + .2 * target.y - .3 * target.x + .3 * source.x;
       
   171     var x4 = .3 * target.y - .3 * source.y + .2 * source.x + .8 * target.x;
       
   172     var y4 = .2 * source.y + .8 * target.y - .3 * target.x + .3 * source.x;
       
   173     return "M" + source.x + " " + source.y + "C" + [x3, y3, x4, y4, target.x, target.y].join(" ");
       
   174 }
       
   175 
       
   176 function addTweet(tweet) {
       
   177     if (!tweet) {
       
   178         console.log(tweet);
       
   179         return;
       
   180     }
       
   181     
       
   182     if (rejectUser(tweet.from_user)) {
       
   183         return;
       
   184     }
       
   185     
       
   186     function backRef(source_id, target_id, type) {
       
   187         var target = tweetById(target_id);
       
   188         if (target) {
       
   189             var brobj = {
       
   190                 "referenced_by_id" : source_id,
       
   191                 "type" : type
       
   192             }
       
   193             if (target.backRefs) {
       
   194                 target.backRefs.push(brobj);
       
   195             } else {
       
   196                 target.backRefs = [ brobj ]
       
   197             }
       
   198         }
       
   199     }
       
   200     
       
   201     _(['id', 'from_user_id', 'in_reply_to_status_id']).each(function(_i) {
       
   202        tweet[_i] = tweet[_i + '_str'];
       
   203        delete tweet[_i + '_str']; 
       
   204     });
       
   205     
       
   206     if (_(twCx.idIndex).indexOf(tweet.id) != -1) {
       
   207         return;
       
   208     }
       
   209     
       
   210     tweet.html_parts = []
       
   211     
       
   212     if (tweet.entities && tweet.entities.user_mentions) {
       
   213         for (var _i = 0; _i < tweet.entities.user_mentions.length; _i++) {
       
   214             var _m = tweet.entities.user_mentions[_i];
       
   215             tweet.html_parts.push({
       
   216                 "text" : "@" + _m.screen_name,
       
   217                 "start" : _m.indices[0],
       
   218                 "end" : _m.indices[1],
       
   219                 "link" :'<a href="http://twitter.com/' + _m.screen_name + '" onclick="filtrerTexte(\'' + _m.screen_name + '\'); return false;" target="_blank">'
       
   220             });
       
   221         }
       
   222     }
       
   223     
       
   224     if (tweet.entities && tweet.entities.hashtags) {
       
   225         for (var _i = 0; _i < tweet.entities.hashtags.length; _i++) {
       
   226             var _m = tweet.entities.hashtags[_i],
       
   227                 _h = "#" + _m.text;
       
   228             tweet.html_parts.push({
       
   229                 "text" : _h,
       
   230                 "start" : _m.indices[0],
       
   231                 "end" : _m.indices[1],
       
   232                 "link" :'<a href="http://twitter.com/search?q=' + encodeURIComponent(_h) + '" onclick="filtrerTexte(\'' + _.escape(_h) + '\'); return false;" target="_blank">'
       
   233             });
       
   234         }
       
   235     }
       
   236     
       
   237     if (tweet.entities && tweet.entities.urls) {
       
   238         for (var _i = 0; _i < tweet.entities.urls.length; _i++) {
       
   239             var _m = tweet.entities.urls[_i];
       
   240             tweet.html_parts.push({
       
   241                 "text" : _m.display_url || _m.url,
       
   242                 "start" : _m.indices[0],
       
   243                 "end" : _m.indices[1],
       
   244                 "link" :'<a href="' + _m.url + '" target="_blank">'
       
   245             });
       
   246         }
       
   247     }
       
   248     tweet.date_value = Date.parse(tweet.created_at.replace(/(\+|-)/,'UTC$1'));
       
   249     
       
   250     var ann = [];
       
   251     for (var j in annotations) {
       
   252         if (j != "default") {
       
   253             for (var k in annotations[j].keywords) {
       
   254                 if (tweet.text.search(annotations[j].keywords[k]) != -1) {
       
   255                     ann.push(j);
       
   256                     break;
       
   257                 }
       
   258             }
       
   259         }
       
   260     }
       
   261     tweet.annotations = ann;
       
   262 
       
   263     if (tweet.in_reply_to_status_id) {
       
   264         backRef( tweet.id, tweet.in_reply_to_status_id, "reply" );
       
   265     }
       
   266     
       
   267     if (tweet.retweeted_status && tweet.retweeted_status.id_str) {
       
   268     	tweet.retweeted_status_id = tweet.retweeted_status.id_str;
       
   269         backRef( tweet.id, tweet.retweeted_status_id, "retweet" );
       
   270     }
       
   271     
       
   272     
       
   273     var tab = tweet.text.replace(twCx.urlRegExp,'').match(twCx.wordRegExp);
       
   274     _(tab).each(function(w) {
       
   275         var word = w.toLowerCase();
       
   276         if (_(twCx.stopWords).indexOf(word) == -1 && _(tracking_keywords).indexOf(word) == -1 && word[0] != '@') {
       
   277             if (twCx.globalWords[word]) {
       
   278                 twCx.globalWords[word].freq++;
       
   279             } else {
       
   280                 twCx.globalWords[word] = {
       
   281                     "freq" : 1,
       
   282                     "annotations" : {}
       
   283                 }
       
   284                 for (var j in annotations) {
       
   285                     if (j != 'default') {
       
   286                         twCx.globalWords[word].annotations[j] = 0;
       
   287                     }
       
   288                 }
       
   289             }
       
   290             for (var j in ann) {
       
   291                 if (typeof twCx.globalWords[word].annotations != "undefined") {
       
   292                     twCx.globalWords[word].annotations[ann[j]]++;
       
   293                 }
       
   294             }
       
   295         }
       
   296     });
       
   297     
       
   298     _(twCx.suggestCount).each(function(_k) {
       
   299         if (tweet.text.search(_k.rgxp) != -1) {
       
   300             _k.freq++;
       
   301             _(ann).each(function(_a) {
       
   302                 _k.annotations[_a] = 1 + ( _k.annotations[_a] || 0 )
       
   303             })
       
   304         }
       
   305     });
       
   306     
       
   307     
       
   308     var p = twCx.idIndex.length;
       
   309     while (p && tweet.id < twCx.idIndex[p-1]) {
       
   310         p--;
       
   311     }
       
   312     twCx.tweets.splice(p, 0, tweet);
       
   313     twCx.idIndex.splice(p, 0, tweet.id);
       
   314     
       
   315     if (!twCx.timeline.length) {
       
   316         twCx.timeline = [ populateDateStruct(0, twCx.date_levels[0] * parseInt(tweet.date_value / twCx.date_levels[0])) ]
       
   317     }
       
   318     while (tweet.date_value > twCx.timeline[twCx.timeline.length - 1].end) {
       
   319         twCx.timeline.push( populateDateStruct(0, twCx.timeline[twCx.timeline.length - 1].end) );
       
   320     }
       
   321     
       
   322     insertIntoDateStruct(twCx.timeline, tweet);
       
   323 }
       
   324 
       
   325 function getSliceContent(slice) {
       
   326     if (slice.slices) {
       
   327         var result = [];
       
   328         for (var i in slice.slices) {
       
   329             result = result.concat(getSliceContent(slice.slices[i]));
       
   330         }
       
   331     } else {
       
   332         var result = slice.tweets;
       
   333     }
       
   334     return result;
       
   335 }
       
   336 
       
   337 function flattenDateStruct(slices, target_level) {
       
   338     var current_level = slices[0].level,
       
   339         result = [];
       
   340     if (current_level < target_level) {
       
   341         if (slices[0].slices) {
       
   342             for (var i in slices) {
       
   343                 result = result.concat(flattenDateStruct(slices[i].slices, target_level));
       
   344             }
       
   345         }
       
   346     }
       
   347     else {
       
   348         for (var i in slices) {
       
   349             result.push({
       
   350                 "start" : slices[i].start,
       
   351                 "end" : slices[i].end,
       
   352                 "tweets" : getSliceContent(slices[i])
       
   353             });
       
   354         }
       
   355     }
       
   356     return result;
       
   357 }
       
   358 
       
   359 function trimFDS() {
       
   360     var slices = flattenDateStruct(twCx.timeline, twCx.timeLevel);
       
   361     while (slices[0].tweets.length == 0) {
       
   362         slices.splice(0,1);
       
   363     }
       
   364     while (slices[slices.length - 1].tweets.length == 0) {
       
   365         slices.pop();
       
   366     }    
       
   367     var centralTweet = ( twCx.centralTweet ? twCx.centralTweet : twCx.tweets[twCx.tweets.length - 1] ),
       
   368         delta = 30 * twCx.date_levels[twCx.timeLevel],
       
   369         centre = Math.min(slices[slices.length - 1].end - delta , Math.max(slices[0].start + delta, centralTweet.date_value)),
       
   370         min = centre - delta,
       
   371         max = centre + delta;
       
   372     while (slices[0].start < min) {
       
   373         slices.splice(0,1);
       
   374     }
       
   375     while (slices[slices.length - 1].end > max) {
       
   376         slices.pop();
       
   377     }
       
   378     return slices;
       
   379 }
       
   380 
       
   381 function populateDateStruct(level, start) {
       
   382     var end = start + twCx.date_levels[level],
       
   383         struct = {
       
   384             "level" : level,
       
   385             "start" : start,
       
   386             "end" : end
       
   387         };
       
   388     if (level < twCx.date_levels.length - 1) {
       
   389         struct.slices = [];
       
   390         var newstart = start;
       
   391         while (newstart < end) {
       
   392             struct.slices.push(populateDateStruct(level + 1, newstart));
       
   393             newstart += twCx.date_levels[level + 1];
       
   394         }
       
   395     } else {
       
   396         struct.tweets = [];
       
   397     }
       
   398     return struct;
       
   399 }
       
   400 
       
   401 function insertIntoDateStruct(slices, tweet) {
       
   402     var creadate = tweet.date_value;
       
   403     for (var i in slices) {
       
   404         if (creadate < slices[i].end) {
       
   405             if (slices[i].slices) {
       
   406                 insertIntoDateStruct(slices[i].slices, tweet);
       
   407             } else {
       
   408                 slices[i].tweets.push(tweet.id);
       
   409             }
       
   410             break;
       
   411         }
       
   412     }
       
   413 }
       
   414 
       
   415 function placeHolder(className) {
       
   416     return '<li class="placeholder ' + className + '"></li>';
       
   417 }
       
   418 
       
   419 function tweetById(tweetid) {
       
   420     var pos = _(twCx.idIndex).indexOf(tweetid);
       
   421     return (pos == -1) ? false : twCx.tweets[pos];
       
   422 }
       
   423 
       
   424 function selectTweet(tweetid) {
       
   425     twCx.position = tweetid;
       
   426     twCx.followLast = (twCx.position == twCx.idIndex[twCx.tweets.length - 1]);
       
   427     updateDisplay();
       
   428 }
       
   429 
       
   430 function goToPos(nPos) {
       
   431     twCx.position = twCx.currentIdIndex[Math.min( twCx.currentIdIndex.length - 1, Math.max(0, nPos ) )];
       
   432     twCx.followLast = (!twCx.filtre && nPos == twCx.tweets.length - 1);
       
   433     updateDisplay();
       
   434 }
       
   435 
       
   436 function movePos(delta) {
       
   437     goToPos( delta + _(twCx.currentIdIndex).indexOf(twCx.position) );
       
   438 }
       
   439 
       
   440 function tweetToHtml(tweet, className, elName) {
       
   441     
       
   442     function highlight(texte) {
       
   443         return ( twCx.filtre ? texte.replace(twCx.filtre, '<span class="highlight">$1</span>' ) : texte );
       
   444     }
       
   445     
       
   446     if (!tweet) {
       
   447         return placeHolder(className);
       
   448     }
       
   449     var el = (elName ? elName : 'li');
       
   450     var html = '<'
       
   451     	+ el
       
   452     	+ ' draggable="true" class="tweet '
       
   453     	+ className
       
   454     	+ '" id="tweet_'
       
   455     	+ tweet.id
       
   456     	+ '" data-title="Tweet by '
       
   457     	+ _(tweet.user.name).escape()
       
   458     	+ '" data-description="'
       
   459     	+ _(tweet.text).escape()
       
   460     	+ '" data-uri="http://twitter.com/'
       
   461     	+ tweet.user.screen_name
       
   462     	+ '/status/'
       
   463     	+ tweet.id
       
   464     	+ '"';
       
   465     if (className != 'full') {
       
   466         html += ' onclick="selectTweet(\'' + tweet.id + '\'); return false;"';
       
   467     }
       
   468     html += ' onmouseover="rolloverTweet(\'' + tweet.id + "', " + ( className == 'icons' ) + ');"';
       
   469     if (twCx.followLast && className == 'full' && el == 'li') {
       
   470         html += ' style="display: none"';
       
   471     }
       
   472     html += '>';
       
   473     if (tweet.annotations.length) {
       
   474         html += '<div class="annotations">';
       
   475         for (var i in tweet.annotations) {
       
   476             html += '<div class="annotation" style="width:' + (100/tweet.annotations.length) + '%; background:' + getColor(tweet.annotations[i], (className == 'icons' ? .4 : .85)).hex + '"></div>';
       
   477         }
       
   478         html += '</div>';
       
   479     }
       
   480     html += '<div class="twmain">';
       
   481     var a_user = '<a href="http://twitter.com/' + tweet.user.screen_name + '"  onclick="filtrerTexte(\'@' + tweet.user.screen_name + '\'); return false;" target="_blank">';
       
   482     html += '<div class="around_img"><img class="profile_image" src="' + tweet.user.profile_image_url + '" />';
       
   483     if (className == 'full') {
       
   484         html += '<p class="created_at">' + new Date(tweet.date_value).toTimeString().substr(0,8) + '</a></p>';
       
   485     }
       
   486     html += '</div>';
       
   487     if (className != 'icons') {
       
   488         lastend = 0;
       
   489         var txt = '';
       
   490         tweet.html_parts.sort(function(a, b) { return a.start - b.start });
       
   491         _(tweet.html_parts).each(function(_e) {
       
   492             txt += highlight( tweet.text.substring(lastend, _e.start) ) + _e.link + highlight( _e.text ) + '</a>';
       
   493             lastend = _e.end;
       
   494         });
       
   495         txt += highlight( tweet.text.substring(lastend) );
       
   496         html += '<p class="tweet_text"><b>' + a_user + highlight('@' + tweet.user.screen_name) + '</a>' + ( className == 'full' ? ' (' + tweet.user.name + ')</b><br />' : '</b> : ') + txt + '</p>';
       
   497         if (className == 'full' && el == 'li') {
       
   498             html += '<div class="tweet_actions"><a href="http://twitter.com/' + tweet.user.screen_name + '/status/' + tweet.id + '" onclick="tweetPopup(this.href); return false;" target="_blank">afficher tweet</a> - ';
       
   499             html += '<a href="http://twitter.com/intent/tweet?in_reply_to=' + tweet.id + '" onclick="tweetPopup(this.href); return false;" target="_blank">répondre</a> · ';
       
   500             html += '<a href="http://twitter.com/intent/retweet?tweet_id=' + tweet.id + '" onclick="tweetPopup(this.href); return false;" target="_blank">retweeter</a> · ';
       
   501             html += '<a href="http://twitter.com/intent/favorite?tweet_id=' + tweet.id + '" onclick="tweetPopup(this.href); return false;" target="_blank">favori</a></div>';
       
   502         }
       
   503     }
       
   504     html += '</div></' + el + '>';
       
   505     return html;
       
   506 }
       
   507 
       
   508 function tlIdFromPos(x, y, outside) {
       
   509     if (!twCx.tlOnDisplay) {
       
   510         return;
       
   511     }
       
   512     var ligne = Math.min( twCx.tlOnDisplay.length - 1, Math.max( 0, Math.floor(( twCx.tlHeight - y ) / twCx.scaleY) ) ),
       
   513         colonne = Math.floor(( x - twCx.deltaX ) / twCx.scaleX ),
       
   514         l = 0;
       
   515     if (colonne >= twCx.tlOnDisplay[ligne].totalTweets || colonne < 0 ) {
       
   516         if (outside) {
       
   517             colonne = Math.min( twCx.tlOnDisplay[ligne].totalTweets - 1, Math.max( 0, colonne ));
       
   518         } else {
       
   519             return null;
       
   520         }
       
   521     }
       
   522     for (var i in twCx.tlOnDisplay[ligne].displayData) {
       
   523         var nl = l + twCx.tlOnDisplay[ligne].displayData[i].length;
       
   524         if (colonne < nl) {
       
   525             return {
       
   526                 "id" : twCx.tlOnDisplay[ligne].displayData[i][colonne - l],
       
   527                 "annotation" : i
       
   528             }
       
   529         }
       
   530         l = nl;
       
   531     }
       
   532 }
       
   533 
       
   534 function tlPosTweet(tweet, annotation) {
       
   535     if (!twCx.tweets) {
       
   536         return;
       
   537     }
       
   538     var x,
       
   539         y,
       
   540         dt = tweet.date_value,
       
   541         ann = ( annotation ? annotation : ( tweet.annotations.length ? tweet.annotations[0] : 'default' ) );
       
   542     for (var i = 0; i < twCx.tlOnDisplay.length; i++) {
       
   543         if (twCx.tlOnDisplay[i].end > dt) {
       
   544             y = twCx.tlHeight - (i + .5) * twCx.scaleY;
       
   545             var l = 0;
       
   546             for (var j in twCx.tlOnDisplay[i].displayData) {
       
   547                 if (j == ann) {
       
   548                     var p = _(twCx.tlOnDisplay[i].displayData[j]).indexOf(tweet.id);
       
   549                     if (p != -1) {
       
   550                         x = twCx.deltaX + twCx.scaleX * ( p + l + .5 );
       
   551                     }
       
   552                     break;
       
   553                 }
       
   554                 l += twCx.tlOnDisplay[i].displayData[j].length;
       
   555             }
       
   556             break;
       
   557         }
       
   558     }
       
   559     return ( x && y ? { "x" : x, "y" : y } : null);
       
   560 }
       
   561 
       
   562 function rolloverTweet(tweetid, showPopup, annotation) {
       
   563     var t = tweetById(tweetid);
       
   564     if (!t) {
       
   565         return;
       
   566     }
       
   567     var p = tlPosTweet(t, annotation);
       
   568     if (!p) {
       
   569         return;
       
   570     }
       
   571     var ptl = $("#timeline").offset();
       
   572     if (showPopup) {
       
   573         $("#hovercontent").html(tweetToHtml(t, 'full', 'div'));
       
   574         $("#hovertweet").css({
       
   575             "left" : parseInt(ptl.left + p.x) + "px",
       
   576             "top" : parseInt(ptl.top + p.y),
       
   577             "display" : "block"});
       
   578     } else {
       
   579         $("#hovertweet").hide();
       
   580     }
       
   581     for (var i in twCx.relHover) {
       
   582         twCx.relHover[i].remove();
       
   583     }
       
   584     twCx.relHover = drawTweetArcs(t, p, '#303030');
       
   585     twCx.relHover.push(drawTweetPos(p, '#ffffff'));
       
   586 }
       
   587 
       
   588 function drawTweetPos(pos, color) {
       
   589     var rel = twCx.tlPaper.rect(pos.x - .5 * twCx.scaleX, pos.y - .5 * twCx.scaleY, twCx.scaleX, twCx.scaleY);
       
   590     rel.attr({ "stroke" : color, "fill" : color, "fill-opacity" : .25 });
       
   591     return rel;
       
   592 }
       
   593 
       
   594 function drawTweetArcs(tweet, pos, color) {
       
   595     
       
   596     var res = [];
       
   597 
       
   598     function tweetAndArc(a, b, aorb) {
       
   599         if (a && b) {
       
   600             res.push(drawTweetPos(aorb ? a : b, color));
       
   601             var aa = twCx.tlPaper.path(arc(a,b))
       
   602                 .attr({ "stroke" : color, "stroke-width" : 1.5, "stroke-opacity" : .8 });
       
   603             res.push(aa);
       
   604         }
       
   605     }
       
   606     
       
   607     if (tweet.retweeted_status_id) {
       
   608         var t = tweetById(tweet.retweeted_status_id);
       
   609         if (t) {
       
   610             tweetAndArc(pos, tlPosTweet(t));
       
   611         }
       
   612     }
       
   613     
       
   614     if (tweet.in_reply_to_status_id) {
       
   615         var t = tweetById(tweet.in_reply_to_status_id);
       
   616         if (t) {
       
   617             tweetAndArc(pos, tlPosTweet(t));
       
   618         }
       
   619     }
       
   620     
       
   621     if (tweet.backRefs) {
       
   622         for (var i in tweet.backRefs) {
       
   623             var t = tweetById(tweet.backRefs[i].referenced_by_id);
       
   624             if (t) {
       
   625             tweetAndArc(tlPosTweet(t), pos, true);
       
   626             }
       
   627         }
       
   628     }
       
   629     
       
   630     return res;
       
   631 }
       
   632 
       
   633 function mouseoverkw() {
       
   634     var _jel = $(this),
       
   635         _off = _jel.offset();
       
   636     _jel.css({
       
   637         color: "#0099ff"
       
   638     });
       
   639     $("#hoverkw")
       
   640         .css({
       
   641             "left" : _off.left + "px",
       
   642             "top" : ( parseInt(_off.top) + ~~ (_jel.height() / 2) )  + "px",
       
   643             "display" : "block"
       
   644         })
       
   645         .attr("kw", _jel.text());
       
   646 }
       
   647 
       
   648 function mouseoutkw() {
       
   649     $("#hoverkw").hide();
       
   650     $(this).css({
       
   651         color: "#000000"
       
   652     });
       
   653 }
       
   654 
       
   655 function makeTagCloud(tab, div) {
       
   656     var minfreq = _(tab).min( function(a) { return a.freq} ).freq,
       
   657         maxfreq = Math.max(minfreq + .1, _(tab).max( function(a) { return a.freq} ).freq),
       
   658         echfreq = 8 / Math.sqrt( maxfreq - minfreq ),
       
   659         html = '';
       
   660     _(tab).each(function(_j) {
       
   661         var maxann = 0,
       
   662             ann = "default";
       
   663         for (var k in _j.annotations) {
       
   664             if (_j.annotations[k] == maxann) {
       
   665                 ann = "default";
       
   666             }
       
   667             if (_j.annotations[k] > maxann) {
       
   668                 ann = k;
       
   669                 maxann = _j.annotations[k];
       
   670             }
       
   671         }
       
   672         if (ann == "default") {
       
   673             var coul = '';
       
   674         } else {
       
   675             var c = getColor(ann, .6),
       
   676                 coul = "background: rgba(" + [ Math.floor(c.r), Math.floor(c.g), Math.floor(c.b), ( _j.annotations[ann] / _j.freq )].join(',') + ")";
       
   677         }
       
   678         var fontsize = Math.floor( ( 12 + Math.sqrt( _j.freq - minfreq ) * echfreq ) );
       
   679         html += '<span style="line-height: ' + (8 + fontsize) + 'px; font-size: ' + fontsize + 'px;' + coul + '">' + _j.word + '</span> ';
       
   680     });
       
   681     $(div).html(html);
       
   682     $(div + " span")
       
   683         .mouseover(mouseoverkw)
       
   684         .mouseout(mouseoutkw)
       
   685         .click(function() {
       
   686             $("#hoverkw").toggle();
       
   687         });
       
   688 }
       
   689 
       
   690 function updateDisplay() {
       
   691     if (!twCx.tweets) {
       
   692         return;
       
   693     }
       
   694     if (twCx.filtre) {
       
   695         var tweets = _(twCx.tweets).filter(function(tweet) {
       
   696             var mention = '@' + tweet.user.screen_name;
       
   697             return ( tweet.text.search(twCx.filtre) != -1 ) || ( mention.search(twCx.filtre) != -1 );
       
   698         });
       
   699         $("#inp_q").val(twCx.filtreTexte + ' (' + tweets.length + ' tweets)');
       
   700         if (tweets.length) {
       
   701             var idIndex = _(tweets).map(function(tweet) {
       
   702                 return tweet.id;
       
   703             });
       
   704             var p = _(idIndex).indexOf(twCx.position);
       
   705             if (p == -1) {
       
   706                 for (p = idIndex.length - 1; p > 0 && idIndex[p] > twCx.position; p--) {
       
   707                 }
       
   708             }
       
   709             twCx.position = idIndex[p];
       
   710             twCx.currentIdIndex = idIndex;
       
   711         }
       
   712         
       
   713     } else {
       
   714         twCx.currentIdIndex = twCx.idIndex;
       
   715         var tweets = twCx.tweets;
       
   716         var p = _(twCx.idIndex).indexOf(twCx.position);
       
   717         if (p == -1) {
       
   718             p = (twCx.followLast ? twCx.idIndex.length - 1 : 0);
       
   719         }
       
   720     }
       
   721     
       
   722     
       
   723     var l = tweets.length,
       
   724         lines = 0,
       
   725         ppy = 0,
       
   726         html = '',
       
   727         tweetsOnDisplay = [];
       
   728     
       
   729     function pushTweet(tp, className) {
       
   730         
       
   731         if (tp < l && tp >= 0) {
       
   732             
       
   733             html += tweetToHtml(tweets[tp], className);
       
   734             
       
   735             tweetsOnDisplay.push(tp);
       
   736             
       
   737         } else {
       
   738             html += placeHolder(className);
       
   739         }
       
   740     }
       
   741     
       
   742     if (l) {
       
   743     
       
   744         twCx.lastScrollPos = Math.floor( twCx.scrollExtent * ( 1 - ( p / l ) ) );
       
   745         $("#scrollcont").scrollTop(twCx.lastScrollPos);
       
   746         
       
   747         if (l > p + 18) {
       
   748             lines++;
       
   749             ppy += 20;
       
   750             for (var i = p + 31; i >= p + 18; i--) {
       
   751                 pushTweet(i, 'icons');
       
   752             }
       
   753         }
       
   754         if (l > p + 4) {
       
   755             lines++;
       
   756             ppy += 20;
       
   757             for (var i = p + 17; i >= p + 4; i--) {
       
   758                 pushTweet(i, 'icons');
       
   759             }
       
   760         }
       
   761         for (var k = 3; k >= 1; k--) {
       
   762             if (l > p + k) {
       
   763                 ppy += 47;
       
   764                 lines++;
       
   765                 pushTweet(p + k, 'half');
       
   766             }
       
   767         }
       
   768         pushTweet(p, 'full');
       
   769         var n = p - 1;
       
   770         for (var i = 0; i < Math.min(6, Math.max(3, 6 - lines)); i++) {
       
   771             if (n < 0) {
       
   772                 break;
       
   773             }
       
   774             pushTweet(n, 'half');
       
   775             n--;
       
   776         }
       
   777         for (var i = 0; i < 14 * Math.min(4, Math.max(2, 7 - lines)); i++) {
       
   778             if (n < 0) {
       
   779                 break;
       
   780             }
       
   781             pushTweet(n, 'icons');
       
   782             n--;
       
   783         }
       
   784         if (html != twCx.tlBuffer) {
       
   785             $("#tweetlist").html(html);
       
   786             $(".tweet.full").fadeIn();
       
   787             twCx.tlBuffer = html;
       
   788         }
       
   789         
       
   790         if (twCx.suggestCount.length) {
       
   791             makeTagCloud(twCx.suggestCount, "#suggkw");
       
   792         }
       
   793         
       
   794         var tab = _(twCx.globalWords).chain()
       
   795         .map(function(v, k) {
       
   796             return {
       
   797                 "word": k,
       
   798                 "freq" : v.freq,
       
   799                 "annotations" : v.annotations
       
   800             };
       
   801         }).filter(function(v) {
       
   802             return v.freq > 3;
       
   803         }).value();
       
   804         
       
   805         if (tab.length) {
       
   806             
       
   807             tab = _(tab).sortBy( function(a) { return ( - a.freq ) }).slice(0,40);
       
   808             makeTagCloud(tab,"#motscles");
       
   809         } else {
       
   810             $("#motscles").html('');
       
   811         }
       
   812         twCx.centralTweet = tweets[p];
       
   813     } else {
       
   814         $("#tweetlist").html('');
       
   815         twCx.tlBuffer = '';
       
   816         $("#motscles").html('');
       
   817     }
       
   818     
       
   819     twCx.tlOnDisplay = trimFDS();
       
   820     twCx.scaleY = twCx.tlHeight / twCx.tlOnDisplay.length;
       
   821     var maxTweets = 0,
       
   822         startTl = 0,
       
   823         endTl = twCx.tlOnDisplay.length - 1;
       
   824     if (l) {
       
   825         var startTw = tweets[tweetsOnDisplay[tweetsOnDisplay.length - 1]].date_value,
       
   826             endTw = tweets[tweetsOnDisplay[0]].date_value;
       
   827     }
       
   828     for (var i = 0; i < twCx.tlOnDisplay.length; i++) {
       
   829         if (l) {
       
   830             if (startTw >= twCx.tlOnDisplay[i].start && startTw < twCx.tlOnDisplay[i].end) {
       
   831                 startTl = i;
       
   832             }
       
   833             if (endTw >= twCx.tlOnDisplay[i].start && endTw < twCx.tlOnDisplay[i].end) {
       
   834                 endTl = i;
       
   835             }
       
   836         }
       
   837         var displayData = {};
       
   838         for (var j in annotations) {
       
   839             displayData[j] = [];
       
   840         }
       
   841         for (var j in twCx.tlOnDisplay[i].tweets) {
       
   842             var tweetid = twCx.tlOnDisplay[i].tweets[j],
       
   843                 tweet = tweetById(tweetid);
       
   844             if (tweet) {
       
   845                 if (tweet.annotations.length) {
       
   846                     for (var k in tweet.annotations) {
       
   847                         displayData[tweet.annotations[k]].push(tweetid);
       
   848                     }
       
   849                 } else {
       
   850                     displayData['default'].push(tweetid);
       
   851                 }
       
   852             }
       
   853         }
       
   854         var nbT = 0;
       
   855         for (var j in displayData) {
       
   856             nbT += displayData[j].length;
       
   857         }
       
   858         maxTweets = Math.max(maxTweets, nbT);
       
   859         twCx.tlOnDisplay[i].displayData = displayData;
       
   860         twCx.tlOnDisplay[i].totalTweets = nbT;
       
   861     }
       
   862     twCx.scaleX = ( twCx.tlWidth - twCx.deltaX ) / maxTweets;
       
   863     twCx.tlPaper.clear();
       
   864     twCx.relHover = null;
       
   865     
       
   866     // Dessin de la correspondance liste-timeline
       
   867     if (l) {
       
   868         var startY = twCx.tlHeight - startTl * twCx.scaleY,
       
   869             endY = twCx.tlHeight - ( endTl + 1 ) * twCx.scaleY,
       
   870             path = "M0 " + twCx.tlHeight + "C" + .7*twCx.deltaX + " " + twCx.tlHeight + " " + .3*twCx.deltaX + " " + startY + " " + twCx.deltaX + " " + startY + "L" + twCx.tlWidth + " " + startY + "L" + twCx.tlWidth + " " + endY + "L" + twCx.deltaX + " " + endY + "C" + .3*twCx.deltaX + " " + endY + " " + .7*twCx.deltaX + " 0 0 0";
       
   871         twCx.tlPaper.path( path ).attr({ "stroke" : "none", "fill" : "#000080", "opacity" : .2 });
       
   872     }   
       
   873     // dessin de la date de début
       
   874     
       
   875     twCx.tlPaper.text(twCx.deltaX / 2, twCx.tlHeight - 7, new Date(twCx.tlOnDisplay[0].start).toTimeString().substr(0,5))
       
   876         .attr({ "text-anchor" : "middle", "font-size": "9px" });
       
   877     
       
   878     // dessin de la date de fin
       
   879     
       
   880     twCx.tlPaper.text(twCx.deltaX / 2, 7, new Date(twCx.tlOnDisplay[twCx.tlOnDisplay.length - 1].end).toTimeString().substr(0,5))
       
   881         .attr({ "text-anchor" : "middle", "font-size": "9px" });
       
   882     
       
   883     for (var i = 0; i < twCx.tlOnDisplay.length; i++) {
       
   884         var n = 0,
       
   885             posY = twCx.tlHeight - ( i + 1 ) * twCx.scaleY;
       
   886         for (var j in twCx.tlOnDisplay[i].displayData) {
       
   887             var ll = twCx.tlOnDisplay[i].displayData[j].length;
       
   888             if (ll > 0) {
       
   889                 twCx.tlPaper.rect( twCx.deltaX + n * twCx.scaleX, posY, ll * twCx.scaleX, twCx.scaleY )
       
   890                     .attr({"stroke" : "none", "fill" : getColor(j, .4).hex });
       
   891                 n += ll;
       
   892             }
       
   893         }
       
   894         
       
   895         // Si on est à une demi-heure, on trace un axe secondaire + heure
       
   896         
       
   897         if (i < twCx.tlOnDisplay.length - 1 && !(twCx.tlOnDisplay[i].end % 1800000)) {
       
   898             twCx.tlPaper.path("M0 "+posY+"L" + twCx.tlWidth +" "+posY).attr({"stroke":"#ccc"});
       
   899             twCx.tlPaper.text(twCx.deltaX / 2, posY, new Date(twCx.tlOnDisplay[i].end).toTimeString().substr(0,5)).attr({ "text-anchor" : "middle", "font-size": "9px" });
       
   900         }
       
   901     }
       
   902     
       
   903     // dessin du tweet courant
       
   904     
       
   905     if (l) {
       
   906         
       
   907         if (twCx.filtre) {
       
   908             for (var i = 0; i < tweets.length; i++) {
       
   909                 if (i != p) {
       
   910                     var pos = tlPosTweet(tweets[i]);
       
   911                     if (pos) {
       
   912                         drawTweetPos(pos, "#ffccff");
       
   913                     }
       
   914                 }
       
   915             }
       
   916             
       
   917         }
       
   918         
       
   919         var posp = tlPosTweet(tweets[p]);
       
   920         if (posp) {
       
   921             
       
   922             drawTweetPos(posp, "#ffff00");
       
   923             var yy = posp.y - .5 * twCx.scaleY,
       
   924             	ppy = $(".tweet.full").offset().top - $("#tweetlist").offset().top
       
   925                 path = "M0 " + ppy + "C" + ( .7 * twCx.deltaX ) + " " + ppy + " " + ( .2 * twCx.deltaX ) + " " + yy + " " + ( twCx.deltaX ) + " " + yy + "L" + ( posp.x - .5 * twCx.scaleX ) + " " + yy;
       
   926             yy = posp.y + .5 * twCx.scaleY;
       
   927             ppy += $(".tweet.full").height();
       
   928             path += "L" + ( posp.x - .5 * twCx.scaleX ) + " " + yy + "L" + twCx.deltaX + " " + yy + "C"  + ( .2 * twCx.deltaX ) + " " + yy + " " + ( .7 * twCx.deltaX ) + " " + ppy + " 0 " + ppy;
       
   929             twCx.tlPaper.path( path ).attr({"stroke":"#ffff00", "fill" : "#ffff00", "fill-opacity" : .15});
       
   930             
       
   931             drawTweetArcs(tweets[p], posp, '#800080');
       
   932         }
       
   933     }
       
   934 }
       
   935 
       
   936 function filtrerAnnotation(annotation) {
       
   937     if (annotations[annotation]) {
       
   938         effectuerFiltrage(annotations[annotation].display_name,
       
   939             new RegExp( "(" + _(annotations[annotation].keywords).map(function(a) { return a.source }).join("|") + ")", "gim" ) );
       
   940     } else {
       
   941         effectuerFiltrage('', null)
       
   942     }
       
   943 }
       
   944 
       
   945 function filtrerTexte(valeur) {
       
   946     effectuerFiltrage( valeur, valeur ? new RegExp("(" + valeur.replace(/(\W)/g, '\\$1') + ")" ,'gim') : null );
       
   947 }
       
   948 
       
   949 function effectuerFiltrage(filtreTexte, tabRegexp) {
       
   950     $("#recherche_annot").slideUp();
       
   951     $("#inp_q").val(filtreTexte).attr("class","rechercheCourante");
       
   952     twCx.filtreTexte = filtreTexte;
       
   953     twCx.filtre = tabRegexp;
       
   954     twCx.followLast = !tabRegexp && (twCx.position == twCx.idIndex[twCx.idIndex.length - 1]);
       
   955     updateDisplay();
       
   956 }
       
   957 
       
   958 function clicTl(evt) {
       
   959     var o = $("#timeline").offset();
       
   960     if (twCx.tlMouseClicked && twCx.tlMouseMoved) {
       
   961         var twid = tlIdFromPos(evt.pageX - o.left + twCx.refPosTl.x - twCx.refMouse.x, evt.pageY - o.top + twCx.refPosTl.y - twCx.refMouse.y, true);
       
   962         if (twid) {
       
   963             selectTweet(twid.id);
       
   964         }
       
   965     } else {
       
   966         var twid = tlIdFromPos(evt.pageX - o.left, evt.pageY - o.top, twCx.tlMouseClicked);
       
   967         if (twCx.tlMouseMoved && !twCx.tlMouseClicked) { 
       
   968             if (twid) {
       
   969                 rolloverTweet(twid.id, true, twid.annotation);
       
   970             } else {
       
   971                 $("#hovertweet").hide();
       
   972             }
       
   973         }
       
   974         if (twCx.tlMouseClicked && !twCx.tlMouseMoved) {
       
   975             if (twid) {
       
   976                 selectTweet(twid.id);
       
   977             }
       
   978         }
       
   979     }
       
   980 }
       
   981 
       
   982 function loadTweets(tweets, append) {
       
   983     if (!append) {
       
   984         twCx.timeline = [];
       
   985         twCx.idIndex = [];
       
   986         twCx.tweets = [];
       
   987     }
       
   988     for (var i in tweets) {
       
   989         addTweet(tweets[i]);
       
   990     }
       
   991     if (twCx.followLast) {
       
   992         twCx.position = twCx.idIndex[twCx.tweets.length - 1];
       
   993     }
       
   994     updateDisplay();
       
   995 }
       
   996 
       
   997 function focusOutRecherche() {
       
   998     $("#recherche_annot").slideUp();
       
   999     var inpq = $("#inp_q"),
       
  1000         val = inpq.val();
       
  1001     if (val == '' || val == twCx.filtreTexte) {
       
  1002         if (twCx.filtre) {
       
  1003             inpq.attr("class", "rechercheCourante").val(twCx.filtreTexte);
       
  1004         } else {
       
  1005             inpq.attr("class", "greyed").val(l10n.rechercher);
       
  1006         }
       
  1007     }
       
  1008 }
       
  1009 
       
  1010 function chaineTimeZoom() {
       
  1011     var chaine = "",
       
  1012         t = twCx.date_levels[twCx.timeLevel],
       
  1013         h = 3600*1000,
       
  1014         m = 60*1000,
       
  1015         s = 1000,
       
  1016         heures = Math.floor(t/h);
       
  1017     if (heures) { chaine += heures + ' h. ' };
       
  1018     t -= (heures * h);
       
  1019     var minutes = Math.floor(t/m);
       
  1020     if (minutes) { chaine += minutes + ' min. ' };
       
  1021     t -= (minutes * m);
       
  1022     if (t) { chaine += Math.floor(t/s) + ' sec.' }
       
  1023     $("#time_scale").html(chaine);
       
  1024     $("#time_zoomout").attr("class",(twCx.timeLevel == 0 ? "inactive" : ""));
       
  1025     $("#time_zoomin").attr("class",(twCx.timeLevel == twCx.date_levels.length - 1 ? "inactive" : ""));
       
  1026 }
       
  1027 
       
  1028 $(document).ready(function() {
       
  1029     twCx.tlWidth = $("#timeline").width();
       
  1030     twCx.tlHeight = $("#timeline").height();
       
  1031     twCx.tlPaper = Raphael("timeline", twCx.tlWidth, twCx.tlHeight);
       
  1032     
       
  1033     connectTweets();
       
  1034     
       
  1035     var html = '';
       
  1036     for (var j in annotations) {
       
  1037         if (j != "default") {
       
  1038             html += '<a href="#" style="background: ' + getColor(j, .7).hex + ';" onclick=filtrerAnnotation(\'' + j + '\'); return false;">' + annotations[j].display_name + '</a> '
       
  1039         }
       
  1040     }
       
  1041     $("#rech_list_annot").html(html);
       
  1042     
       
  1043     chaineTimeZoom();
       
  1044     
       
  1045     $("#tweetlist").mousewheel(function(e, d) {
       
  1046         twCx.wheelDelta += d;
       
  1047         if (Math.abs(twCx.wheelDelta) >= 1) {
       
  1048             movePos( parseInt(twCx.wheelDelta) );
       
  1049             twCx.wheelDelta = 0;
       
  1050         }
       
  1051         return false;
       
  1052     });
       
  1053     $("#tweetlist").delegate(".tweet", "dragstart", function(e) {
       
  1054     	var div = document.createElement('div');
       
  1055     	div.appendChild(this.cloneNode(true));
       
  1056 		try {
       
  1057 			e.originalEvent.dataTransfer.setData("text/html",div.innerHTML);
       
  1058 		}
       
  1059 		catch(err) {
       
  1060 			e.originalEvent.dataTransfer.setData("text",div.innerHTML);
       
  1061 		}
       
  1062     });
       
  1063     $("#timeline").mousewheel(function(e, d) {
       
  1064         twCx.wheelDelta += d;
       
  1065         if (Math.abs(twCx.wheelDelta) >= 1) {
       
  1066             if (twCx.wheelDelta > 0) {
       
  1067                 tl = Math.min(twCx.date_levels.length - 1, twCx.timeLevel + 1);
       
  1068             } else {
       
  1069                 tl = Math.max(0, twCx.timeLevel - 1);
       
  1070             }
       
  1071             if (tl != twCx.timeLevel) {
       
  1072                 twCx.timeLevel = tl;
       
  1073                 chaineTimeZoom();
       
  1074                 updateDisplay();
       
  1075             }
       
  1076             twCx.wheelDelta = 0;
       
  1077         }
       
  1078         return false;
       
  1079     });
       
  1080     $("#time_zoomin").click(function() {
       
  1081         if (twCx.timeLevel < twCx.date_levels.length - 1) {
       
  1082             twCx.timeLevel++;
       
  1083             chaineTimeZoom();
       
  1084             updateDisplay();
       
  1085         }
       
  1086     });
       
  1087     $("#time_zoomout").click(function() {
       
  1088         if (twCx.timeLevel > 0) {
       
  1089             twCx.timeLevel--;
       
  1090             chaineTimeZoom();
       
  1091             updateDisplay();
       
  1092         }
       
  1093     });
       
  1094     $("#timeline, #tweetlist").mouseout(function() {
       
  1095         twCx.tlMouseClicked = false;
       
  1096         twCx.tlMouseMoved = false;
       
  1097         $("#hovertweet").hide();
       
  1098     });
       
  1099     $("#timeline").mousemove(function(evt) {
       
  1100         twCx.tlMouseMoved = true;
       
  1101         clicTl(evt);
       
  1102     }).mousedown(function(evt) {
       
  1103         twCx.tlMouseClicked = true;
       
  1104         twCx.tlMouseMoved = false;
       
  1105         var o = $(this).offset();
       
  1106         twCx.refMouse = { x : evt.pageX - o.left, y : evt.pageY - o.top };
       
  1107         twCx.refPosTl = tlPosTweet(tweetById(twCx.position)) || twCx.refMouse;
       
  1108     }).mouseup(function(evt) {
       
  1109         clicTl(evt);
       
  1110         twCx.tlMouseClicked = false;
       
  1111         twCx.tlMouseMoved = false;
       
  1112     });
       
  1113     $("#inp_q").focus(function() {
       
  1114         $("#recherche_annot").slideDown();
       
  1115         $(this).val($(this).val().replace(/ \(.+\)$/, ''))
       
  1116         if ($(this).hasClass("greyed")) {
       
  1117             $(this).val("");
       
  1118         }
       
  1119         $(this).attr("class","");
       
  1120     }).focusout(function() {
       
  1121         focusOutRecherche();
       
  1122     });
       
  1123     $("#inp_reset").click(function() {
       
  1124         $("#inp_q").val('');
       
  1125         if (twCx.filtre) {
       
  1126             twCx.filtre = null;
       
  1127             updateDisplay();
       
  1128         }
       
  1129         twCx.filtreTexte = '';
       
  1130         focusOutRecherche();
       
  1131         return false;
       
  1132     })
       
  1133     $("#recherche").submit(function(evt) {
       
  1134         evt.preventDefault();
       
  1135         if (!$("#inp_q").hasClass("greyed")) {
       
  1136             var valeur = $("#inp_q").val();
       
  1137             filtrerTexte(valeur);
       
  1138         }
       
  1139         return false;
       
  1140     });
       
  1141     $("#hoverkw").mouseover(function() {
       
  1142         $(this).dequeue().show();
       
  1143     }).mouseout(function() {
       
  1144         $(this).hide();
       
  1145     });
       
  1146     
       
  1147     $("#hkwsearch").click(function() {
       
  1148         var _hkw = $("#hoverkw");
       
  1149         filtrerTexte(_hkw.attr("kw"));
       
  1150         _hkw.hide();
       
  1151         return false;
       
  1152     });
       
  1153     $("#hkwtweet").click(function() {
       
  1154         var _hkw = $("#hoverkw");
       
  1155         add_grammar(_hkw.attr("kw"));
       
  1156         _hkw.hide();
       
  1157         return false;
       
  1158     });
       
  1159     $(".acctitre").click(function() {
       
  1160         $(this).next().slideToggle();
       
  1161         return false;
       
  1162     })
       
  1163     
       
  1164     if (!suggested_keywords.length) {
       
  1165         $("#suggkw").parent().hide();
       
  1166     }
       
  1167     
       
  1168     setInterval(function() {
       
  1169         var sc = $("#scrollcont");
       
  1170         if (sc.scrollTop() != twCx.lastScrollPos && twCx.tweets && twCx.currentIdIndex) {
       
  1171             var p = Math.floor( twCx.currentIdIndex.length * ( 1 - sc.scrollTop() / twCx.scrollExtent ) );
       
  1172             goToPos(p);
       
  1173         }
       
  1174     }, 100)
       
  1175 });
       
  1176 
       
  1177 function connectTweets() {
       
  1178     twCx.tlPaper.clear();
       
  1179     var _sq = twCx.tlPaper.rect(0, twCx.tlHeight, twCx.tlWidth, 0)
       
  1180         .attr({
       
  1181             "stroke" : "none",
       
  1182             "fill" : "#8080cc"
       
  1183         });
       
  1184     var _lb = twCx.tlPaper.text(twCx.tlWidth / 2, twCx.tlHeight / 2, "0 tweet")
       
  1185         .attr({
       
  1186             "font-size" : "20px",
       
  1187             "text-anchor" : "middle"
       
  1188         });
       
  1189     
       
  1190     getTweets({
       
  1191         "keyword" : tracking_keywords.join(" OR "),
       
  1192         "pages" : max_pages,
       
  1193         "rpp" : 100,
       
  1194         "cbData" : function() {
       
  1195             _lb.attr("text", (this.tweets.length - this.currentPage + 1) + " tweets");
       
  1196             var _h = twCx.tlHeight * this.currentPage / this.pages;
       
  1197             _sq.animate({
       
  1198                 "y" : twCx.tlHeight - _h,
       
  1199                 "height" : _h
       
  1200             })
       
  1201         },
       
  1202         "cbEnd" : function() {
       
  1203             loadTweets(this.tweets);
       
  1204             setInterval(function() {
       
  1205                 getTweets({
       
  1206                     "keyword" : tracking_keywords.join(" OR "),
       
  1207                     "pages" : 1,
       
  1208                     "since_id" : twCx.idIndex[twCx.idIndex.length - 1],
       
  1209                     "rpp" : 100,
       
  1210                     "cbEnd" : function() {
       
  1211                         loadTweets(this.tweets, true);
       
  1212                     }
       
  1213                 });
       
  1214             }, 60000)
       
  1215         }
       
  1216     });
       
  1217 }