Merge pull request #7006 from RocketChat/rocketchat-ui3

Rocketchat ui3
pull/7028/head^2
Rodrigo Nascimento 9 years ago committed by GitHub
commit 57b54ea26d
  1. 219
      packages/rocketchat-ui/client/lib/RoomHistoryManager.coffee
  2. 266
      packages/rocketchat-ui/client/lib/RoomHistoryManager.js
  3. 253
      packages/rocketchat-ui/client/lib/RoomManager.coffee
  4. 302
      packages/rocketchat-ui/client/lib/RoomManager.js
  5. 112
      packages/rocketchat-ui/client/lib/rocket.coffee
  6. 121
      packages/rocketchat-ui/client/lib/rocket.js
  7. 2
      packages/rocketchat-ui/client/views/app/room.js
  8. 6
      packages/rocketchat-ui/package.js

@ -1,219 +0,0 @@
@RoomHistoryManager = new class
defaultLimit = 50
histories = {}
getRoom = (rid) ->
if not histories[rid]?
histories[rid] =
hasMore: new ReactiveVar true
hasMoreNext: new ReactiveVar false
isLoading: new ReactiveVar false
unreadNotLoaded: new ReactiveVar 0
firstUnread: new ReactiveVar
loaded: undefined
return histories[rid]
getMore = (rid, limit=defaultLimit) ->
room = getRoom rid
if room.hasMore.curValue isnt true
return
room.isLoading.set true
# ScrollListener.setLoader true
lastMessage = ChatMessage.findOne({rid: rid}, {sort: {ts: 1}})
# lastMessage ?= ChatMessage.findOne({rid: rid}, {sort: {ts: 1}})
if lastMessage?
ts = lastMessage.ts
else
ts = undefined
ls = undefined
typeName = undefined
subscription = ChatSubscription.findOne rid: rid
if subscription?
ls = subscription.ls
typeName = subscription.t + subscription.name
else
curRoomDoc = ChatRoom.findOne(_id: rid)
typeName = curRoomDoc?.t + curRoomDoc?.name
Meteor.call 'loadHistory', rid, ts, limit, ls, (err, result) ->
room.unreadNotLoaded.set result?.unreadNotLoaded
room.firstUnread.set result?.firstUnread
wrapper = $('.messages-box .wrapper').get(0)
if wrapper?
previousHeight = wrapper.scrollHeight
for item in result?.messages or [] when item.t isnt 'command'
item.roles = _.union(UserRoles.findOne(item.u?._id)?.roles, RoomRoles.findOne({rid: item.rid, 'u._id': item.u?._id})?.roles)
ChatMessage.upsert {_id: item._id}, item
if wrapper?
heightDiff = wrapper.scrollHeight - previousHeight
wrapper.scrollTop += heightDiff
Meteor.defer ->
readMessage.refreshUnreadMark(rid, true)
RoomManager.updateMentionsMarksOfRoom typeName
room.isLoading.set false
room.loaded ?= 0
if result?.messages?.length?
room.loaded += result.messages.length
if result?.messages?.length < limit
room.hasMore.set false
getMoreNext = (rid, limit=defaultLimit) ->
room = getRoom rid
if room.hasMoreNext.curValue isnt true
return
instance = Blaze.getView($('.messages-box .wrapper')[0]).templateInstance()
instance.atBottom = false
room.isLoading.set true
lastMessage = ChatMessage.findOne({rid: rid}, {sort: {ts: -1}})
typeName = undefined
subscription = ChatSubscription.findOne rid: rid
if subscription?
ls = subscription.ls
typeName = subscription.t + subscription.name
else
curRoomDoc = ChatRoom.findOne(_id: rid)
typeName = curRoomDoc?.t + curRoomDoc?.name
ts = lastMessage.ts
if ts
Meteor.call 'loadNextMessages', rid, ts, limit, (err, result) ->
for item in result?.messages or []
if item.t isnt 'command'
item.roles = _.union(UserRoles.findOne(item.u?._id)?.roles, RoomRoles.findOne({rid: item.rid, 'u._id': item.u?._id})?.roles)
ChatMessage.upsert {_id: item._id}, item
Meteor.defer ->
RoomManager.updateMentionsMarksOfRoom typeName
room.isLoading.set false
room.loaded ?= 0
if result.messages.length?
room.loaded += result.messages.length
if result.messages.length < limit
room.hasMoreNext.set false
getSurroundingMessages = (message, limit=defaultLimit) ->
unless message?.rid
return
instance = Blaze.getView($('.messages-box .wrapper')[0]).templateInstance()
if ChatMessage.findOne message._id
wrapper = $('.messages-box .wrapper')
msgElement = $("##{message._id}", wrapper)
pos = wrapper.scrollTop() + msgElement.offset().top - wrapper.height()/2
wrapper.animate({
scrollTop: pos
}, 500)
msgElement.addClass('highlight')
setTimeout ->
messages = wrapper[0]
instance.atBottom = messages.scrollTop >= messages.scrollHeight - messages.clientHeight;
setTimeout ->
msgElement.removeClass('highlight')
, 500
else
room = getRoom message.rid
room.isLoading.set true
ChatMessage.remove { rid: message.rid }
typeName = undefined
subscription = ChatSubscription.findOne rid: message.rid
if subscription?
ls = subscription.ls
typeName = subscription.t + subscription.name
else
curRoomDoc = ChatRoom.findOne(_id: message.rid)
typeName = curRoomDoc?.t + curRoomDoc?.name
Meteor.call 'loadSurroundingMessages', message, limit, (err, result) ->
for item in result?.messages or []
if item.t isnt 'command'
item.roles = _.union(UserRoles.findOne(item.u?._id)?.roles, RoomRoles.findOne({rid: item.rid, 'u._id': item.u?._id})?.roles)
ChatMessage.upsert {_id: item._id}, item
Meteor.defer ->
readMessage.refreshUnreadMark(message.rid, true)
RoomManager.updateMentionsMarksOfRoom typeName
wrapper = $('.messages-box .wrapper')
msgElement = $("##{message._id}", wrapper)
pos = wrapper.scrollTop() + msgElement.offset().top - wrapper.height()/2
wrapper.animate({
scrollTop: pos
}, 500)
msgElement.addClass('highlight')
setTimeout ->
room.isLoading.set false
messages = wrapper[0]
instance.atBottom = !result.moreAfter && messages.scrollTop >= messages.scrollHeight - messages.clientHeight;
, 500
setTimeout ->
msgElement.removeClass('highlight')
, 500
room.loaded ?= 0
if result.messages.length?
room.loaded += result.messages.length
room.hasMore.set result.moreBefore
room.hasMoreNext.set result.moreAfter
hasMore = (rid) ->
room = getRoom rid
return room.hasMore.get()
hasMoreNext = (rid) ->
room = getRoom rid
return room.hasMoreNext.get()
getMoreIfIsEmpty = (rid) ->
room = getRoom rid
if room.loaded is undefined
getMore rid
isLoading = (rid) ->
room = getRoom rid
return room.isLoading.get()
clear = (rid) ->
ChatMessage.remove({ rid: rid })
if histories[rid]?
histories[rid].hasMore.set true
histories[rid].isLoading.set false
histories[rid].loaded = undefined
getRoom: getRoom
getMore: getMore
getMoreNext: getMoreNext
getMoreIfIsEmpty: getMoreIfIsEmpty
hasMore: hasMore
hasMoreNext: hasMoreNext
isLoading: isLoading
clear: clear
getSurroundingMessages: getSurroundingMessages

@ -0,0 +1,266 @@
/* globals readMessage UserRoles RoomRoles*/
export const RoomHistoryManager = new class {
constructor() {
this.defaultLimit = 50;
this.histories = {};
}
getRoom(rid) {
if ((this.histories[rid] == null)) {
this.histories[rid] = {
hasMore: new ReactiveVar(true),
hasMoreNext: new ReactiveVar(false),
isLoading: new ReactiveVar(false),
unreadNotLoaded: new ReactiveVar(0),
firstUnread: new ReactiveVar,
loaded: undefined
};
}
return this.histories[rid];
}
getMore(rid, limit) {
let ts;
if (limit == null) { limit = this.defaultLimit; }
const room = this.getRoom(rid);
if (room.hasMore.curValue !== true) {
return;
}
room.isLoading.set(true);
// ScrollListener.setLoader true
const lastMessage = ChatMessage.findOne({rid}, {sort: {ts: 1}});
// lastMessage ?= ChatMessage.findOne({rid: rid}, {sort: {ts: 1}})
if (lastMessage != null) {
({ ts } = lastMessage);
} else {
ts = undefined;
}
let ls = undefined;
let typeName = undefined;
const subscription = ChatSubscription.findOne({rid});
if (subscription != null) {
({ ls } = subscription);
typeName = subscription.t + subscription.name;
} else {
const curRoomDoc = ChatRoom.findOne({_id: rid});
typeName = (curRoomDoc != null ? curRoomDoc.t : undefined) + (curRoomDoc != null ? curRoomDoc.name : undefined);
}
return Meteor.call('loadHistory', rid, ts, limit, ls, function(err, result) {
if (err) {
return;
}
let previousHeight;
room.unreadNotLoaded.set(result.unreadNotLoaded);
room.firstUnread.set(result.firstUnread);
const wrapper = $('.messages-box .wrapper').get(0);
if (wrapper != null) {
previousHeight = wrapper.scrollHeight;
}
result.messages.forEach(item => {
if (item.t !== 'command') {
const roles = [
(item.u && item.u._id && UserRoles.findOne(item.u._id, { fields: { roles: 1 }})) || {},
(item.u && item.u._id && RoomRoles.findOne({rid: item.rid, 'u._id': item.u._id})) || {}
].map(e => e.roles);
item.roles = _.union.apply(_.union, roles);
ChatMessage.upsert({_id: item._id}, item);
}
});
if (wrapper) {
const heightDiff = wrapper.scrollHeight - previousHeight;
wrapper.scrollTop += heightDiff;
}
Meteor.defer(() => {
readMessage.refreshUnreadMark(rid, true);
return RoomManager.updateMentionsMarksOfRoom(typeName);
});
room.isLoading.set(false);
if (room.loaded == null) { room.loaded = 0; }
room.loaded += result.messages.length;
if (result.messages.length < limit) {
return room.hasMore.set(false);
}
});
}
getMoreNext(rid, limit) {
if (limit == null) { limit = this.defaultLimit; }
const room = this.getRoom(rid);
if (room.hasMoreNext.curValue !== true) {
return;
}
const instance = Blaze.getView($('.messages-box .wrapper')[0]).templateInstance();
instance.atBottom = false;
room.isLoading.set(true);
const lastMessage = ChatMessage.findOne({rid}, {sort: {ts: -1}});
let typeName = undefined;
const subscription = ChatSubscription.findOne({rid});
if (subscription != null) {
// const { ls } = subscription;
typeName = subscription.t + subscription.name;
} else {
const curRoomDoc = ChatRoom.findOne({_id: rid});
typeName = (curRoomDoc != null ? curRoomDoc.t : undefined) + (curRoomDoc != null ? curRoomDoc.name : undefined);
}
const { ts } = lastMessage;
if (ts) {
return Meteor.call('loadNextMessages', rid, ts, limit, function(err, result) {
for (const item of Array.from((result != null ? result.messages : undefined) || [])) {
if (item.t !== 'command') {
const roles = [
(item.u && item.u._id && UserRoles.findOne(item.u._id, { fields: { roles: 1 }})) || {},
(item.u && item.u._id && RoomRoles.findOne({rid: item.rid, 'u._id': item.u._id})) || {}
].map(e => e.roles);
item.roles = _.union.apply(_.union, roles);
ChatMessage.upsert({_id: item._id}, item);
}
}
Meteor.defer(() => RoomManager.updateMentionsMarksOfRoom(typeName));
room.isLoading.set(false);
if (room.loaded == null) { room.loaded = 0; }
room.loaded += result.messages.length;
if (result.messages.length < limit) {
room.hasMoreNext.set(false);
}
});
}
}
getSurroundingMessages(message, limit) {
if (limit == null) { limit = this.defaultLimit; }
if (!(message != null ? message.rid : undefined)) {
return;
}
const instance = Blaze.getView($('.messages-box .wrapper')[0]).templateInstance();
if (ChatMessage.findOne(message._id)) {
const wrapper = $('.messages-box .wrapper');
const msgElement = $(`#${ message._id }`, wrapper);
const pos = (wrapper.scrollTop() + msgElement.offset().top) - (wrapper.height()/2);
wrapper.animate({
scrollTop: pos
}, 500);
msgElement.addClass('highlight');
setTimeout(function() {
const messages = wrapper[0];
return instance.atBottom = messages.scrollTop >= (messages.scrollHeight - messages.clientHeight);
});
return setTimeout(() => msgElement.removeClass('highlight')
, 500);
} else {
const room = this.getRoom(message.rid);
room.isLoading.set(true);
ChatMessage.remove({ rid: message.rid });
let typeName = undefined;
const subscription = ChatSubscription.findOne({rid: message.rid});
if (subscription) {
// const { ls } = subscription;
typeName = subscription.t + subscription.name;
} else {
const curRoomDoc = ChatRoom.findOne({_id: message.rid});
typeName = (curRoomDoc != null ? curRoomDoc.t : undefined) + (curRoomDoc != null ? curRoomDoc.name : undefined);
}
return Meteor.call('loadSurroundingMessages', message, limit, function(err, result) {
for (const item of Array.from((result != null ? result.messages : undefined) || [])) {
if (item.t !== 'command') {
const roles = [
(item.u && item.u._id && UserRoles.findOne(item.u._id, { fields: { roles: 1 }})) || {},
(item.u && item.u._id && RoomRoles.findOne({rid: item.rid, 'u._id': item.u._id})) || {}
].map(e => e.roles);
item.roles = _.union.apply(_.union, roles);
ChatMessage.upsert({_id: item._id}, item);
}
}
Meteor.defer(function() {
readMessage.refreshUnreadMark(message.rid, true);
RoomManager.updateMentionsMarksOfRoom(typeName);
const wrapper = $('.messages-box .wrapper');
const msgElement = $(`#${ message._id }`, wrapper);
const pos = (wrapper.scrollTop() + msgElement.offset().top) - (wrapper.height()/2);
wrapper.animate({
scrollTop: pos
}, 500);
msgElement.addClass('highlight');
setTimeout(function() {
room.isLoading.set(false);
const messages = wrapper[0];
instance.atBottom = !result.moreAfter && (messages.scrollTop >= (messages.scrollHeight - messages.clientHeight));
return 500;
});
return setTimeout(() => msgElement.removeClass('highlight')
, 500);
});
if (room.loaded == null) { room.loaded = 0; }
room.loaded += result.messages.length;
room.hasMore.set(result.moreBefore);
return room.hasMoreNext.set(result.moreAfter);
});
}
}
hasMore(rid) {
const room = this.getRoom(rid);
return room.hasMore.get();
}
hasMoreNext(rid) {
const room = this.getRoom(rid);
return room.hasMoreNext.get();
}
getMoreIfIsEmpty(rid) {
const room = this.getRoom(rid);
if (room.loaded === undefined) {
return this.getMore(rid);
}
}
isLoading(rid) {
const room = this.getRoom(rid);
return room.isLoading.get();
}
clear(rid) {
ChatMessage.remove({ rid });
if (this.histories[rid] != null) {
this.histories[rid].hasMore.set(true);
this.histories[rid].isLoading.set(false);
return this.histories[rid].loaded = undefined;
}
}
};
this.RoomHistoryManager = RoomHistoryManager;

@ -1,253 +0,0 @@
loadMissedMessages = (rid) ->
lastMessage = ChatMessage.findOne({rid: rid}, {sort: {ts: -1}, limit: 1})
if not lastMessage?
return
Meteor.call 'loadMissedMessages', rid, lastMessage.ts, (err, result) ->
for item in result
RocketChat.promises.run('onClientMessageReceived', item).then (item) ->
item.roles = _.union(UserRoles.findOne(item.u?._id)?.roles, RoomRoles.findOne({rid: item.rid, 'u._id': item.u?._id})?.roles)
ChatMessage.upsert {_id: item._id}, item
connectionWasOnline = true
Tracker.autorun ->
connected = Meteor.connection.status().connected
if connected is true and connectionWasOnline is false and RoomManager.openedRooms?
for key, value of RoomManager.openedRooms
if value.rid?
loadMissedMessages(value.rid)
connectionWasOnline = connected
# Reload rooms after login
currentUsername = undefined
Tracker.autorun (c) ->
user = Meteor.user()
if currentUsername is undefined and user?.username?
currentUsername = user.username
RoomManager.closeAllRooms()
FlowRouter._current.route.callAction(FlowRouter._current)
Meteor.startup ->
ChatMessage.find().observe
removed: (record) ->
if RoomManager.getOpenedRoomByRid(record.rid)?
recordBefore = ChatMessage.findOne {ts: {$lt: record.ts}}, {sort: {ts: -1}}
if recordBefore?
ChatMessage.update {_id: recordBefore._id}, {$set: {tick: new Date}}
recordAfter = ChatMessage.findOne {ts: {$gt: record.ts}}, {sort: {ts: 1}}
if recordAfter?
ChatMessage.update {_id: recordAfter._id}, {$set: {tick: new Date}}
onDeleteMessageStream = (msg) ->
ChatMessage.remove _id: msg._id
Tracker.autorun ->
if Meteor.userId()
RocketChat.Notifications.onUser 'message', (msg) ->
msg.u =
username: 'rocket.cat'
msg.private = true
ChatMessage.upsert { _id: msg._id }, msg
@RoomManager = new class
openedRooms = {}
msgStream = new Meteor.Streamer 'room-messages'
onlineUsers = new ReactiveVar {}
Dep = new Tracker.Dependency
close = (typeName) ->
if openedRooms[typeName]
if openedRooms[typeName].rid?
msgStream.removeAllListeners openedRooms[typeName].rid
RocketChat.Notifications.unRoom openedRooms[typeName].rid, 'deleteMessage', onDeleteMessageStream
openedRooms[typeName].ready = false
openedRooms[typeName].active = false
if openedRooms[typeName].template?
Blaze.remove openedRooms[typeName].template
delete openedRooms[typeName].dom
delete openedRooms[typeName].template
rid = openedRooms[typeName].rid
delete openedRooms[typeName]
if rid?
RoomHistoryManager.clear rid
computation = Tracker.autorun ->
for typeName, record of openedRooms when record.active is true
do (typeName, record) ->
user = Meteor.user()
if record.ready is true
return
ready = CachedChatRoom.ready.get() and CachedChatSubscription.ready.get() is true
if ready is true
type = typeName.substr(0, 1)
name = typeName.substr(1)
room = Tracker.nonreactive =>
return RocketChat.roomTypes.findRoom(type, name, user)
if not room?
record.ready = true
else
openedRooms[typeName].rid = room._id
RoomHistoryManager.getMoreIfIsEmpty room._id
record.ready = RoomHistoryManager.isLoading(room._id) is false
Dep.changed()
if openedRooms[typeName].streamActive isnt true
openedRooms[typeName].streamActive = true
msgStream.on openedRooms[typeName].rid, (msg) ->
RocketChat.promises.run('onClientMessageReceived', msg).then (msg) ->
# Should not send message to room if room has not loaded all the current messages
if RoomHistoryManager.hasMoreNext(openedRooms[typeName].rid) is false
# Do not load command messages into channel
if msg.t isnt 'command'
msg.roles = _.union(UserRoles.findOne(msg.u?._id)?.roles, RoomRoles.findOne({rid: msg.rid, 'u._id': msg.u?._id})?.roles)
ChatMessage.upsert { _id: msg._id }, msg
Meteor.defer ->
RoomManager.updateMentionsMarksOfRoom typeName
RocketChat.callbacks.run 'streamMessage', msg
window.fireGlobalEvent('new-message', msg);
RocketChat.Notifications.onRoom openedRooms[typeName].rid, 'deleteMessage', onDeleteMessageStream
Dep.changed()
closeOlderRooms = ->
maxRoomsOpen = 10
if Object.keys(openedRooms).length <= maxRoomsOpen
return
roomsToClose = _.sortBy(_.values(openedRooms), 'lastSeen').reverse().slice(maxRoomsOpen)
for roomToClose in roomsToClose
close roomToClose.typeName
closeAllRooms = ->
for key, openedRoom of openedRooms
close openedRoom.typeName
open = (typeName) ->
if not openedRooms[typeName]?
openedRooms[typeName] =
typeName: typeName
active: false
ready: false
unreadSince: new ReactiveVar undefined
openedRooms[typeName].lastSeen = new Date
if openedRooms[typeName].ready
closeOlderRooms()
if CachedChatSubscription.ready.get() is true
if openedRooms[typeName].active isnt true
openedRooms[typeName].active = true
computation?.invalidate()
return {
ready: ->
Dep.depend()
return openedRooms[typeName].ready
}
getOpenedRoomByRid = (rid) ->
for typeName, openedRoom of openedRooms
if openedRoom.rid is rid
return openedRoom
getDomOfRoom = (typeName, rid) ->
room = openedRooms[typeName]
if not room?
return
if not room.dom? and rid?
room.dom = document.createElement 'div'
room.dom.classList.add 'room-container'
contentAsFunc = (content) ->
return -> content
room.template = Blaze._TemplateWith { _id: rid }, contentAsFunc(Template.room)
Blaze.render room.template, room.dom #, nextNode, parentView
return room.dom
existsDomOfRoom = (typeName) ->
room = openedRooms[typeName]
return room?.dom?
updateUserStatus = (user, status, utcOffset) ->
onlineUsersValue = onlineUsers.curValue
if status is 'offline'
delete onlineUsersValue[user.username]
else
onlineUsersValue[user.username] =
_id: user._id
status: status
utcOffset: utcOffset
onlineUsers.set onlineUsersValue
updateMentionsMarksOfRoom = (typeName) ->
dom = getDomOfRoom typeName
if not dom?
return
ticksBar = $(dom).find('.ticks-bar')
$(dom).find('.ticks-bar > .tick').remove()
scrollTop = $(dom).find('.messages-box > .wrapper').scrollTop() - 50
totalHeight = $(dom).find('.messages-box > .wrapper > ul').height() + 40
$('.messages-box .mention-link-me').each (index, item) ->
topOffset = $(item).offset().top + scrollTop
percent = 100 / totalHeight * topOffset
if $(item).hasClass('mention-link-all')
ticksBar.append('<div class="tick background-attention-color" style="top: '+percent+'%;"></div>')
else
ticksBar.append('<div class="tick background-primary-action-color" style="top: '+percent+'%;"></div>')
open: open
close: close
closeAllRooms: closeAllRooms
getDomOfRoom: getDomOfRoom
existsDomOfRoom: existsDomOfRoom
msgStream: msgStream
openedRooms: openedRooms
updateUserStatus: updateUserStatus
onlineUsers: onlineUsers
updateMentionsMarksOfRoom: updateMentionsMarksOfRoom
getOpenedRoomByRid: getOpenedRoomByRid
computation: computation
RocketChat.callbacks.add 'afterLogoutCleanUp', ->
RoomManager.closeAllRooms()
, RocketChat.callbacks.priority.MEDIUM, 'roommanager-after-logout-cleanup'

@ -0,0 +1,302 @@
const RoomManager = new function() {
const openedRooms = {};
const msgStream = new Meteor.Streamer('room-messages');
const onlineUsers = new ReactiveVar({});
const Dep = new Tracker.Dependency();
const Cls = class {
static initClass() {
/* globals CachedChatRoom CachedChatSubscription */
this.prototype.openedRooms = openedRooms;
this.prototype.onlineUsers = onlineUsers;
this.prototype.computation = Tracker.autorun(() => {
Object.keys(openedRooms).forEach(typeName => {
const record = openedRooms[typeName];
if (record.active !== true || record.ready === true) { return; }
const ready = CachedChatRoom.ready.get() && CachedChatSubscription.ready.get() === true;
if (ready !== true) { return; }
const user = Meteor.user();
const type = typeName.substr(0, 1);
const name = typeName.substr(1);
const room = Tracker.nonreactive(() => {
return RocketChat.roomTypes.findRoom(type, name, user);
});
if (room == null) {
record.ready = true;
} else {
openedRooms[typeName].rid = room._id;
RoomHistoryManager.getMoreIfIsEmpty(room._id);
record.ready = RoomHistoryManager.isLoading(room._id) === false;
Dep.changed();
if (openedRooms[typeName].streamActive !== true) {
openedRooms[typeName].streamActive = true;
msgStream.on(openedRooms[typeName].rid, msg =>
RocketChat.promises.run('onClientMessageReceived', msg).then(function(msg) {
// Should not send message to room if room has not loaded all the current messages
if (RoomHistoryManager.hasMoreNext(openedRooms[typeName].rid) === false) {
// Do not load command messages into channel
if (msg.t !== 'command') {
const roles = [
(msg.u && msg.u._id && UserRoles.findOne(msg.u._id, { fields: { roles: 1 }})) || {},
(msg.u && msg.u._id && RoomRoles.findOne({rid: msg.rid, 'u._id': msg.u._id})) || {}
].map(e => e.roles);
msg.roles = _.union.apply(_.union, roles);
ChatMessage.upsert({ _id: msg._id }, msg);
}
Meteor.defer(() => RoomManager.updateMentionsMarksOfRoom(typeName));
RocketChat.callbacks.run('streamMessage', msg);
return window.fireGlobalEvent('new-message', msg);
}
})
);
RocketChat.Notifications.onRoom(openedRooms[typeName].rid, 'deleteMessage', onDeleteMessageStream); // eslint-disable-line no-use-before-define
}
}
return Dep.changed();
});
});
}
getOpenedRoomByRid(rid) {
return Object.keys(openedRooms).map(typeName => openedRooms[typeName]).find(openedRoom => openedRoom.rid === rid);
}
getDomOfRoom(typeName, rid) {
const room = openedRooms[typeName];
if ((room == null)) {
return;
}
if ((room.dom == null) && (rid != null)) {
room.dom = document.createElement('div');
room.dom.classList.add('room-container');
const contentAsFunc = content => () => content;
room.template = Blaze._TemplateWith({ _id: rid }, contentAsFunc(Template.room));
Blaze.render(room.template, room.dom); //, nextNode, parentView
}
return room.dom;
}
close(typeName) {
if (openedRooms[typeName]) {
if (openedRooms[typeName].rid != null) {
msgStream.removeAllListeners(openedRooms[typeName].rid);
RocketChat.Notifications.unRoom(openedRooms[typeName].rid, 'deleteMessage', onDeleteMessageStream); // eslint-disable-line no-use-before-define
}
openedRooms[typeName].ready = false;
openedRooms[typeName].active = false;
if (openedRooms[typeName].template != null) {
Blaze.remove(openedRooms[typeName].template);
}
delete openedRooms[typeName].dom;
delete openedRooms[typeName].template;
const { rid } = openedRooms[typeName];
delete openedRooms[typeName];
if (rid != null) {
return RoomHistoryManager.clear(rid);
}
}
}
closeOlderRooms() {
const maxRoomsOpen = 10;
if (Object.keys(openedRooms).length <= maxRoomsOpen) {
return;
}
const roomsToClose = _.sortBy(_.values(openedRooms), 'lastSeen').reverse().slice(maxRoomsOpen);
return Array.from(roomsToClose).map((roomToClose) =>
this.close(roomToClose.typeName));
}
closeAllRooms() {
Object.keys(openedRooms).forEach(key => {
const openedRoom = openedRooms[key];
this.close(openedRoom.typeName);
});
}
open(typeName) {
if ((openedRooms[typeName] == null)) {
openedRooms[typeName] = {
typeName,
active: false,
ready: false,
unreadSince: new ReactiveVar(undefined)
};
}
openedRooms[typeName].lastSeen = new Date;
if (openedRooms[typeName].ready) {
this.closeOlderRooms();
}
if (CachedChatSubscription.ready.get() === true) {
if (openedRooms[typeName].active !== true) {
openedRooms[typeName].active = true;
if (this.computation) {
this.computation.invalidate();
}
}
}
return {
ready() {
Dep.depend();
return openedRooms[typeName].ready;
}
};
}
existsDomOfRoom(typeName) {
const room = openedRooms[typeName];
return ((room != null ? room.dom : undefined) != null);
}
updateUserStatus(user, status, utcOffset) {
const onlineUsersValue = onlineUsers.curValue;
if (status === 'offline') {
delete onlineUsersValue[user.username];
} else {
onlineUsersValue[user.username] = {
_id: user._id,
status,
utcOffset
};
}
return onlineUsers.set(onlineUsersValue);
}
updateMentionsMarksOfRoom(typeName) {
const dom = this.getDomOfRoom(typeName);
if ((dom == null)) {
return;
}
const ticksBar = $(dom).find('.ticks-bar');
$(dom).find('.ticks-bar > .tick').remove();
const scrollTop = $(dom).find('.messages-box > .wrapper').scrollTop() - 50;
const totalHeight = $(dom).find('.messages-box > .wrapper > ul').height() + 40;
return $('.messages-box .mention-link-me').each(function(index, item) {
const topOffset = $(item).offset().top + scrollTop;
const percent = (100 / totalHeight) * topOffset;
if ($(item).hasClass('mention-link-all')) {
return ticksBar.append(`<div class="tick background-attention-color" style="top: ${ percent }%;"></div>`);
} else {
return ticksBar.append(`<div class="tick background-primary-action-color" style="top: ${ percent }%;"></div>`);
}
});
}
};
Cls.initClass();
return new Cls;
};
const loadMissedMessages = function(rid) {
const lastMessage = ChatMessage.findOne({rid}, {sort: {ts: -1}, limit: 1});
if (lastMessage == null) {
return;
}
return Meteor.call('loadMissedMessages', rid, lastMessage.ts, (err, result) =>
Array.from(result).map((item) =>
RocketChat.promises.run('onClientMessageReceived', item).then(function(item) {
/* globals UserRoles RoomRoles*/
const roles = [
(item.u && item.u._id && UserRoles.findOne(item.u._id)) || {},
(item.u && item.u._id && RoomRoles.findOne({rid: item.rid, 'u._id': item.u._id})) || {}
].map(({roles}) => roles);
item.roles = _.union.apply(_, roles);
return ChatMessage.upsert({_id: item._id}, item);
}))
);
};
let connectionWasOnline = true;
Tracker.autorun(function() {
const { connected } = Meteor.connection.status();
if (connected === true && connectionWasOnline === false && RoomManager.openedRooms != null) {
Object.keys(RoomManager.openedRooms).forEach(key => {
const value = RoomManager.openedRooms[key];
if (value.rid != null) {
loadMissedMessages(value.rid);
}
});
}
return connectionWasOnline = connected;
});
// Reload rooms after login
let currentUsername = undefined;
Tracker.autorun(() => {
const user = Meteor.user();
if ((currentUsername === undefined) && ((user != null ? user.username : undefined) != null)) {
currentUsername = user.username;
RoomManager.closeAllRooms();
return FlowRouter._current.route.callAction(FlowRouter._current);
}
});
Meteor.startup(() =>
ChatMessage.find().observe({
removed(record) {
if (RoomManager.getOpenedRoomByRid(record.rid) != null) {
const recordBefore = ChatMessage.findOne({ts: {$lt: record.ts}}, {sort: {ts: -1}});
if (recordBefore != null) {
ChatMessage.update({_id: recordBefore._id}, {$set: {tick: new Date}});
}
const recordAfter = ChatMessage.findOne({ts: {$gt: record.ts}}, {sort: {ts: 1}});
if (recordAfter != null) {
return ChatMessage.update({_id: recordAfter._id}, {$set: {tick: new Date}});
}
}
}
})
);
const onDeleteMessageStream = msg => ChatMessage.remove({_id: msg._id});
Tracker.autorun(function() {
if (Meteor.userId()) {
return RocketChat.Notifications.onUser('message', function(msg) {
msg.u =
{username: 'rocket.cat'};
msg.private = true;
return ChatMessage.upsert({ _id: msg._id }, msg);
});
}
});
export { RoomManager };
this.RoomManager = RoomManager;
RocketChat.callbacks.add('afterLogoutCleanUp', () => RoomManager.closeAllRooms(), RocketChat.callbacks.priority.MEDIUM, 'roommanager-after-logout-cleanup');

@ -1,112 +0,0 @@
RocketChat.Login = (->
onClick = (el) ->
$el = $(el)
if $el.length
$el.addClass "active"
$el.find("input").focus()
onBlur = (input) ->
$input = $(input)
if $input.length
if input.value == ""
$input.parents(".input-text").removeClass "active"
check = (form) ->
$form = $(form)
if $form.length
inputs = $form.find("input")
inputs.each ->
if @.value != ""
console.log @.value
$(@).parents(".input-text").addClass "active"
check: check
onClick: onClick
onBlur: onBlur
)()
RocketChat.Button = (->
time = undefined
loading = (el) ->
$el = $(el)
next = el.attr("data-loading-text")
html = el.find("span").html()
el.addClass("-progress").attr("data-def-text",html).find("span").html(next)
time = setTimeout ->
el.addClass("going")
, 1
done = (el) ->
$el = $(el)
el.addClass("done")
reset = (el) ->
clearTimeout(time) if time
$el = $(el)
html= $el.attr("data-def-text")
$el.find("span").html(html) if html
$el.removeClass("-progress going done")
done: done
loading: loading
reset: reset
)()
RocketChat.animationSupport = ->
animeEnd =
WebkitAnimation: "webkitAnimationEnd"
OAnimation: "oAnimationEnd"
msAnimation: "MSAnimationEnd"
animation: "animationend"
transEndEventNames =
WebkitTransition: "webkitTransitionEnd"
MozTransition: "transitionend"
OTransition: "oTransitionEnd otransitionend"
msTransition: "MSTransitionEnd"
transition: "transitionend"
prefixB = transEndEventNames[Modernizr.prefixed("transition")]
prefixA = animeEnd[Modernizr.prefixed("animation")]
support = Modernizr.cssanimations
support: support
animation: prefixA
transition: prefixB
RocketChat.animeBack = (el, callback, type) ->
el = $(el)
if not el.length > 0
callback el if callback
return
s = animationSupport()
p = ((if type then s.animation else s.transition))
el.one p, (e) ->
#el.off(p);
callback e
return
return
RocketChat.preLoadImgs = (urls, callback) ->
L_ = (x) ->
if x.width > 0
$(x).addClass("loaded").removeClass "loading"
loaded = $(".loaded", preLoader)
if loaded.length is urls.length and not ended
ended = 1
imgs = preLoader.children()
callback imgs
preLoader.remove()
return
im = new Array()
preLoader = $("<div/>").attr(id: "perverter-preloader")
loaded = undefined
ended = undefined
i = 0
while i < urls.length
im[i] = new Image()
im[i].onload = ->
L_ this
return
$(im[i]).appendTo(preLoader).addClass "loading"
im[i].src = urls[i]
L_ im[i] if im[i].width > 0
i++
return

@ -0,0 +1,121 @@
/* globals Modernizr */
RocketChat.Login = (function() {
function onClick(el) {
const $el = $(el);
if ($el.length) {
$el.addClass('active');
return $el.find('input').focus();
}
}
function onBlur(input) {
const $input = $(input);
if ($input.length) {
if (input.value === '') {
return $input.parents('.input-text').removeClass('active');
}
}
}
function check(form) {
const $form = $(form);
if ($form.length) {
const inputs = $form.find('input');
return inputs.each(function() {
if (this.value !== '') {
console.log(this.value);
return $(this).parents('.input-text').addClass('active');
}
});
}
}
return { check, onClick, onBlur };
}());
RocketChat.Button = (function() {
let time = undefined;
const loading = function(el) {
const next = el.attr('data-loading-text');
const html = el.find('span').html();
el.addClass('-progress').attr('data-def-text', html).find('span').html(next);
return time = setTimeout(() => el.addClass('going'), 1);
};
const done = function(el) {
return el.addClass('done');
};
const reset = function(el) {
if (time) { clearTimeout(time); }
const $el = $(el);
const html= $el.attr('data-def-text');
if (html) { $el.find('span').html(html); }
return $el.removeClass('-progress going done');
};
return { done, loading, reset };
}());
RocketChat.animationSupport = function() {
const animeEnd = {
WebkitAnimation: 'webkitAnimationEnd',
OAnimation: 'oAnimationEnd',
msAnimation: 'MSAnimationEnd',
animation: 'animationend'
};
const transEndEventNames = {
WebkitTransition: 'webkitTransitionEnd',
MozTransition: 'transitionend',
OTransition: 'oTransitionEnd otransitionend',
msTransition: 'MSTransitionEnd',
transition: 'transitionend'
};
const prefixB = transEndEventNames[Modernizr.prefixed('transition')];
const prefixA = animeEnd[Modernizr.prefixed('animation')];
const support = Modernizr.cssanimations;
return {
support,
animation: prefixA,
transition: prefixB
};
};
RocketChat.animeBack = function(e, callback, type) {
const el = $(e);
if (!el.length > 0) {
if (callback) { callback(el); }
return;
}
const s = RocketChat.animationSupport();
const p = ((type ? s.animation : s.transition));
el.one(p, function(e) {
//el.off(p);
callback(e);
});
};
RocketChat.preLoadImgs = function(urls, callback) {
const preLoader = $('<div/>').attr({id: 'perverter-preloader'});
let ended = undefined;
const l_ = function(x) {
if (x.width > 0) {
$(x).addClass('loaded').removeClass('loading');
const loaded = $('.loaded', preLoader);
if ((loaded.length === urls.length) && !ended) {
ended = 1;
const imgs = preLoader.children();
callback(imgs);
preLoader.remove();
}
}
};
return urls.map(url => {
const im = new Image();
im.onload = function() {
l_(this);
};
$(im).appendTo(preLoader).addClass('loading');
im.src = url;
if (im.width > 0) { l_(im); }
return im;
});
};

@ -220,7 +220,7 @@ Template.room.helpers({
canPreview() {
const room = Session.get(`roomData${ this._id }`);
if (room.t !== 'c') {
if (room && room.t !== 'c') {
return true;
}

@ -48,9 +48,9 @@ Package.onUse(function(api) {
api.addFiles('client/lib/notification.js', 'client');
api.addFiles('client/lib/parentTemplate.js', 'client');
api.addFiles('client/lib/readMessages.js', 'client');
api.addFiles('client/lib/rocket.coffee', 'client');
api.addFiles('client/lib/RoomHistoryManager.coffee', 'client');
api.addFiles('client/lib/RoomManager.coffee', 'client');
api.addFiles('client/lib/rocket.js', 'client');
api.addFiles('client/lib/RoomHistoryManager.js', 'client');
api.addFiles('client/lib/RoomManager.js', 'client');
api.addFiles('client/lib/sideNav.js', 'client');
api.addFiles('client/lib/tapi18n.js', 'client');
api.addFiles('client/lib/textarea-autogrow.js', 'client');

Loading…
Cancel
Save