PageSourceSearch

https://www.eastcoastcars.net/v9/media/js/jtweetsanywhere/v1.2.1/jquery.jtweetsanywhere-1.2.1.js

js eastcoastcars.net collected 2026-09-24 12:20:12 UTC 55,970 bytes, 1,821 lines download raw bytes

1/*
2 * jTweetsAnywhere V1.2.1
3 * http://thomasbillenstein.com/jTweetsAnywhere/
4 *
5 * Copyright 2010, Thomas Billenstein
6 * Licensed under the MIT license.
7 * http://thomasbillenstein.com/jTweetsAnywhere/license.txt
8 */
9(function($)
10{
11	$.fn.jTweetsAnywhere = function(options)
12	{
13		// setup the default options
14		var options = $.extend(
15		{
16			/**
17			 * The user's name who's tweet feed or list feed is displayed. This
18			 * param is also used when a Twitter "Follow Button" is displayed. Usually
19			 * this param is a string, but can also be an array of strings. If an array
20			 * is supplied (and the params 'list' and 'searchParams' are null), a
21			 * combined feed of all users is displayed.
22			 *
23			 * Sample: 'tbillenstein' or ['twitterapi', '...', '...']
24			 */
25			username: 'tbillenstein',
26
27			/**
28			 * The name of a user's list where the tweet feed is generated from
29			 */
30			list: null,
31
32			/**
33			 * A single search param string or an array of search params, to be used in
34			 * a Twitter search call. All Twitter Search Params are supported
35			 * See here for the details:
36			 * http://apiwiki.twitter.com/Twitter-Search-API-Method%3A-search
37			 *
38			 * Sample: 'q=twitter' or ['q=twitter', 'geocode=48.856667,2.350833,30km']
39			 */
40			searchParams: null,
41
42			/**
43			 * The number of tweets shown in the tweet feed. If this param is 0, no feed
44			 * is displayed. For user or list feeds the maximum count is 20, for search
45			 * results the maximum count is 100.
46			 *
47			 * Unlike in previous releases, since 1.2.0 jTweetsAnywhere is based on a
48			 * tweets caching algorithm that will always deliver the requested count of
49			 * tweets accepting that this request can only be fullfilled by calling Twitter
50			 * more than once.
51			 *
52			 * IMPORTANT: Please always keep in mind, that the use of the Twitter API is
53			 * rate limited. Non-authenticated users are rated IP-based and you have only
54			 * 150 calls per hour available. Every retrieval of tweets counts and so does
55			 * for example hovering over a profile image to show the hovercard.
56			 * jTweetsAnywhere will always check the remaining count of free API calls before
57			 * actually calling Twitter to avoid black listing your visitor's IP.
58			 */
59			count: 0,
60
61			/**
62			 * A flag (true/false) that specifies whether to display a profile image in
63			 * tweets. If the param is set to null (the default value), a profile image
64			 * is displayed only if the feed represents a user's list or the result of a
65			 * Twitter search.
66			 *
67			 * THIS OPTION IS DEPRECATED. You should use showTweetFeed.showProfileImages
68			 * instead.
69			 */
70			tweetProfileImagePresent: null,
71
72			/**
73			 * Each tweet that is loaded from Twitter will pass the tweetFilter. if
74			 * the filter returns true, the tweet will be added to the tweets cache
75			 * otherwise it is ignored. The defaultTweetFilter alsways retruns true
76			 * but you can supply your own tweet filter to customize the tweet feed.
77			 */
78			tweetFilter: defaultTweetFilter,
79
80			/**
81			 * A flag (true/false) that specifies whether to display a Tweet Feed
82			 * or an object literal representing the configuration options for the
83			 * Tweet Feed. This flag works in conjunction with the count parameter:
84			 * - if count equals 0, no feed is displayed, ignoring showTweetFeed
85			 * - if count not equals 0 and showTweetFeed equals false, no feed
86			 *   is displayed
87			 * {
88			 *     expandHovercards: false,		// Boolean - Show Hovercards in expanded mode.
89			 *
90			 *     showTimestamp: true, 		// A flag (true/false) that specifies whether to display a tweet's timestamp
91			 * 									// or an object literal representing the configuration options for the
92			 * 									// timestamp.
93			 * 									// {
94			 * 									//     refreshInterval: 0,	// Time in seconds to be waited until the
95			 * 									//							// timestamps of the displayed tweets get refreshed
96		     *									// 							// 0 means no refreshing.
97			 * 									// }
98			 *
99			 *     showSource: false,			// Boolean - Show info about the source of the tweet.
100			 *
101			 *     showGeoLocation: true,		// Boolean - Show geolocation info and link to Google maps.
102			 *
103			 *     showInReplyTo: true,			// Boolean - Show link to the "replied to" tweet (if available).
104			 *
105			 *     showProfileImages: null,		// A flag (true/false) that specifies whether to display a profile image in
106			 * 									// tweets. If the param is set to null (the default value), a profile image
107			 * 									// is displayed only if the feed represents a user's list or the result of a
108			 * 									// Twitter search.
109			 *
110			 *     showUserScreenNames: null,	// A flag (true/false/null) that specifies whether to display a username in
111			 * 									// tweets. If the param is set to null (the default value), a username
112			 * 									// is displayed only if the feed represents a user's list or the result of a
113			 * 									// Twitter search.
114			 *
115			 *     showUserFullNames: false,	// A flag (true/false/null) that specifies whether to display a user's full name
116			 * 									// in tweets. If the param is set to null, a user's full name
117			 * 									// is displayed only if the feed represents a user's list or the result of a
118			 * 									// Twitter search.
119			 *
120			 *	   includeRetweets: true,		// Boolean - Include native retweets in a user's tweet feed
121			 *
122			 *     paging:						// An object literal representing the configuration options for the
123			 *     {							// paging support, that specifies how more/earlier tweets can be loaded
124			 *         mode: "none"		   		// by using the supplied UI controls (more/next buttons, scrollbar).
125			 *     },                           // Accepted values for mode are: "none" | "more" | "prev-next" | "endless-scroll"
126			 *									// if mode equals "endless-scroll" you have to set the height of the tweet feed
127			 *									// element (.jta-tweet-list) in your CSS to get a scrollbar! You should also set
128			 *									// the "overflow" attribute to "auto".
129			 *
130		     *     autorefresh:					// An object literal representing the configuration options for the
131		     *	   {							// autorefresh behaviour.
132		     *
133		     *									// IMPORTANT: Please always keep in mind, that using the Twitter API is rate
134		     *									// limited. Non-authenticated users are rated IP-based and you have only 150
135		     *									// calls per hour available. Every retrieval of tweets counts and s
135o does for
136		     *									// example hovering over a profile image to show the hovercard. jTweetsAnywhere will
137			 *									// always check the remaining count of free API calls before actually calling
138			 *									// Twitter to avoid black listing your visitor's IP.
139			 *
140			 * 									// However - choose your settings wisely to keep your visitors happy. An update
141			 *									// interval of 30 seconds on a feed that is updated averaged once per hour
142			 *									// does not make sense and is a total waste of remaining API calls!
143			 *
144		     *	       mode: "none",            // Accepted values for mode are: "none" | "auto-insert" | "trigger-insert"
145		     *									// "none" (the default value) - disables the autorefresh feature
146		     *									// "auto-insert" - automatically insert the new tweets on top of the tweet feed
147		     *									// "trigger-insert" - if new tweets arrived, show or update a button that displays
148		     *									// the number of new tweets. These new tweets are inserted on top of the tweet
149		     *									// feed, if the user clicks on the button.
150		     *
151		     *	       interval: 60,			// Time in seconds to be waited until the next request for new tweets. Minimum
152		     *									// value is 30.
153		     *
154		     *         duration: 3600			// Time in seconds for how long the autorefresh will be active. After
155		     *									// this period of time, autorefreshing will stop. A value of -1 means
156		     *									// autorefresh for ever.
157		     *    }
158			 * }
159			 */
160			showTweetFeed: true,
161
162			/**
163			 * A flag (true/false) that specifies whether to display a Twitter "Follow
164			 * Button".
165			 */
166			showFollowButton: false,
167
168			/**
169			 * A flag (true/false) that specifies whether to display a Twitter "Connect
170			 * Button" or an object literal representing the configuration options for
171			 * the "Tweet Box".
172			 * {
173			 *     size: 'medium'				// String - The size of the Connect Button. Valid values are: small, medium, large, xlarge
174			 * }
175			 */
176			showConnectButton: false,
177
178			/**
179			 * A flag (true/false) that specifies whether to display Login Infos.
180			 */
181			showLoginInfo: false,
182
183			/**
184			 * A flag (true/false) that specifies whether to display a Twitter "Tweet
185			 * Box" or an object literal representing the configuration options for
186			 * the "Tweet Box".
187			 * {
188			 *     counter: true,				// Boolean - Display a counter in the Tweet Box for counting characters
189			 *     width: 515,					// Number - The width of the Tweet Box in pixels
190			 *     height: 65,					// Number - The height of the Tweet Box in pixels
191			 *     label: "What's happening",	// String - The text above the Tweet Box, a call to action
192			 *     defaultContent: <none>,		// String - Pre-populated text in the Tweet Box. Useful for an @mention, a #hashtag, a link, etc.
193			 *     onTweet: <none>				// Function - Specify a listener for when a tweet is sent from the Tweet Box. The listener receives two arguments: a plaintext tweet and an HTML tweet
194			 * }
195			 */
196			showTweetBox: false,
197
198			/**
199			 * A decorator is a function that is responsible for constructing a certain
200			 * element of the widget. Most of the decorators return a HTML string.
201			 * Exceptions are the mainDecorator, which defines the basic sequence of
202			 * the widget's components, plus the linkDecorator, the usernameDecorator
203			 * and the hashtagDecorator which return the string that is supplied as a
204			 * function param, enriched with the HTML tags.
205			 *
206			 * For details, see the implementations of the default decorators. Each
207			 * default decorator can be overwritten by your own implementation.
208			 */
209			mainDecorator: defaultMainDecorator,
210
211			tweetFeedDecorator: defaultTweetFeedDecorator,
212
213			tweetDecorator: defaultTweetDecorator,
214			tweetProfileImageDecorator: defaultTweetProfileImageDecorator,
215			tweetBodyDecorator: defaultTweetBodyDecorator,
216			tweetUsernameDecorator: defaultTweetUsernameDecorator,
217			tweetTextDecorator: defaultTweetTextDecorator,
218			tweetAttributesDecorator: defaultTweetAttributesDecorator,
219			tweetTimestampDecorator: defaultTweetTimestampDecorator,
220			tweetSourceDecorator: defaultTweetSourceDecorator,
221			tweetGeoLocationDecorator: defaultTweetGeoLocationDecorator,
222			tweetInReplyToDecorator: defaultTweetInReplyToDecorator,
223			tweetRetweeterDecorator: defaultTweetRetweeterDecorator,
224
225			tweetFeedControlsDecorator: defaultTweetFeedControlsDecorator,
226			tweetFeedControlsMoreBtnDecorator: defaultTweetFeedControlsMoreBtnDecorator,
227			tweetFeedControlsPrevBtnDecorator: defaultTweetFeedControlsPrevBtnDecorator,
228			tweetFeedControlsNextBtnDecorator: defaultTweetFeedControlsNextBtnDecorator,
229
230			tweetFeedAutorefreshTriggerDecorator: defaultTweetFeedAutorefreshTriggerDecorator,
231			tweetFeedAutorefreshTriggerContentDecorator: defaultTweetFeedAutorefreshTriggerContentDecorator,
232
233			connectButtonDecorator: defaultConnectButtonDecorator,
234
235			loginInfoDecorator: defaultLoginInfoDecorator,
236			loginInfoContentDecorator: defaultLoginInfoContentDecorator,
237
238			followButtonDecorator: defaultFollowButtonDecorator,
239
240			tweetBoxDecorator: defaultTweetBoxDecorator,
241
242			linkDecorator: defaultLinkDecorator,
243			usernameDecorator: defaultUsernameDecorator,
244			hashtagDecorator: defaultHashtagDecorator,
245
246			loadingDecorator: defaultLoadingDecorator,
247			errorDecorator: defaultErrorDecorator,
248			noDataDecorator: defaultNoDataDecorator,
249
250			/**
251			 * Formatters are currently used for date format processing only.
252			 *
253			 * The tweetTimestampFormatter formats the tweet's timestamp to be shown
254			 * in the tweet attributes section
255			 *
256			 * For details, see the implementation of the defaultTweetTimestampFormatter.
257			 */
258			tweetTimestampFormatter : defaultTweetTimestampFormatter,
259
260			/**
261			 * The tweetTimestampTooltipFormatter formats the tweet's timestamp to be shown
262			 * in the tooltip when hovering over the timestamp link.
263			 */
264			tweetTimestampTooltipFormatter : defaultTweetTimestampTooltipFormatter,
265
266			/**
267			 * A visualizer is a function that is responsible for adding one or more
268			 * elements to the DOM and thereby making them visible to the user.
269			 * A visualizer might also be responsible to do the opposite effect:
270			 * To remove one or more elements from the DOM.
271			 *
272			 * The tweetVisualizer gets called each time a tweet element should be
273			 * appended or prepended to the tweet feed element.
274			 *
275			 * For details, see the implementation of the defaultTweetVisualizer.
276			 *
277			 * Each default visualizer can be overwritten by your own implementation.
278			 */
279			tweetVisualizer: defaultTweetVisualizer,
280
281			/**
282			 * The loadingIndicatorVisualizer gets called each time data is retrieved
283			 * from Twitter to visualize the loading indicator. This visualizer is also
284			 * used to hide the loading indicator.
285			 *
286			 * For details, see the implementation of the defaultLoadingIndicatorVisualizer.
287			 */
288			loadingIndicatorVisualizer: defaultLoadingIndicatorVisualizer,
289
290			/**
291			 * The autorefreshTriggerVisualizer will be called if the autorefresh
292			 * trigger should be visualized or hidden.
293			 *
294			 * For details, see the implementation of the autorefreshTriggerVisualizer.
295			 */
296			autorefreshTriggerVisualizer: defaultAutorefreshTriggerVisualizer,
297
298			/**
299			 * An event handler is a function that gets called whenever the event you
300			 * are interested in, occurs.
301			 *
302			 * The onDataRequest event handler will be called immediatly before calling
303			 * Twitter to retrieve new data and gives you the opportunity to deny
304			 * the call by returning false from the function.
305			 *
306			 * This feature might be used in conjunction with the paging feature,
307			 * especially when using the "endless-scroll" paging mode, to avoid the
308			 * exhaustion of remaining Twitter API calls, before the rate limit is
309			 * reached. The stats parameter contains statistical infos and counters
310			 * that you can examine to base your decision whether to return true or
311			 * false.
312			 */
313			onDataRequestHandler: defaultOnDataRequestHandler,
314
315			/**
316			 * The onRateLimitData event handler is called each time
317			 * jTweetsAnywhere retrieved the rate limit data from Twitter. The actual
318			 * rate limit data is contained in the stats object.
319			 */
320			onRateLimitDataHandler: defaultOnRateLimitDataHandler,
321
322			_tweetFeedConfig:
323			{
324				expandHovercards: false,
325				showTimestamp:
326				{
327					refreshInterval: 0
328				},
329				showSource: false,
330				showGeoLocation: true,
331				showInReplyTo: true,
332				showProfileImages: null,
333				showUserScreenNames: null,
334				showUserFullNames: false,
335				includeRetweets: true,
336				paging:
337				{
338					mode: "none",
339					_limit: 0,
340					_offset: 0
341				},
342				autorefresh:
343				{
344					mode: "none",
345					interval: 60,
346					duration: 3600,
347					_startTime: null,
348					_triggerElement: null
349				},
350				_pageParam: 0,
351				_maxId: null,
352				_recLevel: 0,
353				_noData: false,
354				_clearBeforePopulate: false
355			},
356			_tweetBoxConfig:
357			{
358				counter: true,
359				width: 515,
360				height: 65,
361				label: "What's happening?",
362				defaultContent: '',
363				onTweet: function(textTweet, htmlTweet) {}
364			},
365			_connectButtonConfig:
366			{
367				size: "medium"
368			},
369			_baseSelector: null,
370			_baseElement: null,
371			_tweetFeedElement: null,
372			_tweetFeedControlsElement: null,
373			_followButtonElement: null,
374			_loginInfoElement: null,
375			_connectButtonElement: null,
376			_tweetBoxElement: null,
377			_loadingIndicatorElement: null,
378			_noDataElement: null,
379			_tweetsCache: [],
380			_autorefreshTweetsCache: [],
381			_stats:
382			{
383				dataRequestCount: 0,
384				rateLimitPreventionCount: 0,
385				rateLimit:
386				{
387					remaining_hits: 150,
388					hourly_limit: 150
389				}
390			}
391		}, options);
392
393		// no main decorator? nothing to do!
394		if (!options.mainDecorator)
395		{
396			return;
397		}
398
399		options._baseSelector = this.selector;
400
401		// if username is an array, create the search query and flatten username
402		if (typeof(options.username) != 'string')
403		{
404			if (!options.searchParams)
405			{
406				options.searchParams = ['q=from:' + options.username.join(" OR from:")];
407			}
408
409			options.username = options.username[0];
410		}
411
412		// if showTweetFeed is not set to a boolean value, we expect the configuration of
413		// the tweet feed
414		if (typeof(options.showTweetFeed) == 'object')
415		{
416			$.extend(true, options._tweetFeedConfig, options.showTweetFeed);
417		}
418
419		// if showTweetBox is not set to a boolean value, we expect the configuration of
420		// the TweetBox
421		if (typeof(options.showTweetBox) == 'object')
422		{
423			options._tweetBoxConfig = options.showTweetBox;
424			options.showTweetBox = true;
425		}
426
427		// if showConnectButton is not set to a boolean value, we expect the
428		// configuration of the Connect Button
429		if (typeof(options.showConnectButton) == 'object')
430		{
431			options._connectButtonConfig = options.showConnectButton;
432			options.showConnectButton = true;
433		}
434
435		// to be compatible, check the deprecated option 'tweetProfileImagePresent'
436		if (options._tweetFeedConfig.showProfileImages == null)
437		{
438			options._tweetFeedConfig.showProfileImages = options.tweetProfileImagePresent;
439		}
440
441		// if _tweetFeedConfig.showProfileImages is not set to a boolean value,
442		// we decide to show a profile image if the feed represents a user's
443		// list or the results of a Twitter search
444		if (options._tweetFeedConfig.showProfileImages == null)
445		{
446			options._tweetFeedConfig.showProfileImages = (options.list || options.searchParams) && options.tweetProfileImageDecorator;
447		}
448
449		// if _tweetFeedConfig.showUserScreenNames is not set to a boolean value,
450		// we decide to show a username if the feed represents a user's
451		// list or the results of a Twitter search or a tweet is a native retweet
452		if (options._tweetFeedConfig.showUserScreenNames == null)
453		{
454			if (options.list || options.searchParams)
455			{
456				options._tweetFeedConfig.showUserScreenNames = true;
457			}
458
459			if (!options.tweetUsernameDecorator)
460			{
461				options._tweetFeedConfig.showUserScreenNames = false;
462			}
463		}
464
465		// if _tweetFeedConfig.showUserFullNames is not set to a boolean value,
466		// we decide to show a user's full name if the feed represents a user's
467		// list or the results of a Twitter search or a tweet is a native retweet
468		if (options._tweetFeedConfig.showUserFullNames == null)
469		{
470			if (options.list || options.searchParams)
471			{
472				options._tweetFeedConfig.showUserFullNames = true;
473			}
474
475			if (!options.tweetUsernameDecorator)
476			{
477				options._tweetFeedConfig.showUserFullNames = false;
478			}
479		}
480
481		options.count = validateRange(options.count, 0, options.searchParams ? 100 : 20);
482
483		options._tweetFeedConfig.autorefresh.interval = Math.max(30, options._tweetFeedConfig.autorefresh.interval);
484
485		options._tweetFeedConfig.paging._offset = 0;
486		options._tweetFeedConfig.paging._limit = options.count;
487
488		// internally, the decision of what parts of a widget are to be
489		// displayed is based on the existence of the decorators
490		if (options.count == 0 || !options.showTweetFeed)
491		{
492			options.tweetFeedDecorator = null;
493			options.tweetFeedControlsDecorator = null;
494		}
495
496		if (options._tweetFeedConfig.paging.mode == 'none')
497		{
498			options.tweetFeedControlsDecorator = null;
499		}
500
501		if (!options.showFollowButton)
502		{
503			options.followButtonDecorator = null;
504		}
505
506		if (!options.showTweetBox)
507		{
508			options.tweetBoxDecorator = null;
509		}
510
511		if (!options.showConnectButton)
512		{
513			options.connectButtonDecorator = null;
514		}
515
516		if (!options.showLoginInfo)
517		{
518			options.loginInfoDecorator = null;
519		}
520
521		if (!options._tweetFeedConfig.showTimestamp)
522		{
523			options.tweetTimestampDecorator = null;
524		}
525
526		if (!options._tweetFeedConfig.showSource)
527		{
528			options.tweetSourceDecorator = null;
529		}
530
531		if (!options._tweetFeedConfig.showGeoLocation)
532		{
533			options.tweetGeoLocationDecorator = null;
534		}
535
536		if (!options._tweetFeedConfig.showInReplyTo)
537		{
538			options.tweetInReplyToDecorator = null;
539		}
540
541		// setup ajax
542		$.ajaxSetup({ cache: true });
543
544		return this.each(function()
545		{
546			// the DOM element, where to display the widget
547			options._baseElement = $(this);
548
549			// create the widget's necessary sub DOM elements
550			options._tweetFeedElement = options.tweetFeedDecorator ? $(options.tweetFeedDecorator(options)) : null;
551			options._tweetFeedControlsElement = options.tweetFeedControlsDecorator ? $(options.tweetFeedControlsDecorator(options)) : null;
552			options._followButtonElement = options.followButtonDecorator ? $(options.followButtonDecorator(options)) : null;
553			options._tweetBoxElement = options.tweetBoxDecorator ? $(options.tweetBoxDecorator(options)) : null;
554			options._connectButtonElement = options.connectButtonDecorator ? $(options.connectButtonDecorator(options)): null;
555			options._loginInfoElement = options.loginInfoDecorator ? $(options.loginInfoDecorator(options)) : null;
556
557			// add the widget to the DOM
558			options.mainDecorator(options);
559
560			populateTweetFeed(options);
561			populateAnywhereControls(options);
562
563			// bind event handlers to support paging
564			bindEventHandlers(options);
565
566			// install autorefresh support
567			options._tweetFeedConfig.autorefresh._startTime = new Date().getTime();
568			startAutorefresh(options);
569			startTimestampRefresh(options);
570		});
571	};
572	defaultMainDecorator = function(options)
573	{
574		// defines the default sequence of the widget's elements
575		if (options._tweetFeedElement)
576		{
577			options._baseElement.append(options._tweetFeedElement);
578		}
579
580		if (options._tweetFeedControlsElement)
581		{
582			options._baseElement.append(options._tweetFeedControlsElement);
583		}
584
585		if (options._connectButtonElement)
586		{
587			options._baseElement.append(options._connectButtonElement);
588		}
589
590		if (options._loginInfoElement)
591		{
592			options._baseElement.append(options._loginInfoElement);
593		}
594
595		if (options._followButtonElement)
596		{
597			options._baseElement.append(options._followButtonElement);
598		}
599
600		if (options._tweetBoxElement)
601		{
602			options._baseElement.append(options._tweetBoxElement);
603		}
604	};
605	defaultTweetFeedControlsDecorator = function(options)
606	{
607		// the default tweet feed's paging controls
608		var html = '';
609
610		if (options._tweetFeedConfig.paging.mode == 'prev-next')
611		{
612			if (options.tweetFeedControlsPrevBtnDecorator)
613			{
614				html += options.tweetFeedControlsPrevBtnDecorator(options);
615			}
616
617			if (options.tweetFeedControlsNextBtnDecorator)
618			{
619				html += options.tweetFeedControlsNextBtnDecorator(options);
620			}
621		}
622		else if (options._tweetFeedConfig.paging.mode == 'endless-scroll')
623		{
624			// nothing to do here atm
625		}
626		else
627		{
628			if (options.tweetFeedControlsMoreBtnDecorator)
629			{
630				html += options.tweetFeedControlsMoreBtnDecorator(options);
631			}
632		}
633
634		return '<div class="jta-tweet-list-controls">' + html + '</div>';
635	};
636	defaultTweetFeedControlsMoreBtnDecorator = function(options)
637	{
638		return '<span class="jta-tweet-list-controls-button jta-tweet-list-controls-button-more">' + 'More' + '</span>';
639	};
640	defaultTweetFeedControlsPrevBtnDecorator = function(options)
641	{
642		return '<span class="jta-tweet-list-controls-button jta-tweet-list-controls-button-prev">' + 'Prev' + '</span>';
643	};
644	defaultTweetFeedControlsNextBtnDecorator = function(options)
645	{
646		return '<span class="jta-tweet-list-controls-button jta-tweet-list-controls-button-next">' + 'Next' + '</span>';
647	};
648	defaultTweetFeedAutorefreshTriggerDecorator = function(count, options)
649	{
650		var html = '';
651
652		if (options.tweetFeedAutorefreshTriggerContentDecorator)
653		{
654			html = options.tweetFeedAutorefreshTriggerContentDecorator(count, options);
655		}
656
657		return '<li class="jta-tweet-list-autorefresh-trigger">' + html + '</li>';
658	};
659	defaultTweetFeedAutorefreshTriggerContentDecorator = function(count, options)
660	{
661		var content = '' + count + ' new ' + (count > 1 ? ' tweets' : ' tweet');
662
663		return '<span class="jta-tweet-list-autorefresh-trigger-content">' + content + '</span>';
664	};
665	defaultTweetFeedDecorator = function(options)
666	{
667		// the default placeholder for the tweet feed is an unordered list
668		return '<ul class="jta-tweet-list"></ul>';
669	};
670	defaultTweetDecorator = function(tweet, options)
671	{
672		// the default tweet is made of the optional user's profile image and the
673		// tweet body inside a list item element
674		var html = '';
675
676		if (options._tweetFeedConfig.showProfileImages)
677		{
678			html += options.tweetProfileImageDecorator(tweet, options);
679		}
680
681		if (options.tweetBodyDecorator)
682		{
683			html += options.tweetBodyDecorator(tweet, options);
684		}
685
686		html += '<div class="jta-clear">&nbsp;</div>';
687
688		return '<li class="jta-tweet-list-item">' + html + '</li>';
689	};
690	defaultTweetProfileImageDecorator = function(tweet, options)
691	{
692		// if tweet is a native retweet, use the retweet's profile
693		var t = tweet.retweetedStatus || tweet;
694
695		// the default profile image decorator simply adds a link to the user's Twitter profile
696		var screenName = t.user ? t.user.screenName : false || t.from_user;
697		var imageUrl = t.user ? t.user.profileImageURL : false || t.profile_image_url;
698
699		var html =
700			'<a class="jta-tweet-profile-image-link" href="http://twitter.com/' + screenName + '" target="_blank">' +
701			'<img src="' + imageUrl + '" alt="' + screenName + '"' +
702			(isAnywherePresent() ? '' : (' title="' + screenName + '"')) +
703			'/>' +
704			'</a>';
705
706		return '<div class="jta-tweet-profile-image">' + html + '</div>';
707	};
708	defaultTweetBodyDecorator = function(tweet, options)
709	{
710		// the default tweet body contains the tweet text and the tweet's creation date
711		var html = '';
712
713		if (options.tweetTextDecorator)
714		{
715			html += options.tweetTextDecorator(tweet, options);
716		}
717
718		if (options.tweetAttributesDecorator)
719		{
720			html += options.tweetAttributesDecorator(tweet, options);
721		}
722
723		return '<div class="jta-tweet-body ' + (options._tweetFeedConfig.showProfileImages ? 'jta-tweet-body-list-profile-image-present' : '') + '">' + html + '</div>';
724	};
725	defaultTweetTextDecorator = function(tweet, options)
726	{
727		var tweetText = tweet.text;
728
729		// if usernames should be visible and tweet is a native retweet, use
730		// the original tweet text
731		if (tweet.retweeted &&
732			(
733				options._tweetFeedConfig.showUserScreenNames ||
734				options._tweetFeedConfig.showUserScreenNames == null ||
735				options._tweetFeedConfig.showUserFullNames ||
736				options._tweetFeedConfig.showUserFullNames == null
737			)
738		)
739		{
740			tweetText = tweet.retweetedStatus.text;
741		}
742
743		// the default tweet text decorator optionally marks links, @usernames,
744		// and #hashtags
745		if (options.linkDecorator)
746		{
747			tweetText = options.linkDecorator(tweetText, options);
748		}
749
750		if (options.usernameDecorator)
751		{
752			tweetText = options.usernameDecorator(tweetText, options);
753		}
754
755		if (options.hashtagDecorator)
756		{
757			tweetText = options.hashtagDecorator(tweetText, options);
758		}
759
760		if (options._tweetFeedConfig.showUserScreenNames ||
761			options._tweetFeedConfig.showUserFullNames ||
762			tweet.retweeted &&
763			(
764				options._tweetFeedConfig.showUserScreenNames == null ||
765				options._tweetFeedConfig.showUserFullNames == null
766			)
767		)
768		{
769			tweetText = options.tweetUsernameDecorator(tweet, options) + ' ' + tweetText;
770		}
771
772		return '<span class="jta-tweet-text">' + tweetText + '</span>';
773	};
774	defaultTweetUsernameDecorator = function(tweet, options)
775	{
776		// if tweet is a native retweet, use the retweet's profile
777		var t = tweet.retweetedStatus || tweet;
778		var screenName = t.user ? t.user.screenName : false || t.from_user;
779		var fullName = t.user ? t.user.name : null;
780
781		var htmlScreenName;
782		if (screenName && (options._tweetFeedConfig.showUserScreenNames || (options._tweetFeedConfig.showUserScreenNames == null && tweet.retweetedStatus)))
783		{
784			htmlScreenName =
785				'<span class="jta-tweet-user-screen-name">' +
786				'<a class="jta-tweet-user-screen-name-link" href="http://twitter.com/' + screenName + '" target="_blank">' +
787				screenName +
788				'</a>' +
789				'</span>';
790		}
791
792		var htmlFullName;
793		if (fullName && (options._tweetFeedConfig.showUserFullNames || (options._tweetFeedConfig.showUserFullNames == null && tweet.retweetedStatus)))
794		{
795			htmlFullName =
796				'<span class="jta-tweet-user-full-name">' +
797				(htmlScreenName ? ' (' : '') +
798				'<a class="jta-tweet-user-full-name-link" href="http://twitter.com/' + screenName + '" name="' + screenName + '" target="_blank">' +
799				fullName +
800				'</a>' +
801				(htmlScreenName ? ')' : '') +
802				'</span>';
803		}
804
805		var html = "";
806
807		if (htmlScreenName)
808		{
809			html += htmlScreenName;
810		}
811
812		if (htmlFullName)
813		{
814			if (htmlScreenName)
815			{
816				html += ' ';
817			}
818
819			html += htmlFullName;
820		}
821
822		if (htmlScreenName || htmlFullName)
823		{
824			html =
825				'<span class="jta-tweet-user-name">' +
826				(tweet.retweeted ? 'RT ' : '') +
827				html +
828				'</span>';
829		}
830
831		return html;
832	};
833	defaultTweetAttributesDecorator = function(tweet, options)
834	{
835		var html = '';
836
837		if (options.tweetTimestampDecorator ||
838			options.tweetSourceDecorator ||
839			options.tweetGeoLocationDecorator ||
840			options.tweetInReplyToDecorator ||
841			(tweet.retweeted && options.tweetRetweeterDecorator)
842		)
843		{
844			html += '<span class="jta-tweet-attributes">';
845
846			if (options.tweetTimestampDecorator)
847			{
848				html += options.tweetTimestampDecorator(tweet, options);
849			}
850
851			if (options.tweetSourceDecorator)
852			{
853				html += options.tweetSourceDecorator(tweet, options);
854			}
855
856			if (options.tweetGeoLocationDecorator)
857			{
858				html += options.tweetGeoLocationDecorator(tweet, options);
859			}
860
861			if (options.tweetInReplyToDecorator)
862			{
863				html += options.tweetInReplyToDecorator(tweet, options);
864			}
865
866			if (tweet.retweetedStatus && options.tweetRetweeterDecorator)
867			{
868				html += options.tweetRetweeterDecorator(tweet, options);
869			}
870
871			html += '</span>';
872		}
873
874		return html;
875	};
876	defaultTweetTimestampDecorator = function(tweet, options)
877	{
878		// the default tweet timestamp decorator does a little bit of Twitter like formatting.
879
880		// if tweet is a native retweet, use the retweet's timestamp
881		var tw = tweet.retweetedStatus || tweet;
882
883		// reformat timestamp from Twitter, so IE is happy
884		var createdAt = formatDate(tw.createdAt);
885
886		// format the timestamp by the tweetTimestampFormatter
887		var tweetTimestamp = options.tweetTimestampFormatter(createdAt);
888		var tweetTimestampTooltip = options.tweetTimestampTooltipFormatter(createdAt);
889
890		var screenName = tw.user ? tw.user.screenName : false || tw.from_user;
891		var html =
892			'<span class="jta-tweet-timestamp">' +
893			'<a class="jta-tweet-timestamp-link" data-timestamp="' + createdAt +
894			'" href="http://twitter.com/' + screenName + '/status/' + tw.id + '" target="_blank" title="' +
895			tweetTimestampTooltip + '">' +
896			tweetTimestamp +
897			'</a>' +
898			'</span>';
899
900		return html;
901	};
902	defaultTweetTimestampTooltipFormatter = function(timeStamp)
903	{
904		var d = new Date(timeStamp);
905
906		return d.toLocaleString();
907	};
908	defaultTweetTimestampFormatter = function(timeStamp)
909	{
910		var now = new Date();
911
912		var diff = parseInt((now.getTime() - Date.parse(timeStamp)) / 1000);
913
914		var tweetTimestamp = '';
915		if (diff < 60)
916		{
917			tweetTimestamp += diff + ' second' + (diff == 1 ? '' : 's') + ' ago';
918		}
919		else if (diff < 3600)
920		{
921			var t = parseInt((diff + 30) / 60);
922			tweetTimestamp += t + ' minute' + (t == 1 ? '' : 's') + ' ago';
923		}
924		else if (diff < 86400)
925		{
926			var t = parseInt((diff + 1800) / 3600);
927			tweetTimestamp += t + ' hour' + (t == 1 ? '' : 's') + ' ago';
928		}
929		else
930		{
931			var d = new Date(timeStamp);
932			var period = 'AM';
933
934			var hours = d.getHours();
935			if (hours > 12)
936			{
937				hours -= 12;
938				period = 'PM';
939			}
940
941			var mins = d.getMinutes();
942			var minutes = (mins < 10 ? '0' : '') + mins;
943
944			var monthName = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ];
945
946			tweetTimestamp += monthName[d.getMonth()] + ' ' + d.getDate();
947
948			if (d.getFullYear() < now.getFullYear())
949			{
950				tweetTimestamp += ', ' + d.getFullYear();
951			}
952
953			var t = parseInt((diff + 43200) / 86400);
954			tweetTimestamp += ' (' + t + ' day' + (t == 1 ? '' : 's') + ' ago)';
955		}
956
957		return tweetTimestamp;
958	};
959	exTimestampFormatter = function(timeStamp)
960	{
961		var diff = parseInt((new Date().getTime() - Date.parse(timeStamp)) / 1000);
962
963		var tweetTimestamp = '';
964		if (diff < 60)
965		{
966			tweetTimestamp += 'less than a minute ago';
967		}
968		else if (diff < 3600)
969		{
970			var t = parseInt((diff + 30) / 60);
971			tweetTimestamp += t + ' minute' + (t == 1 ? '' : 's') + ' ago';
972		}
973		else if (diff < 86400)
974		{
975			var t = parseInt((diff + 1800) / 3600);
976			tweetTimestamp += 'about ' + t + ' hour' + (t == 1 ? '' : 's') + ' ago';
977		}
978		else
979		{
980			var t = parseInt((diff + 43200) / 86400);
981			tweetTimestamp += 'about ' + t + ' day' + (t == 1 ? '' : 's') + ' ago';
982
983			var d = new Date(timeStamp);
984			var period = 'AM';
985
986			var hours = d.getHours();
987			if (hours > 12)
988			{
989				hours -= 12;
990				period = 'PM';
991			}
992
993			var mins = d.getMinutes();
994			var minutes = (mins < 10 ? '0' : '') + mins;
995
996			tweetTimestamp += ' ('  + hours + ':' + minutes + ' ' + period + ' ' + (d.getMonth() + 1) + '/' + d.getDate() + '/' + d.getFullYear() + ')';
997		}
998
999		return tweetTimestamp;
1000	};
1001	defaultTweetSourceDecorator = function(tweet, options)
1002	{
1003		// if tweet is a native retweet, use the retweet's source
1004		var tw = tweet.retweetedStatus || tweet;
1005
1006		var source = tw.source.replace(/\&lt\;/gi,'<').replace(/\&gt\;/gi,'>').replace(/\&quot\;/gi,'"');
1007		var html =
1008			'<span class="jta-tweet-source">' +
1009			' via ' +
1010			'<span class="jta-tweet-source-link">' +
1011			source +
1012			'</span>' +
1013			'</span>';
1014
1015		return html;
1016	};
1017	defaultTweetGeoLocationDecorator = function(tweet, options)
1018	{
1019		var html = '';
1020
1021		// if tweet is a native retweet, use the retweet's source
1022		var tw = tweet.retweetedStatus || tweet;
1023
1024		var q;
1025		if (tw.geoLocation && tw.geoLocation.latitude && tw.geoLocation.longitude)
1026		{
1027			q = tw.geoLocation.latitude + "," + tw.geoLocation.longitude;
1028		}
1029		else if (tw.place && tw.place.fullName)
1030		{
1031			q = tw.place.fullName;
1032		}
1033
1034		if (q)
1035		{
1036			var location = 'here';
1037			if (tw.place && tw.place.fullName)
1038			{
1039				location = tw.place.fullName;
1040			}
1041
1042			var link = 'http://maps.google.com/maps?q=' + q;
1043
1044			html =
1045				'<span class="jta-tweet-location">' +
1046				' from ' +
1047				'<a class="jta-tweet-location-link" href="' + link + '" target="_blank">' +
1048				location +
1049				'</a>' +
1050				'</span>';
1051		}
1052
1053		return html;
1054	};
1055	defaultTweetInReplyToDecorator = function(tweet, options)
1056	{
1057		// if tweet is a native retweet, use the retweet's source
1058		var tw = tweet.retweetedStatus || tweet;
1059
1060		var html = '';
1061		if (tw.inReplyToStatusId && tw.inReployToScreenName)
1062		{
1063			html =
1064				'<span class="jta-tweet-inreplyto">' +
1065				' ' +
1066				'<a class="jta-tweet-inreplyto-link" href="http://twitter.com/' + tw.inReployToScreenName + '/status/' + tw.inReplyToStatusId + '" target="_blank">' +
1067				'in reply to ' + tw.inReployToScreenName +
1068				'</a>' +
1069				'</span>';
1070		}
1071
1072		return html;
1073	};
1074	defaultTweetRetweeterDecorator = function(tweet, options)
1075	{
1076		var html = '';
1077
1078		if (tweet.retweetedStatus)
1079		{
1080			var screenName = tweet.user ? tweet.user.screenName : false || tweet.from_user;
1081			var rtc = (tweet.retweetedStatus.retweetCount || 0) - 1;
1082
1083			var link =
1084				'<a class="jta-tweet-retweeter-link" href="http://twitter.com/' + screenName + '" target="_blank">' +
1085				screenName +
1086				'</a>';
1087			var rtcount = ' and ' + rtc + (rtc > 1 ? ' others' : ' other');
1088			html =
1089				'<br/>' +
1090				'<span class="jta-tweet-retweeter">' +
1091				'Retweeted by ' + link +
1092				(rtc > 0 ? rtcount : '') +
1093				'</span>';
1094		}
1095
1096		return html;
1097	};
1098	defaultConnectButtonDecorator = function(options)
1099	{
1100		// the default placeholder for the @Anywhere ConnectButton
1101		return '<div class="jta-connect-button"></div>';
1102	};
1103	defaultLoginInfoDecorator = function(options)
1104	{
1105		// the default placeholder for the LoginInfo
1106		return '<div class="jta-login-info"></div>';
1107	};
1108	defaultLoginInfoContentDecorator = function(options, T)
1109	{
1110		// the default markup of the LoginInfo content: the user's profile image, the
1111		// user's screen_name and a "button" to sign out
1112		var html = '';
1113
1114		if (T.isConnected())
1115		{
1116			var screenName = T.currentUser.data('screen_name');
1117			var imageUrl = T.currentUser.data('profile_image_url');
1118
1119			html =
1120				'<div class="jta-login-info-profile-image">' +
1121				'<a href="http://twitter.com/' + screenName + '" target="_blank">' +
1122				'<img src="' + imageUrl + '" alt="' + screenName + '" title="' + screenName + '"/>' +
1123				'</a>' +
1124				'</div>' +
1125				'<div class="jta-login-info-block">' +
1126				'<div class="jta-login-info-screen-name">' +
1127				'<a href="http://twitter.com/' + screenName + '" target="_blank">' + screenName + '</a>' +
1128				'</div>' +
1129				'<div class="jta-login-info-sign-out">' +
1130				'Sign out' +
1131				'</div>' +
1132				'</div>' +
1133				'<div class="jta-clear">&nbsp;</div>'
1134				;
1135		}
1136
1137		return html;
1138	};
1139	defaultFollowButtonDecorator = function(options)
1140	{
1141		// the default placeholder for the @Anywhere FollowButton
1142		return '<div class="jta-follow-button"></div>';
1143	};
1144	defaultTweetBoxDecorator = function(options)
1145	{
1146		// the default placeholder for the @Anywhere TweetBox
1147		return '<div class="jta-tweet-box"></div>';
1148	};
1149	defaultLinkDecorator = function(text, options)
1150	{
1151		// the regex to markup links
1152		return text.replace(/((ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi,'<a href="$1" class="jta-tweet-a jta-tweet-link" target="_blank" rel="nofollow">$1<\/a>');
1153	};
1154	defaultUsernameDecorator = function(text, options)
1155	{
1156		// the regex to markup @usernames. if @Anywhere is present the task is left to
1157		// them
1158		return isAnywherePresent() ? text : text.replace(/@([a-zA-Z0-9_]+)/gi,'@<a href="http://twitter.com/$1" class="jta-tweet-a twitter-anywhere-user" target="_blank" rel="nofollow">$1<\/a>');
1159	};
1160	defaultHashtagDecorator = function(text, options)
1161	{
1162		// the regex to markup #hashtags
1163		return text.replace(/#([a-zA-Z0-9_]+)/gi,'<a href="http://twitter.com/search?q=%23$1" class="jta-tweet-a jta-tweet-hashtag" title="#$1" target="_blank" rel="nofollow">#$1<\/a>');
1164	};
1165	defaultLoadingDecorator = function(options)
1166	{
1167		// the default loading decorator simply says: loading ...
1168		return '<li class="jta-loading">loading ...</li>';
1169	};
1170	defaultErrorDecorator = function(errorText, options)
1171	{
1172		// the default error decorator shows the error message
1173		return '<li class="jta-error">ERROR: ' + errorText + '</li>';
1174	};
1175	defaultNoDataDecorator = function(options)
1176	{
1177		// the default no-data decorator simply says: No more data
1178		return '<li class="jta-nodata">No more data</li>';
1179	};
1180
1181	defaultTweetFilter = function(tweet, options)
1182	{
1183		return true;
1184	};
1185
1186	defaultTweetVisualizer = function(tweetFeedElement, tweetElement, inserter, options)
1187	{
1188		// insert (append/prepend) the tweetElement to the tweetFeedElement
1189		tweetFeedElement[inserter](tweetElement);
1190	};
1191	defaultLoadingIndicatorVisualizer = function(tweetFeedElement, loadingIndicatorElement, options, callback)
1192	{
1193		defaultVisualizer(tweetFeedElement, loadingIndicatorElement, 'append', 'fadeIn', 600, 'fadeOut', 200, callback);
1194	};
1195	defaultAutorefreshTriggerVisualizer = function(tweetFeedElement, triggerElement, options, callback)
1196	{
1197		defaultVisualizer(tweetFeedElement, triggerElement, 'prepend', 'slideDown', 600, 'fadeOut', 200, callback);
1198	};
1199	defaultVisualizer = function(container, element, inserter, effectIn, durationIn, effectOut, durationOut, callback)
1200	{
1201		// if param container is null element has to be removed from
1202		// the DOM, else element has to be inserted in container
1203
1204		// if param callback is not null, the callback function must be called
1205		// in any case, if the visualizer is done
1206
1207		var cb = function()
1208		{
1209			if (callback)
1210			{
1211				callback();
1212			}
1213		};
1214
1215		if (container)
1216		{
1217			element.hide();
1218			container[inserter](element);
1219			element[effectIn](durationIn, cb);
1220		}
1221		else
1222		{
1223			element[effectOut](durationOut, function()
1224			{
1225				element.remove();
1226				cb();
1227			});
1228		}
1229	};
1230
1231	defaultOnDataRequestHandler = function(stats, options)
1232	{
1233		return true;
1234	};
1235	defaultOnRateLimitDataHandler = function(stats, options)
1236	{
1237	};
1238
1239	updateLoginInfoElement = function(options, T)
1240	{
1241		// update the content of the LoginInfo element
1242		if (options._loginInfoElement && options.loginInfoContentDecorator)
1243		{
1244			options._loginInfoElement.children().remove();
1245			options._loginInfoElement.append(options.loginInfoContentDecorator(options, T));
1246			$(options._baseSelector + ' .jta-login-info-sign-out').bind('click', function()
1247			{
1248				twttr.anywhere.signOut();
1249			});
1250		}
1251	};
1252	getFeedUrl = function(options, flPaging)
1253	{
1254		var url = '/apis/twitter';
1255
1256		if (options.searchParams)
1257		{
1258			url += '/search?' +
1259				((options.searchParams instanceof Array) ? options.searchParams.join('&') : options.searchParams) +
1260				'&rpp=100';
1261		}
1262		else if (options.list)
1263		{
1264			url += options.username + '/lists/' + options.list;
1265		}
1266		else
1267		{
1268			url += '/tweets/' + options.username + '?count=20';
1269			
1270			if (options._tweetFeedConfig.includeRetweets)
1271			{
1272				url += '&include_rts=true';
1273			}
1274
1275			if (flPaging)
1276			{
1277				url +=
1278					    (options._tweetFeedConfig._maxId ? '&max_id=' + options._tweetFeedConfig._maxId : '') +
1279						'&page=' + options._tweetFeedConfig._pageParam;
1280			}
1281		}
1282		
1283		url += '&callback=?';
1284		return url;
1285	};
1286	isAnywherePresent = function()
1287	{
1288		// check, if @Anywhere is present
1289		return typeof(twttr) != 'undefined';
1290	};
1291	clearTweetFeed = function(options)
1292	{
1293		if (options._tweetFeedElement)
1294		{
1295			options._tweetFeedElement.empty();
1296		}
1297	};
1298	populateTweetFeed = function(options)
1299	{
1300		// if a tweet feed is to be displayed, get the tweets and show them
1301		if (options.tweetDecorator && options._tweetFeedElement)
1302		{
1303			getPagedTweets(options, function(tweets, options)
1304			{
1305				if (options._tweetFeedConfig._clearBeforePopulate)
1306				{
1307					clearTweetFeed(options);
1308				}
1309
1310				hideLoadingIndicator(options, function()
1311				{
1312					// process the tweets
1313					$.each(tweets, function(idx, tweet)
1314					{
1315						// decorate the tweet and give it to the tweet visualizer
1316						options.tweetVisualizer(
1317							options._tweetFeedElement,
1318							$(options.tweetDecorator(tweet, options)),
1319							'append',
1320							options
1321						);
1322					});
1323
1324					if (options._tweetFeedConfig._noData && options.noDataDecorator && !options._tweetFeedConfig._noDataElement)
1325					{
1326						options._tweetFeedConfig._noDataElement = $(options.noDataDecorator(options));
1327						options._tweetFeedElement.append(options._tweetFeedConfig._noDataElement);
1328					}
1329
1330					if (options._tweetFeedConfig._clearBeforePopulate)
1331					{
1332						options._tweetFeedElement.scrollTop(0);
1333					}
1334
1335					addHovercards(options);
1336				});
1337			});
1338		}
1339	};
1340	populateTweetFeed2 = function(options)
1341	{
1342		if (options._tweetFeedElement && options._autorefreshTweetsCache.length > 0)
1343		{
1344			if (options._tweetFeedConfig.autorefresh.mode == 'trigger-insert')
1345			{
1346				if (options._tweetFeedConfig.autorefresh._triggerElement)
1347				{
1348					if (options.tweetFeedAutorefreshTriggerContentDecorator)
1349					{
1350						options._tweetFeedConfig.autorefresh._triggerElement.html(
1351							options.tweetFeedAutorefreshTriggerContentDecorator(options._autorefreshTweetsCache.length, options)
1352						);
1353					}
1354				}
1355				else
1356				{
1357					if (options.tweetFeedAutorefreshTriggerDecorator)
1358					{
1359						options._tweetFeedConfig.autorefresh._triggerElement =
1360							$(options.tweetFeedAutorefreshTriggerDecorator(options._autorefreshTweetsCache.length, options));
1361						options._tweetFeedConfig.autorefresh._triggerElement.bind('click', function()
1362						{
1363							options.autorefreshTriggerVisualizer(
1364								null,
1365								options._tweetFeedConfig.autorefresh._triggerElement,
1366								options,
1367								function()
1368								{
1369									insertTriggerTweets(options);
1370								}
1371							);
1372							options._tweetFeedConfig.autorefresh._triggerElement = null;
1373						});
1374
1375						options.autorefreshTriggerVisualizer(options._tweetFeedElement, options._tweetFeedConfig.autorefresh._triggerElement, options);
1376					}
1377				}
1378			}
1379			else
1380			{
1381				insertTriggerTweets(options);
1382			}
1383		}
1384	};
1385	insertTriggerTweets = function(options)
1386	{
1387		// populate the tweet feed with tweets from the autorefresh cache
1388		if (options.tweetDecorator && options._autorefreshTweetsCache.length > 0)
1389		{
1390			// process the autorefresh cache
1391			while (options._autorefreshTweetsCache.length > 0)
1392			{
1393				// get the last tweet and remove it from the autorefresh cache
1394				var tweet = options._autorefreshTweetsCache.pop();
1395
1396				// put that tweet on top of the tweets cache
1397				options._tweetsCache.unshift(tweet);
1398
1399				// adjust paging offset
1400				options._tweetFeedConfig.paging._offset++;
1401
1402				// decorate the tweet and give it to the tweet visualizer
1403				options.tweetVisualizer(
1404					options._tweetFeedElement,
1405					$(options.tweetDecorator(tweet, options)),
1406					'prepend',
1407					options
1408				);
1409			}
1410
1411			addHovercards(options);
1412		}
1413	};
1414	addHovercards = function(options)
1415	{
1416		if (isAnywherePresent())
1417		{
1418			// if @Anywhere is present, append Hovercards to @username and
1419			// profile images
1420			twttr.anywhere(function(T)
1421			{
1422				T(options._baseSelector + ' .jta-tweet-list').hovercards({expanded: options._tweetFeedConfig.expandHovercards});
1423				T(options._baseSelector + ' .jta-tweet-profile-image img').hovercards(
1424				{
1425					expanded: options._tweetFeedConfig.expandHovercards,
1426					username: function(node) { return node.alt; }
1427				});
1428				T(options._baseSelector + ' .jta-tweet-retweeter-link').hovercards(
1429				{
1430					expanded: options._tweetFeedConfig.expandHovercards,
1431					username: function(node) { return node.text; }
1432				});
1433				T(options._baseSelector + ' .jta-tweet-user-screen-name-link').hovercards(
1434				{
1435					expanded: options._tweetFeedConfig.expandHovercards,
1436					username: function(node) { return node.text; }
1437				});
1438				T(options._baseSelector + ' .jta-tweet-user-full-name-link').hovercards(
1439				{
1440					expanded: options._tweetFeedConfig.expandHovercards,
1441					username: function(node) { return node.name; }
1442				});
1443			});
1444		}
1445	};
1446	populateAnywhereControls = function(options)
1447	{
1448		if (isAnywherePresent())
1449		{
1450			twttr.anywhere(function(T)
1451			{
1452				// optionally add an @Anywhere TweetBox
1453				if (options.tweetBoxDecorator)
1454				{
1455					T(options._baseSelector + ' .jta-tweet-box').tweetBox(options._tweetBoxConfig);
1456				}
1457
1458				// optionally add an @Anywhere FollowButton
1459				if (options.followButtonDecorator)
1460				{
1461					T(options._baseSelector + ' .jta-follow-button').followButton(options.username);
1462				}
1463
1464				// optionally add an @Anywhere ConnectButton
1465				if (options.connectButtonDecorator)
1466				{
1467					var o = $.extend(
1468					{
1469						authComplete: function(user)
1470						{
1471							// display/update login infos on connect/signin event
1472							updateLoginInfoElement(options, T);
1473						},
1474						signOut: function()
1475						{
1476							// display/update login infos on signout event
1477							updateLoginInfoElement(options, T);
1478						}
1479					}, options._connectButtonConfig);
1480
1481					T(options._baseSelector + ' .jta-connect-button').connectButton(o);
1482
1483					// display/update login infos
1484					updateLoginInfoElement(options, T);
1485				}
1486			});
1487		}
1488	};
1489	bindEventHandlers = function(options)
1490	{
1491		if (options.tweetFeedControlsDecorator)
1492		{
1493			if (options._tweetFeedConfig.paging.mode == 'prev-next')
1494			{
1495				$(options._baseSelector + ' .jta-tweet-list-controls-button-prev').bind('click', function()
1496				{
1497					if (!isLoading(options) && options._tweetFeedConfig.paging._offset > 0)
1498					{
1499						prevPage(options, true);
1500					}
1501				});
1502				$(options._baseSelector + ' .jta-tweet-list-controls-button-next').bind('click', function()
1503				{
1504					if (!isLoading(options))
1505					{
1506						nextPage(options, true);
1507					}
1508				});
1509			}
1510			else if (options._tweetFeedConfig.paging.mode == 'endless-scroll')
1511			{
1512				options._tweetFeedElement.bind("scroll", function()
1513				{
1514				    if (!isLoading(options) && ($(this)[0].scrollHeight - $(this).scrollTop() == $(this).outerHeight()))
1515				    {
1516				    	nextPage(options, false);
1517				    }
1518				});
1519			}
1520			else
1521			{
1522				$(options._baseSelector + ' .jta-tweet-list-controls-button-more').bind('click', function()
1523				{
1524					if (!isLoading(options))
1525					{
1526						nextPage(options, false);
1527					}
1528				});
1529			}
1530		}
1531	};
1532	nextPage = function(options, flClear)
1533	{
1534		doPage(options, flClear, Math.min(options._tweetFeedConfig.paging._offset + options._tweetFeedConfig.paging._limit, options._tweetsCache.length));
1535	};
1536	prevPage = function(options, flClear)
1537	{
1538		doPage(options, flClear, Math.max(0, options._tweetFeedConfig.paging._offset - options._tweetFeedConfig.paging._limit));
1539	};
1540	doPage = function(options, flClear, newOffset)
1541	{
1542		options._tweetFeedConfig.paging._offset = newOffset;
1543		options._tweetFeedConfig._clearBeforePopulate = flClear;
1544
1545		populateTweetFeed(options);
1546	};
1547	startAutorefresh = function(options)
1548	{
1549		if (options._tweetFeedConfig.autorefresh.mode != 'none' &&
1550			options._tweetFeedConfig.paging.mode != 'prev-next' &&
1551			options._tweetFeedConfig.autorefresh.duration != 0 &&
1552			(
1553				options._tweetFeedConfig.autorefresh.duration < 0 ||
1554				(new Date().getTime() - options._tweetFeedConfig.autorefresh._startTime) <= options._tweetFeedConfig.autorefresh.duration * 1000
1555			)
1556		)
1557		{
1558			window.setTimeout(function() { processAutorefresh(options); }, options._tweetFeedConfig.autorefresh.interval * 1000);
1559		}
1560	};
1561	stopAutorefresh = function(options)
1562	{
1563		options._tweetFeedConfig.autorefresh.duration = 0;
1564	};
1565	processAutorefresh = function(options)
1566	{
1567		if (options._tweetFeedConfig.autorefresh.duration != 0)
1568		{
1569			// load the data ...
1570			getRateLimitedData(options, true, getFeedUrl(options, false), function(data, options)
1571			{
1572				// reverse the sequence of the autorefresh tweets ...
1573				var tweets = (data.results || data).slice(0);
1574				tweets.reverse();
1575
1576				// ...then process them
1577				$.each(tweets, function(idx, tweet)
1578				{
1579					// if this tweet is already in the standard tweets cache, ignore
1580					if (!isTweetInCache(tweet, options))
1581					{
1582						// optionally filter tweet ...
1583						if (options.tweetFilter(tweet, options))
1584						{
1585							// ... then put it to the top of the autorefresh cache
1586							options._autorefreshTweetsCache.unshift(tweet);
1587						}
1588					}
1589				});
1590
1591				populateTweetFeed2(options);
1592			});
1593
1594			// restart autorefresh
1595			startAutorefresh(options);
1596		}
1597	};
1598	startTimestampRefresh = function(options)
1599	{
1600		if (
1601			options.tweetTimestampDecorator &&
1602			typeof(options._tweetFeedConfig.showTimestamp) == 'object' &&
1603			options._tweetFeedConfig.showTimestamp.refreshInterval > 0
1604		)
1605		{
1606			window.setTimeout(function() { processTimestampRefresh(options); }, options._tweetFeedConfig.showTimestamp.refreshInterval * 1000);
1607		}
1608	};
1609	processTimestampRefresh = function(options)
1610	{
1611		$.each(options._tweetFeedElement.find('.jta-tweet-timestamp-link'), function(idx, element)
1612		{
1613			var dataTimestamp = $(element).attr('data-timestamp');
1614
1615			$(element).html(options.tweetTimestampFormatter(dataTimestamp));
1616		});
1617
1618		startTimestampRefresh(options);
1619	};
1620	isTweetInCache = function(tweet, options)
1621	{
1622		var l = options._tweetsCache.length;
1623
1624		for (var i = 0; i < l; i++)
1625		{
1626			if (tweet.id == options._tweetsCache[i].id)
1627			{
1628				return true;
1629			}
1630		}
1631
1632		return false;
1633	};
1634	showLoadingIndicator = function(options)
1635	{
1636		if (options._tweetFeedElement && options.loadingDecorator && !options._loadingIndicatorElement)
1637		{
1638			options._loadingIndicatorElement = $(options.loadingDecorator(options));
1639			options.loadingIndicatorVisualizer(options._tweetFeedElement, options._loadingIndicatorElement, options, null);
1640			options._tweetFeedElement.scrollTop(1000000);
1641		}
1642	};
1643	hideLoadingIndicator = function(options, callback)
1644	{
1645		if (options._loadingIndicatorElement)
1646		{
1647			options.loadingIndicatorVisualizer(null, options._loadingIndicatorElement, options, callback);
1648			options._loadingIndicatorElement = null;
1649		}
1650		else
1651		{
1652			if (callback)
1653			{
1654				callback();
1655			}
1656		}
1657	};
1658	isLoading = function(options)
1659	{
1660		return options._loadingIndicatorElement != null;
1661	};
1662    formatDate = function(dateStr)
1663	{
1664    	return dateStr.replace(/^([a-z]{3})( [a-z]{3} \d\d?)(.*)( \d{4})$/i, '$1,$2$4$3');
1665    };
1666	validateRange = function(num, lo, hi)
1667	{
1668		if (num < lo)
1669			num = lo;
1670
1671		if (num > hi)
1672			num = hi;
1673
1674		return num;
1675	};
1676    showError = function(options, errorText)
1677	{
1678    	if (options.errorDecorator && options._tweetFeedElement)
1679    	{
1680    		options._tweetFeedElement.append(options.errorDecorator(errorText, options));
1681    	}
1682    };
1683    getPagedTweets = function(options, callback)
1684    {
1685    	options._tweetFeedConfig._recLevel = 0;
1686
1687    	getRecPagedTweets(options, options._tweetFeedConfig.paging._offset, options._tweetFeedConfig.paging._limit, callback);
1688    };
1689    getRecPagedTweets = function(options, offset, limit, callback)
1690	{
1691    	++options._tweetFeedConfig._recLevel;
1692
1693    	if (offset + limit <= options._tweetsCache.length ||
1694    		options._tweetFeedConfig._recLevel > 3 ||
1695    		options._tweetFeedConfig._noData
1696    	)
1697		{
1698    		// if the requested data is already cached or the max. no. of
1699    		// consecutive API calls is reached, use the records
1700
1701    		if (offset + limit > options._tweetsCache.length)
1702    		{
1703    			limit = Math.max(0, options._tweetsCache.length - offset);
1704    		}
1705
1706			var tweets = [];
1707
1708			for (var i = 0; i < limit; i++)
1709			{
1710				tweets[i] = options._tweetsCache[offset + i];
1711			}
1712
1713			callback(tweets, options);
1714		}
1715    	else
1716		{
1717    		// ... if not, load the data, fill the cache and try again
1718    		++options._tweetFeedConfig._pageParam;
1719
1720    		getRateLimitedData(options, false, getFeedUrl(options, true), function(data, options)
1721    		{
1722    			var tweets = data.results || data;
1723
1724    			if (tweets.length == 0)
1725    			{
1726    				options._tweetFeedConfig._noData = true;
1727   				}
1728    			else
1729				{
1730    				$.each(tweets, function(idx, tweet)
1731    				{
1732    					// save the first tweet id for subsequent paging requests
1733    					if (!options._tweetFeedConfig._maxId)
1734    					{
1735    						options._tweetFeedConfig._maxId = tweet.id;
1736    					}
1737
1738    					// optionally filter tweet ...
1739    					if (options.tweetFilter(tweet, options))
1740    					{
1741    						// then put it into the cache
1742    						options._tweetsCache.push(tweet);
1743    					}
1744    				});
1745				}
1746
1747    			getRecPagedTweets(options, offset, limit, callback);
1748    		});
1749		}
1750	};
1751	getRateLimitedData = function(options, flAutorefresh, url, callback)
1752	{
1753		getRateLimit(options, function(rateLimit)
1754		{
1755			if (rateLimit && rateLimit.remaining <= 0)
1756			{
1757				options._stats.rateLimitPreventionCount++;
1758				hideLoadingIndicator(options, null);
1759				return;
1760			}
1761
1762			getData(options, flAutorefresh, url, callback);
1763		});
1764	};
1765	getData = function(options, flAutorefresh, url, callback)
1766	{
1767		options._stats.dataRequestCount++;
1768
1769		if (!options.onDataRequestHandler(options._stats, options))
1770		{
1771			hideLoadingIndicator(options, null);
1772			return;
1773		}
1774
1775		if (!flAutorefresh)
1776		{
1777			showLoadingIndicator(options);
1778		}
1779
1780		$.getJSON(url, function(data)
1781		{
1782			if (data.error)
1783			{
1784				// in case of an error, display the error message
1785				showError(options, data.error);
1786			}
1787			else
1788			{
1789				callback(data, options);
1790			}
1791		});
1792	};
1793	getRateLimit = function(options, callback)
1794	{
1795		var url = '/apis/twitter/rateLimit';
1796
1797		if (options.searchParams)
1798		{
1799			url += '/search/tweets';
1800		}
1801		else if (options.list)
1802		{
1803			url += '/lists/statuses';
1804		}
1805		else
1806		{
1807			url += '/statuses/user_timeline';
1808		}
1809		
1810		url += '?callback=?';
1811
1812		$.getJSON(url, function(rateLimit)
1813		{
1814			options._stats.rateLimit = rateLimit;
1815
1816			options.onRateLimitDataHandler(options._stats, options);
1817
1818			callback(rateLimit);
1819		});
1820	};
1821})(jQuery);

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.