parent
adf103d3f3
commit
dae37ecb2b
@ -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() |
||||
@ -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 |
||||
@ -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,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 |
||||
} |
||||
@ -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 |
||||
@ -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 |
||||
Loading…
Reference in new issue