Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Ind.ie Projects
Waystone
Commits
b64f9604
Unverified
Commit
b64f9604
authored
Aug 30, 2015
by
Aral Balkan
Browse files
Infinite scroll is working on the Everyone timeline.
parent
a67bacf5
Changes
7
Hide whitespace changes
Inline
Side-by-side
PublicTimelineWeaver.coffee
View file @
b64f9604
...
...
@@ -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
=
5
0
)
=>
getTimeline
:
(
from
=
'
\x00
'
,
to
=
'
\uffff
'
,
limit
=
2
0
)
=>
# console.log "Streamweaver::getTimeline: #{timeline}"
# console.log "getTimeline: from: >#{from.charCodeAt(0)}<, to: >#{to.charCodeAt(0)}<, limit: #{limit}."
...
...
index.coffee
View file @
b64f9604
...
...
@@ -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
...
...
routes/public/posts-after.coffee
View file @
b64f9604
...
...
@@ -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
routes/public/posts-before.coffee
0 → 100644
View file @
b64f9604
#
# 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
static/images/progress-spinner@2x.gif
0 → 100644
View file @
b64f9604
24.7 KB
static/js/public.js
View file @
b64f9604
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
})
});
...
...
views/public.html
View file @
b64f9604
...
...
@@ -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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment