Commits

Rich Manalang [Atlassian]  committed 0c361bc

1.4

  • Participants
  • Parent commits c07de64

Comments (0)

Files changed (11)

File eac_activitystream.crx

Binary file modified.

File src/css/styles.css

   font-size:11px;
   display:inline;
   color:#fff;
-  background-color:darkblue;
+  background-color:darkred;
   padding:1px 4px;
   border:0;
   border-radius:4px;
+}
+.disabled h5 {
+  color: #999 !important;
 }

File src/images/favicon.png

Added
New image

File src/images/icon128.png

Removed
Old image

File src/images/icon16.png

Removed
Old image

File src/images/icon48.png

Removed
Old image

File src/js/jquery.infinitescroll.js

+/*
+	--------------------------------
+	Infinite Scroll
+	--------------------------------
+	+ https://github.com/paulirish/infinite-scroll
+	+ version 2.0b2.120519
+	+ Copyright 2011/12 Paul Irish & Luke Shumard
+	+ Licensed under the MIT license
+	
+	+ Documentation: http://infinite-scroll.com/
+	
+*/
+
+(function (window, $, undefined) {
+	
+	$.infinitescroll = function infscr(options, callback, element) {
+		
+		this.element = $(element);
+		
+                // Flag the object in the event of a failed creation
+		if (!this._create(options, callback)) {
+                  this.failed = true;
+                }
+	
+	};
+	
+	$.infinitescroll.defaults = {
+		loading: {
+			finished: undefined,
+			finishedMsg: "<em>Congratulations, you've reached the end of the internet.</em>",
+			img: "http://www.infinite-scroll.com/loading.gif",
+			msg: null,
+			msgText: "<em>Loading the next set of posts...</em>",
+			selector: null,
+			speed: 'fast',
+			start: undefined
+		},
+		state: {
+			isDuringAjax: false,
+			isInvalidPage: false,
+			isDestroyed: false,
+			isDone: false, // For when it goes all the way through the archive.
+			isPaused: false,
+			currPage: 1
+		},
+		callback: undefined,
+		debug: false,
+		behavior: undefined,
+		binder: $(window), // used to cache the selector
+		nextSelector: "div.navigation a:first",
+		navSelector: "div.navigation",
+		contentSelector: null, // rename to pageFragment
+		extraScrollPx: 150,
+		itemSelector: "div.post",
+		animate: false,
+		pathParse: undefined,
+		dataType: 'html',
+		appendCallback: true,
+		bufferPx: 40,
+		errorCallback: function () { },
+		infid: 0, //Instance ID
+		pixelsFromNavToBottom: undefined,
+		path: undefined
+	};
+
+
+    $.infinitescroll.prototype = {
+
+        /*	
+        ----------------------------
+        Private methods
+        ----------------------------
+        */
+
+        // Bind or unbind from scroll
+        _binding: function infscr_binding(binding) {
+
+            var instance = this,
+				opts = instance.options;
+				
+			opts.v = '2.0b2.111027';
+
+            // if behavior is defined and this function is extended, call that instead of default
+			if (!!opts.behavior && this['_binding_'+opts.behavior] !== undefined) {
+				this['_binding_'+opts.behavior].call(this);
+				return;
+			}
+
+			if (binding !== 'bind' && binding !== 'unbind') {
+                this._debug('Binding value  ' + binding + ' not valid')
+                return false;
+            }
+
+            if (binding == 'unbind') {
+
+                (this.options.binder).unbind('smartscroll.infscr.' + instance.options.infid);
+
+            } else {
+
+                (this.options.binder)[binding]('smartscroll.infscr.' + instance.options.infid, function () {
+                    instance.scroll();
+                });
+
+            };
+
+            this._debug('Binding', binding);
+
+        },
+
+		// Fundamental aspects of the plugin are initialized
+		_create: function infscr_create(options, callback) {
+
+            // Add custom options to defaults
+            var opts = $.extend(true, {}, $.infinitescroll.defaults, options);
+
+            // Validate selectors
+            if (!this._validate(options)) { return false; }
+            this.options = opts;
+
+            // Validate page fragment path
+            var path = $(opts.nextSelector).attr('href');
+            if (!path) {
+              this._debug('Navigation selector not found');
+              return false;
+            }
+
+            // Set the path to be a relative URL from root.
+            opts.path = this._determinepath(path);
+
+            // contentSelector is 'page fragment' option for .load() / .ajax() calls
+            opts.contentSelector = opts.contentSelector || this.element;
+
+            // loading.selector - if we want to place the load message in a specific selector, defaulted to the contentSelector
+            opts.loading.selector = opts.loading.selector || opts.contentSelector;
+
+            // Define loading.msg
+            opts.loading.msg = $('<div id="infscr-loading"><img alt="Loading..." src="' + opts.loading.img + '" /><div>' + opts.loading.msgText + '</div></div>');
+
+            // Preload loading.img
+            (new Image()).src = opts.loading.img;
+
+            // distance from nav links to bottom
+            // computed as: height of the document + top offset of container - top offset of nav link
+            opts.pixelsFromNavToBottom = $(document).height() - $(opts.navSelector).offset().top;
+
+			// determine loading.start actions
+            opts.loading.start = opts.loading.start || function() {
+				
+				$(opts.navSelector).hide();
+				opts.loading.msg
+					.appendTo(opts.loading.selector)
+					.show(opts.loading.speed, function () {
+	                	beginAjax(opts);
+	            });
+			};
+			
+			// determine loading.finished actions
+			opts.loading.finished = opts.loading.finished || function() {
+				opts.loading.msg.fadeOut('normal');
+			};
+
+            // callback loading
+            opts.callback = function(instance,data) {
+				if (!!opts.behavior && instance['_callback_'+opts.behavior] !== undefined) {
+					instance['_callback_'+opts.behavior].call($(opts.contentSelector)[0], data);
+				}
+				if (callback) {
+					callback.call($(opts.contentSelector)[0], data, opts);
+				}
+			};
+
+            this._setup();
+            
+            // Return true to indicate successful creation
+            return true;
+        },
+
+        // Console log wrapper
+        _debug: function infscr_debug() {
+
+			if (this.options && this.options.debug) {
+                return window.console && console.log.call(console, arguments);
+            }
+
+        },
+
+        // find the number to increment in the path.
+        _determinepath: function infscr_determinepath(path) {
+
+            var opts = this.options;
+
+			// if behavior is defined and this function is extended, call that instead of default
+			if (!!opts.behavior && this['_determinepath_'+opts.behavior] !== undefined) {
+				this['_determinepath_'+opts.behavior].call(this,path);
+				return;
+			}
+
+            if (!!opts.pathParse) {
+
+                this._debug('pathParse manual');
+                return opts.pathParse(path, this.options.state.currPage+1);
+
+            } else if (path.match(/^(.*?)\b2\b(.*?$)/)) {
+                path = path.match(/^(.*?)\b2\b(.*?$)/).slice(1);
+
+                // if there is any 2 in the url at all.    
+            } else if (path.match(/^(.*?)2(.*?$)/)) {
+
+                // page= is used in django:
+                // http://www.infinite-scroll.com/changelog/comment-page-1/#comment-127
+                if (path.match(/^(.*?page=)2(\/.*|$)/)) {
+                    path = path.match(/^(.*?page=)2(\/.*|$)/).slice(1);
+                    return path;
+                }
+
+                path = path.match(/^(.*?)2(.*?$)/).slice(1);
+
+            } else {
+
+                // page= is used in drupal too but second page is page=1 not page=2:
+                // thx Jerod Fritz, vladikoff
+                if (path.match(/^(.*?page=)1(\/.*|$)/)) {
+                    path = path.match(/^(.*?page=)1(\/.*|$)/).slice(1);
+                    return path;
+                } else {
+                    this._debug('Sorry, we couldn\'t parse your Next (Previous Posts) URL. Verify your the css selector points to the correct A tag. If you still get this error: yell, scream, and kindly ask for help at infinite-scroll.com.');
+                    // Get rid of isInvalidPage to allow permalink to state
+                    opts.state.isInvalidPage = true;  //prevent it from running on this page.
+                }
+            }
+            this._debug('determinePath', path);
+            return path;
+
+        },
+
+        // Custom error
+        _error: function infscr_error(xhr) {
+
+            var opts = this.options;
+
+			// if behavior is defined and this function is extended, call that instead of default
+			if (!!opts.behavior && this['_error_'+opts.behavior] !== undefined) {
+				this['_error_'+opts.behavior].call(this,xhr);
+				return;
+			}
+
+            if (xhr !== 'destroy' && xhr !== 'end') {
+                xhr = 'unknown';
+            }
+
+            this._debug('Error', xhr);
+
+            if (xhr == 'end') {
+                this._showdonemsg();
+            }
+
+            opts.state.isDone = true;
+            opts.state.currPage = 1; // if you need to go back to this instance
+            opts.state.isPaused = false;
+            this._binding('unbind');
+
+        },
+
+        // Load Callback
+        _loadcallback: function infscr_loadcallback(box, data) {
+
+            var opts = this.options,
+	    		callback = this.options.callback, // GLOBAL OBJECT FOR CALLBACK
+	    		result = (opts.state.isDone) ? 'done' : (!opts.appendCallback) ? 'no-append' : 'append',
+	    		frag;
+	
+			// if behavior is defined and this function is extended, call that instead of default
+			if (!!opts.behavior && this['_loadcallback_'+opts.behavior] !== undefined) {
+				this['_loadcallback_'+opts.behavior].call(this,box,data);
+				return;
+			}
+
+            switch (result) {
+
+                case 'done':
+
+                    this._showdonemsg();
+                    return false;
+
+                    break;
+
+                case 'no-append':
+
+                    if (opts.dataType == 'html') {
+                        data = '<div>' + data + '</div>';
+                        data = $(data).find(opts.itemSelector);
+                    }
+
+                    break;
+
+                case 'append':
+
+                    var children = box.children();
+
+                    // if it didn't return anything
+                    if (children.length == 0) {
+                        return this._error('end');
+                    }
+
+
+                    // use a documentFragment because it works when content is going into a table or UL
+                    frag = document.createDocumentFragment();
+                    while (box[0].firstChild) {
+                        frag.appendChild(box[0].firstChild);
+                    }
+
+                    this._debug('contentSelector', $(opts.contentSelector)[0])
+                    $(opts.contentSelector)[0].appendChild(frag);
+                    // previously, we would pass in the new DOM element as context for the callback
+                    // however we're now using a documentfragment, which doesnt havent parents or children,
+                    // so the context is the contentContainer guy, and we pass in an array
+                    //   of the elements collected as the first argument.
+
+                    data = children.get();
+
+
+                    break;
+
+            }
+
+            // loadingEnd function
+			opts.loading.finished.call($(opts.contentSelector)[0],opts)
+            
+
+            // smooth scroll to ease in the new content
+            if (opts.animate) {
+                var scrollTo = $(window).scrollTop() + $('#infscr-loading').height() + opts.extraScrollPx + 'px';
+                $('html,body').animate({ scrollTop: scrollTo }, 800, function () { opts.state.isDuringAjax = false; });
+            }
+
+            if (!opts.animate) opts.state.isDuringAjax = false; // once the call is done, we can allow it again.
+
+            callback(this,data);
+
+        },
+
+        _nearbottom: function infscr_nearbottom() {
+
+            var opts = this.options,
+	        	pixelsFromWindowBottomToBottom = 0 + $(document).height() - (opts.binder.scrollTop()) - $(window).height();
+
+            // if behavior is defined and this function is extended, call that instead of default
+			if (!!opts.behavior && this['_nearbottom_'+opts.behavior] !== undefined) {
+				return this['_nearbottom_'+opts.behavior].call(this);
+			}
+
+			this._debug('math:', pixelsFromWindowBottomToBottom, opts.pixelsFromNavToBottom);
+
+            // if distance remaining in the scroll (including buffer) is less than the orignal nav to bottom....
+            return (pixelsFromWindowBottomToBottom - opts.bufferPx < opts.pixelsFromNavToBottom);
+
+        },
+
+		// Pause / temporarily disable plugin from firing
+        _pausing: function infscr_pausing(pause) {
+
+            var opts = this.options;
+
+            // if behavior is defined and this function is extended, call that instead of default
+			if (!!opts.behavior && this['_pausing_'+opts.behavior] !== undefined) {
+				this['_pausing_'+opts.behavior].call(this,pause);
+				return;
+			}
+
+			// If pause is not 'pause' or 'resume', toggle it's value
+            if (pause !== 'pause' && pause !== 'resume' && pause !== null) {
+                this._debug('Invalid argument. Toggling pause value instead');
+            };
+
+            pause = (pause && (pause == 'pause' || pause == 'resume')) ? pause : 'toggle';
+
+            switch (pause) {
+                case 'pause':
+                    opts.state.isPaused = true;
+                    break;
+
+                case 'resume':
+                    opts.state.isPaused = false;
+                    break;
+
+                case 'toggle':
+                    opts.state.isPaused = !opts.state.isPaused;
+                    break;
+            }
+
+            this._debug('Paused', opts.state.isPaused);
+            return false;
+
+        },
+
+		// Behavior is determined
+		// If the behavior option is undefined, it will set to default and bind to scroll
+		_setup: function infscr_setup() {
+			
+			var opts = this.options;
+			
+			// if behavior is defined and this function is extended, call that instead of default
+			if (!!opts.behavior && this['_setup_'+opts.behavior] !== undefined) {
+				this['_setup_'+opts.behavior].call(this);
+				return;
+			}
+			
+			this._binding('bind');
+			
+			return false;
+			
+		},
+
+        // Show done message
+        _showdonemsg: function infscr_showdonemsg() {
+
+            var opts = this.options;
+
+			// if behavior is defined and this function is extended, call that instead of default
+			if (!!opts.behavior && this['_showdonemsg_'+opts.behavior] !== undefined) {
+				this['_showdonemsg_'+opts.behavior].call(this);
+				return;
+			}
+
+            opts.loading.msg
+	    		.find('img')
+	    		.hide()
+	    		.parent()
+	    		.find('div').html(opts.loading.finishedMsg).animate({ opacity: 1 }, 2000, function () {
+	    		    $(this).parent().fadeOut('normal');
+	    		});
+
+            // user provided callback when done    
+            opts.errorCallback.call($(opts.contentSelector)[0],'done');
+
+        },
+
+		// grab each selector option and see if any fail
+        _validate: function infscr_validate(opts) {
+
+            for (var key in opts) {
+                if (key.indexOf && key.indexOf('Selector') > -1 && $(opts[key]).length === 0) {
+                    this._debug('Your ' + key + ' found no elements.');
+                    return false;
+                }
+            }
+            
+            return true;
+            
+        },
+
+        /*	
+        ----------------------------
+        Public methods
+        ----------------------------
+        */
+
+		// Bind to scroll
+		bind: function infscr_bind() {
+			this._binding('bind');
+		},
+
+        // Destroy current instance of plugin
+        destroy: function infscr_destroy() {
+
+            this.options.state.isDestroyed = true;
+            return this._error('destroy');
+
+        },
+
+		// Set pause value to false
+		pause: function infscr_pause() {
+			this._pausing('pause');
+		},
+		
+		// Set pause value to false
+		resume: function infscr_resume() {
+			this._pausing('resume');
+		},
+
+        // Retrieve next set of content items
+        retrieve: function infscr_retrieve(pageNum) {
+
+            var instance = this,
+				opts = instance.options,
+				path = opts.path,
+				box, frag, desturl, method, condition,
+	    		pageNum = pageNum || null,
+				getPage = (!!pageNum) ? pageNum : opts.state.currPage;
+				beginAjax = function infscr_ajax(opts) {
+					
+					// increment the URL bit. e.g. /page/3/
+	                opts.state.currPage++;
+
+	                instance._debug('heading into ajax', path);
+
+	                // if we're dealing with a table we can't use DIVs
+	                box = $(opts.contentSelector).is('table') ? $('<tbody/>') : $('<div/>');
+
+	                // desturl = path.join(opts.state.currPage);
+                    desturl = $('#next-page').attr('href');
+
+	                method = (opts.dataType == 'html' || opts.dataType == 'json' ) ? opts.dataType : 'html+callback';
+	                if (opts.appendCallback && opts.dataType == 'html') method += '+callback'
+
+	                switch (method) {
+
+	                    case 'html+callback':
+
+	                        instance._debug('Using HTML via .load() method');
+	                        box.load(desturl + ' ' + opts.itemSelector, null, function infscr_ajax_callback(responseText) {
+	                            instance._loadcallback(box, responseText);
+	                        });
+
+	                        break;
+
+	                    case 'html':
+                            instance._debug('Using ' + (method.toUpperCase()) + ' via $.ajax() method');
+                            $.ajax({
+                                // params
+                                url: desturl,
+                                dataType: opts.dataType,
+                                complete: function infscr_ajax_callback(jqXHR, textStatus) {
+                                    condition = (typeof (jqXHR.isResolved) !== 'undefined') ? (jqXHR.isResolved()) : (textStatus === "success" || textStatus === "notmodified");
+                                    (condition) ? instance._loadcallback(box, jqXHR.responseText) : instance._error('end');
+                                }
+                            });
+    
+                            break;
+	                    case 'json':
+	                        instance._debug('Using ' + (method.toUpperCase()) + ' via $.ajax() method');
+                            $.ajax({
+                              dataType: 'json',
+                              type: 'GET',
+                              url: desturl,
+                              success: function(data, textStatus, jqXHR) {
+                                condition = (typeof (jqXHR.isResolved) !== 'undefined') ? (jqXHR.isResolved()) : (textStatus === "success" || textStatus === "notmodified");
+                                if(opts.appendCallback) {
+                                    // if appendCallback is true, you must defined template in options. 
+                                    // note that data passed into _loadcallback is already an html (after processed in opts.template(data)).
+                                    if(opts.template != undefined) {
+                                        var theData = opts.template(data);
+                                        box.append(theData);
+                                        (condition) ? instance._loadcallback(box, theData) : instance._error('end');
+                                    } else {
+                                        instance._debug("template must be defined.");
+                                        instance._error('end');
+                                    }
+                                } else {
+                                    // if appendCallback is false, we will pass in the JSON object. you should handle it yourself in your callback.
+                                    (condition) ? instance._loadcallback(box, data) : instance._error('end');
+                                }
+                              },
+                              error: function(jqXHR, textStatus, errorThrown) {
+                                instance._debug("JSON ajax request failed.");
+                                instance._error('end');
+                              }
+                            });
+	
+	                        break;
+	                }
+				};
+				
+			// if behavior is defined and this function is extended, call that instead of default
+			if (!!opts.behavior && this['retrieve_'+opts.behavior] !== undefined) {
+				this['retrieve_'+opts.behavior].call(this,pageNum);
+				return;
+			}
+
+            
+			// for manual triggers, if destroyed, get out of here
+			if (opts.state.isDestroyed) {
+                this._debug('Instance is destroyed');
+                return false;
+            };
+
+            // we dont want to fire the ajax multiple times
+            opts.state.isDuringAjax = true;
+
+            opts.loading.start.call($(opts.contentSelector)[0],opts);
+
+        },
+
+        // Check to see next page is needed
+        scroll: function infscr_scroll() {
+
+            var opts = this.options,
+				state = opts.state;
+
+            // if behavior is defined and this function is extended, call that instead of default
+			if (!!opts.behavior && this['scroll_'+opts.behavior] !== undefined) {
+				this['scroll_'+opts.behavior].call(this);
+				return;
+			}
+
+			if (state.isDuringAjax || state.isInvalidPage || state.isDone || state.isDestroyed || state.isPaused) return;
+
+            if (!this._nearbottom()) return;
+
+            this.retrieve();
+
+        },
+		
+		// Toggle pause value
+		toggle: function infscr_toggle() {
+			this._pausing();
+		},
+		
+		// Unbind from scroll
+		unbind: function infscr_unbind() {
+			this._binding('unbind');
+		},
+		
+		// update options
+		update: function infscr_options(key) {
+			if ($.isPlainObject(key)) {
+				this.options = $.extend(true,this.options,key);
+			}
+		}
+
+    }
+
+
+    /*	
+    ----------------------------
+    Infinite Scroll function
+    ----------------------------
+	
+    Borrowed logic from the following...
+	
+    jQuery UI
+    - https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js
+	
+    jCarousel
+    - https://github.com/jsor/jcarousel/blob/master/lib/jquery.jcarousel.js
+	
+    Masonry
+    - https://github.com/desandro/masonry/blob/master/jquery.masonry.js		
+	
+    */
+
+    $.fn.infinitescroll = function infscr_init(options, callback) {
+
+
+        var thisCall = typeof options;
+
+        switch (thisCall) {
+
+            // method 
+            case 'string':
+
+                var args = Array.prototype.slice.call(arguments, 1);
+
+                this.each(function () {
+
+                    var instance = $.data(this, 'infinitescroll');
+
+                    if (!instance) {
+                        // not setup yet
+                        // return $.error('Method ' + options + ' cannot be called until Infinite Scroll is setup');
+						return false;
+                    }
+                    if (!$.isFunction(instance[options]) || options.charAt(0) === "_") {
+                        // return $.error('No such method ' + options + ' for Infinite Scroll');
+						return false;
+                    }
+
+                    // no errors!
+                    instance[options].apply(instance, args);
+
+                });
+
+                break;
+
+            // creation 
+            case 'object':
+
+                this.each(function () {
+
+                    var instance = $.data(this, 'infinitescroll');
+
+                    if (instance) {
+
+                        // update options of current instance
+                        instance.update(options);
+
+                    } else {
+
+                        // initialize new instance
+                        instance = new $.infinitescroll(options, callback, this);
+
+                        // don't attach if instantiation failed
+                        if (!instance.failed) {
+                          $.data(this, 'infinitescroll', instance);
+                        }
+
+                    }
+
+                });
+
+                break;
+
+        }
+
+        return this;
+
+    };
+
+
+
+    /* 
+    * smartscroll: debounced scroll event for jQuery *
+    * https://github.com/lukeshumard/smartscroll
+    * Based on smartresize by @louis_remi: https://github.com/lrbabe/jquery.smartresize.js *
+    * Copyright 2011 Louis-Remi & Luke Shumard * Licensed under the MIT license. *
+    */
+
+    var event = $.event,
+		scrollTimeout;
+
+    event.special.smartscroll = {
+        setup: function () {
+            $(this).bind("scroll", event.special.smartscroll.handler);
+        },
+        teardown: function () {
+            $(this).unbind("scroll", event.special.smartscroll.handler);
+        },
+        handler: function (event, execAsap) {
+            // Save the context
+            var context = this,
+		      args = arguments;
+
+            // set correct event type
+            event.type = "smartscroll";
+
+            if (scrollTimeout) { clearTimeout(scrollTimeout); }
+            scrollTimeout = setTimeout(function () {
+                $.event.handle.apply(context, args);
+            }, execAsap === "execAsap" ? 0 : 100);
+        }
+    };
+
+    $.fn.smartscroll = function (fn) {
+        return fn ? this.bind("smartscroll", fn) : this.trigger("smartscroll", ["execAsap"]);
+    };
+
+
+})(window, jQuery);

File src/js/jquery.infinitescroll.min.js

+/*
+	--------------------------------
+	Infinite Scroll
+	--------------------------------
+	+ https://github.com/paulirish/infinite-scroll
+	+ version 2.0b2.120519
+	+ Copyright 2011/12 Paul Irish & Luke Shumard
+	+ Licensed under the MIT license
+	
+	+ Documentation: http://infinite-scroll.com/
+	
+*/
+
+(function(h,d,e){d.infinitescroll=function(a,c,b){this.element=d(b);this._create(a,c)||(this.failed=!0)};d.infinitescroll.defaults={loading:{finished:e,finishedMsg:"<em>Congratulations, you've reached the end of the internet.</em>",img:"http://www.infinite-scroll.com/loading.gif",msg:null,msgText:"<em>Loading the next set of posts...</em>",selector:null,speed:"fast",start:e},state:{isDuringAjax:!1,isInvalidPage:!1,isDestroyed:!1,isDone:!1,isPaused:!1,currPage:1},callback:e,debug:!1,behavior:e,binder:d(h),
+nextSelector:"div.navigation a:first",navSelector:"div.navigation",contentSelector:null,extraScrollPx:150,itemSelector:"div.post",animate:!1,pathParse:e,dataType:"html",appendCallback:!0,bufferPx:40,errorCallback:function(){},infid:0,pixelsFromNavToBottom:e,path:e};d.infinitescroll.prototype={_binding:function(a){var c=this,b=c.options;b.v="2.0b2.111027";if(b.behavior&&this["_binding_"+b.behavior]!==e)this["_binding_"+b.behavior].call(this);else{if("bind"!==a&&"unbind"!==a)return this._debug("Binding value  "+
+a+" not valid"),!1;if("unbind"==a)this.options.binder.unbind("smartscroll.infscr."+c.options.infid);else this.options.binder[a]("smartscroll.infscr."+c.options.infid,function(){c.scroll()});this._debug("Binding",a)}},_create:function(a,c){var b=d.extend(!0,{},d.infinitescroll.defaults,a);if(!this._validate(a))return!1;this.options=b;var g=d(b.nextSelector).attr("href");if(!g)return this._debug("Navigation selector not found"),!1;b.path=this._determinepath(g);b.contentSelector=b.contentSelector||this.element;
+b.loading.selector=b.loading.selector||b.contentSelector;b.loading.msg=d('<div id="infscr-loading"><img alt="Loading..." src="'+b.loading.img+'" /><div>'+b.loading.msgText+"</div></div>");(new Image).src=b.loading.img;b.pixelsFromNavToBottom=d(document).height()-d(b.navSelector).offset().top;b.loading.start=b.loading.start||function(){d(b.navSelector).hide();b.loading.msg.appendTo(b.loading.selector).show(b.loading.speed,function(){beginAjax(b)})};b.loading.finished=b.loading.finished||function(){b.loading.msg.fadeOut("normal")};
+b.callback=function(a,g){b.behavior&&a["_callback_"+b.behavior]!==e&&a["_callback_"+b.behavior].call(d(b.contentSelector)[0],g);c&&c.call(d(b.contentSelector)[0],g,b)};this._setup();return!0},_debug:function(){if(this.options&&this.options.debug)return h.console&&console.log.call(console,arguments)},_determinepath:function(a){var c=this.options;if(c.behavior&&this["_determinepath_"+c.behavior]!==e)this["_determinepath_"+c.behavior].call(this,a);else{if(c.pathParse)return this._debug("pathParse manual"),
+c.pathParse(a,this.options.state.currPage+1);if(a.match(/^(.*?)\b2\b(.*?$)/))a=a.match(/^(.*?)\b2\b(.*?$)/).slice(1);else if(a.match(/^(.*?)2(.*?$)/)){if(a.match(/^(.*?page=)2(\/.*|$)/))return a=a.match(/^(.*?page=)2(\/.*|$)/).slice(1);a=a.match(/^(.*?)2(.*?$)/).slice(1)}else{if(a.match(/^(.*?page=)1(\/.*|$)/))return a=a.match(/^(.*?page=)1(\/.*|$)/).slice(1);this._debug("Sorry, we couldn't parse your Next (Previous Posts) URL. Verify your the css selector points to the correct A tag. If you still get this error: yell, scream, and kindly ask for help at infinite-scroll.com.");
+c.state.isInvalidPage=!0}this._debug("determinePath",a);return a}},_error:function(a){var c=this.options;c.behavior&&this["_error_"+c.behavior]!==e?this["_error_"+c.behavior].call(this,a):("destroy"!==a&&"end"!==a&&(a="unknown"),this._debug("Error",a),"end"==a&&this._showdonemsg(),c.state.isDone=!0,c.state.currPage=1,c.state.isPaused=!1,this._binding("unbind"))},_loadcallback:function(a,c){var b=this.options,g=this.options.callback,f=b.state.isDone?"done":!b.appendCallback?"no-append":"append";if(b.behavior&&
+this["_loadcallback_"+b.behavior]!==e)this["_loadcallback_"+b.behavior].call(this,a,c);else{switch(f){case "done":return this._showdonemsg(),!1;case "no-append":"html"==b.dataType&&(c=d("<div>"+c+"</div>").find(b.itemSelector));break;case "append":var i=a.children();if(0==i.length)return this._error("end");for(f=document.createDocumentFragment();a[0].firstChild;)f.appendChild(a[0].firstChild);this._debug("contentSelector",d(b.contentSelector)[0]);d(b.contentSelector)[0].appendChild(f);c=i.get()}b.loading.finished.call(d(b.contentSelector)[0],
+b);b.animate&&(f=d(h).scrollTop()+d("#infscr-loading").height()+b.extraScrollPx+"px",d("html,body").animate({scrollTop:f},800,function(){b.state.isDuringAjax=!1}));b.animate||(b.state.isDuringAjax=!1);g(this,c)}},_nearbottom:function(){var a=this.options,c=0+d(document).height()-a.binder.scrollTop()-d(h).height();if(a.behavior&&this["_nearbottom_"+a.behavior]!==e)return this["_nearbottom_"+a.behavior].call(this);this._debug("math:",c,a.pixelsFromNavToBottom);return c-a.bufferPx<a.pixelsFromNavToBottom},
+_pausing:function(a){var c=this.options;if(c.behavior&&this["_pausing_"+c.behavior]!==e)this["_pausing_"+c.behavior].call(this,a);else{"pause"!==a&&("resume"!==a&&null!==a)&&this._debug("Invalid argument. Toggling pause value instead");switch(a&&("pause"==a||"resume"==a)?a:"toggle"){case "pause":c.state.isPaused=!0;break;case "resume":c.state.isPaused=!1;break;case "toggle":c.state.isPaused=!c.state.isPaused}this._debug("Paused",c.state.isPaused);return!1}},_setup:function(){var a=this.options;if(a.behavior&&
+this["_setup_"+a.behavior]!==e)this["_setup_"+a.behavior].call(this);else return this._binding("bind"),!1},_showdonemsg:function(){var a=this.options;a.behavior&&this["_showdonemsg_"+a.behavior]!==e?this["_showdonemsg_"+a.behavior].call(this):(a.loading.msg.find("img").hide().parent().find("div").html(a.loading.finishedMsg).animate({opacity:1},2E3,function(){d(this).parent().fadeOut("normal")}),a.errorCallback.call(d(a.contentSelector)[0],"done"))},_validate:function(a){for(var c in a)if(c.indexOf&&
+-1<c.indexOf("Selector")&&0===d(a[c]).length)return this._debug("Your "+c+" found no elements."),!1;return!0},bind:function(){this._binding("bind")},destroy:function(){this.options.state.isDestroyed=!0;return this._error("destroy")},pause:function(){this._pausing("pause")},resume:function(){this._pausing("resume")},retrieve:function(a){var c=this,b=c.options,g=b.path,f,i,j,h,a=a||null;beginAjax=function(a){a.state.currPage++;c._debug("heading into ajax",g);f=d(a.contentSelector).is("table")?d("<tbody/>"):
+d("<div/>");i=g.join(a.state.currPage);j="html"==a.dataType||"json"==a.dataType?a.dataType:"html+callback";a.appendCallback&&"html"==a.dataType&&(j+="+callback");switch(j){case "html+callback":c._debug("Using HTML via .load() method");f.load(i+" "+a.itemSelector,null,function(a){c._loadcallback(f,a)});break;case "html":c._debug("Using "+j.toUpperCase()+" via $.ajax() method");d.ajax({url:i,dataType:a.dataType,complete:function(a,b){(h="undefined"!==typeof a.isResolved?a.isResolved():"success"===b||
+"notmodified"===b)?c._loadcallback(f,a.responseText):c._error("end")}});break;case "json":c._debug("Using "+j.toUpperCase()+" via $.ajax() method"),d.ajax({dataType:"json",type:"GET",url:i,success:function(b,d,g){h="undefined"!==typeof g.isResolved?g.isResolved():"success"===d||"notmodified"===d;a.appendCallback?a.template!=e?(b=a.template(b),f.append(b),h?c._loadcallback(f,b):c._error("end")):(c._debug("template must be defined."),c._error("end")):h?c._loadcallback(f,b):c._error("end")},error:function(){c._debug("JSON ajax request failed.");
+c._error("end")}})}};if(b.behavior&&this["retrieve_"+b.behavior]!==e)this["retrieve_"+b.behavior].call(this,a);else{if(b.state.isDestroyed)return this._debug("Instance is destroyed"),!1;b.state.isDuringAjax=!0;b.loading.start.call(d(b.contentSelector)[0],b)}},scroll:function(){var a=this.options,c=a.state;a.behavior&&this["scroll_"+a.behavior]!==e?this["scroll_"+a.behavior].call(this):!c.isDuringAjax&&!c.isInvalidPage&&!c.isDone&&!c.isDestroyed&&!c.isPaused&&this._nearbottom()&&this.retrieve()},toggle:function(){this._pausing()},
+unbind:function(){this._binding("unbind")},update:function(a){d.isPlainObject(a)&&(this.options=d.extend(!0,this.options,a))}};d.fn.infinitescroll=function(a,c){switch(typeof a){case "string":var b=Array.prototype.slice.call(arguments,1);this.each(function(){var c=d.data(this,"infinitescroll");if(!c||!d.isFunction(c[a])||"_"===a.charAt(0))return!1;c[a].apply(c,b)});break;case "object":this.each(function(){var b=d.data(this,"infinitescroll");b?b.update(a):(b=new d.infinitescroll(a,c,this),b.failed||
+d.data(this,"infinitescroll",b))})}return this};var k=d.event,l;k.special.smartscroll={setup:function(){d(this).bind("scroll",k.special.smartscroll.handler)},teardown:function(){d(this).unbind("scroll",k.special.smartscroll.handler)},handler:function(a,c){var b=this,e=arguments;a.type="smartscroll";l&&clearTimeout(l);l=setTimeout(function(){d.event.handle.apply(b,e)},"execAsap"===c?0:100)}};d.fn.smartscroll=function(a){return a?this.bind("smartscroll",a):this.trigger("smartscroll",["execAsap"])}})(window,
+jQuery);

File src/js/popup.js

+$.extend($.infinitescroll.prototype,{
+  _loadcallback_custom: function infscr_callback_custom (e,d) {
+    $.each(d.streamItems, function(){
+      $('#entry-tmpl').tmpl(this).appendTo('#entries');
+    });
+    $('#infscr-loading').remove();
+    $('#next-page').attr('href','https://extranet.atlassian.com/rest/mobile/1.0/stream/popular?days=1&pageSize=10&nextPageOffset=' + d.nextPageOffset);
+    $('.navigation').show();
+    this.options.state.isDuringAjax = false;
+  }
+});
+  
 $(function(){
   // Hack to allow all hyperlinks to open in a tab
   $('a').live('click',function(){
   //   });
   // });
 
-  $.get('https://extranet.atlassian.com/rest/mobile/1.0/stream/popular?days=7&pageSize=20',function(d){
-    console.log(d);
+  $('body').delegate('.link','click',function(){
+    //alert($(this).attr('href'));
+    console.log('click',this,$(this).attr('href'))
+    chrome.tabs.create({
+      url: $(this).attr('href'),
+      active: false
+    });
+    $(this).addClass('disabled');
+    return false;
+  });
+
+  $.get('https://extranet.atlassian.com/rest/mobile/1.0/stream/popular?days=1&pageSize=20',function(d){
     if(d.streamItems.length === 0) return false;
+    $('div.navigation a:first').attr('href','https://extranet.atlassian.com/rest/mobile/1.0/stream/popular?days=1&pageSize=10&nextPageOffset=' + d.nextPageOffset);
     $.each(d.streamItems, function(){
       $('#entry-tmpl').tmpl(this).appendTo('#entries');
     });
+    
+    $('#entries').infinitescroll({
+      //itemSelector: "li.row",
+      loading: {
+        finishedMsg: "",
+        finished: function(){
+          console.log('finished', arguments)
+        }
+      },
+      debug: true,
+      behavior: 'custom',
+      nextSelector: "#next-page",
+      dataType: 'json',
+      template: $('#entry-tmpl').tmpl,
+      pathParse: function(url){
+        console.log(arguments)
+        return ["https://extranet.atlassian.com/rest/mobile/1.0/stream/popular?days=1&pageSize=10&nextPageOffset="];
+      },
+      appendCallback:false,
+      errorCallback: function(d){
+        console.log('errorCallback', arguments)
+      }
+    },function(d){
+      console.log('DONE',arguments)
+    });
   });
 
+
   // Set up status update button
   $('#update-status-action').bind('click',function(){
     var status = $('#update-status textarea').val();

File src/manifest.json

 {
 	"browser_action": {
-		"default_icon": "images/icon16.png",
+		"default_icon": "images/favicon.png",
 		"default_popup": "popup.html",
 		"default_title": "EAC Activity Stream"
 	},
 	"description": "Easily view the EAC Activity Stream through a Chrome Extension",
 	"icons": {
-		"128": "images/icon128.png",
-		"16": "images/icon16.png",
-		"48": "images/icon48.png"
+		"128": "images/favicon.png",
+		"16": "images/favicon.png",
+		"48": "images/favicon.png"
 	},
 	"name": "EAC Bling!",
 	"background_page": "background.html",
 	"omnibox": {
 		"keyword": "eac"
 	},
-	"permissions": ["https://extranet.atlassian.com/*", "tabs"],
+	"permissions": [
+		"https://extranet.atlassian.com/*", 
+		"tabs",
+    "experimental",
+    "keybinding"
+	],
+  "commands": {
+	  "browserAction": {
+	    "key": "Ctrl+Shift+E"
+	  }
+	},
 	"homepage_url": "https://extranet.atlassian.com/display/~rmanalang/2011/09/11/EAC+Activity+Stream+Chrome+Extension",
-	"version": "1.2"
+	"version": "1.4"
 }
 

File src/popup.html

 <!DOCTYPE HTML>
 <html>
   <head>
-    <link rel="stylesheet" href="/css/bootstrap-1.2.0.min.css" type="text/css" media="screen" charset="utf-8">
-    <link rel="stylesheet" href="/css/styles.css" type="text/css" media="screen" charset="utf-8">
-    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.3/jquery.min.js"></script>
+    <link rel="stylesheet" href="css/bootstrap-1.2.0.min.css" type="text/css" media="screen" charset="utf-8">
+    <link rel="stylesheet" href="css/styles.css" type="text/css" media="screen" charset="utf-8">
+    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
     <script src="js/jquery.xml2json.pack.js" type="text/javascript" charset="utf-8"></script>
     <script src="js/jquery.tmpl.min.js" type="text/javascript" charset="utf-8"></script>
     <script src="js/sugar-0.9.5.min.js" type="text/javascript" charset="utf-8"></script>
+    <script src="js/jquery.infinitescroll.js" type="text/javascript" charset="utf-8"></script>
     <script src="js/popup.js" type="text/javascript" charset="utf-8"></script>
   </head>
   <body>
     <script id="entry-tmpl" type="text/x-template">
       <li class="row">
-        <a href="https://extranet.atlassian.com${url}">
+        <a class="link" href="https://extranet.atlassian.com${url}">
         <div class="span1 avatar first">
           <img src="https://extranet.atlassian.com${author.avatarUrl}" width="30" height="30"/>
         </div>
         </section>
         <ul id="entries">
         </ul>
+        <div class="navigation">
+          <a id="next-page" href="https://extranet.atlassian.com/rest/mobile/1.0/stream/popular?days=1&pageSize=10&nextPageOffset=">Next page &raquo;</a>
+        </div>
       </div>
     </div>
   </body>