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