From 21367a184732878b73b7f254861f578059ea5ec6 Mon Sep 17 00:00:00 2001
From: Baris Soner Usakli <barisusakli@gmail.com>
Date: Mon, 17 Feb 2014 20:57:12 -0500
Subject: [PATCH] reverse infinite loading

---
 public/src/ajaxify.js      |  22 +---
 public/src/app.js          |  11 +-
 public/src/forum/topic.js  | 264 +++++++++++++++++++++----------------
 public/templates/topic.tpl |   4 -
 src/posts.js               |  10 ++
 src/routes/api.js          |   1 -
 src/socket.io/posts.js     |   4 +
 src/socket.io/topics.js    |   2 +-
 8 files changed, 180 insertions(+), 138 deletions(-)

diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js
index 00637a61bb..977e9a8246 100644
--- a/public/src/ajaxify.js
+++ b/public/src/ajaxify.js
@@ -47,8 +47,6 @@ var ajaxify = {};
 		// Remove trailing slash
 		url = url.replace(/\/$/, "");
 
-		var hash = window.location.hash;
-
 		if (url.indexOf(RELATIVE_PATH.slice(1)) !== -1) {
 			url = url.slice(RELATIVE_PATH.length);
 		}
@@ -66,13 +64,18 @@ var ajaxify = {};
 			tpl_url = url;
 		}
 
+		var hash = '';
+		if(ajaxify.initialLoad && window.location.href.search(/#[0-9]+$/) !== -1) {
+			hash = window.location.hash ? window.location.hash : '';
+		}
+
 		if (templates.is_available(tpl_url) && !templates.force_refresh(tpl_url)) {
 			ajaxify.currentPage = tpl_url;
 
 			if (window.history && window.history.pushState) {
 				window.history[!quiet ? 'pushState' : 'replaceState']({
-					url: url
-				}, url, RELATIVE_PATH + '/' + url);
+					url: url + hash
+				}, url, RELATIVE_PATH + '/' + url + hash);
 
 				$.ajax(RELATIVE_PATH + '/plugins/fireHook', {
 					type: 'PUT',
@@ -110,16 +113,6 @@ var ajaxify = {};
 				jQuery('#content, #footer').stop(true, true).removeClass('ajaxifying');
 				ajaxify.initialLoad = false;
 
-				if (window.location.hash) {
-					hash = window.location.hash;
-				}
-
-				if (hash) {
-					require(['forum/topic'], function(topic) {
-						topic.scrollToPost(hash.substr(1));
-					});
-				}
-
 				app.refreshTitle(url);
 				$(window).trigger('action:ajaxify.end', { url: url });
 			}, url);
@@ -165,7 +158,6 @@ var ajaxify = {};
 					var url = this.href.replace(rootUrl + '/', '');
 
 					if (ajaxify.go(url)) {
-
 						e.preventDefault();
 					}
 				} else if (window.location.pathname !== '/outgoing') {
diff --git a/public/src/app.js b/public/src/app.js
index 18add6bc0f..836748641f 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -420,13 +420,20 @@ var socket,
 		});
 	};
 
+	var previousScrollTop = 0;
+
 	app.enableInfiniteLoading = function(callback) {
 		$(window).off('scroll').on('scroll', function() {
+			var top = $(window).height() * 0.1;
 			var bottom = ($(document).height() - $(window).height()) * 0.9;
+			var currentScrollTop = $(window).scrollTop();
 
-			if ($(window).scrollTop() > bottom) {
-				callback();
+			if($(window).scrollTop() < top && previousScrollTop > currentScrollTop) {
+				callback(-1);
+			} else if ($(window).scrollTop() > bottom && previousScrollTop < currentScrollTop) {
+				callback(1);
 			}
+			previousScrollTop = currentScrollTop;
 		});
 	}
 
diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index b141b70369..9ab4ceac4e 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -16,6 +16,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 	});
 
 	Topic.init = function() {
+
 		var expose_tools = templates.get('expose_tools'),
 			tid = templates.get('topic_id'),
 			thread_state = {
@@ -49,8 +50,6 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 
 			showBottomPostBar();
 
-			updateHeader();
-
 			if (thread_state.locked === '1') set_locked_state(true);
 			if (thread_state.deleted === '1') set_delete_state(true);
 			if (thread_state.pinned === '1') set_pinned_state(true);
@@ -336,9 +335,14 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			enableInfiniteLoading();
 
 			var bookmark = localStorage.getItem('topic:' + tid + ':bookmark');
-
-			if(bookmark) {
-				Topic.scrollToPost(parseInt(bookmark, 10));
+			if(window.location.hash) {
+				Topic.scrollToPost(window.location.hash.substr(1));
+			} else if(bookmark) {
+				if(bookmark) {
+					Topic.scrollToPost(parseInt(bookmark, 10));
+				}
+			} else {
+				updateHeader();
 			}
 
 			$('#post-container').on('mouseenter', '.favourite-tooltip', function(e) {
@@ -356,10 +360,31 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		function enableInfiniteLoading() {
 			if(!config.usePagination) {
 				$('.pagination-block').removeClass('hide');
-				app.enableInfiniteLoading(function() {
+
+				app.enableInfiniteLoading(function(direction) {
+
 					if (!infiniteLoaderActive && $('#post-container').children().length) {
-						loadMorePosts(tid, function(posts) {
+						var after = 0;
+						var el = null;
+						if(direction > 0) {
+							el = $('#post-container .post-row.infiniteloaded').last();
+							after = parseInt(el.attr('data-index'), 10) + 1;
+						} else {
+							el = $('#post-container .post-row.infiniteloaded').first();
+							after = parseInt(el.attr('data-index'), 10);
+							after -= config.postsPerPage;
+							if(after < 0) {
+								after = 0;
+							}
+						}
+
+						var offset = el.offset().top - $('#header-menu').offset().top + $('#header-menu').height();
+
+						loadMorePosts(tid, after, function() {
 							fixDeleteStateForPosts();
+							if(direction < 0 && el) {
+								Topic.scrollToPost(el.attr('data-pid'), 0, offset);
+							}
 						});
 					}
 				});
@@ -1080,52 +1105,59 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			$('#header-topic-title').html('').hide();
 		}
 
-		if (scrollTop < jQuery('.posts > .post-row:first-child').height() && Topic.postCount > 1) {
-			localStorage.removeItem("topic:" + tid + ":bookmark");
-			paginationEl.html('1 out of ' + Topic.postCount);
-			progressBar.width(0);
-
-			return;
-		}
-
-		var count = 0, smallestNonNegative = 0;
-
-		jQuery('.posts > .post-row:not(".deleted")').each(function() {
-			count++;
-			this.postnumber = count;
-
-
-			var el = jQuery(this);
-			var elTop = el.offset().top;
-			var height = Math.floor(el.height());
-			var elBottom = elTop + (height < 300 ? height : 300);
-
-			var inView = ((elBottom >= scrollTop) && (elTop <= scrollBottom) && (elBottom <= scrollBottom) && (elTop >= scrollTop));
+		$($('.posts > .post-row').get().reverse()).each(function() {
+			var el = $(this);
 
+			if (elementInView(el)) {
+				var index = parseInt(el.attr('data-index'), 10) + 1;
+				if(index > Topic.postCount) {
+					index = Topic.postCount;
+				}
+				paginationEl.html(index + ' out of ' + Topic.postCount);
+				progressBar.width((index / Topic.postCount * 100) + '%');
+				return false;
+			}
+		});
 
-			if (inView) {
-				if(elTop - scrollTop > smallestNonNegative) {
+		$('.posts > .post-row').each(function() {
+			var el = $(this);
+			if (elementInView(el)) {
+				var index = parseInt(el.attr('data-index'), 10) + 1;
+				if(index === 0) {
+					localStorage.removeItem("topic:" + tid + ":bookmark");
+				} else {
 					localStorage.setItem("topic:" + tid + ":bookmark", el.attr('data-pid'));
-					smallestNonNegative = Number.MAX_VALUE;
 				}
-
-				paginationEl.html((this.postnumber-1) + ' out of ' + Topic.postCount);
-				progressBar.width(((this.postnumber-1) / Topic.postCount * 100) + '%');
+				return false;
 			}
 		});
+	}
 
-		setTimeout(function() {
-			if (scrollTop + windowHeight == jQuery(document).height() && !infiniteLoaderActive) {
-				paginationEl.html(Topic.postCount + ' out of ' + Topic.postCount);
-				progressBar.width('100%');
-			}
-		}, 100);
+	function elementInView(el) {
+		var scrollTop = $(window).scrollTop();
+		var scrollBottom = scrollTop + $(window).height();
+
+		var elTop = el.offset().top;
+		var height = Math.floor(el.height());
+		var elBottom = elTop + height;
+
+		return ((elBottom >= scrollTop) &&
+				(elTop <= scrollBottom) &&
+				(elBottom <= scrollBottom) &&
+				(elTop >= scrollTop));
 	}
 
-	Topic.scrollToPost = function(pid) {
+	Topic.scrollToPost = function(pid, duration, offset) {
 		if (!pid) {
 			return;
 		}
+		if(!offset) {
+			offset = 0;
+		}
+
+		if($('#post_anchor_' + pid).length) {
+			return scrollToPid(pid);
+		}
 
 		if(config.usePagination) {
 			socket.emit('posts.getPidPage', pid, function(err, page) {
@@ -1139,36 +1171,35 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				}
 			});
 		} else {
-			scrollToPid(pid);
+			socket.emit('posts.getPidIndex', pid, function(err, index) {
+				if(err) {
+					return;
+				}
+				var tid = $('#post-container').attr('data-tid');
+				$('#post-container').empty();
+				var after = index - config.postsPerPage + 1;
+				if(after < 0) {
+					after = 0;
+				}
+				loadMorePosts(tid, after, function() {
+					scrollToPid(pid);
+				});
+			});
 		}
 
 		function scrollToPid(pid) {
-			var container = $(window),
-			scrollTo = $('#post_anchor_' + pid),
-			tid = $('#post-container').attr('data-tid');
+			var scrollTo = $('#post_anchor_' + pid),
+				tid = $('#post-container').attr('data-tid');
 
 			function animateScroll() {
 				$('window,html').animate({
-					scrollTop: scrollTo.offset().top + container.scrollTop() - $('#header-menu').height()
-				}, 400);
+					scrollTop: scrollTo.offset().top - $('#header-menu').height() - offset
+				}, duration !== undefined ? duration : 400, function() {
+					updateHeader();
+				});
 			}
 
-			if (!scrollTo.length && tid) {
-
-				var intervalID = setInterval(function () {
-					loadMorePosts(tid, function (posts) {
-						scrollTo = $('#post_anchor_' + pid);
-
-						if (tid && scrollTo.length) {
-							animateScroll();
-						}
-
-						if (!posts.length || scrollTo.length)
-							clearInterval(intervalID);
-					});
-				}, 100);
-
-			} else if (tid) {
+			if (tid && scrollTo.length) {
 				animateScroll();
 			}
 		}
@@ -1188,7 +1219,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		});
 	}
 
-	function createNewPosts(data, infiniteLoaded) {
+	function createNewPosts(data, infiniteLoaded, callback) {
 		if(!data || (data.posts && !data.posts.length)) {
 			return;
 		}
@@ -1199,12 +1230,14 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			});
 		}
 
+		var after = null,
+			before = null;
+
 		function findInsertionPoint() {
-			var after = null,
-				firstPid = data.posts[0].pid;
+			var firstPid = parseInt(data.posts[0].pid, 10);
 
 			$('#post-container li[data-pid]').each(function() {
-				if(parseInt(firstPid, 10) > parseInt($(this).attr('data-pid'), 10)) {
+				if(firstPid > parseInt($(this).attr('data-pid'), 10)) {
 					after = $(this);
 					if(after.next().length && after.next().hasClass('post-bar')) {
 						after = after.next();
@@ -1213,7 +1246,13 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 					return false;
 				}
 			});
-			return after;
+
+			if(!after) {
+				var firstPost = $('#post-container .post-row').first();
+				if(firstPid < parseInt(firstPost.attr('data-pid'), 10)) {
+					before = firstPost;
+				}
+			}
 		}
 
 		removeAlreadyAddedPosts();
@@ -1221,7 +1260,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 			return;
 		}
 
-		var insertAfter = findInsertionPoint();
+		findInsertionPoint();
 
 		parseAndTranslatePosts(data, function(translatedHTML) {
 			var translated = $(translatedHTML);
@@ -1230,25 +1269,21 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 				translated.removeClass('infiniteloaded');
 			}
 
-			translated.insertAfter(insertAfter)
-				.hide()
-				.fadeIn('slow');
+			if(after) {
+				translated.insertAfter(after)
+			} else if(before) {
+				translated.insertBefore(before);
+			} else {
+				$('#post-container').append(translated);
+			}
 
-			// Remove the extra post-bar and "follow" button that gets added
-			var	postsEl = $('.posts');
-			postsEl.find('.post-bar').each(function(idx, el) {
-				if (idx !== 0) {
-					el.parentNode.removeChild(el);
-				}
-			});
-			postsEl.find('li.post-row[data-index]').each(function(idx, el) {
-				followEl = el.querySelector('.follow');
-				if (idx !== 0 && followEl) {
-					followEl.parentNode.removeChild(followEl);
-				}
-			});
+			translated.hide().fadeIn('slow');
+
+			onNewPostsLoaded(translated, data.posts);
 
-			onNewPostsLoaded(data.posts);
+			if(typeof callback === 'function') {
+				callback();
+			}
 		});
 	}
 
@@ -1258,7 +1293,7 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 	}
 
 
-	function onNewPostsLoaded(posts) {
+	function onNewPostsLoaded(html, posts) {
 		for (var x = 0, numPosts = posts.length; x < numPosts; x++) {
 			socket.emit('posts.getPrivileges', posts[x].pid, function(err, privileges) {
 				if(err) {
@@ -1273,8 +1308,9 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		app.populateOnlineUsers();
 		app.createUserTooltips();
 		app.addCommasToNumbers();
-		$('span.timeago').timeago();
-		$('.post-content img').addClass('img-responsive');
+		app.makeNumbersHumanReadable($('.human-readable-number'));
+		html.find('span.timeago').timeago();
+		html.find('.post-content img').addClass('img-responsive');
 		updatePostCount();
 		showBottomPostBar();
 	}
@@ -1304,41 +1340,39 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
 		});
 	}
 
-	function loadMorePosts(tid, callback) {
+	function loadMorePosts(tid, after, callback) {
 		var indicatorEl = $('.loading-indicator');
 
-		if (infiniteLoaderActive) {
+		if (infiniteLoaderActive || !$('#post-container').length) {
 			return;
 		}
 
-		if (indicatorEl.attr('done') === '0') {
-			infiniteLoaderActive = true;
-			indicatorEl.fadeIn();
-
-			socket.emit('topics.loadMore', {
-				tid: tid,
-				after: parseInt($('#post-container .post-row.infiniteloaded').last().attr('data-index'), 10) + 1
-			}, function (err, data) {
-				if(err) {
-					return app.alertError(err.message);
-				}
-
-				infiniteLoaderActive = false;
-				if (data && data.posts && data.posts.length) {
-					indicatorEl.attr('done', '0');
-					createNewPosts(data, true);
-				} else {
-					indicatorEl.attr('done', '1');
-					updateHeader();
-				}
+		if(after === 0 && $('#post-container li.post-row[data-index="0"]').length) {
+			return;
+		}
 
-				indicatorEl.fadeOut();
+		infiniteLoaderActive = true;
+		indicatorEl.fadeIn();
+
+		socket.emit('topics.loadMore', {
+			tid: tid,
+			after: after
+		}, function (err, data) {
+			infiniteLoaderActive = false;
+			indicatorEl.fadeOut();
+			if(err) {
+				return app.alertError(err.message);
+			}
 
-				if (callback) {
+			if (data && data.posts && data.posts.length) {
+				createNewPosts(data, true, callback);
+			} else {
+				updateHeader();
+				if (typeof callback === 'function') {
 					callback(data.posts);
 				}
-			});
-		}
+			}
+		});
 	}
 
 	return Topic;
diff --git a/public/templates/topic.tpl b/public/templates/topic.tpl
index 571d137cc3..a25e50fcdd 100644
--- a/public/templates/topic.tpl
+++ b/public/templates/topic.tpl
@@ -65,9 +65,7 @@
 						</div>
 
 						<div class="btn-group">
-							<!-- IF @first -->
 							<button class="btn btn-sm btn-default follow" type="button" title="[[topic:notify_me]]"><i class="fa fa-eye"></i></button>
-							<!-- ENDIF @first -->
 							<button class="btn btn-sm btn-default flag" type="button" title="[[topic:flag_title]]"><i class="fa fa-flag-o"></i></button>
 							<button data-favourited="{posts.favourited}" class="favourite favourite-tooltip btn btn-sm btn-default <!-- IF posts.favourited --> btn-warning <!-- ENDIF posts.favourited -->" type="button">
 								<span class="favourite-text">[[topic:favourite]]</span>
@@ -157,7 +155,6 @@
 				<div style="clear:both;"></div>
 			</li>
 
-			<!-- IF @first -->
 			<li class="well post-bar" data-index="{posts.index}">
 				<div class="inline-block">
 					<small class="topic-stats">
@@ -189,7 +186,6 @@
 				</div>
 				<div style="clear:both;"></div>
 			</li>
-			<!-- ENDIF @first -->
 		<!-- END posts -->
 	</ul>
 
diff --git a/src/posts.js b/src/posts.js
index 37faa1b3de..95f0da116b 100644
--- a/src/posts.js
+++ b/src/posts.js
@@ -525,6 +525,16 @@ var db = require('./database'),
 				});
 			});
 		});
+	};
+
+	Posts.getPidIndex = function(pid, callback) {
+		Posts.getPostField(pid, 'tid', function(err, tid) {
+			if(err) {
+				return callback(err);
+			}
+
+			db.sortedSetRank('tid:' + tid + ':posts', pid, callback);
+		});
 	}
 
 }(exports));
diff --git a/src/routes/api.js b/src/routes/api.js
index 9544f3295a..2693b46597 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -205,7 +205,6 @@ var path = require('path'),
 			});
 
 			app.get('/topic/:id/:slug?', function (req, res, next) {
-
 				var uid = (req.user) ? req.user.uid : 0;
 				var page = 1;
 				if(req.query && req.query.page) {
diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js
index 7ad8c7c088..b0685b371d 100644
--- a/src/socket.io/posts.js
+++ b/src/socket.io/posts.js
@@ -235,6 +235,10 @@ SocketPosts.getPidPage = function(socket, pid, callback) {
 	posts.getPidPage(pid, socket.uid, callback);
 }
 
+SocketPosts.getPidIndex = function(socket, pid, callback) {
+	posts.getPidIndex(pid, callback);
+}
+
 SocketPosts.flag = function(socket, pid, callback) {
 	if (!socket.uid) {
 		return callback(new Error('not-logged-in'));
diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js
index d78968c512..c73ea3de37 100644
--- a/src/socket.io/topics.js
+++ b/src/socket.io/topics.js
@@ -230,7 +230,7 @@ SocketTopics.follow = function(socket, tid, callback) {
 };
 
 SocketTopics.loadMore = function(socket, data, callback) {
-	if(!data || !data.tid || !data.after) {
+	if(!data || !data.tid || !(parseInt(data.after, 10) >= 0)) {
 		return callback(new Error('invalid data'));
 	}