Infinite scroll is working on the Everyone timeline.

parent a67bacf5
......@@ -86,7 +86,7 @@ class PublicTimelineWeaver
# Returns a promise to return the requested timeline.
# (We need to stream the timelines contents from the database.)
#
getTimeline: (from='\x00', to='\uffff', limit=50) =>
getTimeline: (from='\x00', to='\uffff', limit=20) =>
# console.log "Streamweaver::getTimeline: #{timeline}"
# console.log "getTimeline: from: >#{from.charCodeAt(0)}<, to: >#{to.charCodeAt(0)}<, limit: #{limit}."
......
......@@ -319,6 +319,7 @@ app.get '/', require('./routes/public/public.coffee')(app)
# Ajax updates
#
app.get '/posts-after/:key', require('./routes/public/posts-after.coffee')(app)
app.get '/posts-before/:key', require('./routes/public/posts-before.coffee')(app)
#
# Middleware: create static assets servers for all users’ public assets
......
......@@ -18,6 +18,5 @@ module.exports = (app) ->
# console.log 'Found new posts: '
# console.log messages
# Reverse the message order to match that of the Cocoa client.
messages.reverse()
response.end JSON.stringify messages
#
# Gets posts before a given lexographical key.
# TODO: Refactor w. Heartbeat to remove redundancy.
#
PublicTimelineWeaver = require '../../PublicTimelineWeaver'
module.exports = (app) ->
route = (request, response) ->
# Read the details
key = request.param 'key'
console.log "Looking up posts prior to key #{key}…"
(new PublicTimelineWeaver).getPostsBefore(key).then (messages) ->
# console.log 'Get posts before: found new posts: '
# console.log messages
response.end JSON.stringify messages
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('https://waystone.ind.ie/posts-before/'+oldestPostID)
.get('http://192.168.59.103:3000/posts-before/'+oldestPostID)
// .get('//' + set.meta.waystoneURL +'/posts-before/'+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><span data-set-text='message.key personFormatter'></span> <a data-set-attribute='href message.key addFriendLinkFormatter' data-set-text='html message.key addFriendTextFormatter'></a></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['profileImagePathFormatter'] = function (messageID) {
var personHandle = messageID.substr(messageID.lastIndexOf('Z-')+2);
var profileImagePath = "/public/"+personHandle+"/about/me.jpg";
return profileImagePath;
}
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 timestamp = matches[2];
// Deserialise the timestamp.
timestamp = timestamp.replace(/_/g, ':');
return timestamp;
} else {
// This should never happen and probably shows that some sort of corrupted ID got through somehow.
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 '';
}
}
set.format['addFriendLinkFormatter'] = function (messageID) {
var personHandleDelimeter = messageID.lastIndexOf('Z-');
if (personHandleDelimeter != -1) {
// From someone else
var personHandle = messageID.substr(personHandleDelimeter+2);
var addFriendLink = "indie://friend/"+personHandle;
return addFriendLink;
}
// // TODO: Filter out the person themselves.
// else
// {
// // This is the person themselves, no need to display a friend link.
// return '';
// }
}
set.format['addFriendTextFormatter'] = function (messageID) {
var personHandleDelimeter = messageID.lastIndexOf('Z-');
var profileImagePath = '';
if (personHandleDelimeter != -1){
return "<img class='add-friend-icon' src='/images/person_add@2x.png' alt='Send friend request'>"
}
// // TODO: Filter out the person themselves.
// else
// {
// //This is the person themselves, no need to display a friend link.
// return ''
// }
}
// Update the repeater node
set(repeaterNode, {messages: posts})
});
}
window.addEventListener('load', function(){
// Set: don’t run in Node.
......@@ -7,20 +228,53 @@ 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();
}
})
setInterval(function(){
// console.log("Polling server for new public posts…")
var messages = document.getElementById('messages');
var newestPostID = messages.firstElementChild.getAttribute('id')
var newestPostID = messages.firstElementChild.getAttribute('id');
// console.log('Getting post after ' + newestPostID)
// TODO: Do not hard code this URL.
// TODO: Fix Set injectData so we don’t have to use this metadata injection workaround.
superagent
.get('https://waystone.ind.ie/posts-after/'+newestPostID)
// .get('http://192.168.59.103:3000/posts-after/'+newestPostID)
// .get('https://waystone.ind.ie/posts-after/'+newestPostID)
.get('http://192.168.59.103:3000/posts-after/'+newestPostID)
// .get('//' + set.meta.waystoneURL +'/posts-after/'+newestPostID)
.end(function(error, posts) {
//
......@@ -36,21 +290,23 @@ window.addEventListener('load', function(){
return;
}
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'>"
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><span data-set-text='message.key personFormatter'></span> <a data-set-attribute='href message.key addFriendLinkFormatter' data-set-text='html message.key addFriendTextFormatter'></a></div>"
+ " </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;
messages.insertBefore(div, messages.firstElementChild)
......@@ -75,6 +331,48 @@ window.addEventListener('load', function(){
return profileImagePath;
}
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)-?(.*)?/;
......@@ -93,6 +391,58 @@ window.addEventListener('load', function(){
}
}
// 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 '';
}
}
set.format['addFriendLinkFormatter'] = function (messageID) {
var personHandleDelimeter = messageID.lastIndexOf('Z-');
if (personHandleDelimeter != -1) {
// From someone else
var personHandle = messageID.substr(personHandleDelimeter+2);
var addFriendLink = "indie://friend/"+personHandle;
return addFriendLink;
}
// // TODO: Filter out the person themselves.
// else
// {
// // This is the person themselves, no need to display a friend link.
// return '';
// }
}
set.format['addFriendTextFormatter'] = function (messageID) {
var personHandleDelimeter = messageID.lastIndexOf('Z-');
var profileImagePath = '';
if (personHandleDelimeter != -1){
return "<img class='add-friend-icon' src='/images/person_add@2x.png' alt='Send friend request'>"
}
// // TODO: Filter out the person themselves.
// else
// {
// //This is the person themselves, no need to display a friend link.
// return ''
// }
}
// Update the repeater node
set(repeaterNode, {messages: posts})
});
......
......@@ -10,6 +10,21 @@
<script src="/js/set.js"></script>
<script src="/js/public.js"></script>
<script 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'>
......@@ -31,7 +46,11 @@
<!-- <div class='messageStatus' data-set-attribute='id message.key messageStatusIDFormatter'></div>-->
</div>
</div>
<section>
<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
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