Unverified Commit 6fb503dc authored by Aral Balkan's avatar Aral Balkan
Browse files

Added infinite scroll to All Friends. Fixed the bug with images not displaying...

Added infinite scroll to All Friends. Fixed the bug with images not displaying if received via a delta update (for the Conversations timeline).
parent ff4b6f3f
......@@ -117,7 +117,7 @@ module.exports = (app) ->
return profileImagePath
app.get('config').streamWeaver.getTimeline('allfriends').then (messages) ->
app.get('config').streamWeaver.getTimeline('allfriends', '\x00', '\uffff', 10).then (messages) ->
# Massage the asset URLs
for message in messages
......
......@@ -11,6 +11,21 @@
<script type='text/javascript' src='/js/zenscroll.min.js'></script>
<script type='text/javascript' src='/js/all-friends.js'></script>
<script type='text/javascript' data-set-text='meta'></script>
<style type="text/css">
.displayNone
{
display:none;
}
.progress-spinner
{
display:inline;
width:16px;
vertical-align:top;
padding-top: 2px;
padding-right:5px;
}
</style>
</head>
<body>
<section id='public-timeline'>
......@@ -40,6 +55,11 @@
<!-- <div class='messageStatus' data-set-attribute='id message.key messageStatusIDFormatter'></div>-->
</div>
</div>
<div id='loadProgress' class='displayNone'>
<p style='text-align:center;'><img class='progress-spinner' src='/images/progress-spinner@2x.gif'></p>
</div>
<section>
</body>
</html>
\ No newline at end of file
function insertAfter(parentNode, referenceNode, newNode) {
//
// Make sure we handle the null reference node gracefully
// (i.e., the parent node being inserted into has no children)
//
var nodeToInsertBefore = referenceNode;
if (referenceNode != null)
{
nodeToInsertBefore = referenceNode.nextSibling
}
parentNode.insertBefore(newNode, nodeToInsertBefore);
}
function getOlderMessages(){
console.log("Polling server for older public posts (infinite scroll)…")
//
// Get the ID of the last message loaded so we can use this
// to poll for new messages that have been received since the
// timeline initially loaded. Compensate for an empty timeline
// with no messages.
//
var messages = document.getElementById('messages');
var lastMessage = messages.lastElementChild;
// The latest message is at the bottom for conversation-style timelines.
var oldestPostID = 0;
if (lastMessage != null) {
oldestPostID = lastMessage.getAttribute('id');
console.log("Oldest post ID: " + oldestPostID);
}
else
{
console.log("No messages.");
return;
}
console.log('Getting posts before ' + oldestPostID);
superagent
.get('http://127.0.0.1:42003/timeline/posts-before/allfriends/'+oldestPostID)
.end(function(error, posts) {
var loadProgress = document.getElementById('loadProgress');
var displayNoneClass = "displayNone";
// Hide the progress indicator
loadProgress.classList.add(displayNoneClass);
posts = JSON.parse(posts.text);
// console.log(posts);
if (posts.length == 0){
console.log('No older posts.');
return;
}
console.log("Got " + posts.length + "older posts.");
var repeaterNodeInnerHTML =
" <div class='messageBody' data-set-attribute='id message.key messageBodyIDFormatter'>"
+ " <div class='image-and-body'>"
+ " <img class='profileImage' data-set-attribute='src message.key profileImagePathFormatter'>"
+ " <div class='bodyText' data-set-text='html message.value'>Message body HTML</div>"
+ " <div class='meta'><span class='postDate' data-set-attribute='data-timestamp message.key timestampFormatter' data-set-text='message.key postDateFormatter'></span></div>"
+ " </div>"
+ " </div>";
var messages = document.getElementById('messages');
// Create the repeater node.
var div = document.createElement('div');
div.setAttribute('data-set-repeat', 'message messages');
div.setAttribute('class', 'message');
div.setAttribute('data-set-attribute', 'id message.key');
div.innerHTML = repeaterNodeInnerHTML;
// Insert new items at the end.
insertAfter(messages, messages.lastElementChild, div);
repeaterNode = messages.lastElementChild;
// Bug: Formatters are not being passed from the server correctly
// when injectData is true. As a workaround: I’m duplicating them on the
// client also.
set.format['messageBodyIDFormatter'] = function (messageID) {
return messageID + "-body";
}
set.format['messageStatusIDFormatter'] = function (messageID) {
return messageID + "-status";
}
set.format['postDateFormatter'] = function (messageID) {
//
// Parses message IDs in the following forms into separate groups for
// * timeline clock (deprecated)
// * message time (replace underscores with colons to convert to valid timestamp)
// * account handle (optional)
//
// 000000001-2015-08-10T18_49_10.467Z-laura
// 000000001-2015-08-10T18_49_10.467Z
// 2015-08-10T18_49_10.467Z-laura
// 2015-08-10T18_49_10.467Z
//
var messageIDParserRegExp = /^(\d{9})?-?(\d{4}-\d{2}-\d{2}T\d{2}_\d{2}_\d{2}\.\d{3}Z)-?(.*)?/;
var matches = messageID.match(messageIDParserRegExp);
if (matches != null){
var depracatedOptionalMessageClock = matches[1];
var timestamp = matches[2];
var optionalAccountHandle = matches[3];
// Desearialise the timestamp.
timestamp = timestamp.replace (/_/g, ':');
var now = new Date();
var timeOfPost = new Date(timestamp);
// to secs -> mins -> hours -> days
var timeSincePostInDays = (now - timeOfPost)/1000/60/60/24;
var humanTime = moment(timeOfPost).fromNow();
return humanTime + " ";
}
else
{
// This should never happen and probably shows that some sort of corrupted date got through somehow.
return 'No date.';
}
}
set.format['timestampFormatter'] = function (messageID) {
var messageIDParserRegExp = /^(\d{9})?-?(\d{4}-\d{2}-\d{2}T\d{2}_\d{2}_\d{2}\.\d{3}Z)-?(.*)?/
var matches = messageID.match(messageIDParserRegExp);
if (matches != null) {
var depracatedOptionalMessageClock = matches[1];
var timestamp = matches[2];
var optionalAccountHandle = matches[3];
// Desearialise the timestamp.
timestamp = timestamp.replace(/_/g, ':');
return timestamp;
} else {
// This should not happen. Return the current date.
return new Date()
}
}
// Format the person’s name
set.format['personFormatter'] = function (messageID) {
var personHandleDelimeter = messageID.lastIndexOf('Z-');
if (personHandleDelimeter != -1) {
// From someone else
personHandle = messageID.substr(personHandleDelimeter+2);
// TODO: Once public profile pages are implemented, link to them.
return " by "+personHandle+".";
}
else {
// This is the person themselves.
// TODO: Once the timestamps are in there, just return that.
return '';
}
}
// Custom formatter for the profile image
set.format['profileImagePathFormatter'] = function (messageID) {
console.log("Formatting profile image for " + messageID)
var personHandleDelimeter = messageID.lastIndexOf('Z-');
var profileImagePath = '';
if (personHandleDelimeter != -1) {
// From someone else
var personHandle = messageID.substr(personHandleDelimeter+2);
console.log("Person handle for message: "+personHandle+".");
profileImagePath = "http://127.0.0.1:42003/all-friends/from/"+personHandle+"/about/me.jpg";
}
else {
// From you
console.log("Message is from you.");
profileImagePath = "http://localhost:42000/about/me.jpg";
}
return profileImagePath;
}
// Update the repeater node
set(repeaterNode, {messages: posts});
});
}
window.addEventListener('load', function(){
// Set: don’t run in Node.
......@@ -7,6 +212,41 @@ window.addEventListener('load', function(){
return;
}
//
// Infinite scroll
//
var lastInfiniteScrollAttemptTime = null
var loadProgress = document.getElementById('loadProgress');
var displayNoneClass = "displayNone";
window.addEventListener('scroll', function(e){
var bodyHeight = document.body.clientHeight;
var bodyScrollHeight = document.body.scrollHeight;
// console.log("bodyHeight: " + bodyHeight);
// console.log("bodyScrollHeight: " + bodyScrollHeight);
var pageYOffset = window.pageYOffset;
var bodyScrollTop = document.body.scrollTop;
// console.log("PageYOffset: " + pageYOffset);
// console.log("Body scroll top: " + bodyScrollTop);
var currentTime = Date.now();
var locationOfContentBottom = bodyScrollHeight - bodyHeight
// Dampen to one second to avoid multiple event fires.
if(pageYOffset >= locationOfContentBottom && (lastInfiniteScrollAttemptTime == null || (currentTime - lastInfiniteScrollAttemptTime)/1000 >= 1.0 ))
{
console.log("At bottom of page… about to get older messages!");
lastInfiniteScrollAttemptTime = currentTime;
loadProgress.classList.remove(displayNoneClass);
getOlderMessages();
}
})
// Check if there are no messages to begin with, remove the pre-rendered repeater.
// (It’s ID will be different if there are.)
var messageRepeater = document.getElementById('messageRepeater');
......@@ -85,22 +325,23 @@ window.addEventListener('load', function(){
document.getElementById('notice').style.display = 'none';
document.getElementById('messages').style.display = 'block';
var repeaterNodeHTML =
" <div style='background-color: rgb(200, 50, 50, 0.5);' class='message' data-set-repeat='message messages' data-set-attribute='id message.key' >"
+ " <div class='messageBody' data-set-attribute='id message.key messageBodyIDFormatter'>"
+ " <div class='image-and-body'>"
+ " <img class='profileImage' data-set-attribute='src message.key profileImagePathFormatter'>"
+ " <div class='bodyText' data-set-text='html message.value'>Message body HTML</div>"
+ " <div class='meta'><span class='postDate' data-set-attribute='data-timestamp message.key timestampFormatter' data-set-text='message.key postDateFormatter'></span></div>"
+ " </div>"
var repeaterNodeInnerHTML =
" <div class='messageBody' data-set-attribute='id message.key messageBodyIDFormatter'>"
+ " <div class='image-and-body'>"
+ " <img class='profileImage' data-set-attribute='src message.key profileImagePathFormatter'>"
+ " <div class='bodyText' data-set-text='html message.value'>Message body HTML</div>"
+ " <div class='meta'><span class='postDate' data-set-attribute='data-timestamp message.key timestampFormatter' data-set-text='message.key postDateFormatter'></span></div>"
+ " </div>"
+ " </div>";
+ " </div>";
var messages = document.getElementById('messages');
var div = document.createElement('div');
div.setAttribute('id', posts[0]['key']+'-0');
div.innerHTML = repeaterNodeHTML;
// Create the repeater node.
var div = document.createElement('div');
div.setAttribute('data-set-repeat', 'message messages');
div.setAttribute('class', 'message');
div.setAttribute('data-set-attribute', 'id message.key');
div.innerHTML = repeaterNodeInnerHTML;
// Insert new items at the beginning.
messages.insertBefore(div, messages.firstElementChild);
......
......@@ -14,6 +14,24 @@ function insertAfter(parentNode, referenceNode, newNode) {
var topmostElementBeforeLoadingOlderMessages = null
function massageAssetURLsInMessages(messages){
// Massage the asset URLs
var massagedMessages = [];
messages.forEach(function (message){
// Replace asset URLs
var personHandleDelimeter = message.key.lastIndexOf('Z-');
if (personHandleDelimeter != -1) {
var personHandle = message.key.substr(personHandleDelimeter+2);
message.value = message.value.replace(new RegExp('/specific-friends/.*?/to/', 'g'), "http://127.0.0.1:42003/specific-friends/"+personHandle+"/from/");
}
massagedMessages.push(message);
});
return messages;
}
function getOlderMessages(){
console.log("Polling server for older messages…")
......@@ -63,6 +81,8 @@ function getOlderMessages(){
return;
}
posts = massageAssetURLsInMessages(posts);
posts.reverse();
// Make sure that the notice is hidden and that the main section is showing.
......@@ -291,7 +311,7 @@ window.addEventListener('load', function(){
console.log("Attempting to compensate for older message loading (infinite scroll).");
topmostElementBeforeLoadingOlderMessages.scrollIntoView();
}
}
}
}
});
});
......@@ -352,6 +372,8 @@ window.addEventListener('load', function(){
return;
}
posts = massageAssetURLsInMessages(posts);
// Make sure that the notice is hidden and that the main section is showing.
// (Set can’t handle this automatically as we are not running it on the
// entire document but only on the delta repeat block below for performance reasons.)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment