web/src/js/live-polemic.js
changeset 1581 2d713fdd532f
parent 1575 ce1d5b0d1479
equal deleted inserted replaced
1580:2b0cb7c8f34d 1581:2d713fdd532f
     1 import _ from 'underscore';
     1 import _ from "underscore";
     2 import $ from 'jquery';
     2 import $ from "jquery";
     3 import Raphael from 'raphael';
     3 import Raphael from "raphael";
     4 import dayjs from 'dayjs';
     4 import dayjs from "dayjs";
       
     5 
       
     6 const delay = (ms) => new Promise((res) => setTimeout(res, ms));
     5 
     7 
     6 function rejectUser(username) {
     8 function rejectUser(username) {
     7     return (/^[A-Z][a-z]{2,8}[0-9]{4,6}$/.test(username))
     9   return /^[A-Z][a-z]{2,8}[0-9]{4,6}$/.test(username);
     8 }
    10 }
     9 
    11 
    10 let annotations = global.annotations;
    12 function getGlobal(varkey, defaultValue) {
    11 
    13   return typeof global[varkey] == "undefined" ? defaultValue : global[varkey];
    12 if (typeof annotations == "undefined" || !annotations) {
    14 }
    13     annotations = {
    15 
    14         "default" : {
    16 let annotations = getGlobal("annotations", {
    15             "colors" : {
    17   default: {
    16                 "h" : 0,
    18     colors: {
    17                 "s" : 0
    19       h: 0,
    18             }
    20       s: 0,
    19         },
    21     },
    20         "positive" : {
    22   },
    21             "display_name" : "++ | important",
    23   positive: {
    22             "keywords" : [ /\+\+/ ],
    24     display_name: "++ | important",
    23             "colors" : {
    25     keywords: [/\+\+/],
    24                 "h" : .3,
    26     colors: {
    25                 "s" : .65
    27       h: 0.3,
    26             }
    28       s: 0.65,
    27         },
    29     },
    28         "negative" : {
    30   },
    29             "display_name" : "** | index",
    31   negative: {
    30             "keywords" : [ /\*\*/ ],
    32     display_name: "** | index",
    31             "colors" : {
    33     keywords: [/\*\*/],
    32                 "h" : 0,
    34     colors: {
    33                 "s" : .8
    35       h: 0,
    34             }
    36       s: 0.8,
    35         },
    37     },
    36         "reference" : {
    38   },
    37             "display_name" : "== | commentaire",
    39   reference: {
    38             "keywords" : [ /\=\=/ ],
    40     display_name: "== | commentaire",
    39             "colors" : {
    41     keywords: [/\=\=/],
    40                 "h" : .16,
    42     colors: {
    41                 "s" : .8
    43       h: 0.16,
    42             }
    44       s: 0.8,
    43         },
    45     },
    44         "question" : {
    46   },
    45             "display_name" : "?? | trouble",
    47   question: {
    46             "keywords" : [ /\?\?/ ],
    48     display_name: "?? | trouble",
    47             "colors" : {
    49     keywords: [/\?\?/],
    48                 "h" : .6,
    50     colors: {
    49                 "s" : .8
    51       h: 0.6,
    50             }
    52       s: 0.8,
       
    53     },
       
    54   },
       
    55 });
       
    56 
       
    57 function getSocialGroupUri(social_group) {
       
    58   const groupParts = social_group.replace(/^\@+/, "").split("@");
       
    59   return `https://${groupParts[1]}/u/${groupParts[0]}`;
       
    60 }
       
    61 
       
    62 var i10n = getGlobal("i10n", {
       
    63   rechercher: "Rechercher",
       
    64   loading_messages: "Chargement messages",
       
    65   loading_messages_error: "Erreur chargement messages",
       
    66 });
       
    67 
       
    68 const suggested_keywords = getGlobal("suggested_keywords", []);
       
    69 
       
    70 const max_pages = getGlobal("max_pages", 5);
       
    71 
       
    72 const social_network = getGlobal("social_network", "Twitter");
       
    73 const social_login_domain = getGlobal("social_login_domain");
       
    74 const social_access_token = getGlobal("social_access_token");
       
    75 const social_group = getGlobal("social_group");
       
    76 
       
    77 tracking_keywords = _(getGlobal("tracking_keywords", [])).map(function (_w) {
       
    78   return _w.toLowerCase();
       
    79 });
       
    80 
       
    81 var twCx = {
       
    82   tlPaper: null,
       
    83   followLast: true,
       
    84   position: "0",
       
    85   currentIdIndex: 0,
       
    86   date_levels: [3600 * 1000, 15 * 60 * 1000, 5 * 60 * 1000, 60 * 1000],
       
    87   timeLevel: 1,
       
    88   deltaX: 40,
       
    89   tlWidth: 150,
       
    90   tlHeight: 480,
       
    91   globalWords: {},
       
    92   suggestCount: _(suggested_keywords).map(function (_w) {
       
    93     return {
       
    94       word: _w,
       
    95       rgxp: new RegExp(_w.replace(/(\W)/g, "\\$1"), "im"),
       
    96       freq: 0,
       
    97       annotations: {},
       
    98     };
       
    99   }),
       
   100   refMouse: { x: 0, y: 0 },
       
   101   refPosTl: { x: 0, y: 0 },
       
   102   tlMouseMoved: false,
       
   103   tlMouseClicked: false,
       
   104   filtre: null,
       
   105   tlBuffer: "",
       
   106   relHover: [],
       
   107   wheelDelta: 0,
       
   108   scrollEnabled: false,
       
   109   scrollExtent: 8000 - 480,
       
   110   lastScrollPos: 0,
       
   111   urlRegExp: /https?:\/\/[0-9a-zA-Z\.%\/-_]+/g,
       
   112   wordRegExp: /[^ \.&;,'"!\?\d\(\)\+\[\]\\\…\-«»:\/]{3,}/g,
       
   113   stopWords: [
       
   114     "aussi",
       
   115     "and",
       
   116     "avec",
       
   117     "aux",
       
   118     "bien",
       
   119     "car",
       
   120     "cette",
       
   121     "comme",
       
   122     "dans",
       
   123     "donc",
       
   124     "des",
       
   125     "elle",
       
   126     "encore",
       
   127     "est",
       
   128     "être",
       
   129     "eux",
       
   130     "faire",
       
   131     "fait",
       
   132     "http",
       
   133     "ici",
       
   134     "ils",
       
   135     "les",
       
   136     "leur",
       
   137     "leurs",
       
   138     "mais",
       
   139     "mes",
       
   140     "même",
       
   141     "mon",
       
   142     "notre",
       
   143     "non",
       
   144     "nos",
       
   145     "nous",
       
   146     "ont",
       
   147     "oui",
       
   148     "par",
       
   149     "pas",
       
   150     "peu",
       
   151     "peut",
       
   152     "plus",
       
   153     "pour",
       
   154     "que",
       
   155     "qui",
       
   156     "ses",
       
   157     "son",
       
   158     "sont",
       
   159     "sur",
       
   160     "tes",
       
   161     "très",
       
   162     "the",
       
   163     "ton",
       
   164     "tous",
       
   165     "tout",
       
   166     "une",
       
   167     "votre",
       
   168     "vos",
       
   169     "vous",
       
   170   ],
       
   171 };
       
   172 
       
   173 function getSocialData() {
       
   174   return $.ajax({
       
   175     beforeSend: function (request) {
       
   176       request.setRequestHeader(
       
   177         "Authorization",
       
   178         `Bearer ${social_access_token}`
       
   179       );
       
   180     },
       
   181     type: "GET",
       
   182     dataType: "json",
       
   183     url: `https://${social_login_domain}/api/v1/accounts/verify_credentials`,
       
   184   });
       
   185 }
       
   186 
       
   187 function getFollowing(user_id) {
       
   188   return $.ajax({
       
   189     beforeSend: function (request) {
       
   190       request.setRequestHeader(
       
   191         "Authorization",
       
   192         `Bearer ${social_access_token}`
       
   193       );
       
   194     },
       
   195     type: "GET",
       
   196     dataType: "json",
       
   197     url: `https://${social_login_domain}/api/v1/accounts/${user_id}/following`,
       
   198   });
       
   199 }
       
   200 
       
   201 function getTweets(options) {
       
   202   function getTweetUrl(url) {
       
   203     $("#tweetLoader").addClass("tweetLoading");
       
   204     $("#tweetLoader").attr("title", i10n["loading_messages"]);
       
   205     $("#tweetLoader").removeClass("tweetLoadingError");
       
   206 
       
   207     $.ajax({
       
   208       url: url,
       
   209       dataType: "json",
       
   210       success: function (data) {
       
   211         $("#tweetLoader").removeClass("tweetLoading");
       
   212         $("#tweetLoader").removeAttr("title");
       
   213         if (typeof data == "undefined" || !data) {
       
   214           $("#tweetLoader").addClass("tweetLoadingError");
       
   215           $("#tweetLoader").attr("title", i10n["loading_messages_error"]);
       
   216           return;
    51         }
   217         }
    52     }
   218         options.tweets = options.tweets.concat(data.statuses);
    53 }
   219         options.currentPage++;
    54 
   220         if (options.cbData) {
    55 function getGlobal(varkey, defaultValue) {
   221           options.cbData();
    56     return typeof(global[varkey]) == "undefined" ? defaultValue : global[varkey];
   222         }
    57 }
   223         var _isLast = true;
    58 
   224         if (data.results && data.results.length) {
    59 function getSocialGroupUri(social_group) {
   225           var _oldestTweetId = data.results[data.results.length - 1].id_str,
    60     const groupParts = social_group.replace(/^\@+/,"").split('@');
   226             _maxId = _oldestTweetId;
    61     return `https://${groupParts[1]}/u/${groupParts[0]}`
   227           if (options.currentPage < options.pages) {
    62 }
   228             _isLast = false;
    63 
   229             getTweetUrl(
    64 
   230               baseurl + firstparams + "&max_id=" + _maxId + lastparams
    65 //var i10n = getGlobal('i10n', { "rechercher" : "Rechercher" });
   231             );
    66 
   232           }
    67 const suggested_keywords = getGlobal('suggested_keywords',[]);
   233         }
    68 
   234         if (_isLast) {
    69 const max_pages = getGlobal('max_pages', 5);
   235           options.tweets.sort(function (a, b) {
    70 
   236             return a.id.localeCompare(b.id);
    71 const social_network = getGlobal('social_network', 'Twitter');
   237           });
    72 const social_login_domain = getGlobal('social_login_domain');
   238           if (options.cbEnd) {
    73 const social_access_token = getGlobal('social_access_token');
   239             options.cbEnd();
    74 const social_group = getGlobal('social_group');
   240           }
    75 
   241         }
    76 tracking_keywords = _(getGlobal('tracking_keywords', [])).map(function(_w) {
   242       },
    77     return _w.toLowerCase();
   243       timeout: 10000, //3 second timeout,
    78 });
   244       error: function (jqXHR, status, errorThrown) {
    79 
   245         $("#tweetLoader").addClass("tweetLoadingError");
    80 var twCx = {
   246         $("#tweetLoader").attr(
    81         tlPaper : null,
   247           "title",
    82         followLast : true,
   248           i10n["loading_messages_error"] + " - " + status
    83         position : "0",
   249         );
    84         currentIdIndex: 0,
   250       },
    85         date_levels : [
       
    86             3600 * 1000,
       
    87             15 * 60 * 1000,
       
    88             5 * 60 * 1000,
       
    89             60 * 1000
       
    90         ],
       
    91         timeLevel : 1,
       
    92         deltaX : 40,
       
    93         tlWidth : 150,
       
    94         tlHeight : 480,
       
    95         globalWords : {},
       
    96         suggestCount : _(suggested_keywords).map(function(_w) {
       
    97             return {
       
    98                 "word" : _w,
       
    99                 "rgxp" : new RegExp(_w.replace(/(\W)/g, '\\$1'), "im"),
       
   100                 "freq" : 0,
       
   101                 "annotations" : {}
       
   102             }
       
   103         }),
       
   104         refMouse : { x : 0, y : 0},
       
   105         refPosTl : { x : 0, y : 0},
       
   106         tlMouseMoved : false,
       
   107         tlMouseClicked : false,
       
   108         filtre : null,
       
   109         tlBuffer : '',
       
   110         relHover : [],
       
   111         wheelDelta : 0,
       
   112         scrollEnabled : false,
       
   113         scrollExtent : 8000 - 480,
       
   114         lastScrollPos : 0,
       
   115         urlRegExp : /https?:\/\/[0-9a-zA-Z\.%\/-_]+/g,
       
   116         wordRegExp : /[^ \.&;,'"!\?\d\(\)\+\[\]\\\…\-«»:\/]{3,}/g,
       
   117         stopWords : [
       
   118             'aussi', 'and', 'avec', 'aux', 'bien', 'car', 'cette', 'comme', 'dans', 'donc', 'des', 'elle', 'encore', 'est',
       
   119             'être', 'eux', 'faire', 'fait', 'http', 'ici', 'ils', 'les', 'leur', 'leurs', 'mais', 'mes', 'même', 'mon', 'notre',
       
   120             'non', 'nos', 'nous', 'ont', 'oui', 'par', 'pas', 'peu', 'peut', 'plus', 'pour', 'que', 'qui', 'ses' ,'son', 'sont', 'sur',
       
   121             'tes', 'très', 'the', 'ton', 'tous', 'tout', 'une', 'votre', 'vos', 'vous'
       
   122             ]
       
   123     }
       
   124 
       
   125 function getSocialData() {
       
   126     return $.ajax({
       
   127         beforeSend: function(request) {
       
   128             request.setRequestHeader('Authorization', `Bearer ${social_access_token}`);
       
   129         },
       
   130         type: "GET",
       
   131         dataType: "json",
       
   132         url: `https://${social_login_domain}/api/v1/accounts/verify_credentials`,
       
   133     });
   251     });
   134 }
   252     $.getJSON(url);
   135 
   253   }
   136 function getFollowing(user_id) {
   254 
   137     return $.ajax({
   255   options.tweets = [];
   138         beforeSend: function(request) {
   256   options.pages || (options.pages = 1);
   139             request.setRequestHeader('Authorization', `Bearer ${social_access_token}`);
   257   options.rpp || (options.rpp = 100);
   140         },
   258   options.currentPage = 0;
   141         type: "GET",
   259 
   142         dataType: "json",
   260   var urlParams = {
   143         url: `https://${social_login_domain}/api/v1/accounts/${user_id}/following`,
   261     tweet_mode: "extended",
   144     });
   262     q: options.keyword,
   145 }
   263     count: options.rpp,
   146 
   264   };
   147 
   265   if (options.lang) {
   148 function getTweets(options) {
   266     urlParams.lang = options.lang;
   149     
   267   }
   150     function getTweetUrl(url) {
   268   if (options.since_id) {
   151         $.getJSON(url, function(data) {
   269     urlParams.since_id = options.since_id;
   152             options.tweets = options.tweets.concat(data.statuses);
   270   }
   153             options.currentPage++;
   271   urlParams.social_network = options.social_network
   154             if (options.cbData) {
   272     ? options.social_network
   155                 options.cbData();
   273     : "Twitter";
   156             }
   274 
   157             var _isLast = true;
   275   var jsonurl = "search_tweets.php?" + $.param(urlParams);
   158             if (data.results && data.results.length) {
   276   getTweetUrl(jsonurl);
   159                 var _oldestTweetId = data.results[data.results.length - 1].id_str,
   277 }
   160                     _maxId = _oldestTweetId;
   278 
   161                 if (options.currentPage < options.pages) {
   279 function getColor(annotation, lum) {
   162                     _isLast = false;
   280   return Raphael.hsl2rgb(
   163                     getTweetUrl(baseurl + firstparams + '&max_id=' + _maxId + lastparams);
   281     annotations[annotation].colors.h,
   164                 }
   282     annotations[annotation].colors.s,
   165             }
   283     lum
   166             if (_isLast) {
   284   );
   167                     options.tweets.sort(function(a,b) {
   285 }
   168                        return a.id.localeCompare(b.id); 
   286 
   169                     });
   287 function tweetPopup(url) {
   170                     if (options.cbEnd) {
   288   var popW = 550,
   171                         options.cbEnd();
   289     popH = 350,
   172                     }
   290     scrW = screen.width,
   173             }
   291     scrH = screen.height,
   174         });
   292     posX = Math.round(scrW / 2 - popW / 2),
   175     }
   293     posY = scrH > popH ? Math.round(scrH / 2 - popH / 2) : 0;
   176     
   294   window.open(
   177     options.tweets = [];
   295     url,
   178     options.pages || (options.pages = 1);
   296     "",
   179     options.rpp || (options.rpp = 100);
   297     "left=" +
   180     options.currentPage = 0;
   298       posX +
   181 
   299       ",top=" +
   182     var urlParams = {
   300       posY +
   183         tweet_mode: 'extended',
   301       ",width=" +
   184         q: options.keyword,
   302       popW +
   185         count: options.rpp
   303       ",height=" +
       
   304       popH +
       
   305       ",personalbar=0,toolbar=0,scrollbars=1,resizable=1"
       
   306   );
       
   307 }
       
   308 
       
   309 function arc(source, target) {
       
   310   var x3 = 0.3 * target.y - 0.3 * source.y + 0.8 * source.x + 0.2 * target.x;
       
   311   var y3 = 0.8 * source.y + 0.2 * target.y - 0.3 * target.x + 0.3 * source.x;
       
   312   var x4 = 0.3 * target.y - 0.3 * source.y + 0.2 * source.x + 0.8 * target.x;
       
   313   var y4 = 0.2 * source.y + 0.8 * target.y - 0.3 * target.x + 0.3 * source.x;
       
   314   return (
       
   315     "M" +
       
   316     source.x +
       
   317     " " +
       
   318     source.y +
       
   319     "C" +
       
   320     [x3, y3, x4, y4, target.x, target.y].join(" ")
       
   321   );
       
   322 }
       
   323 
       
   324 function addTweet(tweet) {
       
   325   if (!tweet) {
       
   326     console.log(tweet);
       
   327     return;
       
   328   }
       
   329 
       
   330   if (rejectUser(tweet.from_user)) {
       
   331     return;
       
   332   }
       
   333 
       
   334   function backRef(source_id, target_id, type) {
       
   335     var target = tweetById(target_id);
       
   336     if (target) {
       
   337       var brobj = {
       
   338         referenced_by_id: source_id,
       
   339         type: type,
       
   340       };
       
   341       if (target.backRefs) {
       
   342         target.backRefs.push(brobj);
       
   343       } else {
       
   344         target.backRefs = [brobj];
       
   345       }
       
   346     }
       
   347   }
       
   348 
       
   349   _(["id", "from_user_id", "in_reply_to_status_id"]).each(function (_i) {
       
   350     if (typeof tweet[_i + "_str"] == "undefined") return;
       
   351     tweet[_i] = tweet[_i + "_str"];
       
   352     delete tweet[_i + "_str"];
       
   353   });
       
   354 
       
   355   if (_(twCx.idIndex).indexOf(tweet.id) != -1) {
       
   356     return;
       
   357   }
       
   358 
       
   359   tweet.html_parts = [];
       
   360 
       
   361   if (tweet.entities && tweet.entities.user_mentions) {
       
   362     for (var _i = 0; _i < tweet.entities.user_mentions.length; _i++) {
       
   363       var _m = tweet.entities.user_mentions[_i];
       
   364       tweet.html_parts.push({
       
   365         text: "@" + _m.screen_name,
       
   366         start: _m.indices[0],
       
   367         end: _m.indices[1],
       
   368         link:
       
   369           '<a href="http://twitter.com/' +
       
   370           _m.screen_name +
       
   371           '" onclick="filtrerTexte(\'' +
       
   372           _m.screen_name +
       
   373           '\'); return false;" target="_blank">',
       
   374       });
       
   375     }
       
   376   }
       
   377 
       
   378   if (tweet.entities && tweet.entities.hashtags) {
       
   379     for (var _i = 0; _i < tweet.entities.hashtags.length; _i++) {
       
   380       var _m = tweet.entities.hashtags[_i],
       
   381         _h = "#" + _m.text;
       
   382       tweet.html_parts.push({
       
   383         text: _h,
       
   384         start: _m.indices[0],
       
   385         end: _m.indices[1],
       
   386         link:
       
   387           '<a href="http://twitter.com/search?q=' +
       
   388           encodeURIComponent(_h) +
       
   389           '" onclick="filtrerTexte(\'' +
       
   390           _.escape(_h) +
       
   391           '\'); return false;" target="_blank">',
       
   392       });
       
   393     }
       
   394   }
       
   395 
       
   396   if (tweet.entities && tweet.entities.urls) {
       
   397     for (var _i = 0; _i < tweet.entities.urls.length; _i++) {
       
   398       var _m = tweet.entities.urls[_i];
       
   399       tweet.html_parts.push({
       
   400         text: _m.display_url || _m.url,
       
   401         start: _m.indices[0],
       
   402         end: _m.indices[1],
       
   403         link: '<a href="' + _m.url + '" target="_blank">',
       
   404       });
       
   405     }
       
   406   }
       
   407   tweet.date_value = dayjs(tweet.created_at).valueOf();
       
   408 
       
   409   var ann = [];
       
   410   for (var j in annotations) {
       
   411     if (j != "default") {
       
   412       for (var k in annotations[j].keywords) {
       
   413         var tweetText = tweet.full_text;
       
   414         if (tweetText.search(annotations[j].keywords[k]) != -1) {
       
   415           ann.push(j);
       
   416           break;
       
   417         }
       
   418       }
       
   419     }
       
   420   }
       
   421   tweet.annotations = ann;
       
   422 
       
   423   if (tweet.in_reply_to_status_id) {
       
   424     backRef(tweet.id, tweet.in_reply_to_status_id, "reply");
       
   425   }
       
   426 
       
   427   if (tweet.retweeted_status && tweet.retweeted_status.id_str) {
       
   428     tweet.retweeted_status_id = tweet.retweeted_status.id_str;
       
   429     backRef(tweet.id, tweet.retweeted_status_id, "retweet");
       
   430   }
       
   431 
       
   432   // clean full text from html
       
   433   const temp_div_element = document.createElement("div");
       
   434   temp_div_element.innerHTML = tweet.full_text;
       
   435   const full_text =
       
   436     temp_div_element.textContent ||
       
   437     temp_div_element.innerText ||
       
   438     tweet.full_text;
       
   439 
       
   440   var tab = full_text.replace(twCx.urlRegExp, "").match(twCx.wordRegExp);
       
   441   _(tab).each(function (w) {
       
   442     var word = w.toLowerCase();
       
   443     if (
       
   444       _(twCx.stopWords).indexOf(word) == -1 &&
       
   445       _(tracking_keywords).indexOf(word) == -1 &&
       
   446       word[0] != "@"
       
   447     ) {
       
   448       if (twCx.globalWords[word]) {
       
   449         twCx.globalWords[word].freq++;
       
   450       } else {
       
   451         twCx.globalWords[word] = {
       
   452           freq: 1,
       
   453           annotations: {},
       
   454         };
       
   455         for (var j in annotations) {
       
   456           if (j != "default") {
       
   457             twCx.globalWords[word].annotations[j] = 0;
       
   458           }
       
   459         }
       
   460       }
       
   461       for (var j in ann) {
       
   462         if (typeof twCx.globalWords[word].annotations != "undefined") {
       
   463           twCx.globalWords[word].annotations[ann[j]]++;
       
   464         }
       
   465       }
       
   466     }
       
   467   });
       
   468 
       
   469   _(twCx.suggestCount).each(function (_k) {
       
   470     if (tweet.full_text.search(_k.rgxp) != -1) {
       
   471       _k.freq++;
       
   472       _(ann).each(function (_a) {
       
   473         _k.annotations[_a] = 1 + (_k.annotations[_a] || 0);
       
   474       });
       
   475     }
       
   476   });
       
   477 
       
   478   var p = twCx.idIndex.length;
       
   479   while (p && tweet.id < twCx.idIndex[p - 1]) {
       
   480     p--;
       
   481   }
       
   482   twCx.tweets.splice(p, 0, tweet);
       
   483   twCx.idIndex.splice(p, 0, tweet.id);
       
   484 
       
   485   if (!twCx.timeline.length) {
       
   486     twCx.timeline = [
       
   487       populateDateStruct(
       
   488         0,
       
   489         twCx.date_levels[0] * parseInt(tweet.date_value / twCx.date_levels[0])
       
   490       ),
       
   491     ];
       
   492   }
       
   493   while (tweet.date_value > twCx.timeline[twCx.timeline.length - 1].end) {
       
   494     twCx.timeline.push(
       
   495       populateDateStruct(0, twCx.timeline[twCx.timeline.length - 1].end)
       
   496     );
       
   497   }
       
   498 
       
   499   insertIntoDateStruct(twCx.timeline, tweet);
       
   500 }
       
   501 
       
   502 function getSliceContent(slice) {
       
   503   if (slice.slices) {
       
   504     var result = [];
       
   505     for (var i in slice.slices) {
       
   506       result = result.concat(getSliceContent(slice.slices[i]));
       
   507     }
       
   508   } else {
       
   509     var result = slice.tweets;
       
   510   }
       
   511   return result;
       
   512 }
       
   513 
       
   514 function flattenDateStruct(slices, target_level) {
       
   515   if (!slices || !slices.length) {
       
   516     return [];
       
   517   }
       
   518   var current_level = slices[0].level,
       
   519     result = [];
       
   520   if (current_level < target_level) {
       
   521     if (slices[0].slices) {
       
   522       for (var i in slices) {
       
   523         result = result.concat(
       
   524           flattenDateStruct(slices[i].slices, target_level)
       
   525         );
       
   526       }
       
   527     }
       
   528   } else {
       
   529     for (var i in slices) {
       
   530       result.push({
       
   531         start: slices[i].start,
       
   532         end: slices[i].end,
       
   533         tweets: getSliceContent(slices[i]),
       
   534       });
       
   535     }
       
   536   }
       
   537   return result;
       
   538 }
       
   539 
       
   540 function trimFDS() {
       
   541   var slices = flattenDateStruct(twCx.timeline, twCx.timeLevel);
       
   542   if (!slices.length) {
       
   543     return [];
       
   544   }
       
   545   while (slices[0].tweets.length == 0) {
       
   546     slices.splice(0, 1);
       
   547   }
       
   548   while (slices[slices.length - 1].tweets.length == 0) {
       
   549     slices.pop();
       
   550   }
       
   551   var centralTweet = twCx.centralTweet
       
   552       ? twCx.centralTweet
       
   553       : twCx.tweets[twCx.tweets.length - 1],
       
   554     delta = 30 * twCx.date_levels[twCx.timeLevel],
       
   555     centre = Math.min(
       
   556       slices[slices.length - 1].end - delta,
       
   557       Math.max(slices[0].start + delta, centralTweet.date_value)
       
   558     ),
       
   559     min = centre - delta,
       
   560     max = centre + delta;
       
   561   while (slices[0].start < min) {
       
   562     slices.splice(0, 1);
       
   563   }
       
   564   while (slices[slices.length - 1].end > max) {
       
   565     slices.pop();
       
   566   }
       
   567   return slices;
       
   568 }
       
   569 
       
   570 function populateDateStruct(level, start) {
       
   571   var end = start + twCx.date_levels[level],
       
   572     struct = {
       
   573       level: level,
       
   574       start: start,
       
   575       end: end,
   186     };
   576     };
   187     if ( options.lang ) {
   577   if (level < twCx.date_levels.length - 1) {
   188         urlParams.lang = options.lang;
   578     struct.slices = [];
   189     }
   579     var newstart = start;
   190     if ( options.since_id ) {
   580     while (newstart < end) {
   191         urlParams.since_id = options.since_id;
   581       struct.slices.push(populateDateStruct(level + 1, newstart));
   192     }
   582       newstart += twCx.date_levels[level + 1];
   193     urlParams.social_network = options.social_network?options.social_network:"Twitter";
   583     }
   194 
   584   } else {
   195     var jsonurl = "search_tweets.php?"+$.param(urlParams);
   585     struct.tweets = [];
   196     getTweetUrl(jsonurl);
   586   }
   197 }
   587   return struct;
   198 
       
   199 function getColor(annotation, lum) {
       
   200     return Raphael.hsl2rgb(annotations[annotation].colors.h, annotations[annotation].colors.s, lum);
       
   201 }
       
   202 
       
   203 function tweetPopup(url) {
       
   204     var popW = 550,
       
   205         popH = 350,
       
   206         scrW = screen.width,
       
   207         scrH = screen.height,
       
   208         posX = Math.round((scrW/2)-(popW/2)),
       
   209         posY = (scrH > popH ? Math.round((scrH/2)-(popH/2)) : 0);
       
   210     window.open(url,
       
   211         '',
       
   212         'left=' + posX + ',top=' + posY + ',width=' + popW + ',height=' + popH + ',personalbar=0,toolbar=0,scrollbars=1,resizable=1');
       
   213 }
       
   214 
       
   215 function arc(source, target) {
       
   216     var x3 = .3 * target.y - .3 * source.y + .8 * source.x + .2 * target.x;
       
   217     var y3 = .8 * source.y + .2 * target.y - .3 * target.x + .3 * source.x;
       
   218     var x4 = .3 * target.y - .3 * source.y + .2 * source.x + .8 * target.x;
       
   219     var y4 = .2 * source.y + .8 * target.y - .3 * target.x + .3 * source.x;
       
   220     return "M" + source.x + " " + source.y + "C" + [x3, y3, x4, y4, target.x, target.y].join(" ");
       
   221 }
       
   222 
       
   223 function addTweet(tweet) {
       
   224     if (!tweet) {
       
   225         console.log(tweet);
       
   226         return;
       
   227     }
       
   228     
       
   229     if (rejectUser(tweet.from_user)) {
       
   230         return;
       
   231     }
       
   232     
       
   233     function backRef(source_id, target_id, type) {
       
   234         var target = tweetById(target_id);
       
   235         if (target) {
       
   236             var brobj = {
       
   237                 "referenced_by_id" : source_id,
       
   238                 "type" : type
       
   239             }
       
   240             if (target.backRefs) {
       
   241                 target.backRefs.push(brobj);
       
   242             } else {
       
   243                 target.backRefs = [ brobj ]
       
   244             }
       
   245         }
       
   246     }
       
   247     
       
   248     _(['id', 'from_user_id', 'in_reply_to_status_id']).each(function(_i) {
       
   249         if(typeof(tweet[_i + '_str']) == 'undefined') return;
       
   250         tweet[_i] = tweet[_i + '_str'];
       
   251         delete tweet[_i + '_str']; 
       
   252     });
       
   253     
       
   254     if (_(twCx.idIndex).indexOf(tweet.id) != -1) {
       
   255         return;
       
   256     }
       
   257     
       
   258     tweet.html_parts = []
       
   259     
       
   260     if (tweet.entities && tweet.entities.user_mentions) {
       
   261         for (var _i = 0; _i < tweet.entities.user_mentions.length; _i++) {
       
   262             var _m = tweet.entities.user_mentions[_i];
       
   263             tweet.html_parts.push({
       
   264                 "text" : "@" + _m.screen_name,
       
   265                 "start" : _m.indices[0],
       
   266                 "end" : _m.indices[1],
       
   267                 "link" :'<a href="http://twitter.com/' + _m.screen_name + '" onclick="filtrerTexte(\'' + _m.screen_name + '\'); return false;" target="_blank">'
       
   268             });
       
   269         }
       
   270     }
       
   271     
       
   272     if (tweet.entities && tweet.entities.hashtags) {
       
   273         for (var _i = 0; _i < tweet.entities.hashtags.length; _i++) {
       
   274             var _m = tweet.entities.hashtags[_i],
       
   275                 _h = "#" + _m.text;
       
   276             tweet.html_parts.push({
       
   277                 "text" : _h,
       
   278                 "start" : _m.indices[0],
       
   279                 "end" : _m.indices[1],
       
   280                 "link" :'<a href="http://twitter.com/search?q=' + encodeURIComponent(_h) + '" onclick="filtrerTexte(\'' + _.escape(_h) + '\'); return false;" target="_blank">'
       
   281             });
       
   282         }
       
   283     }
       
   284     
       
   285     if (tweet.entities && tweet.entities.urls) {
       
   286         for (var _i = 0; _i < tweet.entities.urls.length; _i++) {
       
   287             var _m = tweet.entities.urls[_i];
       
   288             tweet.html_parts.push({
       
   289                 "text" : _m.display_url || _m.url,
       
   290                 "start" : _m.indices[0],
       
   291                 "end" : _m.indices[1],
       
   292                 "link" :'<a href="' + _m.url + '" target="_blank">'
       
   293             });
       
   294         }
       
   295     }
       
   296     tweet.date_value = dayjs(tweet.created_at).valueOf();
       
   297     
       
   298     var ann = [];
       
   299     for (var j in annotations) {
       
   300         if (j != "default") {
       
   301             for (var k in annotations[j].keywords) {
       
   302                 var tweetText = tweet.full_text;
       
   303                 if (tweetText.search(annotations[j].keywords[k]) != -1) {
       
   304                     ann.push(j);
       
   305                     break;
       
   306                 }
       
   307             }
       
   308         }
       
   309     }
       
   310     tweet.annotations = ann;
       
   311 
       
   312     if (tweet.in_reply_to_status_id) {
       
   313         backRef( tweet.id, tweet.in_reply_to_status_id, "reply" );
       
   314     }
       
   315     
       
   316     if (tweet.retweeted_status && tweet.retweeted_status.id_str) {
       
   317     	tweet.retweeted_status_id = tweet.retweeted_status.id_str;
       
   318         backRef( tweet.id, tweet.retweeted_status_id, "retweet" );
       
   319     }
       
   320 
       
   321     // clean full text from html
       
   322     const temp_div_element = document.createElement("div");
       
   323     temp_div_element.innerHTML = tweet.full_text;
       
   324     const full_text = temp_div_element.textContent || temp_div_element.innerText || tweet.full_text;
       
   325 
       
   326     var tab = full_text.replace(twCx.urlRegExp,'').match(twCx.wordRegExp);
       
   327     _(tab).each(function(w) {
       
   328         var word = w.toLowerCase();
       
   329         if (_(twCx.stopWords).indexOf(word) == -1 && _(tracking_keywords).indexOf(word) == -1 && word[0] != '@') {
       
   330             if (twCx.globalWords[word]) {
       
   331                 twCx.globalWords[word].freq++;
       
   332             } else {
       
   333                 twCx.globalWords[word] = {
       
   334                     "freq" : 1,
       
   335                     "annotations" : {}
       
   336                 }
       
   337                 for (var j in annotations) {
       
   338                     if (j != 'default') {
       
   339                         twCx.globalWords[word].annotations[j] = 0;
       
   340                     }
       
   341                 }
       
   342             }
       
   343             for (var j in ann) {
       
   344                 if (typeof twCx.globalWords[word].annotations != "undefined") {
       
   345                     twCx.globalWords[word].annotations[ann[j]]++;
       
   346                 }
       
   347             }
       
   348         }
       
   349     });
       
   350     
       
   351     _(twCx.suggestCount).each(function(_k) {
       
   352         if (tweet.full_text.search(_k.rgxp) != -1) {
       
   353             _k.freq++;
       
   354             _(ann).each(function(_a) {
       
   355                 _k.annotations[_a] = 1 + ( _k.annotations[_a] || 0 )
       
   356             })
       
   357         }
       
   358     });
       
   359     
       
   360     
       
   361     var p = twCx.idIndex.length;
       
   362     while (p && tweet.id < twCx.idIndex[p-1]) {
       
   363         p--;
       
   364     }
       
   365     twCx.tweets.splice(p, 0, tweet);
       
   366     twCx.idIndex.splice(p, 0, tweet.id);
       
   367     
       
   368     if (!twCx.timeline.length) {
       
   369         twCx.timeline = [ populateDateStruct(0, twCx.date_levels[0] * parseInt(tweet.date_value / twCx.date_levels[0])) ]
       
   370     }
       
   371     while (tweet.date_value > twCx.timeline[twCx.timeline.length - 1].end) {
       
   372         twCx.timeline.push( populateDateStruct(0, twCx.timeline[twCx.timeline.length - 1].end) );
       
   373     }
       
   374     
       
   375     insertIntoDateStruct(twCx.timeline, tweet);
       
   376 }
       
   377 
       
   378 function getSliceContent(slice) {
       
   379     if (slice.slices) {
       
   380         var result = [];
       
   381         for (var i in slice.slices) {
       
   382             result = result.concat(getSliceContent(slice.slices[i]));
       
   383         }
       
   384     } else {
       
   385         var result = slice.tweets;
       
   386     }
       
   387     return result;
       
   388 }
       
   389 
       
   390 function flattenDateStruct(slices, target_level) {
       
   391 	if (!slices || !slices.length) {
       
   392 		return [];
       
   393 	}
       
   394     var current_level = slices[0].level,
       
   395         result = [];
       
   396     if (current_level < target_level) {
       
   397         if (slices[0].slices) {
       
   398             for (var i in slices) {
       
   399                 result = result.concat(flattenDateStruct(slices[i].slices, target_level));
       
   400             }
       
   401         }
       
   402     }
       
   403     else {
       
   404         for (var i in slices) {
       
   405             result.push({
       
   406                 "start" : slices[i].start,
       
   407                 "end" : slices[i].end,
       
   408                 "tweets" : getSliceContent(slices[i])
       
   409             });
       
   410         }
       
   411     }
       
   412     return result;
       
   413 }
       
   414 
       
   415 function trimFDS() {
       
   416     var slices = flattenDateStruct(twCx.timeline, twCx.timeLevel);
       
   417     if (!slices.length) { return []; }
       
   418     while (slices[0].tweets.length == 0) {
       
   419         slices.splice(0,1);
       
   420     }
       
   421     while (slices[slices.length - 1].tweets.length == 0) {
       
   422         slices.pop();
       
   423     }    
       
   424     var centralTweet = ( twCx.centralTweet ? twCx.centralTweet : twCx.tweets[twCx.tweets.length - 1] ),
       
   425         delta = 30 * twCx.date_levels[twCx.timeLevel],
       
   426         centre = Math.min(slices[slices.length - 1].end - delta , Math.max(slices[0].start + delta, centralTweet.date_value)),
       
   427         min = centre - delta,
       
   428         max = centre + delta;
       
   429     while (slices[0].start < min) {
       
   430         slices.splice(0,1);
       
   431     }
       
   432     while (slices[slices.length - 1].end > max) {
       
   433         slices.pop();
       
   434     }
       
   435     return slices;
       
   436 }
       
   437 
       
   438 function populateDateStruct(level, start) {
       
   439     var end = start + twCx.date_levels[level],
       
   440         struct = {
       
   441             "level" : level,
       
   442             "start" : start,
       
   443             "end" : end
       
   444         };
       
   445     if (level < twCx.date_levels.length - 1) {
       
   446         struct.slices = [];
       
   447         var newstart = start;
       
   448         while (newstart < end) {
       
   449             struct.slices.push(populateDateStruct(level + 1, newstart));
       
   450             newstart += twCx.date_levels[level + 1];
       
   451         }
       
   452     } else {
       
   453         struct.tweets = [];
       
   454     }
       
   455     return struct;
       
   456 }
   588 }
   457 
   589 
   458 function insertIntoDateStruct(slices, tweet) {
   590 function insertIntoDateStruct(slices, tweet) {
   459     var creadate = tweet.date_value;
   591   var creadate = tweet.date_value;
   460     for (var i in slices) {
   592   for (var i in slices) {
   461         if (creadate < slices[i].end) {
   593     if (creadate < slices[i].end) {
   462             if (slices[i].slices) {
   594       if (slices[i].slices) {
   463                 insertIntoDateStruct(slices[i].slices, tweet);
   595         insertIntoDateStruct(slices[i].slices, tweet);
   464             } else {
   596       } else {
   465                 slices[i].tweets.push(tweet.id);
   597         slices[i].tweets.push(tweet.id);
   466             }
   598       }
   467             break;
   599       break;
   468         }
   600     }
   469     }
   601   }
   470 }
   602 }
   471 
   603 
   472 function placeHolder(className) {
   604 function placeHolder(className) {
   473     return '<li class="placeholder ' + className + '"></li>';
   605   return '<li class="placeholder ' + className + '"></li>';
   474 }
   606 }
   475 
   607 
   476 function tweetById(tweetid) {
   608 function tweetById(tweetid) {
   477     var pos = _(twCx.idIndex).indexOf(tweetid);
   609   var pos = _(twCx.idIndex).indexOf(tweetid);
   478     return (pos == -1) ? false : twCx.tweets[pos];
   610   return pos == -1 ? false : twCx.tweets[pos];
   479 }
   611 }
   480 
   612 
   481 function selectTweet(tweetid) {
   613 function selectTweet(tweetid) {
   482     twCx.position = tweetid;
   614   twCx.position = tweetid;
   483     twCx.followLast = (twCx.position == twCx.idIndex[twCx.tweets.length - 1]);
   615   twCx.followLast = twCx.position == twCx.idIndex[twCx.tweets.length - 1];
   484     updateDisplay();
   616   updateDisplay();
   485 }
   617 }
   486 
   618 
   487 function goToPos(nPos) {
   619 function goToPos(nPos) {
   488     twCx.position = twCx.currentIdIndex[Math.min( twCx.currentIdIndex.length - 1, Math.max(0, nPos ) )];
   620   twCx.position =
   489     twCx.followLast = (!twCx.filtre && nPos == twCx.tweets.length - 1);
   621     twCx.currentIdIndex[
   490     updateDisplay();
   622       Math.min(twCx.currentIdIndex.length - 1, Math.max(0, nPos))
       
   623     ];
       
   624   twCx.followLast = !twCx.filtre && nPos == twCx.tweets.length - 1;
       
   625   updateDisplay();
   491 }
   626 }
   492 
   627 
   493 function movePos(delta) {
   628 function movePos(delta) {
   494     goToPos( delta + _(twCx.currentIdIndex).indexOf(twCx.position) );
   629   goToPos(delta + _(twCx.currentIdIndex).indexOf(twCx.position));
   495 }
   630 }
   496 
   631 
   497 // getMsgUrl
   632 // getMsgUrl
   498 // getUserScreenName
   633 // getUserScreenName
   499 // getDescription
   634 // getDescription
   500 //
   635 //
   501 
   636 
   502 function getMsgHtmlAdapter(msg) {
   637 function getMsgHtmlAdapter(msg) {
   503     return {
   638   return {
   504       Twitter: {
   639     Twitter: {
   505         
   640       getMsgUrl: function () {
   506         getMsgUrl: function() {
   641         return (
   507             return 'https://twitter.com/'
   642           "https://twitter.com/" + msg.user.screen_name + "/status/" + msg.id
   508             + msg.user.screen_name
   643         );
   509             + '/status/'
   644       },
   510             + msg.id;
   645 
   511         },
   646       getMsgUri: function () {
   512 
   647         return (
   513         getMsgUri: function() {
   648           "https://twitter.com/" + msg.user.screen_name + "/status/" + msg.id
   514             return 'https://twitter.com/'
   649         );
   515             + msg.user.screen_name
   650       },
   516             + '/status/'
   651 
   517             + msg.id;
   652       getMsgId: function () {
   518         },
   653         return "tweet_" + msg.id;
   519 
   654       },
   520         getMsgId: function() {
   655 
   521             return 'tweet_' + msg.id;
   656       getMsgTitle: function () {
   522         },
   657         return "Tweet by " + _(msg.user.name).escape();
   523         
   658       },
   524         getMsgTitle: function() {
   659 
   525             return 'Tweet by ' + _(msg.user.name).escape();
   660       getMsgDescription: function () {
   526         },
   661         return _(msg.full_text).escape();
   527 
   662       },
   528         getMsgDescription: function() {
   663 
   529             return _(msg.full_text).escape();
   664       getMsgUserUrl: function () {
   530         },
   665         return "https://twitter.com/" + msg.user.screen_name;
   531 
   666       },
   532         getMsgUserUrl: function() {
   667 
   533             return 'https://twitter.com/' + msg.user.screen_name;
   668       getMsgReplyUrl: function () {
   534         },
   669         return "https://twitter.com/intent/tweet?in_reply_to=" + msg.id;
   535 
   670       },
   536         getMsgReplyUrl: function() {
   671       getMsgRetweetUrl: function () {
   537             return 'https://twitter.com/intent/tweet?in_reply_to=' + msg.id;
   672         return "https://twitter.com/intent/retweet?tweet_id=" + msg.id;
   538         },
   673       },
   539         getMsgRetweetUrl: function() {
   674       getMsgFavoriteUrl: function () {
   540             return 'https://twitter.com/intent/retweet?tweet_id=' + msg.id;
   675         return "https://twitter.com/intent/favorite?tweet_id=" + msg.id;
   541         },
   676       },
   542         getMsgFavoriteUrl: function() {
   677     },
   543             return 'https://twitter.com/intent/favorite?tweet_id=' + msg.id;
   678     Mastodon: {
       
   679       getMsgUrl: function () {
       
   680         return msg.url;
       
   681       },
       
   682 
       
   683       getMsgUri: function () {
       
   684         return msg.uri;
       
   685       },
       
   686 
       
   687       getMsgId: function () {
       
   688         return "toot_" + msg.id;
       
   689       },
       
   690 
       
   691       getMsgTitle: function () {
       
   692         return "Toot by " + _(msg.user.name).escape();
       
   693       },
       
   694 
       
   695       getMsgDescription: function () {
       
   696         return _($(msg.full_text).text()).escape();
       
   697       },
       
   698 
       
   699       getMsgUserUrl: function () {
       
   700         return msg.user.url;
       
   701       },
       
   702 
       
   703       getMsgReplyUrl: function () {
       
   704         return "";
       
   705       },
       
   706       getMsgRetweetUrl: function () {
       
   707         return "";
       
   708       },
       
   709       getMsgFavoriteUrl: function () {
       
   710         return "";
       
   711       },
       
   712     },
       
   713   }[msg.type];
       
   714 }
       
   715 
       
   716 function tweetToHtml(tweet, className, elName) {
       
   717   function highlight(texte) {
       
   718     return twCx.filtre
       
   719       ? texte.replace(twCx.filtre, '<span class="highlight">$1</span>')
       
   720       : texte;
       
   721   }
       
   722 
       
   723   if (!tweet) {
       
   724     return placeHolder(className);
       
   725   }
       
   726 
       
   727   const htmlAdapter = getMsgHtmlAdapter(tweet);
       
   728 
       
   729   var el = elName ? elName : "li";
       
   730   var html =
       
   731     "<" +
       
   732     el +
       
   733     ' draggable="true" class="tweet ' +
       
   734     className +
       
   735     '" id="tweet_' +
       
   736     tweet.id +
       
   737     '" data-title="' +
       
   738     htmlAdapter.getMsgTitle() +
       
   739     '" data-description="' +
       
   740     htmlAdapter.getMsgDescription() +
       
   741     '" data-uri="' +
       
   742     htmlAdapter.getMsgUri() +
       
   743     '"';
       
   744   if (className != "full") {
       
   745     html += " onclick=\"selectTweet('" + tweet.id + "'); return false;\"";
       
   746   }
       
   747   html +=
       
   748     " onmouseover=\"rolloverTweet('" +
       
   749     tweet.id +
       
   750     "', " +
       
   751     (className == "icons") +
       
   752     ');"';
       
   753   if (twCx.followLast && className == "full" && el == "li") {
       
   754     html += ' style="display: none"';
       
   755   }
       
   756   html += ">";
       
   757   if (tweet.annotations && tweet.annotations.length) {
       
   758     html += '<div class="annotations">';
       
   759     for (var i in tweet.annotations) {
       
   760       html +=
       
   761         '<div class="annotation" style="width:' +
       
   762         100 / tweet.annotations.length +
       
   763         "%; background:" +
       
   764         getColor(tweet.annotations[i], className == "icons" ? 0.4 : 0.85).hex +
       
   765         '"></div>';
       
   766     }
       
   767     html += "</div>";
       
   768   }
       
   769   html += '<div class="twmain">';
       
   770   var a_user =
       
   771     '<a href="' +
       
   772     htmlAdapter.getMsgUserUrl() +
       
   773     '"  onclick="filtrerTexte(\'@' +
       
   774     tweet.user.screen_name +
       
   775     '\'); return false;" target="_blank">';
       
   776   html +=
       
   777     '<div class="around_img"><img class="profile_image" src="' +
       
   778     tweet.user.profile_image_url_https +
       
   779     '" />';
       
   780   if (className == "full") {
       
   781     html +=
       
   782       '<p class="created_at">' +
       
   783       new Date(tweet.date_value).toTimeString().substr(0, 8) +
       
   784       "</a></p>";
       
   785   }
       
   786   html += "</div>";
       
   787   if (className != "icons") {
       
   788     let lastend = 0;
       
   789     var txt = "";
       
   790     const full_text = $(tweet.full_text).text();
       
   791     tweet.html_parts.sort(function (a, b) {
       
   792       return a.start - b.start;
       
   793     });
       
   794     _(tweet.html_parts).each(function (_e) {
       
   795       txt +=
       
   796         highlight(full_text.substring(lastend, _e.start)) +
       
   797         _e.link +
       
   798         highlight(_e.text) +
       
   799         "</a>";
       
   800       lastend = _e.end;
       
   801     });
       
   802     //lastend = Math.max(120, lastend);
       
   803     txt += highlight(full_text.substring(lastend));
       
   804     html +=
       
   805       '<p class="tweet_text"><b>' +
       
   806       a_user +
       
   807       '<span title="' +
       
   808       tweet.user.screen_name +
       
   809       '">' +
       
   810       highlight("@" + tweet.user.name) +
       
   811       "</span></a>" +
       
   812       (className == "full"
       
   813         ? " (" + tweet.user.name + ")</b><br />"
       
   814         : "</b> : ") +
       
   815       txt +
       
   816       "</p>";
       
   817     if (className == "full" && el == "li") {
       
   818       html +=
       
   819         '<div class="tweet_actions"><a href="' +
       
   820         htmlAdapter.getMsgUrl() +
       
   821         '" onclick="tweetPopup(this.href); return false;" target="_blank">afficher message</a>';
       
   822       const replyUrl = htmlAdapter.getMsgReplyUrl();
       
   823       if (replyUrl) {
       
   824         html +=
       
   825           '<a href="' +
       
   826           replyUrl +
       
   827           '" onclick="tweetPopup(this.href); return false;" target="_blank">répondre</a> · ';
       
   828       }
       
   829       const retweetUrl = htmlAdapter.getMsgRetweetUrl();
       
   830       if (retweetUrl) {
       
   831         html +=
       
   832           '<a href="' +
       
   833           retweetUrl +
       
   834           '" onclick="tweetPopup(this.href); return false;" target="_blank">retransmettre</a> · ';
       
   835       }
       
   836       const favoriteUrl = htmlAdapter.getMsgFavoriteUrl();
       
   837       if (favoriteUrl) {
       
   838         html +=
       
   839           '<a href="' +
       
   840           favoriteUrl +
       
   841           '" onclick="tweetPopup(this.href); return false;" target="_blank">favori</a>';
       
   842       }
       
   843       html += "</div>";
       
   844     }
       
   845   }
       
   846   html += "</div></" + el + ">";
       
   847   return html;
       
   848 }
       
   849 
       
   850 function tlIdFromPos(x, y, outside) {
       
   851   if (!twCx.tlOnDisplay || !twCx.tlOnDisplay.length) {
       
   852     return;
       
   853   }
       
   854   var ligne = Math.min(
       
   855       twCx.tlOnDisplay.length - 1,
       
   856       Math.max(0, Math.floor((twCx.tlHeight - y) / twCx.scaleY))
       
   857     ),
       
   858     colonne = Math.floor((x - twCx.deltaX) / twCx.scaleX),
       
   859     l = 0;
       
   860   if (colonne >= twCx.tlOnDisplay[ligne].totalTweets || colonne < 0) {
       
   861     if (outside) {
       
   862       colonne = Math.min(
       
   863         twCx.tlOnDisplay[ligne].totalTweets - 1,
       
   864         Math.max(0, colonne)
       
   865       );
       
   866     } else {
       
   867       return null;
       
   868     }
       
   869   }
       
   870   for (var i in twCx.tlOnDisplay[ligne].displayData) {
       
   871     var nl = l + twCx.tlOnDisplay[ligne].displayData[i].length;
       
   872     if (colonne < nl) {
       
   873       return {
       
   874         id: twCx.tlOnDisplay[ligne].displayData[i][colonne - l],
       
   875         annotation: i,
       
   876       };
       
   877     }
       
   878     l = nl;
       
   879   }
       
   880 }
       
   881 
       
   882 function tlPosTweet(tweet, annotation) {
       
   883   if (!twCx.tweets) {
       
   884     return;
       
   885   }
       
   886   var x,
       
   887     y,
       
   888     dt = tweet.date_value,
       
   889     ann = annotation
       
   890       ? annotation
       
   891       : tweet.annotations && tweet.annotations.length
       
   892       ? tweet.annotations[0]
       
   893       : "default";
       
   894   for (var i = 0; i < twCx.tlOnDisplay.length; i++) {
       
   895     if (twCx.tlOnDisplay[i].end > dt) {
       
   896       y = twCx.tlHeight - (i + 0.5) * twCx.scaleY;
       
   897       var l = 0;
       
   898       for (var j in twCx.tlOnDisplay[i].displayData) {
       
   899         if (j == ann) {
       
   900           var p = _(twCx.tlOnDisplay[i].displayData[j]).indexOf(tweet.id);
       
   901           if (p != -1) {
       
   902             x = twCx.deltaX + twCx.scaleX * (p + l + 0.5);
       
   903           }
       
   904           break;
   544         }
   905         }
   545 
   906         l += twCx.tlOnDisplay[i].displayData[j].length;
       
   907       }
       
   908       break;
       
   909     }
       
   910   }
       
   911   return x && y ? { x: x, y: y } : null;
       
   912 }
       
   913 
       
   914 function rolloverTweet(tweetid, showPopup, annotation) {
       
   915   var t = tweetById(tweetid);
       
   916   if (!t) {
       
   917     return;
       
   918   }
       
   919   var p = tlPosTweet(t, annotation);
       
   920   if (!p) {
       
   921     return;
       
   922   }
       
   923   var ptl = $("#timeline").offset();
       
   924   if (showPopup) {
       
   925     $("#hovercontent").html(tweetToHtml(t, "full", "div"));
       
   926     $("#hovertweet").css({
       
   927       left: parseInt(ptl.left + p.x) + "px",
       
   928       top: parseInt(ptl.top + p.y),
       
   929       display: "block",
       
   930     });
       
   931   } else {
       
   932     $("#hovertweet").hide();
       
   933   }
       
   934   for (var i in twCx.relHover) {
       
   935     twCx.relHover[i].remove();
       
   936   }
       
   937   twCx.relHover = drawTweetArcs(t, p, "#303030");
       
   938   twCx.relHover.push(drawTweetPos(p, "#ffffff"));
       
   939 }
       
   940 
       
   941 function drawTweetPos(pos, color) {
       
   942   var rel = twCx.tlPaper.rect(
       
   943     pos.x - 0.5 * twCx.scaleX,
       
   944     pos.y - 0.5 * twCx.scaleY,
       
   945     twCx.scaleX,
       
   946     twCx.scaleY
       
   947   );
       
   948   rel.attr({ stroke: color, fill: color, "fill-opacity": 0.25 });
       
   949   return rel;
       
   950 }
       
   951 
       
   952 function drawTweetArcs(tweet, pos, color) {
       
   953   var res = [];
       
   954 
       
   955   function tweetAndArc(a, b, aorb) {
       
   956     if (a && b) {
       
   957       res.push(drawTweetPos(aorb ? a : b, color));
       
   958       var aa = twCx.tlPaper
       
   959         .path(arc(a, b))
       
   960         .attr({ stroke: color, "stroke-width": 1.5, "stroke-opacity": 0.8 });
       
   961       res.push(aa);
       
   962     }
       
   963   }
       
   964 
       
   965   if (tweet.retweeted_status_id) {
       
   966     var t = tweetById(tweet.retweeted_status_id);
       
   967     if (t) {
       
   968       tweetAndArc(pos, tlPosTweet(t));
       
   969     }
       
   970   }
       
   971 
       
   972   if (tweet.in_reply_to_status_id) {
       
   973     var t = tweetById(tweet.in_reply_to_status_id);
       
   974     if (t) {
       
   975       tweetAndArc(pos, tlPosTweet(t));
       
   976     }
       
   977   }
       
   978 
       
   979   if (tweet.backRefs) {
       
   980     for (var i in tweet.backRefs) {
       
   981       var t = tweetById(tweet.backRefs[i].referenced_by_id);
       
   982       if (t) {
       
   983         tweetAndArc(tlPosTweet(t), pos, true);
       
   984       }
       
   985     }
       
   986   }
       
   987 
       
   988   return res;
       
   989 }
       
   990 
       
   991 function mouseoverkw() {
       
   992   var _jel = $(this),
       
   993     _off = _jel.offset();
       
   994   _jel.css({
       
   995     color: "#0099ff",
       
   996   });
       
   997   $("#hoverkw")
       
   998     .css({
       
   999       left: _off.left + "px",
       
  1000       top: parseInt(_off.top) + ~~(_jel.height() / 2) + "px",
       
  1001       display: "block",
       
  1002     })
       
  1003     .attr("kw", _jel.text());
       
  1004 }
       
  1005 
       
  1006 function mouseoutkw() {
       
  1007   $("#hoverkw").hide();
       
  1008   $(this).css({
       
  1009     color: "#000000",
       
  1010   });
       
  1011 }
       
  1012 
       
  1013 function makeTagCloud(tab, div) {
       
  1014   var minfreq = _(tab).min(function (a) {
       
  1015       return a.freq;
       
  1016     }).freq,
       
  1017     maxfreq = Math.max(
       
  1018       minfreq + 0.1,
       
  1019       _(tab).max(function (a) {
       
  1020         return a.freq;
       
  1021       }).freq
       
  1022     ),
       
  1023     echfreq = 8 / Math.sqrt(maxfreq - minfreq),
       
  1024     html = "";
       
  1025   _(tab).each(function (_j) {
       
  1026     var maxann = 0,
       
  1027       ann = "default";
       
  1028     for (var k in _j.annotations) {
       
  1029       if (_j.annotations[k] == maxann) {
       
  1030         ann = "default";
       
  1031       }
       
  1032       if (_j.annotations[k] > maxann) {
       
  1033         ann = k;
       
  1034         maxann = _j.annotations[k];
       
  1035       }
       
  1036     }
       
  1037     if (ann == "default") {
       
  1038       var coul = "";
       
  1039     } else {
       
  1040       var c = getColor(ann, 0.6),
       
  1041         coul =
       
  1042           "background: rgba(" +
       
  1043           [
       
  1044             Math.floor(c.r),
       
  1045             Math.floor(c.g),
       
  1046             Math.floor(c.b),
       
  1047             _j.annotations[ann] / _j.freq,
       
  1048           ].join(",") +
       
  1049           ")";
       
  1050     }
       
  1051     var fontsize = Math.floor(12 + Math.sqrt(_j.freq - minfreq) * echfreq);
       
  1052     html +=
       
  1053       '<span style="line-height: ' +
       
  1054       (8 + fontsize) +
       
  1055       "px; font-size: " +
       
  1056       fontsize +
       
  1057       "px;" +
       
  1058       coul +
       
  1059       '">' +
       
  1060       _j.word +
       
  1061       "</span> ";
       
  1062   });
       
  1063   $(div).html(html);
       
  1064   $(div + " span")
       
  1065     .mouseover(mouseoverkw)
       
  1066     .mouseout(mouseoutkw)
       
  1067     .click(function () {
       
  1068       $("#hoverkw").toggle();
       
  1069     });
       
  1070 }
       
  1071 
       
  1072 function updateDisplay() {
       
  1073   if (!twCx.tweets) {
       
  1074     return;
       
  1075   }
       
  1076   if (twCx.filtre) {
       
  1077     var tweets = _(twCx.tweets).filter(function (tweet) {
       
  1078       var mention = "@" + tweet.user.screen_name;
       
  1079       return (
       
  1080         tweet.full_text.search(twCx.filtre) != -1 ||
       
  1081         mention.search(twCx.filtre) != -1
       
  1082       );
       
  1083     });
       
  1084     $("#inp_q").val(twCx.filtreTexte + " (" + tweets.length + " tweets)");
       
  1085     if (tweets.length) {
       
  1086       var idIndex = _(tweets).map(function (tweet) {
       
  1087         return tweet.id;
       
  1088       });
       
  1089       var p = _(idIndex).indexOf(twCx.position);
       
  1090       if (p == -1) {
       
  1091         for (
       
  1092           p = idIndex.length - 1;
       
  1093           p > 0 && idIndex[p] > twCx.position;
       
  1094           p--
       
  1095         ) {}
       
  1096       }
       
  1097       twCx.position = idIndex[p];
       
  1098       twCx.currentIdIndex = idIndex;
       
  1099     }
       
  1100   } else {
       
  1101     twCx.currentIdIndex = twCx.idIndex;
       
  1102     var tweets = twCx.tweets;
       
  1103     var p = _(twCx.idIndex).indexOf(twCx.position);
       
  1104     if (p == -1) {
       
  1105       p = twCx.followLast ? twCx.idIndex.length - 1 : 0;
       
  1106     }
       
  1107   }
       
  1108 
       
  1109   var l = tweets.length,
       
  1110     lines = 0,
       
  1111     ppy = 0,
       
  1112     html = "",
       
  1113     tweetsOnDisplay = [];
       
  1114 
       
  1115   function pushTweet(tp, className) {
       
  1116     if (tp < l && tp >= 0) {
       
  1117       html += tweetToHtml(tweets[tp], className);
       
  1118 
       
  1119       tweetsOnDisplay.push(tp);
       
  1120     } else {
       
  1121       html += placeHolder(className);
       
  1122     }
       
  1123   }
       
  1124 
       
  1125   if (l) {
       
  1126     twCx.lastScrollPos = Math.floor(twCx.scrollExtent * (1 - p / l));
       
  1127     $("#scrollcont").scrollTop(twCx.lastScrollPos);
       
  1128 
       
  1129     if (l > p + 18) {
       
  1130       lines++;
       
  1131       ppy += 20;
       
  1132       for (var i = p + 31; i >= p + 18; i--) {
       
  1133         pushTweet(i, "icons");
       
  1134       }
       
  1135     }
       
  1136     if (l > p + 4) {
       
  1137       lines++;
       
  1138       ppy += 20;
       
  1139       for (var i = p + 17; i >= p + 4; i--) {
       
  1140         pushTweet(i, "icons");
       
  1141       }
       
  1142     }
       
  1143     for (var k = 3; k >= 1; k--) {
       
  1144       if (l > p + k) {
       
  1145         ppy += 47;
       
  1146         lines++;
       
  1147         pushTweet(p + k, "half");
       
  1148       }
       
  1149     }
       
  1150     pushTweet(p, "full");
       
  1151     var n = p - 1;
       
  1152     for (var i = 0; i < Math.min(6, Math.max(3, 6 - lines)); i++) {
       
  1153       if (n < 0) {
       
  1154         break;
       
  1155       }
       
  1156       pushTweet(n, "half");
       
  1157       n--;
       
  1158     }
       
  1159     for (var i = 0; i < 14 * Math.min(4, Math.max(2, 7 - lines)); i++) {
       
  1160       if (n < 0) {
       
  1161         break;
       
  1162       }
       
  1163       pushTweet(n, "icons");
       
  1164       n--;
       
  1165     }
       
  1166     if (html != twCx.tlBuffer) {
       
  1167       $("#tweetlist").html(html);
       
  1168       $(".tweet.full").fadeIn();
       
  1169       twCx.tlBuffer = html;
       
  1170     }
       
  1171 
       
  1172     if (twCx.suggestCount.length) {
       
  1173       makeTagCloud(twCx.suggestCount, "#suggkw");
       
  1174     }
       
  1175 
       
  1176     var tab = _(twCx.globalWords)
       
  1177       .chain()
       
  1178       .map(function (v, k) {
       
  1179         return {
       
  1180           word: k,
       
  1181           freq: v.freq,
       
  1182           annotations: v.annotations,
       
  1183         };
       
  1184       })
       
  1185       .filter(function (v) {
       
  1186         return v.freq > 3;
       
  1187       })
       
  1188       .value();
       
  1189 
       
  1190     if (tab.length) {
       
  1191       tab = _(tab)
       
  1192         .sortBy(function (a) {
       
  1193           return -a.freq;
       
  1194         })
       
  1195         .slice(0, 40);
       
  1196       makeTagCloud(tab, "#motscles");
       
  1197     } else {
       
  1198       $("#motscles").html("");
       
  1199     }
       
  1200     twCx.centralTweet = tweets[p];
       
  1201   } else {
       
  1202     $("#tweetlist").html("");
       
  1203     twCx.tlBuffer = "";
       
  1204     $("#motscles").html("");
       
  1205   }
       
  1206 
       
  1207   twCx.tlOnDisplay = trimFDS();
       
  1208   if (!twCx.tlOnDisplay || !twCx.tlOnDisplay.length) {
       
  1209     return;
       
  1210   }
       
  1211   twCx.scaleY = twCx.tlHeight / twCx.tlOnDisplay.length;
       
  1212   var maxTweets = 0,
       
  1213     startTl = 0,
       
  1214     endTl = twCx.tlOnDisplay.length - 1;
       
  1215   if (l) {
       
  1216     var startTw =
       
  1217         tweets[tweetsOnDisplay[tweetsOnDisplay.length - 1]].date_value,
       
  1218       endTw = tweets[tweetsOnDisplay[0]].date_value;
       
  1219   }
       
  1220   for (var i = 0; i < twCx.tlOnDisplay.length; i++) {
       
  1221     if (l) {
       
  1222       if (
       
  1223         startTw >= twCx.tlOnDisplay[i].start &&
       
  1224         startTw < twCx.tlOnDisplay[i].end
       
  1225       ) {
       
  1226         startTl = i;
       
  1227       }
       
  1228       if (
       
  1229         endTw >= twCx.tlOnDisplay[i].start &&
       
  1230         endTw < twCx.tlOnDisplay[i].end
       
  1231       ) {
       
  1232         endTl = i;
       
  1233       }
       
  1234     }
       
  1235     var displayData = {};
       
  1236     for (var j in annotations) {
       
  1237       displayData[j] = [];
       
  1238     }
       
  1239     for (var j in twCx.tlOnDisplay[i].tweets) {
       
  1240       var tweetid = twCx.tlOnDisplay[i].tweets[j],
       
  1241         tweet = tweetById(tweetid);
       
  1242       if (tweet) {
       
  1243         if (tweet.annotations && tweet.annotations.length) {
       
  1244           for (var k in tweet.annotations) {
       
  1245             displayData[tweet.annotations[k]].push(tweetid);
       
  1246           }
       
  1247         } else {
       
  1248           displayData["default"].push(tweetid);
       
  1249         }
       
  1250       }
       
  1251     }
       
  1252     var nbT = 0;
       
  1253     for (var j in displayData) {
       
  1254       nbT += displayData[j].length;
       
  1255     }
       
  1256     maxTweets = Math.max(maxTweets, nbT);
       
  1257     twCx.tlOnDisplay[i].displayData = displayData;
       
  1258     twCx.tlOnDisplay[i].totalTweets = nbT;
       
  1259   }
       
  1260   twCx.scaleX = (twCx.tlWidth - twCx.deltaX) / maxTweets;
       
  1261   twCx.tlPaper.clear();
       
  1262   twCx.relHover = null;
       
  1263 
       
  1264   // Dessin de la correspondance liste-timeline
       
  1265   if (l) {
       
  1266     var startY = twCx.tlHeight - startTl * twCx.scaleY,
       
  1267       endY = twCx.tlHeight - (endTl + 1) * twCx.scaleY,
       
  1268       path =
       
  1269         "M0 " +
       
  1270         twCx.tlHeight +
       
  1271         "C" +
       
  1272         0.7 * twCx.deltaX +
       
  1273         " " +
       
  1274         twCx.tlHeight +
       
  1275         " " +
       
  1276         0.3 * twCx.deltaX +
       
  1277         " " +
       
  1278         startY +
       
  1279         " " +
       
  1280         twCx.deltaX +
       
  1281         " " +
       
  1282         startY +
       
  1283         "L" +
       
  1284         twCx.tlWidth +
       
  1285         " " +
       
  1286         startY +
       
  1287         "L" +
       
  1288         twCx.tlWidth +
       
  1289         " " +
       
  1290         endY +
       
  1291         "L" +
       
  1292         twCx.deltaX +
       
  1293         " " +
       
  1294         endY +
       
  1295         "C" +
       
  1296         0.3 * twCx.deltaX +
       
  1297         " " +
       
  1298         endY +
       
  1299         " " +
       
  1300         0.7 * twCx.deltaX +
       
  1301         " 0 0 0";
       
  1302     twCx.tlPaper
       
  1303       .path(path)
       
  1304       .attr({ stroke: "none", fill: "#000080", opacity: 0.2 });
       
  1305   }
       
  1306   // dessin de la date de début
       
  1307 
       
  1308   twCx.tlPaper
       
  1309     .text(
       
  1310       twCx.deltaX / 2,
       
  1311       twCx.tlHeight - 7,
       
  1312       new Date(twCx.tlOnDisplay[0].start).toTimeString().substr(0, 5)
       
  1313     )
       
  1314     .attr({ "text-anchor": "middle", "font-size": "9px" });
       
  1315 
       
  1316   // dessin de la date de fin
       
  1317 
       
  1318   twCx.tlPaper
       
  1319     .text(
       
  1320       twCx.deltaX / 2,
       
  1321       7,
       
  1322       new Date(twCx.tlOnDisplay[twCx.tlOnDisplay.length - 1].end)
       
  1323         .toTimeString()
       
  1324         .substr(0, 5)
       
  1325     )
       
  1326     .attr({ "text-anchor": "middle", "font-size": "9px" });
       
  1327 
       
  1328   for (var i = 0; i < twCx.tlOnDisplay.length; i++) {
       
  1329     var n = 0,
       
  1330       posY = twCx.tlHeight - (i + 1) * twCx.scaleY;
       
  1331     for (var j in twCx.tlOnDisplay[i].displayData) {
       
  1332       var ll = twCx.tlOnDisplay[i].displayData[j].length;
       
  1333       if (ll > 0) {
       
  1334         twCx.tlPaper
       
  1335           .rect(
       
  1336             twCx.deltaX + n * twCx.scaleX,
       
  1337             posY,
       
  1338             ll * twCx.scaleX,
       
  1339             twCx.scaleY
       
  1340           )
       
  1341           .attr({ stroke: "none", fill: getColor(j, 0.4).hex });
       
  1342         n += ll;
       
  1343       }
       
  1344     }
       
  1345 
       
  1346     // Si on est à une demi-heure, on trace un axe secondaire + heure
       
  1347 
       
  1348     if (
       
  1349       i < twCx.tlOnDisplay.length - 1 &&
       
  1350       !(twCx.tlOnDisplay[i].end % 1800000)
       
  1351     ) {
       
  1352       twCx.tlPaper
       
  1353         .path("M0 " + posY + "L" + twCx.tlWidth + " " + posY)
       
  1354         .attr({ stroke: "#ccc" });
       
  1355       twCx.tlPaper
       
  1356         .text(
       
  1357           twCx.deltaX / 2,
       
  1358           posY,
       
  1359           new Date(twCx.tlOnDisplay[i].end).toTimeString().substr(0, 5)
       
  1360         )
       
  1361         .attr({ "text-anchor": "middle", "font-size": "9px" });
       
  1362     }
       
  1363   }
       
  1364 
       
  1365   // dessin du tweet courant
       
  1366 
       
  1367   if (l) {
       
  1368     if (twCx.filtre) {
       
  1369       for (var i = 0; i < tweets.length; i++) {
       
  1370         if (i != p) {
       
  1371           var pos = tlPosTweet(tweets[i]);
       
  1372           if (pos) {
       
  1373             drawTweetPos(pos, "#ffccff");
       
  1374           }
       
  1375         }
       
  1376       }
       
  1377     }
       
  1378 
       
  1379     var posp = tlPosTweet(tweets[p]);
       
  1380     if (posp) {
       
  1381       drawTweetPos(posp, "#ffff00");
       
  1382       var yy = posp.y - 0.5 * twCx.scaleY,
       
  1383         path =
       
  1384           "M0 " +
       
  1385           ppy +
       
  1386           "C" +
       
  1387           0.7 * twCx.deltaX +
       
  1388           " " +
       
  1389           ppy +
       
  1390           " " +
       
  1391           0.2 * twCx.deltaX +
       
  1392           " " +
       
  1393           yy +
       
  1394           " " +
       
  1395           twCx.deltaX +
       
  1396           " " +
       
  1397           yy +
       
  1398           "L" +
       
  1399           (posp.x - 0.5 * twCx.scaleX) +
       
  1400           " " +
       
  1401           yy;
       
  1402       yy = posp.y + 0.5 * twCx.scaleY;
       
  1403       ppy += 117;
       
  1404       path +=
       
  1405         "L" +
       
  1406         (posp.x - 0.5 * twCx.scaleX) +
       
  1407         " " +
       
  1408         yy +
       
  1409         "L" +
       
  1410         twCx.deltaX +
       
  1411         " " +
       
  1412         yy +
       
  1413         "C" +
       
  1414         0.2 * twCx.deltaX +
       
  1415         " " +
       
  1416         yy +
       
  1417         " " +
       
  1418         0.7 * twCx.deltaX +
       
  1419         " " +
       
  1420         ppy +
       
  1421         " 0 " +
       
  1422         ppy;
       
  1423       twCx.tlPaper
       
  1424         .path(path)
       
  1425         .attr({ stroke: "#ffff00", fill: "#ffff00", "fill-opacity": 0.15 });
       
  1426 
       
  1427       drawTweetArcs(tweets[p], posp, "#800080");
       
  1428     }
       
  1429   }
       
  1430 }
       
  1431 
       
  1432 function filtrerAnnotation(annotation) {
       
  1433   if (annotations[annotation]) {
       
  1434     effectuerFiltrage(
       
  1435       annotations[annotation].display_name,
       
  1436       new RegExp(
       
  1437         "(" +
       
  1438           _(annotations[annotation].keywords)
       
  1439             .map(function (a) {
       
  1440               return a.source;
       
  1441             })
       
  1442             .join("|") +
       
  1443           ")",
       
  1444         "gim"
       
  1445       )
       
  1446     );
       
  1447   } else {
       
  1448     effectuerFiltrage("", null);
       
  1449   }
       
  1450 }
       
  1451 
       
  1452 function filtrerTexte(valeur) {
       
  1453   effectuerFiltrage(
       
  1454     valeur,
       
  1455     valeur
       
  1456       ? new RegExp("(" + valeur.replace(/(\W)/g, "\\$1") + ")", "gim")
       
  1457       : null
       
  1458   );
       
  1459 }
       
  1460 
       
  1461 function effectuerFiltrage(filtreTexte, tabRegexp) {
       
  1462   $("#recherche_annot").slideUp();
       
  1463   $("#inp_q").val(filtreTexte).attr("class", "rechercheCourante");
       
  1464   twCx.filtreTexte = filtreTexte;
       
  1465   twCx.filtre = tabRegexp;
       
  1466   twCx.followLast =
       
  1467     !tabRegexp && twCx.position == twCx.idIndex[twCx.idIndex.length - 1];
       
  1468   updateDisplay();
       
  1469 }
       
  1470 
       
  1471 function clicTl(evt) {
       
  1472   var o = $("#timeline").offset();
       
  1473   if (twCx.tlMouseClicked && twCx.tlMouseMoved) {
       
  1474     var twid = tlIdFromPos(
       
  1475       evt.pageX - o.left + twCx.refPosTl.x - twCx.refMouse.x,
       
  1476       evt.pageY - o.top + twCx.refPosTl.y - twCx.refMouse.y,
       
  1477       true
       
  1478     );
       
  1479     if (twid) {
       
  1480       selectTweet(twid.id);
       
  1481     }
       
  1482   } else {
       
  1483     var twid = tlIdFromPos(
       
  1484       evt.pageX - o.left,
       
  1485       evt.pageY - o.top,
       
  1486       twCx.tlMouseClicked
       
  1487     );
       
  1488     if (twCx.tlMouseMoved && !twCx.tlMouseClicked) {
       
  1489       if (twid) {
       
  1490         rolloverTweet(twid.id, true, twid.annotation);
       
  1491       } else {
       
  1492         $("#hovertweet").hide();
       
  1493       }
       
  1494     }
       
  1495     if (twCx.tlMouseClicked && !twCx.tlMouseMoved) {
       
  1496       if (twid) {
       
  1497         selectTweet(twid.id);
       
  1498       }
       
  1499     }
       
  1500   }
       
  1501 }
       
  1502 
       
  1503 function loadTweets(tweets, append) {
       
  1504   if (!append) {
       
  1505     twCx.timeline = [];
       
  1506     twCx.idIndex = [];
       
  1507     twCx.tweets = [];
       
  1508   }
       
  1509   for (var i in tweets) {
       
  1510     addTweet(tweets[i]);
       
  1511   }
       
  1512   if (twCx.followLast) {
       
  1513     twCx.position = twCx.idIndex[twCx.tweets.length - 1];
       
  1514   }
       
  1515   updateDisplay();
       
  1516 }
       
  1517 
       
  1518 function focusOutRecherche() {
       
  1519   $("#recherche_annot").slideUp();
       
  1520   var inpq = $("#inp_q"),
       
  1521     val = inpq.val();
       
  1522   if (val == "" || val == twCx.filtreTexte) {
       
  1523     if (twCx.filtre) {
       
  1524       inpq.attr("class", "rechercheCourante").val(twCx.filtreTexte);
       
  1525     } else {
       
  1526       inpq.attr("class", "greyed").val(l10n.rechercher);
       
  1527     }
       
  1528   }
       
  1529 }
       
  1530 
       
  1531 function chaineTimeZoom() {
       
  1532   var chaine = "",
       
  1533     t = twCx.date_levels[twCx.timeLevel],
       
  1534     h = 3600 * 1000,
       
  1535     m = 60 * 1000,
       
  1536     s = 1000,
       
  1537     heures = Math.floor(t / h);
       
  1538   if (heures) {
       
  1539     chaine += heures + " h. ";
       
  1540   }
       
  1541   t -= heures * h;
       
  1542   var minutes = Math.floor(t / m);
       
  1543   if (minutes) {
       
  1544     chaine += minutes + " min. ";
       
  1545   }
       
  1546   t -= minutes * m;
       
  1547   if (t) {
       
  1548     chaine += Math.floor(t / s) + " sec.";
       
  1549   }
       
  1550   $("#time_scale").html(chaine);
       
  1551   $("#time_zoomout").attr("class", twCx.timeLevel == 0 ? "inactive" : "");
       
  1552   $("#time_zoomin").attr(
       
  1553     "class",
       
  1554     twCx.timeLevel == twCx.date_levels.length - 1 ? "inactive" : ""
       
  1555   );
       
  1556 }
       
  1557 
       
  1558 function saveJSON() {
       
  1559   var _txt = JSON.stringify(twCx.tweets),
       
  1560     _buf = "";
       
  1561   for (var i = 0; i < _txt.length; i++) {
       
  1562     var _n = _txt.charCodeAt(i);
       
  1563     if (_n > 127) {
       
  1564       var _h = _n.toString(16);
       
  1565       while (_h.length < 4) {
       
  1566         _h = "0" + _h;
       
  1567       }
       
  1568       _buf += "\\u" + _h;
       
  1569     } else {
       
  1570       _buf += _txt.charAt(i);
       
  1571     }
       
  1572   }
       
  1573   document.location.href = "data:text/json;base64," + btoa(_buf);
       
  1574 }
       
  1575 
       
  1576 function followSocialGroup() {
       
  1577   $.ajax({
       
  1578     beforeSend: function (request) {
       
  1579       request.setRequestHeader(
       
  1580         "Authorization",
       
  1581         `Bearer ${social_access_token}`
       
  1582       );
   546     },
  1583     },
   547       Mastodon: {
  1584     type: "POST",
   548 
  1585     dataType: "json",
   549         getMsgUrl: function() {
  1586     url: `https://${social_login_domain}/api/v1/accounts/`,
   550             return msg.url;
  1587   });
   551         },
  1588 }
   552 
  1589 
   553         getMsgUri: function() {
  1590 function buildFollowLink() {
   554             return msg.uri;
  1591   const sgroup = social_group.replace(/^\@+/, "");
   555         },
  1592   $("#twwWrap").prepend(
   556         
  1593     `<div id="socialGroupFollow" ><a target=”_blank” href="https://${social_login_domain}/authorize_interaction?uri=${sgroup}">Suivez le groupe ${social_group}</a> et recharger cette page</div>`
   557         getMsgId: function() {
  1594   );
   558             return 'toot_' + msg.id;
  1595 }
   559         },
  1596 
   560         
  1597 function checkSocialGroup() {
   561         getMsgTitle: function() {
  1598   getSocialData().then((data) => {
   562             return 'Toot by ' + _(msg.user.name).escape();
  1599     getFollowing(data.id).then((following_data) => {
   563         },
  1600       let is_following_group = false;
   564 
  1601       for (const user_def of following_data) {
   565         getMsgDescription: function() {
  1602         const group_uri = getSocialGroupUri(social_group);
   566             return _($(msg.full_text).text()).escape();
  1603         if (user_def.uri == group_uri) {
   567         },
  1604           is_following_group = true;
   568 
       
   569         getMsgUserUrl: function() {
       
   570             return msg.user.url;
       
   571         },
       
   572 
       
   573         getMsgReplyUrl: function() {
       
   574             return '';
       
   575         },
       
   576         getMsgRetweetUrl: function() {
       
   577             return '';
       
   578         },
       
   579         getMsgFavoriteUrl: function() {
       
   580             return '';
       
   581         }
  1605         }
   582 
  1606       }
   583       }
  1607 
   584     }[msg.type];
  1608       $("#socialGroupFollow").remove();
   585 }
  1609       if (!is_following_group) {
   586 
  1610         buildFollowLink();
   587 function tweetToHtml(tweet, className, elName) {
  1611         setTimeout(function () {
   588 
  1612           checkSocialGroup();
   589     function highlight(texte) {
  1613         }, 5000);
   590         return ( twCx.filtre ? texte.replace(twCx.filtre, '<span class="highlight">$1</span>' ) : texte );
  1614       }
   591     }
  1615     });
   592     
  1616   });
   593     if (!tweet) {
  1617 }
   594         return placeHolder(className);
  1618 
   595     }
  1619 $(function () {
   596 
  1620   //twwWrap
   597     const htmlAdapter = getMsgHtmlAdapter(tweet);
  1621   if (social_network == "Mastodon") {
   598 
  1622     checkSocialGroup();
   599     var el = (elName ? elName : 'li');
  1623   }
   600     var html = '<'
  1624 
   601     	+ el
  1625   twCx.tlWidth = $("#timeline").width();
   602     	+ ' draggable="true" class="tweet '
  1626   twCx.tlHeight = $("#timeline").height();
   603     	+ className
  1627   twCx.tlPaper = Raphael("timeline", twCx.tlWidth, twCx.tlHeight);
   604     	+ '" id="tweet_'
  1628 
   605     	+ tweet.id
  1629   connectTweets();
   606     	+ '" data-title="'
  1630 
   607     	+ htmlAdapter.getMsgTitle()
  1631   var html = "";
   608     	+ '" data-description="'
  1632   for (var j in annotations) {
   609     	+ htmlAdapter.getMsgDescription()
  1633     if (j != "default") {
   610     	+ '" data-uri="'
  1634       html +=
   611     	+ htmlAdapter.getMsgUri()
  1635         '<a href="#" style="background: ' +
   612     	+ '"';
  1636         getColor(j, 0.7).hex +
   613     if (className != 'full') {
  1637         ";\" onclick=filtrerAnnotation('" +
   614         html += ' onclick="selectTweet(\'' + tweet.id + '\'); return false;"';
  1638         j +
   615     }
  1639         "'); return false;\">" +
   616     html += ' onmouseover="rolloverTweet(\'' + tweet.id + "', " + ( className == 'icons' ) + ');"';
  1640         annotations[j].display_name +
   617     if (twCx.followLast && className == 'full' && el == 'li') {
  1641         "</a> ";
   618         html += ' style="display: none"';
  1642     }
   619     }
  1643   }
   620     html += '>';
  1644   $("#rech_list_annot").html(html);
   621     if (tweet.annotations && tweet.annotations.length) {
  1645 
   622         html += '<div class="annotations">';
  1646   chaineTimeZoom();
   623         for (var i in tweet.annotations) {
  1647 
   624             html += '<div class="annotation" style="width:' + (100/tweet.annotations.length) + '%; background:' + getColor(tweet.annotations[i], (className == 'icons' ? .4 : .85)).hex + '"></div>';
  1648   $("#tweetlist").mousewheel(function (e, d) {
   625         }
  1649     twCx.wheelDelta += d;
   626         html += '</div>';
  1650     if (Math.abs(twCx.wheelDelta) >= 1) {
   627     }
  1651       movePos(parseInt(twCx.wheelDelta));
   628     html += '<div class="twmain">';
  1652       twCx.wheelDelta = 0;
   629     var a_user = '<a href="' + htmlAdapter.getMsgUserUrl() + '"  onclick="filtrerTexte(\'@' + tweet.user.screen_name + '\'); return false;" target="_blank">';
  1653     }
   630     html += '<div class="around_img"><img class="profile_image" src="' + tweet.user.profile_image_url_https + '" />';
  1654     return false;
   631     if (className == 'full') {
  1655   });
   632         html += '<p class="created_at">' + new Date(tweet.date_value).toTimeString().substr(0,8) + '</a></p>';
  1656   $("#tweetlist").delegate(".tweet", "dragstart", function (e) {
   633     }
  1657     var div = document.createElement("div");
   634     html += '</div>';
  1658     div.appendChild(this.cloneNode(true));
   635     if (className != 'icons') {
  1659     try {
   636         let lastend = 0;
  1660       e.originalEvent.dataTransfer.setData("text/html", div.innerHTML);
   637         var txt = '';
  1661     } catch (err) {
   638         const full_text = $(tweet.full_text).text();
  1662       e.originalEvent.dataTransfer.setData("text", div.innerHTML);
   639         tweet.html_parts.sort(function(a, b) { return a.start - b.start });
  1663     }
   640         _(tweet.html_parts).each(function(_e) {
  1664   });
   641             txt += highlight( full_text.substring(lastend, _e.start) ) + _e.link + highlight( _e.text ) + '</a>';
  1665   $("#timeline").mousewheel(function (e, d) {
   642             lastend = _e.end;
  1666     twCx.wheelDelta += d;
       
  1667     let tl = 0;
       
  1668     if (Math.abs(twCx.wheelDelta) >= 1) {
       
  1669       if (twCx.wheelDelta > 0) {
       
  1670         tl = Math.min(twCx.date_levels.length - 1, twCx.timeLevel + 1);
       
  1671       } else {
       
  1672         tl = Math.max(0, twCx.timeLevel - 1);
       
  1673       }
       
  1674       if (tl != twCx.timeLevel) {
       
  1675         twCx.timeLevel = tl;
       
  1676         chaineTimeZoom();
       
  1677         updateDisplay();
       
  1678       }
       
  1679       twCx.wheelDelta = 0;
       
  1680     }
       
  1681     return false;
       
  1682   });
       
  1683   $("#time_zoomin").click(function () {
       
  1684     if (twCx.timeLevel < twCx.date_levels.length - 1) {
       
  1685       twCx.timeLevel++;
       
  1686       chaineTimeZoom();
       
  1687       updateDisplay();
       
  1688     }
       
  1689   });
       
  1690   $("#time_zoomout").click(function () {
       
  1691     if (twCx.timeLevel > 0) {
       
  1692       twCx.timeLevel--;
       
  1693       chaineTimeZoom();
       
  1694       updateDisplay();
       
  1695     }
       
  1696   });
       
  1697   $("#timeline, #tweetlist").mouseout(function () {
       
  1698     twCx.tlMouseClicked = false;
       
  1699     twCx.tlMouseMoved = false;
       
  1700     $("#hovertweet").hide();
       
  1701   });
       
  1702   $("#timeline")
       
  1703     .mousemove(function (evt) {
       
  1704       twCx.tlMouseMoved = true;
       
  1705       clicTl(evt);
       
  1706     })
       
  1707     .mousedown(function (evt) {
       
  1708       twCx.tlMouseClicked = true;
       
  1709       twCx.tlMouseMoved = false;
       
  1710       var o = $(this).offset();
       
  1711       twCx.refMouse = { x: evt.pageX - o.left, y: evt.pageY - o.top };
       
  1712       twCx.refPosTl = tlPosTweet(tweetById(twCx.position)) || twCx.refMouse;
       
  1713     })
       
  1714     .mouseup(function (evt) {
       
  1715       clicTl(evt);
       
  1716       twCx.tlMouseClicked = false;
       
  1717       twCx.tlMouseMoved = false;
       
  1718     });
       
  1719   $("#inp_q")
       
  1720     .focus(function () {
       
  1721       $("#recherche_annot").slideDown();
       
  1722       $(this).val(
       
  1723         $(this)
       
  1724           .val()
       
  1725           .replace(/ \(.+\)$/, "")
       
  1726       );
       
  1727       if ($(this).hasClass("greyed")) {
       
  1728         $(this).val("");
       
  1729       }
       
  1730       $(this).attr("class", "");
       
  1731     })
       
  1732     .focusout(function () {
       
  1733       focusOutRecherche();
       
  1734     });
       
  1735   $("#inp_reset").click(function () {
       
  1736     $("#inp_q").val("");
       
  1737     if (twCx.filtre) {
       
  1738       twCx.filtre = null;
       
  1739       updateDisplay();
       
  1740     }
       
  1741     twCx.filtreTexte = "";
       
  1742     focusOutRecherche();
       
  1743     return false;
       
  1744   });
       
  1745   $("#recherche").submit(function (evt) {
       
  1746     evt.preventDefault();
       
  1747     if (!$("#inp_q").hasClass("greyed")) {
       
  1748       var valeur = $("#inp_q").val();
       
  1749       filtrerTexte(valeur);
       
  1750     }
       
  1751     return false;
       
  1752   });
       
  1753   $("#hoverkw")
       
  1754     .mouseover(function () {
       
  1755       $(this).dequeue().show();
       
  1756     })
       
  1757     .mouseout(function () {
       
  1758       $(this).hide();
       
  1759     });
       
  1760 
       
  1761   $("#hkwsearch").click(function () {
       
  1762     var _hkw = $("#hoverkw");
       
  1763     filtrerTexte(_hkw.attr("kw"));
       
  1764     _hkw.hide();
       
  1765     return false;
       
  1766   });
       
  1767   $("#hkwtweet").click(function () {
       
  1768     var _hkw = $("#hoverkw");
       
  1769     add_grammar(_hkw.attr("kw"));
       
  1770     _hkw.hide();
       
  1771     return false;
       
  1772   });
       
  1773   $(".acctitre").click(function () {
       
  1774     $(this).next().slideToggle();
       
  1775     return false;
       
  1776   });
       
  1777 
       
  1778   if (!suggested_keywords.length) {
       
  1779     $("#suggkw").parent().hide();
       
  1780   }
       
  1781 
       
  1782   setInterval(function () {
       
  1783     var sc = $("#scrollcont");
       
  1784     if (
       
  1785       sc.scrollTop() != twCx.lastScrollPos &&
       
  1786       twCx.tweets &&
       
  1787       twCx.currentIdIndex
       
  1788     ) {
       
  1789       var p = Math.floor(
       
  1790         twCx.currentIdIndex.length * (1 - sc.scrollTop() / twCx.scrollExtent)
       
  1791       );
       
  1792       goToPos(p);
       
  1793     }
       
  1794   }, 100);
       
  1795 });
       
  1796 
       
  1797 function connectTweets() {
       
  1798   twCx.tlPaper.clear();
       
  1799   var _sq = twCx.tlPaper.rect(0, twCx.tlHeight, twCx.tlWidth, 0).attr({
       
  1800     stroke: "none",
       
  1801     fill: "#8080cc",
       
  1802   });
       
  1803   var _lb = twCx.tlPaper
       
  1804     .text(twCx.tlWidth / 2, twCx.tlHeight / 2, "0 tweet")
       
  1805     .attr({
       
  1806       "font-size": "20px",
       
  1807       "text-anchor": "middle",
       
  1808     });
       
  1809 
       
  1810   getTweets({
       
  1811     social_network: social_network,
       
  1812     keyword: tracking_keywords.join(" OR "),
       
  1813     pages: max_pages,
       
  1814     rpp: 100,
       
  1815     cbData: function () {
       
  1816       _lb.attr("text", this.tweets.length - this.currentPage + 1 + " tweets");
       
  1817       var _h = (twCx.tlHeight * this.currentPage) / this.pages;
       
  1818       _sq.animate({
       
  1819         y: twCx.tlHeight - _h,
       
  1820         height: _h,
       
  1821       });
       
  1822     },
       
  1823     cbEnd: function () {
       
  1824       loadTweets(this.tweets);
       
  1825       setInterval(function () {
       
  1826         getTweets({
       
  1827           social_network: social_network,
       
  1828           keyword: tracking_keywords.join(" OR "),
       
  1829           pages: 1,
       
  1830           since_id: twCx.idIndex[twCx.idIndex.length - 1],
       
  1831           rpp: 100,
       
  1832           cbEnd: function () {
       
  1833             loadTweets(this.tweets, true);
       
  1834           },
   643         });
  1835         });
   644         //lastend = Math.max(120, lastend);
  1836       }, 20000);
   645         txt += highlight( full_text.substring(lastend) );
  1837     },
   646         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>';
  1838   });
   647         if (className == 'full' && el == 'li') {
       
   648             html += '<div class="tweet_actions"><a href="' + htmlAdapter.getMsgUrl() + '" onclick="tweetPopup(this.href); return false;" target="_blank">afficher message</a>';
       
   649             const replyUrl = htmlAdapter.getMsgReplyUrl();
       
   650             if (replyUrl) { html += '<a href="' + replyUrl + '" onclick="tweetPopup(this.href); return false;" target="_blank">répondre</a> · '; }
       
   651             const retweetUrl = htmlAdapter.getMsgRetweetUrl();
       
   652             if (retweetUrl) { html += '<a href="' + retweetUrl + '" onclick="tweetPopup(this.href); return false;" target="_blank">retransmettre</a> · '; }
       
   653             const favoriteUrl = htmlAdapter.getMsgFavoriteUrl();
       
   654             if (favoriteUrl) { html += '<a href="' + favoriteUrl + '" onclick="tweetPopup(this.href); return false;" target="_blank">favori</a>'; }
       
   655             html += '</div>';
       
   656         }
       
   657     }
       
   658     html += '</div></' + el + '>';
       
   659     return html;
       
   660 }
       
   661 
       
   662 function tlIdFromPos(x, y, outside) {
       
   663     if (!twCx.tlOnDisplay || !twCx.tlOnDisplay.length) {
       
   664         return;
       
   665     }
       
   666     var ligne = Math.min( twCx.tlOnDisplay.length - 1, Math.max( 0, Math.floor(( twCx.tlHeight - y ) / twCx.scaleY) ) ),
       
   667         colonne = Math.floor(( x - twCx.deltaX ) / twCx.scaleX ),
       
   668         l = 0;
       
   669     if (colonne >= twCx.tlOnDisplay[ligne].totalTweets || colonne < 0 ) {
       
   670         if (outside) {
       
   671             colonne = Math.min( twCx.tlOnDisplay[ligne].totalTweets - 1, Math.max( 0, colonne ));
       
   672         } else {
       
   673             return null;
       
   674         }
       
   675     }
       
   676     for (var i in twCx.tlOnDisplay[ligne].displayData) {
       
   677         var nl = l + twCx.tlOnDisplay[ligne].displayData[i].length;
       
   678         if (colonne < nl) {
       
   679             return {
       
   680                 "id" : twCx.tlOnDisplay[ligne].displayData[i][colonne - l],
       
   681                 "annotation" : i
       
   682             }
       
   683         }
       
   684         l = nl;
       
   685     }
       
   686 }
       
   687 
       
   688 function tlPosTweet(tweet, annotation) {
       
   689     if (!twCx.tweets) {
       
   690         return;
       
   691     }
       
   692     var x,
       
   693         y,
       
   694         dt = tweet.date_value,
       
   695         ann = ( annotation ? annotation : ( tweet.annotations && tweet.annotations.length ? tweet.annotations[0] : 'default' ) );
       
   696     for (var i = 0; i < twCx.tlOnDisplay.length; i++) {
       
   697         if (twCx.tlOnDisplay[i].end > dt) {
       
   698             y = twCx.tlHeight - (i + .5) * twCx.scaleY;
       
   699             var l = 0;
       
   700             for (var j in twCx.tlOnDisplay[i].displayData) {
       
   701                 if (j == ann) {
       
   702                     var p = _(twCx.tlOnDisplay[i].displayData[j]).indexOf(tweet.id);
       
   703                     if (p != -1) {
       
   704                         x = twCx.deltaX + twCx.scaleX * ( p + l + .5 );
       
   705                     }
       
   706                     break;
       
   707                 }
       
   708                 l += twCx.tlOnDisplay[i].displayData[j].length;
       
   709             }
       
   710             break;
       
   711         }
       
   712     }
       
   713     return ( x && y ? { "x" : x, "y" : y } : null);
       
   714 }
       
   715 
       
   716 function rolloverTweet(tweetid, showPopup, annotation) {
       
   717     var t = tweetById(tweetid);
       
   718     if (!t) {
       
   719         return;
       
   720     }
       
   721     var p = tlPosTweet(t, annotation);
       
   722     if (!p) {
       
   723         return;
       
   724     }
       
   725     var ptl = $("#timeline").offset();
       
   726     if (showPopup) {
       
   727         $("#hovercontent").html(tweetToHtml(t, 'full', 'div'));
       
   728         $("#hovertweet").css({
       
   729             "left" : parseInt(ptl.left + p.x) + "px",
       
   730             "top" : parseInt(ptl.top + p.y),
       
   731             "display" : "block"});
       
   732     } else {
       
   733         $("#hovertweet").hide();
       
   734     }
       
   735     for (var i in twCx.relHover) {
       
   736         twCx.relHover[i].remove();
       
   737     }
       
   738     twCx.relHover = drawTweetArcs(t, p, '#303030');
       
   739     twCx.relHover.push(drawTweetPos(p, '#ffffff'));
       
   740 }
       
   741 
       
   742 function drawTweetPos(pos, color) {
       
   743     var rel = twCx.tlPaper.rect(pos.x - .5 * twCx.scaleX, pos.y - .5 * twCx.scaleY, twCx.scaleX, twCx.scaleY);
       
   744     rel.attr({ "stroke" : color, "fill" : color, "fill-opacity" : .25 });
       
   745     return rel;
       
   746 }
       
   747 
       
   748 function drawTweetArcs(tweet, pos, color) {
       
   749     
       
   750     var res = [];
       
   751 
       
   752     function tweetAndArc(a, b, aorb) {
       
   753         if (a && b) {
       
   754             res.push(drawTweetPos(aorb ? a : b, color));
       
   755             var aa = twCx.tlPaper.path(arc(a,b))
       
   756                 .attr({ "stroke" : color, "stroke-width" : 1.5, "stroke-opacity" : .8 });
       
   757             res.push(aa);
       
   758         }
       
   759     }
       
   760     
       
   761     if (tweet.retweeted_status_id) {
       
   762         var t = tweetById(tweet.retweeted_status_id);
       
   763         if (t) {
       
   764             tweetAndArc(pos, tlPosTweet(t));
       
   765         }
       
   766     }
       
   767     
       
   768     if (tweet.in_reply_to_status_id) {
       
   769         var t = tweetById(tweet.in_reply_to_status_id);
       
   770         if (t) {
       
   771             tweetAndArc(pos, tlPosTweet(t));
       
   772         }
       
   773     }
       
   774     
       
   775     if (tweet.backRefs) {
       
   776         for (var i in tweet.backRefs) {
       
   777             var t = tweetById(tweet.backRefs[i].referenced_by_id);
       
   778             if (t) {
       
   779             tweetAndArc(tlPosTweet(t), pos, true);
       
   780             }
       
   781         }
       
   782     }
       
   783     
       
   784     return res;
       
   785 }
       
   786 
       
   787 function mouseoverkw() {
       
   788     var _jel = $(this),
       
   789         _off = _jel.offset();
       
   790     _jel.css({
       
   791         color: "#0099ff"
       
   792     });
       
   793     $("#hoverkw")
       
   794         .css({
       
   795             "left" : _off.left + "px",
       
   796             "top" : ( parseInt(_off.top) + ~~ (_jel.height() / 2) )  + "px",
       
   797             "display" : "block"
       
   798         })
       
   799         .attr("kw", _jel.text());
       
   800 }
       
   801 
       
   802 function mouseoutkw() {
       
   803     $("#hoverkw").hide();
       
   804     $(this).css({
       
   805         color: "#000000"
       
   806     });
       
   807 }
       
   808 
       
   809 function makeTagCloud(tab, div) {
       
   810     var minfreq = _(tab).min( function(a) { return a.freq} ).freq,
       
   811         maxfreq = Math.max(minfreq + .1, _(tab).max( function(a) { return a.freq} ).freq),
       
   812         echfreq = 8 / Math.sqrt( maxfreq - minfreq ),
       
   813         html = '';
       
   814     _(tab).each(function(_j) {
       
   815         var maxann = 0,
       
   816             ann = "default";
       
   817         for (var k in _j.annotations) {
       
   818             if (_j.annotations[k] == maxann) {
       
   819                 ann = "default";
       
   820             }
       
   821             if (_j.annotations[k] > maxann) {
       
   822                 ann = k;
       
   823                 maxann = _j.annotations[k];
       
   824             }
       
   825         }
       
   826         if (ann == "default") {
       
   827             var coul = '';
       
   828         } else {
       
   829             var c = getColor(ann, .6),
       
   830                 coul = "background: rgba(" + [ Math.floor(c.r), Math.floor(c.g), Math.floor(c.b), ( _j.annotations[ann] / _j.freq )].join(',') + ")";
       
   831         }
       
   832         var fontsize = Math.floor( ( 12 + Math.sqrt( _j.freq - minfreq ) * echfreq ) );
       
   833         html += '<span style="line-height: ' + (8 + fontsize) + 'px; font-size: ' + fontsize + 'px;' + coul + '">' + _j.word + '</span> ';
       
   834     });
       
   835     $(div).html(html);
       
   836     $(div + " span")
       
   837         .mouseover(mouseoverkw)
       
   838         .mouseout(mouseoutkw)
       
   839         .click(function() {
       
   840             $("#hoverkw").toggle();
       
   841         });
       
   842 }
       
   843 
       
   844 function updateDisplay() {
       
   845     if (!twCx.tweets) {
       
   846         return;
       
   847     }
       
   848     if (twCx.filtre) {
       
   849         var tweets = _(twCx.tweets).filter(function(tweet) {
       
   850             var mention = '@' + tweet.user.screen_name;
       
   851             return ( tweet.full_text.search(twCx.filtre) != -1 ) || ( mention.search(twCx.filtre) != -1 );
       
   852         });
       
   853         $("#inp_q").val(twCx.filtreTexte + ' (' + tweets.length + ' tweets)');
       
   854         if (tweets.length) {
       
   855             var idIndex = _(tweets).map(function(tweet) {
       
   856                 return tweet.id;
       
   857             });
       
   858             var p = _(idIndex).indexOf(twCx.position);
       
   859             if (p == -1) {
       
   860                 for (p = idIndex.length - 1; p > 0 && idIndex[p] > twCx.position; p--) {
       
   861                 }
       
   862             }
       
   863             twCx.position = idIndex[p];
       
   864             twCx.currentIdIndex = idIndex;
       
   865         }
       
   866         
       
   867     } else {
       
   868         twCx.currentIdIndex = twCx.idIndex;
       
   869         var tweets = twCx.tweets;
       
   870         var p = _(twCx.idIndex).indexOf(twCx.position);
       
   871         if (p == -1) {
       
   872             p = (twCx.followLast ? twCx.idIndex.length - 1 : 0);
       
   873         }
       
   874     }
       
   875     
       
   876     
       
   877     var l = tweets.length,
       
   878         lines = 0,
       
   879         ppy = 0,
       
   880         html = '',
       
   881         tweetsOnDisplay = [];
       
   882     
       
   883     function pushTweet(tp, className) {
       
   884         
       
   885         if (tp < l && tp >= 0) {
       
   886             
       
   887             html += tweetToHtml(tweets[tp], className);
       
   888             
       
   889             tweetsOnDisplay.push(tp);
       
   890             
       
   891         } else {
       
   892             html += placeHolder(className);
       
   893         }
       
   894     }
       
   895     
       
   896     if (l) {
       
   897     
       
   898         twCx.lastScrollPos = Math.floor( twCx.scrollExtent * ( 1 - ( p / l ) ) );
       
   899         $("#scrollcont").scrollTop(twCx.lastScrollPos);
       
   900         
       
   901         if (l > p + 18) {
       
   902             lines++;
       
   903             ppy += 20;
       
   904             for (var i = p + 31; i >= p + 18; i--) {
       
   905                 pushTweet(i, 'icons');
       
   906             }
       
   907         }
       
   908         if (l > p + 4) {
       
   909             lines++;
       
   910             ppy += 20;
       
   911             for (var i = p + 17; i >= p + 4; i--) {
       
   912                 pushTweet(i, 'icons');
       
   913             }
       
   914         }
       
   915         for (var k = 3; k >= 1; k--) {
       
   916             if (l > p + k) {
       
   917                 ppy += 47;
       
   918                 lines++;
       
   919                 pushTweet(p + k, 'half');
       
   920             }
       
   921         }
       
   922         pushTweet(p, 'full');
       
   923         var n = p - 1;
       
   924         for (var i = 0; i < Math.min(6, Math.max(3, 6 - lines)); i++) {
       
   925             if (n < 0) {
       
   926                 break;
       
   927             }
       
   928             pushTweet(n, 'half');
       
   929             n--;
       
   930         }
       
   931         for (var i = 0; i < 14 * Math.min(4, Math.max(2, 7 - lines)); i++) {
       
   932             if (n < 0) {
       
   933                 break;
       
   934             }
       
   935             pushTweet(n, 'icons');
       
   936             n--;
       
   937         }
       
   938         if (html != twCx.tlBuffer) {
       
   939             $("#tweetlist").html(html);
       
   940             $(".tweet.full").fadeIn();
       
   941             twCx.tlBuffer = html;
       
   942         }
       
   943         
       
   944         if (twCx.suggestCount.length) {
       
   945             makeTagCloud(twCx.suggestCount, "#suggkw");
       
   946         }
       
   947         
       
   948         var tab = _(twCx.globalWords).chain()
       
   949         .map(function(v, k) {
       
   950             return {
       
   951                 "word": k,
       
   952                 "freq" : v.freq,
       
   953                 "annotations" : v.annotations
       
   954             };
       
   955         }).filter(function(v) {
       
   956             return v.freq > 3;
       
   957         }).value();
       
   958         
       
   959         if (tab.length) {
       
   960             
       
   961             tab = _(tab).sortBy( function(a) { return ( - a.freq ) }).slice(0,40);
       
   962             makeTagCloud(tab,"#motscles");
       
   963         } else {
       
   964             $("#motscles").html('');
       
   965         }
       
   966         twCx.centralTweet = tweets[p];
       
   967     } else {
       
   968         $("#tweetlist").html('');
       
   969         twCx.tlBuffer = '';
       
   970         $("#motscles").html('');
       
   971     }
       
   972     
       
   973     twCx.tlOnDisplay = trimFDS();
       
   974     if (!twCx.tlOnDisplay || !twCx.tlOnDisplay.length) {
       
   975     	return;
       
   976     }
       
   977     twCx.scaleY = twCx.tlHeight / twCx.tlOnDisplay.length;
       
   978     var maxTweets = 0,
       
   979         startTl = 0,
       
   980         endTl = twCx.tlOnDisplay.length - 1;
       
   981     if (l) {
       
   982         var startTw = tweets[tweetsOnDisplay[tweetsOnDisplay.length - 1]].date_value,
       
   983             endTw = tweets[tweetsOnDisplay[0]].date_value;
       
   984     }
       
   985     for (var i = 0; i < twCx.tlOnDisplay.length; i++) {
       
   986         if (l) {
       
   987             if (startTw >= twCx.tlOnDisplay[i].start && startTw < twCx.tlOnDisplay[i].end) {
       
   988                 startTl = i;
       
   989             }
       
   990             if (endTw >= twCx.tlOnDisplay[i].start && endTw < twCx.tlOnDisplay[i].end) {
       
   991                 endTl = i;
       
   992             }
       
   993         }
       
   994         var displayData = {};
       
   995         for (var j in annotations) {
       
   996             displayData[j] = [];
       
   997         }
       
   998         for (var j in twCx.tlOnDisplay[i].tweets) {
       
   999             var tweetid = twCx.tlOnDisplay[i].tweets[j],
       
  1000                 tweet = tweetById(tweetid);
       
  1001             if (tweet) {
       
  1002                 if (tweet.annotations && tweet.annotations.length) {
       
  1003                     for (var k in tweet.annotations) {
       
  1004                         displayData[tweet.annotations[k]].push(tweetid);
       
  1005                     }
       
  1006                 } else {
       
  1007                     displayData['default'].push(tweetid);
       
  1008                 }
       
  1009             }
       
  1010         }
       
  1011         var nbT = 0;
       
  1012         for (var j in displayData) {
       
  1013             nbT += displayData[j].length;
       
  1014         }
       
  1015         maxTweets = Math.max(maxTweets, nbT);
       
  1016         twCx.tlOnDisplay[i].displayData = displayData;
       
  1017         twCx.tlOnDisplay[i].totalTweets = nbT;
       
  1018     }
       
  1019     twCx.scaleX = ( twCx.tlWidth - twCx.deltaX ) / maxTweets;
       
  1020     twCx.tlPaper.clear();
       
  1021     twCx.relHover = null;
       
  1022     
       
  1023     // Dessin de la correspondance liste-timeline
       
  1024     if (l) {
       
  1025         var startY = twCx.tlHeight - startTl * twCx.scaleY,
       
  1026             endY = twCx.tlHeight - ( endTl + 1 ) * twCx.scaleY,
       
  1027             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";
       
  1028         twCx.tlPaper.path( path ).attr({ "stroke" : "none", "fill" : "#000080", "opacity" : .2 });
       
  1029     }   
       
  1030     // dessin de la date de début
       
  1031     
       
  1032     twCx.tlPaper.text(twCx.deltaX / 2, twCx.tlHeight - 7, new Date(twCx.tlOnDisplay[0].start).toTimeString().substr(0,5))
       
  1033         .attr({ "text-anchor" : "middle", "font-size": "9px" });
       
  1034     
       
  1035     // dessin de la date de fin
       
  1036     
       
  1037     twCx.tlPaper.text(twCx.deltaX / 2, 7, new Date(twCx.tlOnDisplay[twCx.tlOnDisplay.length - 1].end).toTimeString().substr(0,5))
       
  1038         .attr({ "text-anchor" : "middle", "font-size": "9px" });
       
  1039     
       
  1040     for (var i = 0; i < twCx.tlOnDisplay.length; i++) {
       
  1041         var n = 0,
       
  1042             posY = twCx.tlHeight - ( i + 1 ) * twCx.scaleY;
       
  1043         for (var j in twCx.tlOnDisplay[i].displayData) {
       
  1044             var ll = twCx.tlOnDisplay[i].displayData[j].length;
       
  1045             if (ll > 0) {
       
  1046                 twCx.tlPaper.rect( twCx.deltaX + n * twCx.scaleX, posY, ll * twCx.scaleX, twCx.scaleY )
       
  1047                     .attr({"stroke" : "none", "fill" : getColor(j, .4).hex });
       
  1048                 n += ll;
       
  1049             }
       
  1050         }
       
  1051         
       
  1052         // Si on est à une demi-heure, on trace un axe secondaire + heure
       
  1053         
       
  1054         if (i < twCx.tlOnDisplay.length - 1 && !(twCx.tlOnDisplay[i].end % 1800000)) {
       
  1055             twCx.tlPaper.path("M0 "+posY+"L" + twCx.tlWidth +" "+posY).attr({"stroke":"#ccc"});
       
  1056             twCx.tlPaper.text(twCx.deltaX / 2, posY, new Date(twCx.tlOnDisplay[i].end).toTimeString().substr(0,5)).attr({ "text-anchor" : "middle", "font-size": "9px" });
       
  1057         }
       
  1058     }
       
  1059     
       
  1060     // dessin du tweet courant
       
  1061     
       
  1062     if (l) {
       
  1063         
       
  1064         if (twCx.filtre) {
       
  1065             for (var i = 0; i < tweets.length; i++) {
       
  1066                 if (i != p) {
       
  1067                     var pos = tlPosTweet(tweets[i]);
       
  1068                     if (pos) {
       
  1069                         drawTweetPos(pos, "#ffccff");
       
  1070                     }
       
  1071                 }
       
  1072             }
       
  1073             
       
  1074         }
       
  1075         
       
  1076         var posp = tlPosTweet(tweets[p]);
       
  1077         if (posp) {
       
  1078             
       
  1079             drawTweetPos(posp, "#ffff00");
       
  1080             var yy = posp.y - .5 * twCx.scaleY,
       
  1081                 path = "M0 " + ppy + "C" + ( .7 * twCx.deltaX ) + " " + ppy + " " + ( .2 * twCx.deltaX ) + " " + yy + " " + ( twCx.deltaX ) + " " + yy + "L" + ( posp.x - .5 * twCx.scaleX ) + " " + yy;
       
  1082             yy = posp.y + .5 * twCx.scaleY;
       
  1083             ppy += 117;
       
  1084             path += "L" + ( posp.x - .5 * twCx.scaleX ) + " " + yy + "L" + twCx.deltaX + " " + yy + "C"  + ( .2 * twCx.deltaX ) + " " + yy + " " + ( .7 * twCx.deltaX ) + " " + ppy + " 0 " + ppy;
       
  1085             twCx.tlPaper.path( path ).attr({"stroke":"#ffff00", "fill" : "#ffff00", "fill-opacity" : .15});
       
  1086             
       
  1087             drawTweetArcs(tweets[p], posp, '#800080');
       
  1088         }
       
  1089     }
       
  1090 }
       
  1091 
       
  1092 function filtrerAnnotation(annotation) {
       
  1093     if (annotations[annotation]) {
       
  1094         effectuerFiltrage(annotations[annotation].display_name,
       
  1095             new RegExp( "(" + _(annotations[annotation].keywords).map(function(a) { return a.source }).join("|") + ")", "gim" ) );
       
  1096     } else {
       
  1097         effectuerFiltrage('', null)
       
  1098     }
       
  1099 }
       
  1100 
       
  1101 function filtrerTexte(valeur) {
       
  1102     effectuerFiltrage( valeur, valeur ? new RegExp("(" + valeur.replace(/(\W)/g, '\\$1') + ")" ,'gim') : null );
       
  1103 }
       
  1104 
       
  1105 function effectuerFiltrage(filtreTexte, tabRegexp) {
       
  1106     $("#recherche_annot").slideUp();
       
  1107     $("#inp_q").val(filtreTexte).attr("class","rechercheCourante");
       
  1108     twCx.filtreTexte = filtreTexte;
       
  1109     twCx.filtre = tabRegexp;
       
  1110     twCx.followLast = !tabRegexp && (twCx.position == twCx.idIndex[twCx.idIndex.length - 1]);
       
  1111     updateDisplay();
       
  1112 }
       
  1113 
       
  1114 function clicTl(evt) {
       
  1115     var o = $("#timeline").offset();
       
  1116     if (twCx.tlMouseClicked && twCx.tlMouseMoved) {
       
  1117         var twid = tlIdFromPos(evt.pageX - o.left + twCx.refPosTl.x - twCx.refMouse.x, evt.pageY - o.top + twCx.refPosTl.y - twCx.refMouse.y, true);
       
  1118         if (twid) {
       
  1119             selectTweet(twid.id);
       
  1120         }
       
  1121     } else {
       
  1122         var twid = tlIdFromPos(evt.pageX - o.left, evt.pageY - o.top, twCx.tlMouseClicked);
       
  1123         if (twCx.tlMouseMoved && !twCx.tlMouseClicked) { 
       
  1124             if (twid) {
       
  1125                 rolloverTweet(twid.id, true, twid.annotation);
       
  1126             } else {
       
  1127                 $("#hovertweet").hide();
       
  1128             }
       
  1129         }
       
  1130         if (twCx.tlMouseClicked && !twCx.tlMouseMoved) {
       
  1131             if (twid) {
       
  1132                 selectTweet(twid.id);
       
  1133             }
       
  1134         }
       
  1135     }
       
  1136 }
       
  1137 
       
  1138 function loadTweets(tweets, append) {
       
  1139     if (!append) {
       
  1140         twCx.timeline = [];
       
  1141         twCx.idIndex = [];
       
  1142         twCx.tweets = [];
       
  1143     }
       
  1144     for (var i in tweets) {
       
  1145         addTweet(tweets[i]);
       
  1146     }
       
  1147     if (twCx.followLast) {
       
  1148         twCx.position = twCx.idIndex[twCx.tweets.length - 1];
       
  1149     }
       
  1150     updateDisplay();
       
  1151 }
       
  1152 
       
  1153 function focusOutRecherche() {
       
  1154     $("#recherche_annot").slideUp();
       
  1155     var inpq = $("#inp_q"),
       
  1156         val = inpq.val();
       
  1157     if (val == '' || val == twCx.filtreTexte) {
       
  1158         if (twCx.filtre) {
       
  1159             inpq.attr("class", "rechercheCourante").val(twCx.filtreTexte);
       
  1160         } else {
       
  1161             inpq.attr("class", "greyed").val(l10n.rechercher);
       
  1162         }
       
  1163     }
       
  1164 }
       
  1165 
       
  1166 function chaineTimeZoom() {
       
  1167     var chaine = "",
       
  1168         t = twCx.date_levels[twCx.timeLevel],
       
  1169         h = 3600*1000,
       
  1170         m = 60*1000,
       
  1171         s = 1000,
       
  1172         heures = Math.floor(t/h);
       
  1173     if (heures) { chaine += heures + ' h. ' };
       
  1174     t -= (heures * h);
       
  1175     var minutes = Math.floor(t/m);
       
  1176     if (minutes) { chaine += minutes + ' min. ' };
       
  1177     t -= (minutes * m);
       
  1178     if (t) { chaine += Math.floor(t/s) + ' sec.' }
       
  1179     $("#time_scale").html(chaine);
       
  1180     $("#time_zoomout").attr("class",(twCx.timeLevel == 0 ? "inactive" : ""));
       
  1181     $("#time_zoomin").attr("class",(twCx.timeLevel == twCx.date_levels.length - 1 ? "inactive" : ""));
       
  1182 }
       
  1183 
       
  1184 function saveJSON() {
       
  1185     var _txt = JSON.stringify(twCx.tweets),
       
  1186         _buf = '';
       
  1187     for (var i = 0; i < _txt.length; i++) {
       
  1188         var _n = _txt.charCodeAt(i);
       
  1189         if (_n > 127) {
       
  1190             var _h = _n.toString(16);
       
  1191             while (_h.length < 4) {
       
  1192                 _h = '0' + _h;
       
  1193             }
       
  1194             _buf += '\\u' + _h;
       
  1195         } else {
       
  1196             _buf += _txt.charAt(i);
       
  1197         }
       
  1198     }
       
  1199     document.location.href = "data:text/json;base64," + btoa(_buf);
       
  1200 }
       
  1201 
       
  1202 function followSocialGroup() {
       
  1203     $.ajax({
       
  1204         beforeSend: function(request) {
       
  1205             request.setRequestHeader('Authorization', `Bearer ${social_access_token}`);
       
  1206         },
       
  1207         type: "POST",
       
  1208         dataType: "json",
       
  1209         url: `https://${social_login_domain}/api/v1/accounts/`,
       
  1210     });
       
  1211 }
       
  1212 
       
  1213 function buildFollowLink() {
       
  1214     const sgroup = social_group.replace(/^\@+/,"")
       
  1215     $("#twwWrap").prepend(`<div id="socialGroupFollow" ><a target=”_blank” href="https://${social_login_domain}/authorize_interaction?uri=${sgroup}">Suivez le groupe ${social_group}</a> et recharger cette page</div>`)
       
  1216 }
       
  1217 
       
  1218 function checkSocialGroup() {
       
  1219     getSocialData().then( (data) => {
       
  1220         getFollowing(data.id).then((following_data) => {
       
  1221             let is_following_group = false;
       
  1222             for(const user_def of following_data) {
       
  1223                 const group_uri = getSocialGroupUri(social_group)
       
  1224                 if (user_def.uri == group_uri) {
       
  1225                     is_following_group = true;
       
  1226                 }
       
  1227             }
       
  1228 
       
  1229             $("#socialGroupFollow").remove();
       
  1230             if(!is_following_group) {
       
  1231                 buildFollowLink();
       
  1232                 setTimeout(function() {
       
  1233                     checkSocialGroup();
       
  1234                 }, 5000);
       
  1235             }
       
  1236         });
       
  1237     });
       
  1238 }
       
  1239 
       
  1240 $(function() {
       
  1241     //twwWrap
       
  1242     if(social_network == "Mastodon") {
       
  1243         checkSocialGroup();
       
  1244     }
       
  1245 
       
  1246     twCx.tlWidth = $("#timeline").width();
       
  1247     twCx.tlHeight = $("#timeline").height();
       
  1248     twCx.tlPaper = Raphael("timeline", twCx.tlWidth, twCx.tlHeight);
       
  1249     
       
  1250     connectTweets();
       
  1251     
       
  1252     var html = '';
       
  1253     for (var j in annotations) {
       
  1254         if (j != "default") {
       
  1255             html += '<a href="#" style="background: ' + getColor(j, .7).hex + ';" onclick=filtrerAnnotation(\'' + j + '\'); return false;">' + annotations[j].display_name + '</a> '
       
  1256         }
       
  1257     }
       
  1258     $("#rech_list_annot").html(html);
       
  1259     
       
  1260     chaineTimeZoom();
       
  1261     
       
  1262     $("#tweetlist").mousewheel(function(e, d) {
       
  1263         twCx.wheelDelta += d;
       
  1264         if (Math.abs(twCx.wheelDelta) >= 1) {
       
  1265             movePos( parseInt(twCx.wheelDelta) );
       
  1266             twCx.wheelDelta = 0;
       
  1267         }
       
  1268         return false;
       
  1269     });
       
  1270     $("#tweetlist").delegate(".tweet", "dragstart", function(e) {
       
  1271     	var div = document.createElement('div');
       
  1272     	div.appendChild(this.cloneNode(true));
       
  1273 		try {
       
  1274 			e.originalEvent.dataTransfer.setData("text/html",div.innerHTML);
       
  1275 		}
       
  1276 		catch(err) {
       
  1277 			e.originalEvent.dataTransfer.setData("text",div.innerHTML);
       
  1278 		}
       
  1279     });
       
  1280     $("#timeline").mousewheel(function(e, d) {
       
  1281         twCx.wheelDelta += d;
       
  1282         let tl = 0;
       
  1283         if (Math.abs(twCx.wheelDelta) >= 1) {
       
  1284             if (twCx.wheelDelta > 0) {
       
  1285                 tl = Math.min(twCx.date_levels.length - 1, twCx.timeLevel + 1);
       
  1286             } else {
       
  1287                 tl = Math.max(0, twCx.timeLevel - 1);
       
  1288             }
       
  1289             if (tl != twCx.timeLevel) {
       
  1290                 twCx.timeLevel = tl;
       
  1291                 chaineTimeZoom();
       
  1292                 updateDisplay();
       
  1293             }
       
  1294             twCx.wheelDelta = 0;
       
  1295         }
       
  1296         return false;
       
  1297     });
       
  1298     $("#time_zoomin").click(function() {
       
  1299         if (twCx.timeLevel < twCx.date_levels.length - 1) {
       
  1300             twCx.timeLevel++;
       
  1301             chaineTimeZoom();
       
  1302             updateDisplay();
       
  1303         }
       
  1304     });
       
  1305     $("#time_zoomout").click(function() {
       
  1306         if (twCx.timeLevel > 0) {
       
  1307             twCx.timeLevel--;
       
  1308             chaineTimeZoom();
       
  1309             updateDisplay();
       
  1310         }
       
  1311     });
       
  1312     $("#timeline, #tweetlist").mouseout(function() {
       
  1313         twCx.tlMouseClicked = false;
       
  1314         twCx.tlMouseMoved = false;
       
  1315         $("#hovertweet").hide();
       
  1316     });
       
  1317     $("#timeline").mousemove(function(evt) {
       
  1318         twCx.tlMouseMoved = true;
       
  1319         clicTl(evt);
       
  1320     }).mousedown(function(evt) {
       
  1321         twCx.tlMouseClicked = true;
       
  1322         twCx.tlMouseMoved = false;
       
  1323         var o = $(this).offset();
       
  1324         twCx.refMouse = { x : evt.pageX - o.left, y : evt.pageY - o.top };
       
  1325         twCx.refPosTl = tlPosTweet(tweetById(twCx.position)) || twCx.refMouse;
       
  1326     }).mouseup(function(evt) {
       
  1327         clicTl(evt);
       
  1328         twCx.tlMouseClicked = false;
       
  1329         twCx.tlMouseMoved = false;
       
  1330     });
       
  1331     $("#inp_q").focus(function() {
       
  1332         $("#recherche_annot").slideDown();
       
  1333         $(this).val($(this).val().replace(/ \(.+\)$/, ''))
       
  1334         if ($(this).hasClass("greyed")) {
       
  1335             $(this).val("");
       
  1336         }
       
  1337         $(this).attr("class","");
       
  1338     }).focusout(function() {
       
  1339         focusOutRecherche();
       
  1340     });
       
  1341     $("#inp_reset").click(function() {
       
  1342         $("#inp_q").val('');
       
  1343         if (twCx.filtre) {
       
  1344             twCx.filtre = null;
       
  1345             updateDisplay();
       
  1346         }
       
  1347         twCx.filtreTexte = '';
       
  1348         focusOutRecherche();
       
  1349         return false;
       
  1350     })
       
  1351     $("#recherche").submit(function(evt) {
       
  1352         evt.preventDefault();
       
  1353         if (!$("#inp_q").hasClass("greyed")) {
       
  1354             var valeur = $("#inp_q").val();
       
  1355             filtrerTexte(valeur);
       
  1356         }
       
  1357         return false;
       
  1358     });
       
  1359     $("#hoverkw").mouseover(function() {
       
  1360         $(this).dequeue().show();
       
  1361     }).mouseout(function() {
       
  1362         $(this).hide();
       
  1363     });
       
  1364     
       
  1365     $("#hkwsearch").click(function() {
       
  1366         var _hkw = $("#hoverkw");
       
  1367         filtrerTexte(_hkw.attr("kw"));
       
  1368         _hkw.hide();
       
  1369         return false;
       
  1370     });
       
  1371     $("#hkwtweet").click(function() {
       
  1372         var _hkw = $("#hoverkw");
       
  1373         add_grammar(_hkw.attr("kw"));
       
  1374         _hkw.hide();
       
  1375         return false;
       
  1376     });
       
  1377     $(".acctitre").click(function() {
       
  1378         $(this).next().slideToggle();
       
  1379         return false;
       
  1380     })
       
  1381     
       
  1382     if (!suggested_keywords.length) {
       
  1383         $("#suggkw").parent().hide();
       
  1384     }
       
  1385     
       
  1386     setInterval(function() {
       
  1387         var sc = $("#scrollcont");
       
  1388         if (sc.scrollTop() != twCx.lastScrollPos && twCx.tweets && twCx.currentIdIndex) {
       
  1389             var p = Math.floor( twCx.currentIdIndex.length * ( 1 - sc.scrollTop() / twCx.scrollExtent ) );
       
  1390             goToPos(p);
       
  1391         }
       
  1392     }, 100)
       
  1393 });
       
  1394 
       
  1395 function connectTweets() {
       
  1396     twCx.tlPaper.clear();
       
  1397     var _sq = twCx.tlPaper.rect(0, twCx.tlHeight, twCx.tlWidth, 0)
       
  1398         .attr({
       
  1399             "stroke" : "none",
       
  1400             "fill" : "#8080cc"
       
  1401         });
       
  1402     var _lb = twCx.tlPaper.text(twCx.tlWidth / 2, twCx.tlHeight / 2, "0 tweet")
       
  1403         .attr({
       
  1404             "font-size" : "20px",
       
  1405             "text-anchor" : "middle"
       
  1406         });
       
  1407     
       
  1408     getTweets({
       
  1409         "social_network" : social_network,
       
  1410         "keyword" : tracking_keywords.join(" OR "),
       
  1411         "pages" : max_pages,
       
  1412         "rpp" : 100,
       
  1413         "cbData" : function() {
       
  1414             _lb.attr("text", (this.tweets.length - this.currentPage + 1) + " tweets");
       
  1415             var _h = twCx.tlHeight * this.currentPage / this.pages;
       
  1416             _sq.animate({
       
  1417                 "y" : twCx.tlHeight - _h,
       
  1418                 "height" : _h
       
  1419             })
       
  1420         },
       
  1421         "cbEnd" : function() {
       
  1422             loadTweets(this.tweets);
       
  1423             setInterval(function() {
       
  1424                 getTweets({
       
  1425                     "social_network" : social_network,
       
  1426                     "keyword" : tracking_keywords.join(" OR "),
       
  1427                     "pages" : 1,
       
  1428                     "since_id" : twCx.idIndex[twCx.idIndex.length - 1],
       
  1429                     "rpp" : 100,
       
  1430                     "cbEnd" : function() {
       
  1431                         loadTweets(this.tweets, true);
       
  1432                     }
       
  1433                 });
       
  1434             }, 20000)
       
  1435         }
       
  1436     });
       
  1437 }
  1839 }
  1438 
  1840 
  1439 export {
  1841 export {
  1440     rolloverTweet,
  1842   rolloverTweet,
  1441     selectTweet,
  1843   selectTweet,
  1442     filtrerTexte,
  1844   filtrerTexte,
  1443     tweetPopup,
  1845   tweetPopup,
  1444     getSocialData,
  1846   getSocialData,
  1445     getFollowing
  1847   getFollowing,
  1446 }
  1848 };