Sort out scroll in chat

This commit is contained in:
James Allen 2014-07-15 18:25:12 +01:00
parent ef4b7f7036
commit b9f2b02594
10 changed files with 198 additions and 99 deletions

View file

@ -18,7 +18,7 @@ module.exports =
getMessages: (req, res)->
project_id = req.params.Project_id
query = req.body
query = req.query
logger.log project_id:project_id, query:query, "getting messages"
ChatHandler.getMessages project_id, query, (err, messages)->
if err?

View file

@ -61,7 +61,7 @@ block content
include ./editor/track-changes
.ui-layout-east
//- include ./editor/chat
include ./editor/chat
script(type="text/ng-template", id="genericMessageModalTemplate")
.modal-header
@ -118,4 +118,5 @@ block content
data-ace-base=jsPath+'ace',
src=jsPath+'libs/require.js?fingerprint='+fingerprint(jsPath + 'libs/require.js')
)

View file

@ -1,24 +1,25 @@
aside.chat(
ng-controller="ChatController"
infinite-scroll="loadMore()"
infinite-scroll-disabled="chat.loading || chat.atEnd"
infinite-scroll-initialize="ui.chatOpen"
)
.messages(
infinite-scroll="loadMore()"
infinite-scroll="loadMoreMessages()"
infinite-scroll-disabled="chat.loading || chat.atEnd"
infinite-scroll-initialize="ui.chatOpen"
scroll-glue
infinite-scroll-upwards="true"
update-scroll-bottom-on="messages:updated"
)
.infinite-scroll-inner
.loading(ng-show="chat.loading")
i.fa.fa-fw.fa-spin.fa-refresh
| Loading...
ul.list-unstyled
li.message(
ng-repeat="message in chat.groupedMessages | orderBy:'timestamp':false"
ng-repeat="message in chat.messages"
ng-controller="ChatMessageController"
ng-class="{'self': message.user.id == user.id }"
)
div.date(ng-if="$index == 0 || (message.timestamp - chat.groupedMessages[$index - 1].timestamp) > 5 * 60 * 1000")
{{ message.timestamp | formatDate:'h:MMa' }} {{ message.timestamp | relativeDate }}
div.date(ng-if="$index == 0 || (message.timestamp - chat.messages[$index - 1].timestamp) > 5 * 60 * 1000")
{{ message.timestamp | formatDate:'h:mm a' }} {{ message.timestamp | relativeDate }}
span.avatar
img(ng-src="{{message.user.gravatar_url}}")
div.message-wrapper
@ -30,7 +31,7 @@ aside.chat(
}"
)
.arrow(ng-style="{'border-color': 'hsl({{ hue(message.user) }}, 60%, 80%)'}")
p(ng-repeat="content in message.contents") {{ content }}
p(ng-repeat="content in message.contents track by $index") {{ content }}
.new-message
textarea(placeholder="Your message...", on-enter="sendMessage()", ng-model="newMessageContent")

View file

@ -50,12 +50,12 @@ header.toolbar.toolbar-header(ng-cloak, ng-hide="state.loading")
tooltip-placement="bottom"
)
i.fa.fa-fw.fa-history
//- a.btn.btn-full-height(
//- href,
//- tooltip="Chat",
//- tooltip-placement="bottom",
//- ng-class="{ active: ui.chatOpen }",
//- ng-click="toggleChat()",
//- ng-controller="ChatButtonController"
//- )
//- i.fa.fa-fw.fa-comment
a.btn.btn-full-height(
href,
tooltip="Chat",
tooltip-placement="bottom",
ng-class="{ active: ui.chatOpen }",
ng-click="toggleChat()",
ng-controller="ChatButtonController"
)
i.fa.fa-fw.fa-comment

View file

@ -1,34 +1,74 @@
define [
"base"
], (App) ->
fakeNgModel = (initValue) ->
$setViewValue: (value) ->
@$viewValue = value
return
$viewValue: initValue
App.directive "updateScrollBottomOn", ->
# return {
# priority: 1
# require: ["?ngModel"]
# restrict: "A"
# link: (scope, $el, attrs, ctrls) ->
# scrollToBottom = ->
# el.scrollTop = el.scrollHeight
# return
#
# shouldActivateAutoScroll = ->
# # + 1 catches off by one errors in chrome
# el.scrollTop + el.clientHeight + 1 >= el.scrollHeight
#
#
# el = $el[0]
# ngModel = ctrls[0] or fakeNgModel(true)
#
# scope.$watch ->
# console.log "SCOPE CHANGED", ngModel.$viewValue
# if ngModel.$viewValue
# scrollToBottom()
#
# $el.bind "scroll", ->
# activate = shouldActivateAutoScroll()
# scope.$apply ngModel.$setViewValue.bind(ngModel, activate) if activate isnt ngModel.$viewValue
#
# return
# }
App.directive "scrollGlue", ->
return {
priority: 1
require: ["?ngModel"]
restrict: "A"
link: (scope, $el, attrs, ctrls) ->
scrollToBottom = ->
el.scrollTop = el.scrollHeight
shouldActivateAutoScroll = ->
el.scrollTop + el.clientHeight + 10 >= el.scrollHeight
el = $el[0]
ngModel = ctrls[0] or fakeNgModel(true)
scope.$watch ->
scrollToBottom() if ngModel.$viewValue
$el.bind "scroll", ->
activate = shouldActivateAutoScroll()
scope.$apply ngModel.$setViewValue.bind(ngModel, activate) if activate isnt ngModel.$viewValue
link: (scope, element, attrs, ctrls) ->
# We keep the offset from the bottom fixed whenever the event fires
#
# ^ | ^
# | | | scrollTop
# | | v
# | |-----------
# | | ^
# | | |
# | | | clientHeight (viewable area)
# | | |
# | | |
# | | v
# | |-----------
# | | ^
# | | | scrollBottom
# v | v
# \
# scrollHeight
scrollBottom = 0
element.on "scroll", (e) ->
scrollBottom = element[0].scrollHeight - element[0].scrollTop - element[0].clientHeight
console.log "SCROLL BOTTOM CHANGED", scrollBottom
scope.$on attrs.updateScrollBottomOn, () ->
setTimeout () ->
console.log "RESTORING SCROLL BOTTOM", scrollBottom
element.scrollTop(element[0].scrollHeight - element[0].clientHeight - scrollBottom)
, 0
}

View file

@ -1,48 +1,21 @@
define [
"base"
"ide/chat/services/chatMessages"
], (App) ->
App.controller "ChatController", ["$scope", "$http", "ide", "$location", ($scope, $http, @ide, $location) ->
MESSAGES_URL = "/project/#{$scope.project_id}/messages"
CONNECTED_USER_URL = "/project/#{$scope.project_id}/connected_users"
$scope.$on "project:joined", =>
@ide.socket.on "new-chat-message", (message) =>
$scope.chat.messages.push(message)
$http.get(MESSAGES_URL).success (data, status, headers, config)->
$scope.chat.messages = data
$http.get(CONNECTED_USER_URL).success (data)->
console.log data
App.controller "ChatController", ($scope, chatMessages, @ide, $location) ->
$scope.chat = chatMessages.state
$scope.$watchCollection "chat.messages", (messages) ->
if messages?
console.log "grouping messages"
$scope.chat.groupedMessages = groupMessages(messages)
$scope.$emit "messages:updated"
$scope.sendMessage = ->
body =
content:$scope.newMessageContent
_csrf : window.csrfToken
$http.post(MESSAGES_URL, body).success (data, status, headers, config)->
$scope.newMessageContent = ""
TIMESTAMP_GROUP_SIZE = 5 * 60 * 1000 # 5 minutes
groupMessages = (messages) ->
previousMessage = null
groupedMessages = []
for message in messages
shouldGroup = previousMessage? and
previousMessage.user == message.user and
message.timestamp - previousMessage.timestamp < TIMESTAMP_GROUP_SIZE
if shouldGroup
previousMessage.timestamp = message.timestamp
previousMessage.contents.push message.content
else
groupedMessages.push(previousMessage = {
user: message.user
timestamp: message.timestamp
contents: [message.content]
})
return groupedMessages
]
chatMessages
.sendMessage $scope.newMessageContent
.then () ->
$scope.newMessageContent = ""
$scope.loadMoreMessages = ->
chatMessages.loadMoreMessages()

View file

@ -0,0 +1,82 @@
define [
"base"
], (App) ->
App.factory "chatMessages", ($http, ide) ->
MESSAGES_URL = "/project/#{ide.project_id}/messages"
MESSAGE_LIMIT = 3
CONNECTED_USER_URL = "/project/#{ide.project_id}/connected_users"
chat = {
state:
messages: []
loading: false
atEnd: false
nextBeforeTimestamp: null
}
ide.socket.on "new-chat-message", (message) =>
appendMessage(message)
chat.loadMoreMessages = () ->
return if chat.state.atEnd
url = MESSAGES_URL + "?limit=#{MESSAGE_LIMIT}"
if chat.state.nextBeforeTimestamp?
url += "&before=#{chat.state.nextBeforeTimestamp}"
chat.state.loading = true
return $http
.get(url)
.success (messages = [])->
chat.state.loading = false
if messages.length < MESSAGE_LIMIT
chat.state.atEnd = true
messages.reverse()
prependMessages(messages)
chat.state.nextBeforeTimestamp = chat.state.messages[0]?.timestamp
console.log messages, chat.state
TIMESTAMP_GROUP_SIZE = 5 * 60 * 1000 # 5 minutes
prependMessage = (message) ->
console.log "PREPENDING MESSAGE", message
firstMessage = chat.state.messages[0]
shouldGroup = firstMessage? and
firstMessage.user.id == message.user.id and
firstMessage.timestamp - message.timestamp < TIMESTAMP_GROUP_SIZE
if shouldGroup
firstMessage.timestamp = message.timestamp
firstMessage.contents.unshift message.content
else
chat.state.messages.unshift({
user: message.user
timestamp: message.timestamp
contents: [message.content]
})
prependMessages = (messages) ->
for message in messages.slice(0).reverse()
prependMessage(message)
appendMessage = (message) ->
lastMessage = chat.state.messages[chat.state.messages.length - 1]
shouldGroup = lastMessage? and
lastMessage.user.id == message.user.id and
message.timestamp - lastMessage.timestamp < TIMESTAMP_GROUP_SIZE
if shouldGroup
lastMessage.timestamp = message.timestamp
lastMessage.contents.push message.content
else
chat.state.messages.push({
user: message.user
timestamp: message.timestamp
contents: [message.content]
})
chat.sendMessage = (message) ->
body =
content: message
_csrf : window.csrfToken
return $http.post(MESSAGES_URL, body)
return chat

View file

@ -63,7 +63,7 @@ define [
@$scope.trackChanges.updates[indexOfLastUpdateNotByMe].selectedFrom = true
BATCH_SIZE: 4
BATCH_SIZE: 10
fetchNextBatchOfUpdates: () ->
url = "/project/#{@ide.project_id}/updates?min_count=#{@BATCH_SIZE}"
if @$scope.trackChanges.nextBeforeTimestamp?

View file

@ -8,29 +8,25 @@ define [
element.css 'overflow-y': 'auto'
atEndOfListView = () ->
if attrs.infiniteScrollUpwards?
atTopOfListView()
else
atBottomOfList()
atTopOfListView = () ->
element.scrollTop() < 30
atBottomOfListView = () ->
element.scrollTop() + element.height() >= innerElement.height() - 30
listShorterThanContainer = () ->
element.innerHeight() > @$(".change-list").outerHeight()
element.height() > innerElement.height()
loadUntilFull = () ->
if (listShorterThanContainer() or atEndOfListView()) and not scope.$eval(attrs.infiniteScrollDisabled)
promise = scope.$eval(attrs.infiniteScroll)
promise.then () ->
loadUntilFull()
# @collection.fetchNextBatch
# error: (error) =>
# @hideLoading()
# @showEmptyMessageIfCollectionEmpty()
# callback(error)
# success: (collection, response) =>
# @hideLoading()
# if @collection.isAtEnd()
# @atEndOfCollection = true
# @showEmptyMessageIfCollectionEmpty()
# callback()
# else
# @loadUntilFull(callback)
element.on "scroll", (event) ->
loadUntilFull()

View file

@ -1,6 +1,12 @@
@new-message-height: 80px;
.chat {
.loading {
font-family: @font-family-serif;
padding: @line-height-computed / 2;
text-align: center;
}
.messages {
position: absolute;
top: 0;