The communications platform that puts data protection first.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
Rocket.Chat/packages/rocketchat-ui/client/views/app/room.coffee

698 lines
20 KiB

import moment from 'moment'
import mime from 'mime-type/with-db'
socialSharing = (options = {}) ->
window.plugins.socialsharing.share(options.message, options.subject, options.file, options.link)
isSubscribed = (_id) ->
return ChatSubscription.find({ rid: _id }).count() > 0
favoritesEnabled = ->
return RocketChat.settings.get 'Favorite_Rooms'
userCanDrop = (_id) ->
return !RocketChat.roomTypes.readOnly _id, Meteor.user()
Template.room.helpers
isTranslated: ->
sub = ChatSubscription.findOne { rid: this._id }, { fields: { autoTranslate: 1, autoTranslateLanguage: 1 } }
return RocketChat.settings.get('AutoTranslate_Enabled') and sub?.autoTranslate is true and sub.autoTranslateLanguage?
embeddedVersion: ->
return RocketChat.Layout.isEmbedded()
favorite: ->
sub = ChatSubscription.findOne { rid: this._id }, { fields: { f: 1 } }
return 'icon-star favorite-room pending-color' if sub?.f? and sub.f and favoritesEnabled()
return 'icon-star-empty'
favoriteLabel: ->
sub = ChatSubscription.findOne { rid: this._id }, { fields: { f: 1 } }
return "Unfavorite" if sub?.f? and sub.f and favoritesEnabled()
return "Favorite"
subscribed: ->
return isSubscribed(this._id)
messagesHistory: ->
hideMessagesOfType = []
RocketChat.settings.collection.find({_id: /Message_HideType_.+/}).forEach (record) ->
type = record._id.replace('Message_HideType_', '')
switch (type)
when 'mute_unmute'
types = [ 'user-muted', 'user-unmuted' ]
else
types = [ type ]
types.forEach (type) ->
index = hideMessagesOfType.indexOf(type)
if record.value is true and index is -1
hideMessagesOfType.push(type)
else if index > -1
hideMessagesOfType.splice(index, 1)
query =
rid: this._id
if hideMessagesOfType.length > 0
query.t =
$nin: hideMessagesOfType
options =
sort:
ts: 1
return ChatMessage.find(query, options)
hasMore: ->
return RoomHistoryManager.hasMore this._id
hasMoreNext: ->
return RoomHistoryManager.hasMoreNext this._id
isLoading: ->
return RoomHistoryManager.isLoading this._id
windowId: ->
return "chat-window-#{this._id}"
uploading: ->
return Session.get 'uploading'
roomName: ->
roomData = Session.get('roomData' + this._id)
return '' unless roomData
return RocketChat.roomTypes.getRoomName roomData.t, roomData
secondaryName: ->
roomData = Session.get('roomData' + this._id)
return '' unless roomData
return RocketChat.roomTypes.getSecondaryRoomName roomData.t, roomData
roomTopic: ->
roomData = Session.get('roomData' + this._id)
return '' unless roomData
return roomData.topic
showAnnouncement: ->
roomData = Session.get('roomData' + this._id)
return false unless roomData
Meteor.defer =>
if window.chatMessages and window.chatMessages[roomData._id]
window.chatMessages[roomData._id].resize()
return roomData.announcement isnt undefined and roomData.announcement isnt ''
roomAnnouncement: ->
roomData = Session.get('roomData' + this._id)
return '' unless roomData
return roomData.announcement
roomIcon: ->
roomData = Session.get('roomData' + this._id)
return '' unless roomData?.t
return RocketChat.roomTypes.getIcon roomData?.t
userStatus: ->
roomData = Session.get('roomData' + this._id)
return RocketChat.roomTypes.getUserStatus(roomData.t, this._id) or 'offline'
maxMessageLength: ->
return RocketChat.settings.get('Message_MaxAllowedSize')
unreadData: ->
data =
count: RoomHistoryManager.getRoom(this._id).unreadNotLoaded.get() + Template.instance().unreadCount.get()
room = RoomManager.getOpenedRoomByRid this._id
if room?
data.since = room.unreadSince?.get()
return data
containerBarsShow: (unreadData, uploading) ->
return 'show' if (unreadData?.count > 0 and unreadData.since?) or uploading?.length > 0
formatUnreadSince: ->
if not this.since? then return
return moment(this.since).calendar(null, {sameDay: 'LT'})
flexData: ->
flexData =
tabBar: Template.instance().tabBar
data:
rid: this._id
userDetail: Template.instance().userDetail.get(),
clearUserDetail: Template.instance().clearUserDetail
return flexData
adminClass: ->
return 'admin' if RocketChat.authz.hasRole(Meteor.userId(), 'admin')
showToggleFavorite: ->
return true if isSubscribed(this._id) and favoritesEnabled()
viewMode: ->
viewMode = Meteor.user()?.settings?.preferences?.viewMode
switch viewMode
when 1 then cssClass = 'cozy'
when 2 then cssClass = 'compact'
else cssClass = ''
return cssClass
selectable: ->
return Template.instance().selectable.get()
hideUsername: ->
return if Meteor.user()?.settings?.preferences?.hideUsernames then 'hide-usernames'
hideAvatar: ->
return if Meteor.user()?.settings?.preferences?.hideAvatars then 'hide-avatars'
userCanDrop: ->
return userCanDrop @_id
canPreview: ->
room = Session.get('roomData' + this._id)
if room.t isnt 'c'
return true
if RocketChat.settings.get('Accounts_AllowAnonymousRead') is true
return true
if RocketChat.authz.hasAllPermission('preview-c-room')
return true
return RocketChat.models.Subscriptions.findOne({rid: this._id})?
isSocialSharingOpen = false
touchMoved = false
Template.room.events
"click, touchend": (e, t) ->
Meteor.setTimeout ->
t.sendToBottomIfNecessaryDebounced()
, 100
"click .messages-container": (e) ->
if Template.instance().tabBar.getState() is 'opened' and Meteor.user()?.settings?.preferences?.hideFlexTab
Template.instance().tabBar.close()
"touchstart .message": (e, t) ->
touchMoved = false
isSocialSharingOpen = false
if e.originalEvent.touches.length isnt 1
return
if $(e.currentTarget).hasClass('system')
return
if e.target and e.target.nodeName is 'AUDIO'
return
if e.target and e.target.nodeName is 'A' and /^https?:\/\/.+/.test(e.target.getAttribute('href'))
e.preventDefault()
e.stopPropagation()
message = this._arguments[1]
doLongTouch = =>
if window.plugins?.socialsharing?
isSocialSharingOpen = true
if e.target and e.target.nodeName is 'A' and /^https?:\/\/.+/.test(e.target.getAttribute('href'))
if message.attachments?
attachment = _.find message.attachments, (item) -> return item.title is e.target.innerText
if attachment?
socialSharing
file: e.target.href
subject: e.target.innerText
message: message.msg
return
socialSharing
link: e.target.href
subject: e.target.innerText
message: message.msg
return
if e.target and e.target.nodeName is 'IMG'
socialSharing
file: e.target.src
message: message.msg
return
mobileMessageMenu.show(message, t, e, this)
Meteor.clearTimeout t.touchtime
t.touchtime = Meteor.setTimeout doLongTouch, 500
"click .message img": (e, t) ->
Meteor.clearTimeout t.touchtime
if isSocialSharingOpen is true or touchMoved is true
e.preventDefault()
e.stopPropagation()
"touchend .message": (e, t) ->
Meteor.clearTimeout t.touchtime
if isSocialSharingOpen is true
e.preventDefault()
e.stopPropagation()
return
if e.target and e.target.nodeName is 'A' and /^https?:\/\/.+/.test(e.target.getAttribute('href'))
if touchMoved is true
e.preventDefault()
e.stopPropagation()
return
if cordova?.InAppBrowser?
cordova.InAppBrowser.open(e.target.href, '_system')
else
window.open(e.target.href)
"touchmove .message": (e, t) ->
touchMoved = true
Meteor.clearTimeout t.touchtime
"touchcancel .message": (e, t) ->
Meteor.clearTimeout t.touchtime
"click .upload-progress-text > button": (e) ->
e.preventDefault();
Session.set "uploading-cancel-#{this.id}", true
"click .unread-bar > button.mark-read": ->
readMessage.readNow(true)
"click .unread-bar > button.jump-to": (e, t) ->
_id = t.data._id
message = RoomHistoryManager.getRoom(_id)?.firstUnread.get()
if message?
RoomHistoryManager.getSurroundingMessages(message, 50)
else
subscription = ChatSubscription.findOne({ rid: _id })
message = ChatMessage.find({ rid: _id, ts: { $gt: subscription?.ls } }, { sort: { ts: 1 }, limit: 1 }).fetch()[0]
RoomHistoryManager.getSurroundingMessages(message, 50)
'click .toggle-favorite': (event) ->
event.stopPropagation()
event.preventDefault()
Meteor.call 'toggleFavorite', @_id, !$('i', event.currentTarget).hasClass('favorite-room'), (err) ->
if err
return handleError(err)
'click .edit-room-title': (event) ->
event.preventDefault()
Session.set('editRoomTitle', true)
$(".fixed-title").addClass "visible"
Meteor.setTimeout ->
$('#room-title-field').focus().select()
, 10
"click .flex-tab .user-image > button" : (e, instance) ->
if not Meteor.userId()?
return
instance.tabBar.open()
instance.setUserDetail @user.username
'click .user-card-message': (e, instance) ->
if not Meteor.userId()?
return
roomData = Session.get('roomData' + this._arguments[1].rid)
if RocketChat.Layout.isEmbedded()
fireGlobalEvent('click-user-card-message', { username: this._arguments[1].u.username })
e.preventDefault()
e.stopPropagation()
return
if roomData.t in ['c', 'p', 'd']
instance.setUserDetail this._arguments[1].u.username
instance.tabBar.setTemplate('membersList')
instance.tabBar.open()
'scroll .wrapper': _.throttle (e, instance) ->
if RoomHistoryManager.isLoading(@_id) is false and (RoomHistoryManager.hasMore(@_id) is true or RoomHistoryManager.hasMoreNext(@_id) is true)
if RoomHistoryManager.hasMore(@_id) is true and e.target.scrollTop is 0
RoomHistoryManager.getMore(@_id)
else if RoomHistoryManager.hasMoreNext(@_id) is true and e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight
RoomHistoryManager.getMoreNext(@_id)
, 200
'click .new-message': (e) ->
Template.instance().atBottom = true
Template.instance().find('.input-message').focus()
'click .message-cog': (e) ->
message = @_arguments[1]
RocketChat.MessageAction.hideDropDown()
dropDown = $(".messages-box \##{message._id} .message-dropdown")
if dropDown.length is 0
actions = RocketChat.MessageAction.getButtons message, 'message'
el = Blaze.toHTMLWithData Template.messageDropdown,
actions: actions
$(".messages-box \##{message._id} .message-cog-container").append el
dropDown = $(".messages-box \##{message._id} .message-dropdown")
dropDown.show()
'click .message-dropdown .message-action': (e, t) ->
el = $(e.currentTarget)
button = RocketChat.MessageAction.getButtonById el.data('id')
if button?.action?
button.action.call @, e, t
'click .message-dropdown-close': ->
RocketChat.MessageAction.hideDropDown()
"click .mention-link": (e, instance) ->
if not Meteor.userId()?
return
channel = $(e.currentTarget).data('channel')
if channel?
if RocketChat.Layout.isEmbedded()
return fireGlobalEvent('click-mention-link', { path: FlowRouter.path('channel', {name: channel}), channel: channel })
FlowRouter.go 'channel', { name: channel }, FlowRouter.current().queryParams
return
if RocketChat.Layout.isEmbedded()
fireGlobalEvent('click-mention-link', { username: $(e.currentTarget).data('username') })
e.stopPropagation();
e.preventDefault();
return
instance.tabBar.setTemplate 'membersList'
instance.setUserDetail $(e.currentTarget).data('username')
instance.tabBar.open()
'click .image-to-download': (event) ->
ChatMessage.update {_id: this._arguments[1]._id, 'urls.url': $(event.currentTarget).data('url')}, {$set: {'urls.$.downloadImages': true}}
ChatMessage.update {_id: this._arguments[1]._id, 'attachments.image_url': $(event.currentTarget).data('url')}, {$set: {'attachments.$.downloadImages': true}}
'click .collapse-switch': (e) ->
index = $(e.currentTarget).data('index')
collapsed = $(e.currentTarget).data('collapsed')
id = @_arguments[1]._id
if @_arguments[1]?.attachments?
ChatMessage.update {_id: id}, {$set: {"attachments.#{index}.collapsed": !collapsed}}
if @_arguments[1]?.urls?
ChatMessage.update {_id: id}, {$set: {"urls.#{index}.collapsed": !collapsed}}
'dragenter .dropzone': (e) ->
types = e.originalEvent?.dataTransfer?.types
if types?.length > 0 and _.every(types, (type) => type.indexOf('text/') is -1 or type.indexOf('text/uri-list') isnt -1) and userCanDrop this._id
e.currentTarget.classList.add 'over'
'dragleave .dropzone-overlay': (e) ->
e.currentTarget.parentNode.classList.remove 'over'
'dragover .dropzone-overlay': (e) ->
e = e.originalEvent or e
if e.dataTransfer.effectAllowed in ['move', 'linkMove']
e.dataTransfer.dropEffect = 'move'
else
e.dataTransfer.dropEffect = 'copy'
'dropped .dropzone-overlay': (event) ->
event.currentTarget.parentNode.classList.remove 'over'
e = event.originalEvent or event
files = e.dataTransfer?.files or []
filesToUpload = []
for file in files
# `file.type = mime.lookup(file.name)` does not work.
Object.defineProperty(file, 'type', { value: mime.lookup(file.name) })
filesToUpload.push
file: file
name: file.name
fileUpload filesToUpload
'load img': (e, template) ->
template.sendToBottomIfNecessary?()
'click .jump-recent button': (e, template) ->
e.preventDefault()
template.atBottom = true
RoomHistoryManager.clear(template?.data?._id)
'click .message': (e, template) ->
if template.selectable.get()
document.selection?.empty() or window.getSelection?().removeAllRanges()
data = Blaze.getData(e.currentTarget)
_id = data?._arguments?[1]?._id
if !template.selectablePointer
template.selectablePointer = _id
if !e.shiftKey
template.selectedMessages = template.getSelectedMessages()
template.selectedRange = []
template.selectablePointer = _id
template.selectMessages _id
selectedMessages = $('.messages-box .message.selected').map((i, message) -> message.id)
removeClass = _.difference selectedMessages, template.getSelectedMessages()
addClass = _.difference template.getSelectedMessages(), selectedMessages
for message in removeClass
$(".messages-box ##{message}").removeClass('selected')
for message in addClass
$(".messages-box ##{message}").addClass('selected')
Template.room.onCreated ->
# this.scrollOnBottom = true
# this.typing = new msgTyping this.data._id
this.showUsersOffline = new ReactiveVar false
this.atBottom = if FlowRouter.getQueryParam('msg') then false else true
this.unreadCount = new ReactiveVar 0
this.selectable = new ReactiveVar false
this.selectedMessages = []
this.selectedRange = []
this.selectablePointer = null
this.flexTemplate = new ReactiveVar
this.userDetail = new ReactiveVar FlowRouter.getParam('username')
this.tabBar = new RocketChatTabBar();
this.tabBar.showGroup(FlowRouter.current().route.name);
this.resetSelection = (enabled) =>
this.selectable.set(enabled)
$('.messages-box .message.selected').removeClass 'selected'
this.selectedMessages = []
this.selectedRange = []
this.selectablePointer = null
this.selectMessages = (to) =>
if this.selectablePointer is to and this.selectedRange.length > 0
this.selectedRange = []
else
message1 = ChatMessage.findOne this.selectablePointer
message2 = ChatMessage.findOne to
minTs = _.min([message1.ts, message2.ts])
maxTs = _.max([message1.ts, message2.ts])
this.selectedRange = _.pluck(ChatMessage.find({ rid: message1.rid, ts: { $gte: minTs, $lte: maxTs } }).fetch(), '_id')
this.getSelectedMessages = =>
messages = this.selectedMessages
addMessages = false
for message in this.selectedRange
if messages.indexOf(message) is -1
addMessages = true
break
if addMessages
previewMessages = _.compact(_.uniq(this.selectedMessages.concat(this.selectedRange)))
else
previewMessages = _.compact(_.difference(this.selectedMessages, this.selectedRange))
return previewMessages
this.setUserDetail = (username) =>
this.userDetail.set username
this.clearUserDetail = =>
this.userDetail.set null
Meteor.call 'getRoomRoles', @data._id, (error, results) ->
if error
return handleError(error)
for record in results
delete record._id
RoomRoles.upsert { rid: record.rid, "u._id": record.u._id }, record
RoomRoles.find({ rid: @data._id }).observe
added: (role) =>
ChatMessage.update { rid: @data._id, "u._id": role?.u?._id }, { $addToSet: { roles: role._id } }, { multi: true } # Update message to re-render DOM
changed: (role, oldRole) =>
ChatMessage.update { rid: @data._id, "u._id": role?.u?._id }, { $inc: { rerender: 1 } }, { multi: true } # Update message to re-render DOM
removed: (role) =>
ChatMessage.update { rid: @data._id, "u._id": role?.u?._id }, { $pull: { roles: role._id } }, { multi: true } # Update message to re-render DOM
Template.room.onDestroyed ->
window.removeEventListener 'resize', this.onWindowResize
Template.room.onRendered ->
unless window.chatMessages
window.chatMessages = {}
unless window.chatMessages[Session.get('openedRoom')]
window.chatMessages[Session.get('openedRoom')] = new ChatMessages
chatMessages[Session.get('openedRoom')].init(this.firstNode)
if Meteor.Device.isDesktop()
setTimeout ->
$('.message-form .input-message').focus()
, 100
# ScrollListener.init()
wrapper = this.find('.wrapper')
wrapperUl = this.find('.wrapper > ul')
newMessage = this.find(".new-message")
template = this
messageBox = $('.messages-box')
template.isAtBottom = (scrollThreshold) ->
if not scrollThreshold? then scrollThreshold = 0
if wrapper.scrollTop + scrollThreshold >= wrapper.scrollHeight - wrapper.clientHeight
newMessage.className = "new-message background-primary-action-color color-content-background-color not"
return true
return false
template.sendToBottom = ->
wrapper.scrollTop = wrapper.scrollHeight - wrapper.clientHeight
newMessage.className = "new-message background-primary-action-color color-content-background-color not"
template.checkIfScrollIsAtBottom = ->
template.atBottom = template.isAtBottom(100)
readMessage.enable()
readMessage.read()
template.sendToBottomIfNecessary = ->
if template.atBottom is true and template.isAtBottom() isnt true
template.sendToBottom()
template.sendToBottomIfNecessaryDebounced = _.debounce template.sendToBottomIfNecessary, 10
template.sendToBottomIfNecessary()
if not window.MutationObserver?
wrapperUl.addEventListener 'DOMSubtreeModified', ->
template.sendToBottomIfNecessaryDebounced()
else
observer = new MutationObserver (mutations) ->
mutations.forEach (mutation) ->
template.sendToBottomIfNecessaryDebounced()
observer.observe wrapperUl,
childList: true
# observer.disconnect()
template.onWindowResize = ->
Meteor.defer ->
template.sendToBottomIfNecessaryDebounced()
window.addEventListener 'resize', template.onWindowResize
wrapper.addEventListener 'mousewheel', ->
template.atBottom = false
Meteor.defer ->
template.checkIfScrollIsAtBottom()
wrapper.addEventListener 'wheel', ->
template.atBottom = false
Meteor.defer ->
template.checkIfScrollIsAtBottom()
wrapper.addEventListener 'touchstart', ->
template.atBottom = false
wrapper.addEventListener 'touchend', ->
Meteor.defer ->
template.checkIfScrollIsAtBottom()
Meteor.setTimeout ->
template.checkIfScrollIsAtBottom()
, 1000
Meteor.setTimeout ->
template.checkIfScrollIsAtBottom()
, 2000
wrapper.addEventListener 'scroll', ->
template.atBottom = false
Meteor.defer ->
template.checkIfScrollIsAtBottom()
$('.flex-tab-bar').on 'click', (e, t) ->
Meteor.setTimeout ->
template.sendToBottomIfNecessaryDebounced()
, 50
rtl = $('html').hasClass('rtl')
updateUnreadCount = _.throttle ->
messageBoxOffset = messageBox.offset()
if rtl
lastInvisibleMessageOnScreen = document.elementFromPoint(messageBoxOffset.left+messageBox.width()-1, messageBoxOffset.top+1)
else
lastInvisibleMessageOnScreen = document.elementFromPoint(messageBoxOffset.left+1, messageBoxOffset.top+1)
if lastInvisibleMessageOnScreen?.id?
lastMessage = ChatMessage.findOne lastInvisibleMessageOnScreen.id
if lastMessage?
subscription = ChatSubscription.findOne rid: template.data._id
count = ChatMessage.find({rid: template.data._id, ts: {$lte: lastMessage.ts, $gt: subscription?.ls}}).count()
template.unreadCount.set count
else
template.unreadCount.set 0
, 300
readMessage.onRead (rid) ->
if rid is template.data._id
template.unreadCount.set 0
wrapper.addEventListener 'scroll', ->
updateUnreadCount()
# salva a data da renderização para exibir alertas de novas mensagens
$.data(this.firstNode, 'renderedAt', new Date)
webrtc = WebRTC.getInstanceByRoomId template.data._id
if webrtc?
Tracker.autorun =>
if webrtc.remoteItems.get()?.length > 0
@tabBar.setTemplate 'membersList'
@tabBar.open()
if webrtc.localUrl.get()?
@tabBar.setTemplate 'membersList'
@tabBar.open()