mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-09 19:39:06 +00:00
Sort out scroll in chat
This commit is contained in:
parent
ef4b7f7036
commit
b9f2b02594
10 changed files with 198 additions and 99 deletions
|
@ -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?
|
||||
|
|
|
@ -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')
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
@ -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
|
|
@ -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?
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue