PageSourceSearch

https://blog.museuciencies.cat/wp-content/plugins/tweet-blender/js/main.js?ver=4.7.29

js museuciencies.cat collected 2026-10-02 03:13:42 UTC 33,731 bytes, 1,078 lines download raw bytes

1/**
2 * @author http://kirill-novitchenko.com
3 * 
4 */
5
6var TB_version = '4.0.0b4',	// Plugin version 
7TB_rateLimitData,
8TB_tmp,
9TB_mode = 'widget',
10TB_started = false,
11TB_allSources = new Array(),
12jQnc = jQuery,
13TB_sourceCounts = new Array(),
14TB_sourceNames = new Array(),
15TB_seenTweets = new Array(),
16selectedCell,
17TB_token;
18
19// initialize each widget
20function TB_start() {
21
22	// prevent initializing twice
23	if (TB_started) {
24		return;
25	}
26	else {
27		TB_started = true;
28	}
29	
30	// check to make sure config is included
31	if (typeof(TB_config) == 'undefined') {
32		TB_showMessage(null,'noconf',TB_labels.no_config,true);
33		return;
34	}
35			
36	// process widget configuration
37	TB_config.widgets = new Object();
38	jQuery.each(jQuery('form.tb-widget-configuration'),function(i,obj){
39
40		var widgetConfId = obj.id,
41		widgetId,
42		widgetHTML,
43		needWidgetHTML = false,
44		nextTag = jQuery(obj).next(),
45		isChart = false;
46		
47		// if there is widget HTML div following the form we don't need to build HTML
48		if (nextTag.length > 0) { 
49			if (typeof(nextTag.attr('id')) != 'undefined' && nextTag.attr('id') != '') {
50				if (nextTag.attr('id').indexOf('-mc') > 0) {
51					widgetId = widgetConfId.substr(0, widgetConfId.length - 2);
52				}
53				else {
54					needWidgetHTML = true;
55					widgetId = widgetConfId;
56				}
57			}
58			else {
59				needWidgetHTML = true;
60				widgetId = widgetConfId;
61			}
62		}
63		// if it's just a form -> assume that's post/page body content
64		else {
65			needWidgetHTML = true;
66			widgetId = widgetConfId;
67		}
68		
69		TB_config.widgets[widgetId] = new Object;
70		
71		// set all properties
72		jQuery.each(jQuery('#'+widgetConfId).find('input'),function(j,property) {
73			TB_config.widgets[widgetId][property.name] = property.value;
74		});
75		
76		if (typeof(TB_config.widgets[widgetId].sources) != 'undefined') {
77			TB_allSources = TB_allSources.concat(TB_config.widgets[widgetId].sources.split(','));
78		}
79
80		if(typeof(TB_config.widgets[widgetId].chartType) != 'undefined') {
81			isChart = true;
82		}
83		
84		// if no view more url, use default
85		if(typeof(TB_config.widgets[widgetId].viewMoreUrl) == 'undefined') {
86			TB_config.widgets[widgetId].viewMoreUrl = TB_config.default_view_more_url;
87		}
88
89		if (needWidgetHTML) {
90			// add widget HTML
91			widgetHTML = '<div id="' + widgetId + '-mc"><div class="tb_header">' +
92				'<img class="tb_twitterlogo" src="' + TB_pluginPath + '/img/twitter-logo.png" alt="' + TB_labels.twitter_logo + '" />' +
93				'<div class="tb_tools" style="background-image:url(' + TB_pluginPath + '/img/bg_sm.png)">' +
94				'<a class="tb_infolink" href="http://kirill-novitchenko.com" title="' + TB_labels.kino + '" style="background-image:url(' + TB_pluginPath + '/img/info-kino.png)"> </a>' +
95				'<a class="tb_refreshlink" href="javascript:TB_blend(\'' + widgetId + '\');" title="' + TB_labels.refresh + '"><img src="' + TB_pluginPath + '/img/ajax-refresh-icon.gif" alt="' + TB_labels.refresh + '" /></a></div></div>';
96			
97			if (TB_config.general_seo_tweets_googleoff && !isChart) {
98				widgetHTML += '<!--googleoff: index--><div class="tb_tweetlist"></div><!--googleon: index-->';
99			}
100			else if (isChart){
101				widgetHTML += '<div id="' + widgetId + '-chart" class="tb_tweetchart"></div>';
102			}
103			else {
104				widgetHTML += '<div class="tb_tweetlist"></div>';
105			
106				widgetHTML += '<div class="tb_footer">';
107				if (!TB_config.archive_is_disabled) {
108					
109					if (typeof(TB_config.widgets[widgetId].viewMoreText) == 'undefined') {
110						TB_config.widgets[widgetId].viewMoreText = TB_labels.view_more + ' &raquo;';
111					}
112					
113					if (TB_config.widgets[widgetId].viewMoreUrl) {
114						widgetHTML += '<a class="tb_archivelink" href="' + TB_config.widgets[widgetId].viewMoreUrl + '">' + TB_config.widgets[widgetId].viewMoreText + '</a>';
115					}
116					else if (TB_config.default_view_more_url) {
117						widgetHTML += '<a class="tb_archivelink" href="' + TB_config.default_view_more_url + '">' + TB_config.widgets[widgetId].viewMoreText + '</a>';
118						TB_config.widgets[widgetId].viewMoreUrl = TB_config.default_view_more_url;
119					}
120				}
121				widgetHTML += '</div>';
122			}
123			
124			widgetHTML += '</div>';
125			
126			jQuery('#'+obj.id).after(widgetHTML);
127		}
128		
129		// if it's a chart - update width/height
130		if(isChart) {
131			
132			// determine width automatically, if needed
133			if (typeof(TB_config.widgets[widgetId].chartWidth) == 'undefined' || TB_config.widgets[widgetId].chartWidth <= 0) {
134				TB_config.widgets[widgetId].chartWidth = jQuery('#' + widgetId + '-chart').parent().width();
135			}
136			// determine height automatically, if needed
137			if (typeof(TB_config.widgets[widgetId].chartHeight) == 'undefined' || TB_config.widgets[widgetId].chartHeight <= 0) {
138				TB_config.widgets[widgetId].chartHeight = jQuery('#' + widgetId + '-chart').parent().width() + 60;
139			}
140		}
141	});
142
143	// if there are no widgets on the page - no need to continue
144	if (TB_getObjectSize(TB_config.widgets) < 1) {
145		return;
146	}
147	
148	// de-dupe list of all sources
149	TB_allSources = TB_getUniqueElements(TB_allSources);
150	
151	jQuery('div.tb_tools').css('background-image','url(' + TB_pluginPath + '/img/bg.png)').width(56);
152	jQuery('a.tb_infolink').css('display','inline').css('margin-right','11px');
153	
154	// if there is no archive page, hide view more links
155	if (!TB_config.default_view_more_url) {
156		jQuery('a.defaultUrl').hide();
157	}
158	
159	// get config options and blend
160	if (typeof(TB_config) != 'undefined') {
161				
162		// for each widget on the page
163		for (widgetId in TB_config.widgets) {
164			
165			if (typeof(TB_config.widgets[widgetId].sources) == 'undefined' || TB_config.widgets[widgetId].sources == '') {
166				TB_showMessage(widgetId,'nosrc',TB_labels.no_sources, true);
167			}
168			else {
169	
170				// create info box shown when Twitter logo is clicked
171				TB_initInfoBox(widgetId);
172				
173				// create all the urls for refresh calls
174				TB_makeAjaxURLs(widgetId);			
175	
176				// if it's not a chart, update values to reflect cache use if there are divs with tweets already
177				if (typeof(TB_config.widgets[widgetId].chartType) == 'undefined') {
178					TB_config.widgets[widgetId]['minTweetId'] = 0;
179					TB_config.widgets[widgetId]['maxTweetId'] = 0;
180					if (jQuery('#'+widgetId + '-mc > div.tb_tweetlist > div.tb_tweet').size() > 0) {
181						if (TB_tmp = jQuery('#'+widgetId + '-mc > div.tb_tweetlist > div:last').attr('id')) {
182							TB_config.widgets[widgetId]['minTweetId'] = TB_tmp;
183						}
184						if (TB_tmp = jQuery('#'+widgetId + '-mc > div.tb_tweetlist > div:first').attr('id')) {
185							TB_config.widgets[widgetId]['maxTweetId'] = TB_tmp;
186						}
187					}
188					TB_config.widgets[widgetId]['tweetsShown'] = jQuery('#'+widgetId + '-mc > div.tb_tweetlist').children('div').size();
189					
190					// wire mouse overs to existing tweets
191					jQuery.each(jQuery('#' + widgetId + '-mc > div.tb_tweetlist').children('div'),function(i,obj){ TB_wireMouseOver(obj.id); });
192	
193						// wire target="_blank" on links
194						jQuery('a.tb_photo, .tb_author a, .tb_msg a, .tweet-tools a, .tb_infolink').click(function(){
195							this.target = "_blank";
196						});
197				}
198		
199				// add automatic refresh
200				if (parseInt(TB_config.widgets[widgetId].refreshRate) > 1) {
201					setInterval('TB_blend(\''+widgetId+'\');',parseInt(TB_config.widgets[widgetId].refreshRate) * 1000);
202				}
203				
204				
205				// if we need to refresh once or 
206				// if there are no tweets shown from cache
207				// or if there are less tweets then needed
208				// or if it's a chart
209				// then blend right away
210				if (parseInt(TB_config.widgets[widgetId].refreshRate) == 1 || typeof(TB_config.widgets[widgetId].chartType) != 'undefined' || TB_config.widgets[widgetId].tweetsShown < TB_config.widgets[widgetId].tweetsNum) {
211					TB_blend(widgetId);
212				}
213			}
214		}
215	}
216	else {
217		TB_showMessage(null,'noconf',TB_labels.no_global_config,true);
218	
219		// disable refresh
220		jQuery('a.tb_refreshlink').remove();
221		jQuery('div.tb_tools').css('background-image','url(' + TB_pluginPath + '/img/bg_sm.png)').width(28);
222	}
223}
224
225
226// form refresh queries
227function TB_makeAjaxURLs(widgetId) {
228	var TB_searchTerms = new Array(),
229	TB_screenNameQueries = new Array(),
230	TB_screenNames = new Array(),
231	screenName = '',
232	modifier = '',
233	colonPos,
234	pipePos;
235		
236	TB_config.widgets[widgetId]['ajaxURLs'] = new Array();
237	
238	// if it's a chart widget, use all sources for the url
239	if (typeof(TB_config.widgets[widgetId].chartType) != 'undefined') {
240		TB_addAjaxUrl(widgetId,'chart-data',{'chart_type':TB_config.widgets[widgetId].chartType,'chart_period':TB_config.widgets[widgetId].chartPeriod},TB_config.widgets[widgetId].sources,0,null);
241		return;
242	}
243
244	// for other widget types, iterate over sources and make individual urls
245	jQuery.each(TB_config.widgets[widgetId].sources.split(','),function(i,src) {
246
247		// remove spaces
248		src = jQuery.trim(src);
249		
250		// if it's a screen name
251		if (src.charAt(0) == '@' && src.indexOf('/') == -1) {
252			
253			// if we are serving only favorites
254			if (TB_config.widgets[widgetId].favoritesOnly) {
255				screenName = src.substr(1);
256
257				TB_addAjaxUrl(widgetId,'favorites',{'screen_name':screenName},src,0,null);
258			}
259			// if it includes modifiers, use a one-off URL
260			else if ((pipePos = src.indexOf('|')) > 1) {
261
262				screenName = src.substr(1,pipePos-1);
263				modifier = src.substr(pipePos+1);
264				
265				// if modifier is a hashtag
266				if (modifier.charAt(0) == '#') {
267					TB_addAjaxUrl(widgetId,'search',{'from':screenName,'tag':modifier.substr(1)},src,modifier);
268				}
269				// else modifier is just a keyword
270				else {
271					TB_addAjaxUrl(widgetId,'search',{'from':screenName,'ors':modifier},src,modifier);
272				}
273			}
274			else {
275
276				screenName = src.substr(1);
277				
278				// if we are not using Search API
279				if (TB_config.advanced_no_search_api) {
280					TB_addAjaxUrl(widgetId,'user_timeline',{'screen_name':screenName},src,null);
281				}
282				// else, group with other screen names
283				else {
284					// check to make sure we are not over the query length limit
285					if (escape(TB_screenNameQueries.join(' OR ')).length + src.length > 140) {
286						TB_addAjaxUrl(widgetId,'search',{'q':TB_screenNameQueries.join(' OR ')},escape('@'+TB_screenNames.join(',@')),null);
287						TB_screenNames = new Array();
288						TB_screenNameQueries = new Array();
289					}
290					TB_screenNames.push(screenName);
291					if (TB_config.filter_hide_mentions) {
292						TB_screenNameQueries.push('from:' + screenName);
293					}
294					else {
295						TB_screenNameQueries.push(src + ' OR from:' + screenName);
296					}
297				}
298			}
299		}
300		// if it's a list
301		else if (src.charAt(0) == '@' && src.indexOf('/') > 1) {
302			
303			// if we have a modifier
304			if ((pipePos = src.indexOf('|')) > 1) {
305				
306				TB_addAjaxUrl(widgetId, 'list_timeline', {
307					'user': src.substr(1, src.indexOf('/') - 1),
308					'list': src.substr(src.indexOf('/') + 1, pipePos - src.indexOf('/') - 1)
309				}, src, src.substr(pipePos+1));
310			}
311			// if it's just a regular list
312			else {
313				TB_addAjaxUrl(widgetId, 'list_timeline', {
314					'user': src.substr(1, src.indexOf('/') - 1),
315					'list': src.substr(src.indexOf('/') + 1)
316				}, src, null);
317			}
318		}
319		// else it's a hash or keyword 
320		else if (src != '') {
321
322 			// if it's a multi-word keyword give it a dedicated ajax call
323			if (src.indexOf(' ') > 0) {
324				// if it's not in quotes already then add them
325				if (src.charAt(0) != '"') {
326					src = '"' + src + '"';
327				}
328			}
329
330			// check to make sure we are not over the query length limit
331			if (TB_searchTerms.join(' OR ').length + src.length > 140) {
332				TB_addAjaxUrl(widgetId,'search',{'q':TB_searchTerms.join(' OR ')},TB_searchTerms.join(','),null);
333				TB_searchTerms = new Array();
334			}
335			TB_searchTerms.push(src);
336		}
337	});
338	
339	// if there are terms that are not part of a query - add another query
340	if (TB_searchTerms.length > 0) {
341		TB_addAjaxUrl(widgetId,'search',{'q':TB_searchTerms.join(' OR ')},TB_searchTerms.join(','),null);
342	}
343	
344	// if there are screenNames - join them into a single query
345	if (TB_screenNames.length > 0) {
346		TB_addAjaxUrl(widgetId,'search',{'q':TB_screenNameQueries.join(' OR ')},encodeURI('@'+TB_screenNames.join(',@')),null);
347	}
348}
349
350function TB_addAjaxUrl(widgetId,actionType,queryData,src,modifier) {
351	
352	var url;
353	
354	// check language filter	
355	if (typeof(TB_config['filter_lang']) != 'undefined' && TB_config.filter_lang.length == 2) {
356		queryData.lang = TB_config.filter_lang;
357	}
358	else {
359		queryData.lang = 'all';
360	}
361			
362	if (actionType == 'search') {
363		queryData.action = actionType;
364		TB_config.widgets[widgetId]['ajaxURLs'].push({
365			'url':TB_pluginPath + '/ws.php',
366			'data':queryData,
367			'source':src,
368			'modifier':modifier
369		});
370	}
371	else if (actionType == 'list_timeline') {
372		queryData.action = actionType;
373
374		// remove unneeded params
375		delete queryData.nots;
376		delete queryData.lang;
377
378		TB_config.widgets[widgetId]['ajaxURLs'].push({
379			'url':TB_pluginPath + '/ws.php',
380			'data':queryData,
381			'source':src,
382			'modifier':modifier
383		});
384	}
385	else if (actionType == 'user_timeline') {
386		queryData.action = actionType;
387		TB_config.widgets[widgetId]['ajaxURLs'].push({
388			'url':TB_pluginPath + '/ws.php',
389			'data':queryData,
390			'source':src,
391			'modifier':modifier
392		});
393	}
394	else if (actionType == 'favorites') {
395		queryData.action = actionType;
396		TB_config.widgets[widgetId]['ajaxURLs'].push({
397			'data':queryData,
398			'url':TB_pluginPath + '/ws.php',
399			'source':src,
400			'modifier':modifier
401		});
402	}
403	else if (actionType == 'chart-data') {
404		queryData.action = actionType;
405		TB_config.widgets[widgetId]['ajaxURLs'].push({
406			'data':queryData,
407			'url':TB_C_pluginPath + '/ws.php',
408			'source':src,
409			'method':'post'
410		});
411	}
412}
413
414function TB_initInfoBox(widgetId) {
415	// create HTML for sources
416	TB_config.widgets[widgetId].sourcesHTML = '';
417	TB_config.widgets[widgetId].sourcesCount = 0;
418	jQuery.each(TB_config.widgets[widgetId].sources.split(','),function(i,src) {
419		// if there is a modifier - strip it
420		if ((pipePos = src.indexOf('|')) > 0) {
421			src = src.substr(0, pipePos);
422		}
423		
424		TB_config.widgets[widgetId].sourcesHTML += '<a href="';
425		if (src.charAt(0) == '@') {
426		 	TB_config.widgets[widgetId].sourcesHTML += 'http://twitter.com/' + src.substr(1);
427		}
428		else {
429		 	TB_config.widgets[widgetId].sourcesHTML += 'https://twitter.com/search?q=' + escape(src);
430		}
431		TB_config.widgets[widgetId].sourcesHTML += '">' + src + '</a> ';
432		TB_config.widgets[widgetId].sourcesCount++;
433	});		
434	
435	// add action to twitter logo
436	jQuery('#' + widgetId + '-mc').children('div.tb_header').children('img.tb_twitterlogo').click(function(){
437		TB_showMessage(widgetId,'info',TB_labels.version_msg.format(TB_version,TB_config.widgets[widgetId].sourcesHTML),false);
438	});	
439}
440
441function TB_blend(widgetId) {
442
443	// show loading indicator
444	TB_showLoader(widgetId);
445	
446	// if it's a chart
447	if (typeof(TB_config.widgets[widgetId].chartType) != 'undefined') {
448		
449		// if there is no chart yet
450		if (typeof(TB_config.widgets[widgetId].wrapper) == 'undefined') {
451			TB_C_makeChart(widgetId);
452		}
453		// if we have a chart
454		else {
455			// draw chart
456			TB_config.widgets[widgetId].wrapper.draw();
457		}
458
459		// hide loader
460		TB_hideLoader(widgetId);
461
462		return;
463	}
464
465	TB_getTweets(widgetId);
466}
467
468function TB_checkComplete(widgetId) {
469	
470	if (TB_config.widgets[widgetId].urlsDone == TB_config.widgets[widgetId].ajaxURLs.length) {
471
472		// hide loading message
473		TB_hideLoader(widgetId);
474
475		// if nothing added after we are through all sources let user know
476		if(jQuery('#' + widgetId + '-mc > div.tb_tweetlist').children('div').size() == 0) {
477			// show no tweets message
478			TB_showMessage(widgetId, 'notweets', TB_labels.no_tweets_msg.format(TB_config.widgets[widgetId].sourcesHTML), true);
479		}
480		else {
481			TB_hideMessage(widgetId,'notweets');
482		}
483	}
484}
485	
486function TB_getTweets(widgetId) {
487		
488	TB_config.widgets[widgetId]['urlsDone'] = 0;
489	
490	// iterate over AJAX URLs
491	jQuery.each(TB_config.widgets[widgetId].ajaxURLs,function(i,urlInfo) {
492	
493		// add sources to data
494		
495		jQuery.ajax({
496			data:urlInfo.data,
497			dataType: 'json',
498			url: urlInfo.url,
499			timeout: 6000,
500			success: function (json) {
501//console.log('got response');
502				// if we had valid JSON but with error
503				if (json.error) {
504					TB_config.widgets[widgetId].urlsDone++;
505					TB_checkComplete(widgetId);
506				}
507				else {
508//console.log('got results');
509					TB_addTweets(widgetId,json,urlInfo);
510				}
511			},
512			error: function(jqXHR, textStatus, errorThrown) {
513//console.log('error in ajax: ' + textStatus + ' err: ' + errorThrown);
514				TB_config.widgets[widgetId].urlsDone++;
515				TB_checkComplete(widgetId);
516			}
517		});
518	});
519}
520
521function TB_addTweets(widgetId,jsonData,urlInfo) {
522
523	var tweets = jsonData,
524	isNewTweet = false,
525	tb_tweet,
526	tweetExists;
527	
528	if (typeof(jsonData.statuses) != 'undefined') {
529		tweets = jsonData.statuses;
530	}
531
532//console.log(jsonData);
533	
534	jQuery.each(tweets,function(i,tweetJson) {
535	
536		// account for cached tweets returning div ID and json
537		if (typeof(tweetJson.tweet_json) != 'undefined'){
538			tb_tweet = new TB_tweet(tweetJson.tweet_json);
539		}
540		else {
541			tb_tweet = new TB_tweet(tweetJson);
542		}
543//console.log(i + ": " + tweetJson );
544		
545		tb_tweet.modifier = urlInfo.modifier;
546		
547		// make sure it's OK to show
548		if (!tb_tweet.isOKtoShow())	{
549			return true;
550		}
551
552		tb_tweet.selectRelevantSources(urlInfo.source);
553				
554		isNewTweet = false;
555
556		// if this tweet already in the set, skip it
557		tweetExists = false;
558		jQuery('div.tb_tweet').each(function() {
559			if (tb_tweet.isSameId(this.id)) {
560				tweetExists = true;
561			}
562		});
563		if (tweetExists) {
564			return true;
565		}
566		
567		// if this is the first tweet, just add it and set it to be both min and max
568		if (TB_config.widgets[widgetId].tweetsShown == 0) {
569			TB_config.widgets[widgetId].tweetsShown++;
570			TB_config.widgets[widgetId].minTweetId = tb_tweet.divId;
571			TB_config.widgets[widgetId].maxTweetId = tb_tweet.divId;			
572
573			// add at the end
574			jQuery('#'+widgetId+'-mc > div.tb_tweetlist').append(tb_tweet.getHTML());
575			
576			isNewTweet = true;
577		}
578		// if tweet older than the oldest
579		else if (tb_tweet.isOlderThan(TB_config.widgets[widgetId].minTweetId)) {
580			// if we are at max already, no need to work through the rest of this set as the rest will be older
581			if (TB_config.widgets[widgetId].tweetsShown >= TB_config.widgets[widgetId].tweetsNum) {
582				return false;
583			}
584			else {
585				TB_config.widgets[widgetId].tweetsShown++;
586
587				// add at the end
588				jQuery('#'+widgetId+'-mc > div.tb_tweetlist').append(tb_tweet.getHTML());
589
590				// make it the oldest
591				TB_config.widgets[widgetId].minTweetId = tb_tweet.divId;
592				
593				// if we have only one tweet then make it the newest as well
594				if (TB_config.widgets[widgetId].tweetsNum == 1) {
595					TB_config.widgets[widgetId].maxTweetId = tb_tweet.divId;
596				}
597				
598				isNewTweet = true;
599			}
600		}
601		// if tweet is newer than the newest
602		else if (tb_tweet.isNewerThan(TB_config.widgets[widgetId].maxTweetId)) {
603			// if we are at max already, remove bottom tweet
604			TB_enforceLimit(widgetId);
605			
606			// add in the beginning
607			jQuery('#'+widgetId+'-mc > div.tb_tweetlist').prepend(tb_tweet.getHTML());
608			TB_config.widgets[widgetId].tweetsShown++;
609
610			// make it the newest
611			TB_config.widgets[widgetId].maxTweetId = tb_tweet.divId;
612			
613			// if we have only one tweet then make it the oldest as well
614			if (TB_config.widgets[widgetId].tweetsNum == 1) {
615				TB_config.widgets[widgetId].minTweetId = tb_tweet.divId;
616			}
617
618			isNewTweet = true;
619		}
620		// if tweet is in the middle
621		else {
622			// if we are at max already, remove bottom tweet
623			TB_enforceLimit(widgetId);
624
625			// traverse currently shown tweets and insert in the appropriate spot
626			var prevTweetId = TB_config.widgets[widgetId].maxTweetId,
627			nextTweetId;
628			jQuery('#'+widgetId+'-mc > div.tb_tweetlist > div.tb_tweet').each(function(i,nextTweet){
629				nextTweetId = nextTweet.id;
630				if (tb_tweet.isOlderThan(prevTweetId) && tb_tweet.isNewerThan(nextTweetId)) {
631					jQuery('#'+prevTweetId).after(tb_tweet.getHTML());
632					TB_config.widgets[widgetId].tweetsShown++;
633					return false;
634				}
635				prevTweetId = nextTweetId;
636			});
637			
638			// if got to here and tweet still not there, make it the last
639			if (jQuery('#'+tb_tweet.divId).length <= 0) {
640					jQuery('#'+TB_config.widgets[widgetId].minTweetId).after(tb_tweet.getHTML());
641					TB_config.widgets[widgetId].minTweetId = tb_tweet.divId;
642					// if we have only one tweet then make it the newest as well
643					if (TB_config.widgets[widgetId].tweetsNum == 1) {
644						TB_config.widgets[widgetId].maxTweetId = tb_tweet.divId;
645					}
646					TB_config.widgets[widgetId].tweetsShown++;
647			}
648			
649			isNewTweet = true;
650		}
651		
652		// wire mouseover action items
653        TB_wireMouseOver(tb_tweet.divId);
654		
655		// if filtering out same tweets - add text to seen tweets
656		if (TB_config.filter_hide_same_text) {
657			TB_seenTweets.push(tb_tweet.jsonCode.text);
658		}
659	});
660	
661	TB_config.widgets[widgetId].urlsDone++;
662	
663	// wire target="_blank" on links
664	jQuery('a.tb_photo, .tb_author a, .tb_msg a, .tweet-tools a, .tb_infolink').click(function(){
665		this.target = "_blank";
666	});
667	
668	TB_checkComplete(widgetId);
669}
670
671function TB_wireMouseOver(tweetId) {
672	// wire mouseover action items
673    if(TB_config[TB_mode + '_show_reply_link'] || TB_config[TB_mode + '_show_follow_link']) {
674		jQuery('#'+tweetId).hover(
675		      function () {
676				jQuery(this).find("div:last").slideDown()
677		      }, 
678		      function () {
679		        jQuery(this).find("div:last").slideUp();
680		      }
681		);
682	}		
683}
684
685function TB_enforceLimit(widgetId) {
686	
687	if (TB_config.widgets[widgetId].tweetsShown == TB_config.widgets[widgetId].tweetsNum) {
688		var lastTweet = jQuery('#' + TB_config.widgets[widgetId].minTweetId),
689		nextToLastTweet = lastTweet.prev('div.tb_tweet');
690		
691		// remove last tweet
692		lastTweet.remove();
693		TB_config.widgets[widgetId].tweetsShown--;
694				
695		// if no tweets left, reset min and max and finish
696		if (TB_config.widgets[widgetId].tweetsShown == 0) {
697			TB_config.widgets[widgetId].minTweetId = 0;
698			TB_config.widgets[widgetId].maxTweetId = 0;
699			return;
700		}
701		else {
702			// make next to last to be last now
703			if(nextToLastTweet.length > 0) {
704				TB_config.widgets[widgetId].minTweetId = nextToLastTweet.attr('id');
705			}
706		}
707	}
708}
709
710function TB_showLoader(widgetId) {
711	// if there are not tweets, show loading message
712	if(TB_config.widgets[widgetId].tweetsShown == 0) {
713		TB_showMessage(widgetId,'loading',TB_labels.loading_msg,true);
714	}
715	// show animated icon
716	jQuery('#' + widgetId + '-mc > div.tb_header > div.tb_tools > a.tb_refreshlink > img').attr('src',TB_pluginPath + '/img/ajax-refresh.gif');
717	jQuery('#' + widgetId + '-mc > div.tb_header > div.tb_tools > a.tb_refreshlink').addClass('loading');
718}
719
720function TB_hideLoader(widgetId) {
721	// hide loading message
722	TB_hideMessage(widgetId,'loading');
723
724	// show static icon
725	jQuery('#' + widgetId + '-mc > div.tb_header > div.tb_tools > a.tb_refreshlink > img').attr('src',TB_pluginPath + '/img/ajax-refresh-icon.gif');
726	jQuery('#' + widgetId + '-mc > div.tb_header > div.tb_tools > a.tb_refreshlink').removeClass('loading');
727}
728
729function TB_showMessage(widgetId, messageId, msg, keepOnScreen){
730
731	// if no widgetId is given -> show message in all widgets and ignore keepOnScreen
732	if(!widgetId) {
733		jQuery('div.tb_tweetlist').before('<div id="msg_' + messageId + '" class="tb_msg" style="display:none;">' + msg + '</div>');
734		return;
735	}
736	
737	// if it doesn't exist
738	if (!jQuery('#' + widgetId + '-mc').children('#msg_' + messageId).length) {
739		jQuery('#' + widgetId + '-mc').children('div.tb_tweetlist').before('<div id="msg_' + messageId + '" class="tb_msg" style="display:none;">' + msg + '</div>');
740		jQuery('#' + widgetId + '-mc').children('#msg_' + messageId).slideDown();
741		if (!keepOnScreen) {
742			setTimeout('TB_hideMessage("' + widgetId + '","' + messageId + '")', 8000);
743		}
744	}
745	// else if it's hidden
746	else if (jQuery('#' + widgetId + '-mc').children('#msg_' + messageId).is(':hidden')) {
747		jQuery('#' + widgetId + '-mc').children('#msg_' + messageId).slideDown();
748	}
749}
750
751function TB_hideAllMessages() {
752	jQuery('div.tb_msg').slideUp(1000,function(){jQuery('div.tb_msg').remove()});
753}
754
755function TB_hideMessage(widgetId,messageId) {
756	jQuery('#' + widgetId + '-mc').children('#msg_' + messageId).slideUp(1000,function(){jQuery('#' + widgetId + '-mc').children('#msg_' + messageId).remove()});
757}
758
759// TB Class for tweets
760function TB_tweet(tweetJson) {
761
762	// raw JSON for the tweet
763	this.jsonCode  = tweetJson;
764		
765	// tweetDate property
766	this.tweetDate = TB_str2date(tweetJson.created_at);
767	
768	// author screen name property
769	if (typeof(tweetJson.from_user) != 'undefined') {
770		this.screenName = tweetJson.from_user;
771	}
772	else if (typeof(tweetJson.user.screen_name) != 'undefined') {
773		this.screenName = tweetJson.user.screen_name;
774	}
775	
776	// id of the tweet
777	this.id = tweetJson.id_str;
778		
779	// sources relevant to this tweet
780	this.sources = '';
781	
782	// process sources and select the ones relevant to this tweet
783	this.selectRelevantSources = function(urlSources) {
784		var allSources = new Array(),
785		sourceScreenName = '@'+this.screenName,
786		jsonCode = this.jsonCode;
787		jQuery.each(urlSources.split(','),function(i,src) {
788			if (sourceScreenName == src || jsonCode.text.indexOf(src) > 0){
789				allSources.push(src);
790			}
791		});
792		//  set property with relevant sources
793		if (allSources.length > 0) {
794			this.sources = allSources.join(',');
795		}
796		// just in case to make sure we don't have empty source
797		else {
798			this.sources = urlSources;
799		}
800	}
801	
802	/* setup template
803	 * the following placeholders will be used
804	 * {0} - tweet div id
805	 * {1} - screen name
806	 * {2} - profile image url
807	 * {3} - tweet message
808	 * {4} - tweet id as string
809	 * {5} - date
810	 * {6} - source as link
811	 * {7} - author name
812	 */
813	// if user supplied a custom format - use it
814	var template;
815	if (TB_config.custom_template) {
816		template = TB_config.custom_template;
817	}
818	// if not, use default format
819	else {
820		template = '<div id="{0}" class="tb_tweet">';
821		template += '<a class="tb_photo" rel="nofollow" href="https://twitter.com/{1}"><img src="{2}" alt="{1}"></a>';
822		template += '<span class="tb_author">{7}<br /><a rel="nofollow" href="https://twitter.com/{1}">{1}</a>: </span> ';
823		template += '<span class="tb_msg">{3}</span><br />';
824		// start tweet footer with info
825		if (!TB_config.general_seo_tweets_googleoff && TB_config.general_seo_footer_googleoff) {
826			template += '<!--googleoff: index-->';
827		}
828		template += ' <span class="tb_tweet-info">';
829		// show timestamp
830		template += '<a rel="nofollow" href="http://twitter.com/{1}/statuses/{4}">{5}</a>';		
831		// show source if requested
832		if (TB_config['widget_show_source'] && tweetJson.source) {
833			template += ' ' + TB_labels.from + ' {6}';
834		}
835		// end tweet footer
836		template += '</span>';
837		if (!TB_config.general_seo_tweets_googleoff && TB_config.general_seo_footer_googleoff) {
838			template += '<!--googleon: index-->';
839		}
840		// add tweet tools
841	   if (TB_config.widget_show_follow_link || TB_config.widget_show_reply_link) {
842			template += '<div class="tweet-tools" style="display:none;">';
843	        if (TB_config.widget_show_reply_link) {
844	        	template += '<a rel="nofollow" href="http://twitter.com/home?status=@{1}%20&in_reply_to_status_id={4}&in_reply_to={1}">' + TB_labels.reply + '</a>';
845	        }
846	        if (TB_config.widget_show_follow_link && TB_config.widget_show_reply_link) {
847	        	template += ' | ';
848	        }
849	        if (TB_config.widget_show_follow_link) {
850	        	template += '<a rel="nofollow" href="http://twitter.com/{1}">' + TB_labels.follow + ' {1}</a>';
851	        }
852	        template += '</div>'; 
853		}
854		// end tweet	
855		template += "</div>\n";
856	}
857	
858	// outputs current template
859	getTemplate = function() {
860		return template;
861	}
862	
863	// creates unique div ID for this tweet
864	getDivId = function(tweetDate,screenName,strId) {
865		return 't-' + tweetDate.getTime() + '-' + screenName + '-' + strId;	
866	}
867
868	// div id of the tweet
869	this.divId = getDivId(this.tweetDate,this.screenName,this.id);
870	
871	// makes HTML for each tweet
872	this.getHTML = function() {
873		/* the following placeholders will be used in the template
874		 * {0} - tweet div id
875		 * {1} - screen name
876		 * {2} - profile image url
877		 * {3} - tweet message
878		 * {4} - tweet id as string
879		 * {5} - date
880		 * {6} - source as link
881		 */
882		var textHtml = this.jsonCode.text,
883		nameHtml,
884		imageUrl = '',
885		dateHtml = '',
886		sourceHtml = '';
887
888		if (typeof(TB_sourceNames[this.screenName.toLowerCase()]) != 'undefined') {
889			nameHtml = TB_sourceNames[this.screenName.toLowerCase()];
890		}
891		else {
892			nameHtml =  this.screenName;
893		}
894				
895		// link URLs
896		textHtml = textHtml.replace(/(https?:\/\/\S+)/gi, '<a rel="nofollow" href="$1">$1</a>');
897		// link screen names
898		textHtml = textHtml.replace(/\@([\w]+)/gi,'<a rel="nofollow" href="http://twitter.com/$1">@$1</a>'); 
899		// link hash tags
900		textHtml = textHtml.replace(/\#(\S+)/gi,'<a rel="nofollow" href="http://twitter.com/search?q=%23$1">#$1</a>'); 
901
902		if (tweetJson.profile_image_url) {
903			imageUrl = tweetJson.profile_image_url;
904		}
905		else {
906			imageUrl = tweetJson.user.profile_image_url;
907		}
908		// make date
909		if (TB_config.general_timestamp_format) {
910			if (typeof(jQuery.PHPDate) != 'undefined') {
911				dateHtml += jQuery.PHPDate(TB_config.general_timestamp_format,this.tweetDate);
912			}
913			else if (typeof(jQnc.PHPDate) != 'undefined') {
914				dateHtml += jQnc.PHPDate(TB_config.general_timestamp_format,this.tweetDate);
915			}
916		}
917		else {
918			dateHtml += TB_verbalTime(this.tweetDate);
919		} 
920		// if source is url encoded -> decode
921		if (tweetJson.source.indexOf('&lt;') >= 0) {
922			sourceHtml += jQuery('<textarea/>').html(tweetJson.source).val();
923		}
924		// else use as is
925		else {
926			sourceHtml += tweetJson.source;
927		}
928
929		// return formatted string
930		return template.format(this.divId,'@' + nameHtml,imageUrl,textHtml,tweetJson.id_str,dateHtml,sourceHtml,this.jsonCode.user.name);
931	}
932	
933	this.isNewerThan = function(TB_tweetId) {
934		var tweetIdParts,
935		ourTimeStamp,
936		otherTweetTimeStamp,
937		otherTweetScreenName;
938		
939		// if other tweet's ID is not defined - assume we are newer
940		if (typeof(TB_tweetId) == 'undefined') {
941			return true;
942		}
943		// if it's some weird format - assume we are newer
944		else if (TB_tweetId.indexOf('-') <= 0) {
945			return true;
946		}
947		// else, prepare for real comparisons
948		else {
949			tweetIdParts = TB_tweetId.split('-');
950			otherTweetTimeStamp = tweetIdParts[1];
951			otherTweetScreenName = tweetIdParts[2];
952			ourTimeStamp = this.tweetDate.getTime();
953		}
954
955		// if our timestamp is later
956		if (ourTimeStamp > otherTweetTimeStamp) {
957			return true;
958		}
959		// if timestamp is older
960		else if (ourTimeStamp < otherTweetTimeStamp) {
961			return false;
962		}
963		// if timestamps are same but users are different
964		else if (this.screenName != otherTweetScreenName){
965			return true;
966		}
967		// if timestamps are same and users same
968		else {
969			return false;
970		}
971	}
972	
973	this.isOlderThan = function(TB_tweetId) {
974		return !this.isNewerThan(TB_tweetId);
975	}
976	
977	
978	this.isSameId = function(TB_tweetId) {
979
980		var parts1, parts2;
981
982		// if other tweet's ID is not defined - assume we are not the same
983		if (typeof(TB_tweetId) == 'undefined') {
984			return false;
985		}
986		// if it's some weird format - assume we are not the same
987		else if (TB_tweetId.indexOf('-') <= 0) {
988			return false;
989		}
990		// else, do the real comparisons
991		else {
992			
993			parts1 = TB_tweetId.split('-');
994			parts2 = this.divId.split('-');
995			
996			// if one of the IDs is in old format - compare only the first two parts
997			if (parts1.length == 3 || parts2.length == 3) {
998				return (parts1[1] == parts2[1] && parts1[2] == parts2[2]);
999			}
1000			// for new formats - compare all 3 parts
1001			else {
1002				return (parts1[1] == parts2[1] && parts1[2] == parts2[2] && parts1[3] == parts2[3]);
1003			}
1004		}
1005	}
1006	
1007	/* returns true if this tweet doesn't contain any words that are supposed to be filtered out 
1008	 * and if it's not supposed to be hidden due to other criteria
1009	 */
1010	this.isOKtoShow = function() {
1011		
1012		var i;
1013
1014		// if we have a modifier on that source and text doesn't contain it
1015		if (this.modifier) {
1016			if (this.modifier.length > 0 && this.jsonCode.text.indexOf(this.modifier) < 0) {
1017				return false;
1018			}
1019		}
1020		
1021		// if we don't show tweets with same content
1022		if (TB_config.filter_hide_same_text) {
1023			if (jQuery.inArray(this.jsonCode.text,TB_seenTweets) > 0) {
1024				return false;
1025			}
1026		}
1027
1028		// if this is a reply
1029		if (this.jsonCode.in_reply_to_user_id || this.jsonCode.in_reply_to_status_id || this.jsonCode.in_reply_to_screen_name) {
1030			// if we don't show replies
1031			if (TB_config.filter_hide_replies) {
1032				return false;
1033			}
1034		}
1035		// else, if it's not a reply but we are showing only replies
1036		else if (TB_config.filter_hide_not_replies) {
1037			return false;
1038		}
1039		
1040		// if it's a retweet and we are hiding retweets
1041		if (this.jsonCode.retweeted_status && TB_config.filter_hide_retweets) {
1042			return false;
1043		}
1044		
1045
1046		// if there are filtered words and the tweet text matches any of them - skip this tweet
1047		if (typeof(TB_config['filter_bad_strings']) != 'undefined' && TB_config.filter_bad_strings.length > 0) {
1048			badStrings = TB_config.filter_bad_strings.base64_decode().split(',');
1049			for (i = 0; i < badStrings.length; i++) {
1050				if (this.jsonCode.text.indexOf(badStrings[i]) >= 0 || this.screenName.indexOf(badStrings[i]) >= 0) {
1051					return false;
1052				}
1053			}
1054		}
1055		
1056		// if throttling is ON and we are at max for this user, skip it
1057		if (TB_config.filter_limit_per_source > 0) {
1058			if (typeof(TB_sourceCounts[this.screenName]) != 'undefined' || TB_sourceCounts[this.screenName] == 0) {
1059			
1060				if (TB_sourceCounts[this.screenName] >= TB_config.filter_limit_per_source) {
1061					return false;
1062				}
1063				else {
1064					TB_sourceCounts[this.screenName]++;
1065				}
1066			}
1067			else {
1068				TB_sourceCounts[this.screenName] = 1;
1069			}
1070		}
1071		
1072		return true;
1073	}
1074	
1075}
1076
1077// initialize
1078TB_addLoadEvent(TB_start); jQuery(document).ready(TB_start);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.