pull/6836/head
Guilherme Gazzo 9 years ago
parent adf103d3f3
commit dae37ecb2b
  1. 425
      packages/rocketchat-ui/client/lib/chatMessages.coffee
  2. 35
      packages/rocketchat-ui/client/lib/chatMessages.js
  3. 70
      packages/rocketchat-ui/client/lib/cordova/push.coffee
  4. 6
      packages/rocketchat-ui/client/lib/cordova/push.js
  5. 14
      packages/rocketchat-ui/client/lib/cordova/urls.coffee
  6. 4
      packages/rocketchat-ui/client/lib/cordova/urls.js
  7. 69
      packages/rocketchat-ui/client/lib/msgTyping.coffee
  8. 2
      packages/rocketchat-ui/client/lib/msgTyping.js
  9. 94
      packages/rocketchat-ui/client/lib/notification.coffee
  10. 7
      packages/rocketchat-ui/client/lib/notification.js
  11. 179
      packages/rocketchat-ui/client/lib/readMessages.coffee
  12. 41
      packages/rocketchat-ui/client/lib/recorderjs/audioRecorder.coffee
  13. 78
      packages/rocketchat-ui/client/lib/recorderjs/videoRecorder.coffee
  14. 12
      packages/rocketchat-ui/client/lib/recorderjs/videoRecorder.js
  15. 2
      packages/rocketchat-ui/package.js

@ -1,425 +0,0 @@
import moment from 'moment'
import toastr from 'toastr'
class @ChatMessages
init: (node) ->
this.editing = {}
this.records = {}
this.messageMaxSize = RocketChat.settings.get('Message_MaxAllowedSize')
this.wrapper = $(node).find(".wrapper")
this.input = $(node).find(".input-message").get(0)
this.$input = $(this.input)
this.hasValue = new ReactiveVar false
this.bindEvents()
return
resize: ->
dif = (if RocketChat.Layout.isEmbedded() then 0 else 60) + $(".messages-container").find("footer").outerHeight()
dif += if $(".announcement").length > 0 then 40 else 0
$(".messages-box").css
height: "calc(100% - #{dif}px)"
getEditingIndex: (element) ->
msgs = this.wrapper.get(0).querySelectorAll(".own:not(.system)")
index = 0
for msg in msgs
if msg is element
return index
index++
return -1
recordInputAsDraft: () ->
id = this.editing.id
message = this.getMessageById id
record = this.records[id] || {}
draft = this.input.value
if(draft is message.msg)
this.clearCurrentDraft()
else
record.draft = draft
this.records[id] = record
getMessageDraft: (id) ->
return this.records[id]
clearMessageDraft: (id) ->
delete this.records[id]
clearCurrentDraft: () ->
this.clearMessageDraft this.editing.id
resetToDraft: (id) ->
message = this.getMessageById id
old_value = this.input.value
this.input.value = message.msg
return old_value isnt message.msg
getMessageById: (id) ->
return ChatMessage.findOne(id)
toPrevMessage: ->
index = this.editing.index
this.editByIndex if index? then index - 1 else undefined
toNextMessage: ->
index = this.editing.index
this.clearEditing() unless this.editByIndex index + 1
editByIndex: (index) ->
return false if not this.editing.element and index?
msgs = this.wrapper.get(0).querySelectorAll(".own:not(.system)")
index = msgs.length - 1 if not index?
return false unless msgs[index]
element = msgs[index]
this.edit element, index
return true
edit: (element, index) ->
index = this.getEditingIndex(element) if not index?
message = this.getMessageById element.getAttribute("id")
hasPermission = RocketChat.authz.hasAtLeastOnePermission('edit-message', message.rid)
editAllowed = RocketChat.settings.get 'Message_AllowEditing'
editOwn = message?.u?._id is Meteor.userId()
return unless hasPermission or (editAllowed and editOwn)
return if element.classList.contains("system")
blockEditInMinutes = RocketChat.settings.get 'Message_AllowEditing_BlockEditInMinutes'
if blockEditInMinutes? and blockEditInMinutes isnt 0
msgTs = moment(message.ts) if message.ts?
currentTsDiff = moment().diff(msgTs, 'minutes') if msgTs?
if currentTsDiff > blockEditInMinutes
return
msg = this.getMessageDraft(message._id)?.draft
msg = message.msg unless msg?
editingNext = this.editing.index < index
old_input = this.input.value
this.clearEditing()
this.hasValue.set true
this.editing.element = element
this.editing.index = index
this.editing.id = message._id
element.classList.add("editing")
this.input.classList.add("editing")
this.$input.closest('.message-form').addClass('editing')
this.input.focus()
if message.attachments? and message.attachments[0].description?
this.input.value = message.attachments[0].description
else
this.input.value = msg
cursor_pos = if editingNext then 0 else -1
this.$input.setCursorPosition(cursor_pos)
clearEditing: ->
if this.editing.element
this.recordInputAsDraft()
this.editing.element.classList.remove("editing")
this.input.classList.remove("editing")
this.$input.closest('.message-form').removeClass('editing')
delete this.editing.id
delete this.editing.element
delete this.editing.index
this.input.value = this.editing.saved or ""
cursor_pos = this.editing.savedCursor ? -1
this.$input.setCursorPosition(cursor_pos)
this.hasValue.set this.input.value isnt ''
else
this.editing.saved = this.input.value
this.editing.savedCursor = this.input.selectionEnd
###*
# * @param {string} rim room ID
# * @param {Element} input DOM element
# * @param {function?} done callback
###
send: (rid, input, done = ->) ->
if _.trim(input.value) isnt ''
readMessage.enable()
readMessage.readNow()
$('.message.first-unread').removeClass('first-unread')
msg = input.value
msgObject = { _id: Random.id(), rid: rid, msg: msg}
# Run to allow local encryption, and maybe other client specific actions to be run before send
RocketChat.promises.run('onClientBeforeSendMessage', msgObject).then (msgObject) =>
# checks for the final msgObject.msg size before actually sending the message
if this.isMessageTooLong(msgObject.msg)
return toastr.error t('Message_too_long')
this.clearCurrentDraft()
if this.editing.id
this.update(this.editing.id, rid, msgObject.msg)
return
KonchatNotification.removeRoomNotification(rid)
input.value = ''
input.updateAutogrow?()
this.hasValue.set false
this.stopTyping(rid)
#Check if message starts with /command
if msg[0] is '/'
match = msg.match(/^\/([^\s]+)(?:\s+(.*))?$/m)
if match?
if RocketChat.slashCommands.commands[match[1]]
commandOptions = RocketChat.slashCommands.commands[match[1]]
command = match[1]
param = if match[2]? then match[2] else ''
if commandOptions.clientOnly
commandOptions.callback(command, param, msgObject)
else
Meteor.call 'slashCommand', {cmd: command, params: param, msg: msgObject }, (err, result) -> commandOptions.result?(err, result, {cmd: command, params: param, msg: msgObject })
return
if !RocketChat.settings.get('Message_AllowUnrecognizedSlashCommand')
invalidCommandMsg =
_id: Random.id()
rid: rid
ts: new Date
msg: TAPi18n.__('No_such_command', { command: match[1] })
u:
username: "rocketbot"
private: true
ChatMessage.upsert { _id: invalidCommandMsg._id }, invalidCommandMsg
return
Meteor.call 'sendMessage', msgObject
done()
# If edited message was emptied we ask for deletion
else if this.editing.element
message = this.getMessageById this.editing.id
if message.attachments? and message.attachments[0].description?
this.update(this.editing.id, rid, '', true)
return
# Restore original message in textbox in case delete is canceled
this.resetToDraft this.editing.id
this.confirmDeleteMsg message, done
confirmDeleteMsg: (message, done = ->) ->
return if RocketChat.MessageTypes.isSystemMessage(message)
swal {
title: t('Are_you_sure')
text: t('You_will_not_be_able_to_recover')
type: 'warning'
showCancelButton: true
confirmButtonColor: '#DD6B55'
confirmButtonText: t('Yes_delete_it')
cancelButtonText: t('Cancel')
closeOnConfirm: false
html: false
}, =>
swal
title: t('Deleted')
text: t('Your_entry_has_been_deleted')
type: 'success'
timer: 1000
showConfirmButton: false
if this.editing.id is message._id
this.clearEditing message
this.deleteMsg message
this.$input.focus()
done()
# In order to avoid issue "[Callback not called when still animating](https://github.com/t4t5/sweetalert/issues/528)"
$('.sweet-alert').addClass 'visible'
deleteMsg: (message) ->
blockDeleteInMinutes = RocketChat.settings.get 'Message_AllowDeleting_BlockDeleteInMinutes'
if blockDeleteInMinutes? and blockDeleteInMinutes isnt 0
msgTs = moment(message.ts) if message.ts?
currentTsDiff = moment().diff(msgTs, 'minutes') if msgTs?
if currentTsDiff > blockDeleteInMinutes
toastr.error(t('Message_deleting_blocked'))
return
Meteor.call 'deleteMessage', { _id: message._id }, (error, result) ->
if error
return handleError(error)
pinMsg: (message) ->
message.pinned = true
Meteor.call 'pinMessage', message, (error, result) ->
if error
return handleError(error)
unpinMsg: (message) ->
message.pinned = false
Meteor.call 'unpinMessage', message, (error, result) ->
if error
return handleError(error)
update: (id, rid, msg, isDescription) ->
if _.trim(msg) isnt '' or isDescription is true
Meteor.call 'updateMessage', { _id: id, msg: msg, rid: rid }
this.clearEditing()
this.stopTyping(rid)
startTyping: (rid, input) ->
if _.trim(input.value) isnt ''
MsgTyping.start(rid)
else
MsgTyping.stop(rid)
stopTyping: (rid) ->
MsgTyping.stop(rid)
bindEvents: ->
if this.wrapper?.length
$(".input-message").autogrow
postGrowCallback: =>
this.resize()
tryCompletion: (input) ->
value = input.value.match(/[^\s]+$/)
if value?.length > 0
value = value[0]
re = new RegExp value, 'i'
user = Meteor.users.findOne username: re
if user?
input.value = input.value.replace value, "@#{user.username} "
keyup: (rid, event) ->
input = event.currentTarget
k = event.which
keyCodes = [
13, # Enter
20, # Caps lock
16, # Shift
9, # Tab
27, # Escape Key
17, # Control Key
91, # Windows Command Key
19, # Pause Break
18, # Alt Key
93, # Right Click Point Key
45, # Insert Key
34, # Page Down
35, # Page Up
144, # Num Lock
145 # Scroll Lock
]
keyCodes.push i for i in [35..40] # Home, End, Arrow Keys
keyCodes.push i for i in [112..123] # F1 - F12
unless k in keyCodes
this.startTyping(rid, input)
this.hasValue.set input.value isnt ''
keydown: (rid, event) ->
sendOnEnter = Meteor.user()?.settings?.preferences?.sendOnEnter
input = event.currentTarget
$input = $(input)
k = event.which
this.resize(input)
if k is 13
if not sendOnEnter? || sendOnEnter is 'normal' || (sendOnEnter is 'desktop' and Meteor.Device.isDesktop())
if not event.shiftKey and not event.ctrlKey and not event.altKey and not event.metaKey # Enter without shift/ctrl/alt
event.preventDefault()
event.stopPropagation()
this.send(rid, input)
return
else if not event.shiftKey
return input.value +='\n'
else if sendOnEnter is 'alternative'
if event.shiftKey or event.ctrlKey or event.altKey or event.metaKey # Enter with shift/ctrl/alt
event.preventDefault()
event.stopPropagation()
this.send(rid, input)
return
if k is 9 # Tab
event.preventDefault()
event.stopPropagation()
@tryCompletion input
if k is 27 # Escape
if this.editing.index?
record = this.getMessageDraft(this.editing.id)
# If resetting did nothing then edited message is same as original
unless this.resetToDraft this.editing.id
this.clearCurrentDraft()
this.clearEditing()
event.preventDefault()
event.stopPropagation()
return
else if k is 38 or k is 40 # Arrow Up or down
return true if event.shiftKey
cursor_pos = input.selectionEnd
if k is 38 # Arrow Up
if cursor_pos is 0
this.toPrevMessage()
else if not event.altKey
return true
this.$input.setCursorPosition(0) if event.altKey
else # Arrow Down
if cursor_pos is input.value.length
this.toNextMessage()
else if not event.altKey
return true
this.$input.setCursorPosition(-1) if event.altKey
return false
# ctrl (command) + shift + k -> clear room messages
else if k is 75 and ((navigator?.platform?.indexOf('Mac') isnt -1 and event.metaKey and event.shiftKey) or (navigator?.platform?.indexOf('Mac') is -1 and event.ctrlKey and event.shiftKey))
RoomHistoryManager.clear rid
valueChanged: (rid, event) ->
if this.input.value.length is 1
this.determineInputDirection()
determineInputDirection: () ->
this.input.dir = if this.isMessageRtl(this.input.value) then 'rtl' else 'ltr'
# http://stackoverflow.com/a/14824756
isMessageRtl: (message) ->
ltrChars = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF'+'\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF'
rtlChars = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC'
rtlDirCheck = new RegExp "^[^#{ltrChars}]*[#{rtlChars}]"
return rtlDirCheck.test message
isMessageTooLong: (message) ->
message?.length > this.messageMaxSize
isEmpty: ->
return !this.hasValue.get()

@ -83,21 +83,20 @@ this.ChatMessages = class ChatMessages {
}
editByIndex(index) {
if (!this.editing.element && (index != null)) { return false; }
if (!this.editing.element && index) { return false; }
const msgs = this.wrapper.get(0).querySelectorAll('.own:not(.system)');
if ((index == null)) { index = msgs.length - 1; }
if (index == null) { index = msgs.length - 1; }
if (!msgs[index]) { return false; }
const element = msgs[index];
this.edit(element, index);
return true;
}
edit(element, index) {
if ((index == null)) { index = this.getEditingIndex(element); }
index = index != null ? index : this.getEditingIndex(element);
const message = this.getMessageById(element.getAttribute('id'));
@ -105,7 +104,7 @@ this.ChatMessages = class ChatMessages {
const editAllowed = RocketChat.settings.get('Message_AllowEditing');
const editOwn = message && message.u && message.u._id === Meteor.userId();
if (!hasPermission && (!editAllowed || !editOwn)) { return; }
if (!hasPermission && !editAllowed || !editOwn) { return; }
if (element.classList.contains('system')) { return; }
const blockEditInMinutes = RocketChat.settings.get('Message_AllowEditing_BlockEditInMinutes');
@ -121,7 +120,7 @@ this.ChatMessages = class ChatMessages {
const draft = this.getMessageDraft(message._id);
let msg = draft && draft.draft;
if (msg == null) { ({ msg } = message); }
msg = msg || message.msg;
const editingNext = this.editing.index < index;
@ -138,7 +137,7 @@ this.ChatMessages = class ChatMessages {
this.$input.closest('.message-form').addClass('editing');
this.input.focus();
if ((message.attachments != null) && (message.attachments[0].description != null)) {
if (message.attachments && message.attachments[0].description) {
this.input.value = message.attachments[0].description;
} else {
this.input.value = msg;
@ -175,8 +174,7 @@ this.ChatMessages = class ChatMessages {
* * @param {Element} input DOM element
* * @param {function?} done callback
*/
send(rid, input, done) {
if (done == null) { done = function() {}; }
send(rid, input, done = function() {}) {
if (_.trim(input.value) !== '') {
readMessage.enable();
readMessage.readNow();
@ -210,16 +208,16 @@ this.ChatMessages = class ChatMessages {
//Check if message starts with /command
if (msg[0] === '/') {
const match = msg.match(/^\/([^\s]+)(?:\s+(.*))?$/m);
if (match != null) {
if (match) {
let command;
if (RocketChat.slashCommands.commands[match[1]]) {
const commandOptions = RocketChat.slashCommands.commands[match[1]];
command = match[1];
const param = (match[2] != null) ? match[2] : '';
const param = match[2] || '';
if (commandOptions.clientOnly) {
commandOptions.callback(command, param, msgObject);
} else {
Meteor.call('slashCommand', {cmd: command, params: param, msg: msgObject }, (err, result) => typeof commandOptions.result === 'function' ? commandOptions.result(err, result, {cmd: command, params: param, msg: msgObject }) : undefined);
Meteor.call('slashCommand', {cmd: command, params: param, msg: msgObject }, (err, result) => typeof commandOptions.result === 'function' && commandOptions.result(err, result, {cmd: command, params: param, msg: msgObject }));
}
return;
}
@ -258,8 +256,7 @@ this.ChatMessages = class ChatMessages {
}
}
confirmDeleteMsg(message, done) {
if (done == null) { done = function() {}; }
confirmDeleteMsg(message, done = function() {}) {
if (RocketChat.MessageTypes.isSystemMessage(message)) { return; }
swal({
title: t('Are_you_sure'),
@ -352,7 +349,7 @@ this.ChatMessages = class ChatMessages {
}
bindEvents() {
if (this.wrapper != null ? this.wrapper.length : undefined) {
if (this.wrapper && this.wrapper.length) {
return $('.input-message').autogrow({
postGrowCallback: () => {
return this.resize();
@ -366,7 +363,7 @@ this.ChatMessages = class ChatMessages {
if (!value) { return; }
const re = new RegExp(value, 'i');
const user = Meteor.users.findOne({username: re});
if (user != null) {
if (user) {
return input.value = input.value.replace(value, `@${ user.username } `);
}
}
@ -411,7 +408,7 @@ this.ChatMessages = class ChatMessages {
this.resize(input);
if (k === 13) {
if (sendOnEnter == null || sendOnEnter === 'normal' || (sendOnEnter === 'desktop' && Meteor.Device.isDesktop())) {
if (sendOnEnter == null || sendOnEnter === 'normal' || sendOnEnter === 'desktop' && Meteor.Device.isDesktop()) {
if (!event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) { // Enter without shift/ctrl/alt
event.preventDefault();
event.stopPropagation();
@ -452,7 +449,7 @@ this.ChatMessages = class ChatMessages {
event.stopPropagation();
return;
}
} else if ((k === 38) || (k === 40)) { // Arrow Up or down
} else if (k === 38 || k === 40) { // Arrow Up or down
if (event.shiftKey) { return true; }
const cursor_pos = input.selectionEnd;
@ -504,7 +501,7 @@ this.ChatMessages = class ChatMessages {
}
isMessageTooLong(message) {
return (message != null ? message.length : undefined) > this.messageMaxSize;
return message && message.length > this.messageMaxSize;
}
isEmpty() {

@ -1,70 +0,0 @@
if Meteor.isCordova
# Push.addListener 'token', (token) ->
# Meteor.call 'log', 'CLIENT', 'token', arguments
# Push.addListener 'error', (err) ->
# Meteor.call 'log', 'CLIENT', 'error', arguments
# if err.type == 'apn.cordova'
# Meteor.call 'log', 'CLIENT', err.error
# Push.addListener 'register', (evt) ->
# Meteor.call 'log', 'CLIENT', 'register', arguments
# Push.addListener 'alert', (notification) ->
# Meteor.call 'log', 'CLIENT', 'alert', arguments
# Push.addListener 'sound', (notification) ->
# Meteor.call 'log', 'CLIENT', 'sound', arguments
# Push.addListener 'badge', (notification) ->
# Meteor.call 'log', 'CLIENT', 'badge', arguments
# Push.addListener 'message', (notification) ->
# Meteor.call 'log', 'CLIENT', 'message', arguments
Push.addListener 'startup', (notification) ->
# Meteor.call 'log', 'CLIENT', 'startup', arguments
if notification.payload?.rid?
if notification.payload.host is Meteor.absoluteUrl()
switch notification.payload.type
when 'c'
FlowRouter.go 'channel', { name: notification.payload.name }, FlowRouter.current().queryParams
when 'p'
FlowRouter.go 'group', { name: notification.payload.name }, FlowRouter.current().queryParams
when 'd'
FlowRouter.go 'direct', { username: notification.payload.sender.username }, FlowRouter.current().queryParams
else
path = ''
switch notification.payload.type
when 'c'
path = 'channel/' + notification.payload.name
when 'p'
path = 'group/' + notification.payload.name
when 'd'
path = 'direct/' + notification.payload.sender.username
host = notification.payload.host.replace /\/$/, ''
if Servers.serverExists(host) isnt true
return
Servers.startServer host, path, (err, url) ->
if err?
# TODO err
return console.log err
Meteor.startup ->
Tracker.autorun ->
if RocketChat.settings.get('Push_enable') is true
Push.Configure
android:
senderID: window.ANDROID_SENDER_ID
sound: true
vibrate: true
ios:
badge: true
clearBadge: true
sound: true
alert: true

@ -26,7 +26,7 @@ if (Meteor.isCordova) {
Push.addListener('startup', function(notification) {
// Meteor.call 'log', 'CLIENT', 'startup', arguments
if ((notification.payload != null ? notification.payload.rid : undefined) != null) {
if (notification.payload && notification.payload.rid) {
if (notification.payload.host === Meteor.absoluteUrl()) {
switch (notification.payload.type) {
case 'c':
@ -56,7 +56,7 @@ if (Meteor.isCordova) {
}
return Servers.startServer(host, path, function(err) {
if (err != null) {
if (err) {
// TODO err
return console.log(err);
}
@ -67,7 +67,7 @@ if (Meteor.isCordova) {
Meteor.startup(() =>
Tracker.autorun(function() {
Tracker.autorun(() => {
if (RocketChat.settings.get('Push_enable') === true) {
Push.Configure({

@ -1,14 +0,0 @@
Meteor.startup ->
return unless Meteor.isCordova
# Handle click events for all external URLs
$(document).on 'deviceready', ->
platform = device.platform.toLowerCase()
$(document).on 'click', (e) ->
$link = $(e.target).closest('a[href]')
return unless $link.length > 0
url = $link.attr('href')
if /^https?:\/\/.+/.test(url) is true
window.open url, '_system'
e.preventDefault()

@ -1,7 +1,7 @@
Meteor.startup(function() {
Meteor.startup(() => {
if (!Meteor.isCordova) { return; }
// Handle click events for all external URLs
$(document).on('deviceready', function() {
$(document).on('deviceready', () => {
// const platform = device.platform.toLowerCase();
$(document).on('click', function(e) {
const $link = $(e.target).closest('a[href]');

@ -1,69 +0,0 @@
@MsgTyping = do ->
timeout = 15000
timeouts = {}
renew = true
renewTimeout = 10000
selfTyping = new ReactiveVar false
usersTyping = {}
dep = new Tracker.Dependency
addStream = (room) ->
if _.isEmpty usersTyping[room]?.users
usersTyping[room] = { users: {} }
RocketChat.Notifications.onRoom room, 'typing', (username, typing) ->
unless username is Meteor.user()?.username
if typing is true
users = usersTyping[room].users
users[username] = Meteor.setTimeout ->
delete users[username]
usersTyping[room].users = users
dep.changed()
, timeout
usersTyping[room].users = users
dep.changed()
else
users = usersTyping[room].users
delete users[username]
usersTyping[room].users = users
dep.changed()
Tracker.autorun ->
if Session.get 'openedRoom'
addStream Session.get 'openedRoom'
start = (room) ->
return unless renew
setTimeout ->
renew = true
, renewTimeout
renew = false
selfTyping.set true
RocketChat.Notifications.notifyRoom room, 'typing', Meteor.user()?.username, true
clearTimeout timeouts[room]
timeouts[room] = Meteor.setTimeout ->
stop(room)
, timeout
stop = (room) ->
renew = true
selfTyping.set false
if timeouts?[room]?
clearTimeout(timeouts[room])
timeouts[room] = null
RocketChat.Notifications.notifyRoom room, 'typing', Meteor.user()?.username, false
get = (room) ->
dep.depend()
unless usersTyping[room]
usersTyping[room] = { users: {} }
users = usersTyping[room].users
return _.keys(users) or []
return {
start: start
stop: stop
get: get
selfTyping: selfTyping
}

@ -36,7 +36,7 @@ export const MsgTyping = (function() {
const stop = function(room) {
renew = true;
selfTyping.set(false);
if ((timeouts != null ? timeouts[room] : undefined) != null) {
if (timeouts && timeouts[room]) {
clearTimeout(timeouts[room]);
timeouts[room] = null;
}

@ -1,94 +0,0 @@
# @TODO implementar 'clicar na notificacao' abre a janela do chat
@KonchatNotification =
notificationStatus: new ReactiveVar
# notificacoes HTML5
getDesktopPermission: ->
if window.Notification && Notification.permission != "granted" && !Meteor.settings.public.sandstorm
Notification.requestPermission (status) ->
KonchatNotification.notificationStatus.set status
if Notification.permission != status
Notification.permission = status
notify: (notification) ->
if window.Notification && Notification.permission == "granted"
message = { rid: notification.payload?.rid, msg: notification.text, notification: true }
RocketChat.promises.run('onClientMessageReceived', message).then (message) ->
n = new Notification notification.title,
icon: notification.icon or getAvatarUrlFromUsername notification.payload.sender.username
body: _.stripTags(message.msg)
tag: notification.payload._id,
silent: true
canReply: true
notificationDuration = (notification.duration - 0) or (Meteor.user()?.settings?.preferences?.desktopNotificationDuration - 0) or RocketChat.settings.get('Desktop_Notifications_Duration')
if notificationDuration > 0
setTimeout ( -> n.close() ), notificationDuration * 1000
if notification.payload?.rid?
if n.addEventListener?
n.addEventListener 'reply', ({response}) ->
Meteor.call 'sendMessage',
_id: Random.id()
rid: notification.payload.rid
msg: response
n.onclick = ->
this.close()
window.focus()
switch notification.payload.type
when 'd'
FlowRouter.go 'direct', { username: notification.payload.sender.username }, FlowRouter.current().queryParams
when 'c'
FlowRouter.go 'channel', { name: notification.payload.name }, FlowRouter.current().queryParams
when 'p'
FlowRouter.go 'group', { name: notification.payload.name }, FlowRouter.current().queryParams
showDesktop: (notification) ->
if notification.payload.rid is Session.get('openedRoom') and window.document.hasFocus?()
return
if Meteor.user().status is 'busy' or Meteor.settings.public.sandstorm?
return
getAvatarAsPng notification.payload.sender.username, (avatarAsPng) ->
notification.icon = avatarAsPng
KonchatNotification.notify(notification)
newMessage: (rid) ->
if not Session.equals('user_' + Meteor.userId() + '_status', 'busy')
newMessageNotification = Meteor.user()?.settings?.preferences?.newMessageNotification || 'chime'
sub = ChatSubscription.findOne({ rid: rid }, { fields: { audioNotification: 1 } });
if sub?.audioNotification isnt 'none'
if sub?.audioNotification
$("audio##{sub.audioNotification}")?[0]?.play?()
else if newMessageNotification isnt 'none'
$("audio##{newMessageNotification}")?[0]?.play?()
newRoom: (rid, withSound = true) ->
Tracker.nonreactive ->
newRoomSound = Session.get('newRoomSound')
if newRoomSound?
newRoomSound = _.union newRoomSound, rid
else
newRoomSound = [rid]
Session.set('newRoomSound', newRoomSound)
# $('.link-room-' + rid).addClass('new-room-highlight')
removeRoomNotification: (rid) ->
Tracker.nonreactive ->
Session.set('newRoomSound', [])
$('.link-room-' + rid).removeClass('new-room-highlight')
Tracker.autorun ->
newRoomNotification = Meteor.user()?.settings?.preferences?.newRoomNotification || 'door'
if Session.get('newRoomSound')?.length > 0
Tracker.nonreactive ->
if not Session.equals('user_' + Meteor.userId() + '_status', 'busy') and newRoomNotification isnt 'none'
$("audio##{newRoomNotification}")?[0]?.play?()
else
$("audio##{newRoomNotification}")?[0]?.pause?()
$("audio##{newRoomNotification}")?[0]?.currentTime = 0

@ -81,7 +81,7 @@ const KonchatNotification = {
const user = Meteor.user();
const newMessageNotification = user && user.settings && user.settings.preferences && user.settings.preferences.newMessageNotification || 'chime';
const sub = ChatSubscription.findOne({ rid }, { fields: { audioNotification: 1 } });
if ((sub && sub.audioNotification) !== 'none') {
if (sub && sub.audioNotification !== 'none') {
if (sub && sub.audioNotification) {
const [audio] = $(`audio#${ sub.audioNotification }`);
return audio && audio.play && audio.play();
@ -93,8 +93,7 @@ const KonchatNotification = {
}
},
newRoom(rid, withSound) {
if (withSound == null) { withSound = true; }
newRoom(rid/*, withSound = true*/) {
Tracker.nonreactive(function() {
let newRoomSound = Session.get('newRoomSound');
if (newRoomSound != null) {
@ -119,7 +118,7 @@ const KonchatNotification = {
Tracker.autorun(function() {
const user = Meteor.user();
const newRoomNotification = user && user.settings && user.settings.preferences && user.settings.preferences.newRoomNotification || 'door';
if ((Session.get('newRoomSound') || []) > 0) {
if ((Session.get('newRoomSound') || []).length > 0) {
Tracker.nonreactive(function() {
if (!Session.equals(`user_${ Meteor.userId() }_status`, 'busy') && newRoomNotification !== 'none') {
const [audio] = $(`audio#${ newRoomNotification }`);

@ -1,179 +0,0 @@
### DEFINITIONS
- If window loses focus user needs to scroll or click/touch some place
- On hit ESC enable read, force read of current room and remove unread mark
- When user change room disable read until user interaction
- Only read if mark of *first-unread* is visible for user or if flag *force* was passed
- Always read the opened room
- The default method *read* has a delay of 2000ms to prevent multiple reads and to user be able to see the mark
###
# Meteor.startup ->
# window.addEventListener 'focus', ->
# readMessage.refreshUnreadMark(undefined, true)
@readMessage = new class
debug: false
callbacks: []
constructor: ->
@canReadMessage = false
readNow: (force=false) ->
console.log '--------------' if @debug
console.log 'readMessage -> readNow init process force:', force if @debug
self = @
self.refreshUnreadMark()
if force isnt true and @canReadMessage is false
console.log 'readMessage -> readNow canceled by canReadMessage: false' if @debug
return
rid = Session.get 'openedRoom'
if not rid?
console.log 'readMessage -> readNow canceled, no rid informed' if @debug
return
if force is true
console.log 'readMessage -> readNow via force rid:', rid if @debug
return Meteor.call 'readMessages', rid, ->
RoomHistoryManager.getRoom(rid).unreadNotLoaded.set 0
self.refreshUnreadMark()
self.fireRead rid
subscription = ChatSubscription.findOne rid: rid
if not subscription?
console.log 'readMessage -> readNow canceled, no subscription found for rid:', rid if @debug
return
if subscription.alert is false and subscription.unread is 0
console.log 'readMessage -> readNow canceled, alert', subscription.alert, 'and unread', subscription.unread if @debug
return
room = RoomManager.getOpenedRoomByRid rid
if not room?
console.log 'readMessage -> readNow canceled, no room found for typeName:', subscription.t + subscription.name if @debug
return
# Only read messages if user saw the first unread message
unreadMark = $('.message.first-unread')
if unreadMark.length > 0
position = unreadMark.position()
visible = position?.top >= 0
if not visible and room.unreadSince.get()?
console.log 'readMessage -> readNow canceled, unread mark visible:', visible, 'unread since exists', room.unreadSince.get()? if @debug
return
console.log 'readMessage -> readNow rid:', rid if @debug
Meteor.call 'readMessages', rid, ->
RoomHistoryManager.getRoom(rid).unreadNotLoaded.set 0
self.refreshUnreadMark()
self.fireRead rid
read: _.debounce (force) ->
@readNow(force)
, 1000
disable: ->
@canReadMessage = false
enable: ->
@canReadMessage = document.hasFocus()
isEnable: ->
return @canReadMessage is true
onRead: (cb) ->
@callbacks.push cb
fireRead: (rid) ->
for cb in @callbacks
cb(rid)
refreshUnreadMark: (rid, force) ->
self = @
rid ?= Session.get 'openedRoom'
if not rid?
return
subscription = ChatSubscription.findOne rid: rid, {reactive: false}
if not subscription?
return
room = RoomManager.openedRooms[subscription.t + subscription.name]
if not room?
return
$roomDom = $(room.dom)
$roomDom.find('.message.first-unread').addClass('first-unread-opaque')
if not subscription.alert and subscription.unread is 0
room.unreadSince.set undefined
return
if not force? and subscription.rid is Session.get('openedRoom') and document.hasFocus()
return
$roomDom.find('.message.first-unread').removeClass('first-unread').removeClass('first-unread-opaque')
lastReadRecord = ChatMessage.findOne
rid: subscription.rid
ts:
$lt: subscription.ls
# 'u._id':
# $ne: Meteor.userId()
,
sort:
ts: -1
if not lastReadRecord? and RoomHistoryManager.getRoom(room.rid).unreadNotLoaded.get() is 0
lastReadRecord =
ts: new Date(0)
if lastReadRecord? or RoomHistoryManager.getRoom(room.rid).unreadNotLoaded.get() > 0
room.unreadSince.set subscription.ls
else
room.unreadSince.set undefined
if lastReadRecord?
firstUnreadRecord = ChatMessage.findOne
rid: subscription.rid
ts:
$gt: lastReadRecord.ts
'u._id':
$ne: Meteor.userId()
,
sort:
ts: 1
if firstUnreadRecord?
room.unreadFirstId = firstUnreadRecord._id
$roomDom.find('.message#'+firstUnreadRecord._id).addClass('first-unread')
Meteor.startup ->
$(window).on 'blur', ->
readMessage.disable()
$(window).on 'focus', ->
readMessage.enable()
readMessage.read()
$(window).on 'click', (e) ->
readMessage.enable()
readMessage.read()
$(window).on 'touchend', (e) ->
readMessage.enable()
readMessage.read()
$(window).on 'keyup', (e) ->
key = e.which
if key is 27
readMessage.enable()
readMessage.readNow(true)
$('.message.first-unread').removeClass('first-unread')

@ -1,41 +0,0 @@
@AudioRecorder = new class
start: (cb) ->
window.AudioContext = window.AudioContext or window.webkitAudioContext
navigator.getUserMedia = navigator.getUserMedia or navigator.webkitGetUserMedia
window.URL = window.URL or window.webkitURL
window.audioContext = new AudioContext
ok = (stream) =>
@startUserMedia(stream)
cb?.call(@)
if not navigator.getUserMedia?
return cb false
navigator.getUserMedia {audio: true}, ok, (e) ->
console.log('No live audio input: ' + e)
startUserMedia: (stream) ->
@stream = stream
input = window.audioContext.createMediaStreamSource(stream)
@recorder = new Recorder(input, {workerPath: '/recorderWorker.js'})
@recorder.record()
stop: (cb) ->
@recorder.stop()
if cb?
@getBlob cb
@stream.getAudioTracks()[0].stop()
@recorder.clear()
window.audioContext.close()
delete window.audioContext
delete @recorder
delete @stream
getBlob: (cb) ->
@recorder.exportWAV cb

@ -1,78 +0,0 @@
@VideoRecorder = new class
started: false
cameraStarted: new ReactiveVar false
recording: new ReactiveVar false
recordingAvailable: new ReactiveVar false
start: (videoel, cb) ->
navigator.getUserMedia = navigator.getUserMedia or navigator.webkitGetUserMedia or navigator.mozGetUserMedia
window.URL = window.URL or window.webkitURL
@videoel = videoel
ok = (stream) =>
@startUserMedia(stream)
cb?.call(@)
if not navigator.getUserMedia?
return cb false
navigator.getUserMedia {audio: true, video: true}, ok, (e) ->
console.log('No live video input: ' + e)
record: ->
@chunks = []
if not @stream?
return
@mediaRecorder = new MediaRecorder(@stream)
@mediaRecorder.stream = @stream
@mediaRecorder.mimeType = 'video/webm'
@mediaRecorder.ondataavailable = (blobev) =>
@chunks.push(blobev.data)
if not @recordingAvailable.get()
@recordingAvailable.set true
@mediaRecorder.start()
@recording.set true
startUserMedia: (stream) ->
@stream = stream
@videoel.src = URL.createObjectURL(stream)
@videoel.onloadedmetadata = (e) =>
@videoel.play()
@started = true
@cameraStarted.set true
stop: (cb) ->
if @started
@stopRecording()
if @stream?
vtracks = @stream.getVideoTracks()
for vtrack in vtracks
vtrack.stop()
atracks = @stream.getAudioTracks()
for atrack in atracks
atrack.stop()
if @videoel?
@videoel.pause
@videoel.src = ''
@started = false
@cameraStarted.set false
@recordingAvailable.set false
if cb? and @chunks?
blob = new Blob(@chunks, { 'type' : 'video/webm' })
cb(blob)
delete @recorder
delete @stream
delete @videoel
stopRecording: ->
if @started and @recording and @mediaRecorder?
@mediaRecorder.stop()
@recording.set false
delete @mediaRecorder

@ -16,7 +16,7 @@ this.VideoRecorder = new class {
return (cb != null ? cb.call(this) : undefined);
};
if ((navigator.getUserMedia == null)) {
if (navigator.getUserMedia == null) {
return cb(false);
}
@ -25,7 +25,7 @@ this.VideoRecorder = new class {
record() {
this.chunks = [];
if ((this.stream == null)) {
if (this.stream == null) {
return;
}
this.mediaRecorder = new MediaRecorder(this.stream);
@ -56,7 +56,7 @@ this.VideoRecorder = new class {
if (this.started) {
this.stopRecording();
if (this.stream != null) {
if (this.stream) {
const vtracks = this.stream.getVideoTracks();
for (const vtrack of Array.from(vtracks)) {
vtrack.stop();
@ -68,7 +68,7 @@ this.VideoRecorder = new class {
}
}
if (this.videoel != null) {
if (this.videoel) {
this.videoel.pause;
this.videoel.src = '';
}
@ -77,7 +77,7 @@ this.VideoRecorder = new class {
this.cameraStarted.set(false);
this.recordingAvailable.set(false);
if ((cb != null) && (this.chunks != null)) {
if (cb && this.chunks) {
const blob = new Blob(this.chunks, { 'type' : 'video/webm' });
cb(blob);
}
@ -89,7 +89,7 @@ this.VideoRecorder = new class {
}
stopRecording() {
if (this.started && this.recording && this.mediaRecorder != null) {
if (this.started && this.recording && this.mediaRecorder) {
this.mediaRecorder.stop();
this.recording.set(false);
return delete this.mediaRecorder;

@ -35,7 +35,7 @@ Package.onUse(function(api) {
api.addFiles('client/lib/accountBox.js', 'client');
api.addFiles('client/lib/accounts.js', 'client');
api.addFiles('client/lib/avatar.js', 'client');
api.addFiles('client/lib/chatMessages.coffee', 'client');
api.addFiles('client/lib/chatMessages.js', 'client');
api.addFiles('client/lib/collections.js', 'client');
api.addFiles('client/lib/customEventPolyfill.js', 'client');
api.addFiles('client/lib/fileUpload.coffee', 'client');

Loading…
Cancel
Save