forked from GNUsocial/gnu-social
		
	
		
			
				
	
	
		
			262 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			262 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 
 | |
| /*!
 | |
| // Infinite Scroll jQuery plugin
 | |
| // copyright Paul Irish, licensed GPL & MIT
 | |
| // version 1.2.090804
 | |
| 
 | |
| // home and docs: http://www.infinite-scroll.com
 | |
| */
 | |
| 
 | |
| // todo: add preloading option.
 | |
|  
 | |
| ;(function($){
 | |
|     
 | |
|   $.fn.infinitescroll = function(options,callback){
 | |
|     
 | |
|     // console log wrapper.
 | |
|     function debug(){
 | |
|       if (opts.debug) { window.console && console.log.call(console,arguments)}
 | |
|     }
 | |
|     
 | |
|     // grab each selector option and see if any fail.
 | |
|     function areSelectorsValid(opts){
 | |
|       for (var key in opts){
 | |
|         if (key.indexOf && key.indexOf('Selector') && $(opts[key]).length === 0){
 | |
|             debug('Your ' + key + ' found no elements.');    
 | |
|             return false;
 | |
|         } 
 | |
|         return true;
 | |
|       }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     // find the number to increment in the path.
 | |
|     function determinePath(path){
 | |
|       
 | |
|       path.match(relurl) ? path.match(relurl)[2] : path; 
 | |
| 
 | |
|       // there is a 2 in the url surrounded by slashes, e.g. /page/2/
 | |
|       if ( path.match(/^(.*?)\b2\b(.*?$)/) ){  
 | |
|           path = path.match(/^(.*?)\b2\b(.*?$)/).slice(1);
 | |
|       } else 
 | |
|         // if there is any 2 in the url at all.
 | |
|         if (path.match(/^(.*?)2(.*?$)/)){
 | |
|           debug('Trying backup next selector parse technique. Treacherous waters here, matey.');
 | |
|           path = path.match(/^(.*?)2(.*?$)/).slice(1);
 | |
|       } else {
 | |
|         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.');    
 | |
|         props.isInvalidPage = true;  //prevent it from running on this page.
 | |
|       }
 | |
|       
 | |
|       return path;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     // 'document' means the full document usually, but sometimes the content of the overflow'd div in local mode
 | |
|     function getDocumentHeight(){
 | |
|       // weird doubletouch of scrollheight because http://soulpass.com/2006/07/24/ie-and-scrollheight/
 | |
|       return opts.localMode ? ($(props.container)[0].scrollHeight && $(props.container)[0].scrollHeight) 
 | |
|                                 // needs to be document's height. (not props.container's) html's height is wrong in IE.
 | |
|                                 : $(document).height()
 | |
|     }
 | |
|     
 | |
|     
 | |
|         
 | |
|     function isNearBottom(opts,props){
 | |
|       
 | |
|       // distance remaining in the scroll
 | |
|       // computed as: document height - distance already scroll - viewport height - buffer
 | |
|       var pixelsFromWindowBottomToBottom = getDocumentHeight()  -
 | |
|                                             (opts.localMode ? $(props.container).scrollTop() : 
 | |
|                                               // have to do this bs because safari doesnt report a scrollTop on the html element
 | |
|                                               ($(props.container).scrollTop() || $(props.container.ownerDocument.body).scrollTop())) - 
 | |
|                                             $(opts.localMode ? props.container : window).height();
 | |
|       
 | |
|       debug('math:',pixelsFromWindowBottomToBottom, props.pixelsFromNavToBottom);
 | |
|       
 | |
|       // if distance remaining in the scroll (including buffer) is less than the orignal nav to bottom....
 | |
|       return (pixelsFromWindowBottomToBottom  - opts.bufferPx < props.pixelsFromNavToBottom);    
 | |
|     }    
 | |
|     
 | |
|     function showDoneMsg(){
 | |
|       props.loadingMsg
 | |
|         .find('img').hide()
 | |
|         .parent()
 | |
|           .find('div').html(opts.donetext).animate({opacity: 1},2000).fadeOut('normal');
 | |
|       
 | |
|       // user provided callback when done    
 | |
|       opts.errorCallback();
 | |
|     }
 | |
|     
 | |
|     function infscrSetup(path,opts,props,callback){
 | |
|     
 | |
|         if (props.isDuringAjax || props.isInvalidPage || props.isDone) return; 
 | |
|     
 | |
|     		if ( opts.infiniteScroll && !isNearBottom(opts,props) ) return; 
 | |
|     		  
 | |
|     		// we dont want to fire the ajax multiple times
 | |
|     		props.isDuringAjax = true; 
 | |
|     		
 | |
|     		// show the loading message and hide the previous/next links
 | |
|     		props.loadingMsg.appendTo( opts.contentSelector ).show();
 | |
|     		if(opts.infiniteScroll) $( opts.navSelector ).hide(); 
 | |
|     		
 | |
|     		// increment the URL bit. e.g. /page/3/
 | |
|     		props.currPage++;
 | |
|     		
 | |
|     		debug('heading into ajax',path);
 | |
|     		
 | |
|     		// if we're dealing with a table we can't use DIVs
 | |
|     		var box = $(opts.contentSelector).is('table') ? $('<tbody/>') : $('<div/>');  
 | |
|     		
 | |
|     		box
 | |
|     		  .attr('id','infscr-page-'+props.currPage)
 | |
|     		  .addClass('infscr-pages')
 | |
|     		  .appendTo( opts.contentSelector )
 | |
|     		  .load( path.join( props.currPage ) + ' ' + opts.itemSelector,null,function(){
 | |
|     		    
 | |
|     		        // if we've hit the last page...
 | |
|     		        if (props.isDone){ 
 | |
|                     showDoneMsg();
 | |
|         			      return false;    
 | |
|         			      
 | |
|   	            } else {
 | |
|   	              
 | |
|   	                // if it didn't return anything
 | |
|   	                if (box.children().length == 0){
 | |
|   	                  // fake an ajaxError so we can quit.
 | |
|   	                  $.event.trigger( "ajaxError", [{status:404}] ); 
 | |
|   	                } 
 | |
|   	                
 | |
|   	                // fadeout currently makes the <em>'d text ugly in IE6
 | |
|     		            props.loadingMsg.fadeOut('normal' ); 
 | |
|   
 | |
|     		            // 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(){ props.isDuringAjax = false; }); 
 | |
|     		            }
 | |
|                     
 | |
|                     // pass in the new DOM element as context for the callback
 | |
|                     callback.call( box[0] );
 | |
|                     
 | |
|     		            if (!opts.animate) props.isDuringAjax = false; // once the call is done, we can allow it again.
 | |
|   	            }
 | |
|     		    }); // end of load()
 | |
|     			
 | |
|     		    
 | |
|       }  // end of infscrSetup()
 | |
|           
 | |
|   
 | |
|     
 | |
|       
 | |
|     // lets get started.
 | |
|     
 | |
|     var opts    = $.extend({}, $.infinitescroll.defaults, options);
 | |
|     var props   = $.infinitescroll; // shorthand
 | |
|     callback    = callback || function(){};
 | |
|     
 | |
|     if (!areSelectorsValid(opts)){ return false;  }
 | |
|     
 | |
|      // we doing this on an overflow:auto div?
 | |
|     props.container   =  opts.localMode ? this : document.documentElement;
 | |
|                           
 | |
|     // contentSelector we'll use for our .load()
 | |
|     opts.contentSelector = opts.contentSelector || this; 
 | |
|     
 | |
|     
 | |
|     // get the relative URL - everything past the domain name.
 | |
|     var relurl        = /(.*?\/\/).*?(\/.*)/;
 | |
|     var path          = $(opts.nextSelector).attr('href');
 | |
|     
 | |
|     
 | |
|     if (!path) { debug('Navigation selector not found'); return; }
 | |
|     
 | |
|     // set the path to be a relative URL from root.
 | |
|     path          = determinePath(path);
 | |
|     
 | |
| 
 | |
|     // reset scrollTop in case of page refresh:
 | |
|     if (opts.localMode) $(props.container)[0].scrollTop = 0;
 | |
| 
 | |
|     // distance from nav links to bottom
 | |
|     // computed as: height of the document + top offset of container - top offset of nav link
 | |
|     props.pixelsFromNavToBottom =  getDocumentHeight()  +
 | |
|                                      $(props.container).offset().top - 
 | |
|                                      $(opts.navSelector).offset().top;
 | |
|     
 | |
|     // define loading msg
 | |
|     props.loadingMsg = $('<div id="infscr-loading" style="text-align: center;"><img alt="Loading..." src="'+
 | |
|                                   opts.loadingImg+'" /><div>'+opts.loadingText+'</div></div>');    
 | |
|      // preload the image
 | |
|     (new Image()).src    = opts.loadingImg;
 | |
|   		      
 | |
| 
 | |
|   
 | |
|     // set up our bindings
 | |
|     $(document).ajaxError(function(e,xhr,opt){
 | |
|       debug('Page not found. Self-destructing...');    
 | |
|       
 | |
|       // die if we're out of pages.
 | |
|       if (xhr.status == 404){ 
 | |
|         showDoneMsg();
 | |
|         props.isDone = true; 
 | |
|         $(opts.localMode ? this : window).unbind('scroll.infscr');
 | |
|       } 
 | |
|     });
 | |
|     
 | |
|     if(opts.infiniteScroll){
 | |
|       // bind scroll handler to element (if its a local scroll) or window  
 | |
|       $(opts.localMode ? this : window)
 | |
|         .bind('scroll.infscr', function(){ infscrSetup(path,opts,props,callback); } )
 | |
|         .trigger('scroll.infscr'); // trigger the event, in case it's a short page
 | |
|     }else{
 | |
|       $(opts.nextSelector).click(
 | |
|         function(){
 | |
|           infscrSetup(path,opts,props,callback);
 | |
|           return false;
 | |
|         }
 | |
|       );
 | |
|     }
 | |
|     
 | |
|     
 | |
|     return this;
 | |
|   
 | |
|   }  // end of $.fn.infinitescroll()
 | |
|   
 | |
| 
 | |
|   
 | |
|   // options and read-only properties object
 | |
|   
 | |
|   $.infinitescroll = {     
 | |
|         defaults      : {
 | |
|                           debug           : false,
 | |
|                           infiniteScroll  : true,
 | |
|                           preload         : false,
 | |
|                           nextSelector    : "div.navigation a:first",
 | |
|                           loadingImg      : "http://www.infinite-scroll.com/loading.gif",
 | |
|                           loadingText     : "<em>Loading the next set of posts...</em>",
 | |
|                           donetext        : "<em>Congratulations, you've reached the end of the internet.</em>",
 | |
|                           navSelector     : "div.navigation",
 | |
|                           contentSelector : null,           // not really a selector. :) it's whatever the method was called on..
 | |
|                           extraScrollPx   : 150,
 | |
|                           itemSelector    : "div.post",
 | |
|                           animate         : false,
 | |
|                           localMode      : false,
 | |
|                           bufferPx        : 40,
 | |
|                           errorCallback   : function(){}
 | |
|                         }, 
 | |
|         loadingImg    : undefined,
 | |
|         loadingMsg    : undefined,
 | |
|         container     : undefined,
 | |
|         currPage      : 1,
 | |
|         currDOMChunk  : null,  // defined in setup()'s load()
 | |
|         isDuringAjax  : false,
 | |
|         isInvalidPage : false,
 | |
|         isDone        : false  // for when it goes all the way through the archive.
 | |
|   };
 | |
|   
 | |
| 
 | |
| 
 | |
| })(jQuery);
 |