pull/6914/head
Guilherme Gazzo 8 years ago
parent 6ac7193519
commit b3f07535b9
  1. 254
      packages/rocketchat-ui-message/client/message.coffee
  2. 142
      packages/rocketchat-ui-message/client/message.js
  3. 391
      packages/rocketchat-ui-message/client/messageBox.coffee
  4. 6
      packages/rocketchat-ui-message/client/messageBox.js
  5. 282
      packages/rocketchat-ui-message/client/popup/messagePopup.coffee
  6. 235
      packages/rocketchat-ui-message/client/popup/messagePopupConfig.coffee
  7. 4
      packages/rocketchat-ui-message/client/popup/messagePopupEmoji.coffee
  8. 3
      packages/rocketchat-ui-message/package.js

@ -1,254 +0,0 @@
import moment from 'moment'
Template.message.helpers
encodeURI: (text) ->
return encodeURI(text)
isBot: ->
return 'bot' if this.bot?
roleTags: ->
if not RocketChat.settings.get('UI_DisplayRoles') or Meteor.user()?.settings?.preferences?.hideRoles
return []
roles = _.union(UserRoles.findOne(this.u?._id)?.roles, RoomRoles.findOne({'u._id': this.u?._id, rid: this.rid })?.roles)
return RocketChat.models.Roles.find({ _id: { $in: roles }, description: { $exists: 1, $ne: '' } }, { fields: { description: 1 } })
isGroupable: ->
return 'false' if this.groupable is false
isSequential: ->
return 'sequential' if this.groupable isnt false
avatarFromUsername: ->
if this.avatar? and this.avatar[0] is '@'
return this.avatar.replace(/^@/, '')
getEmoji: (emoji) ->
return renderEmoji emoji
getName: ->
if this.alias
return this.alias
if RocketChat.settings.get('UI_Use_Real_Name') and this.u?.name
return this.u.name
return this.u?.username
showUsername: ->
return this.alias or (RocketChat.settings.get('UI_Use_Real_Name') and this.u?.name)
own: ->
return 'own' if this.u?._id is Meteor.userId()
timestamp: ->
return +this.ts
chatops: ->
return 'chatops-message' if this.u?.username is RocketChat.settings.get('Chatops_Username')
time: ->
return moment(this.ts).format(RocketChat.settings.get('Message_TimeFormat'))
date: ->
return moment(this.ts).format(RocketChat.settings.get('Message_DateFormat'))
isTemp: ->
if @temp is true
return 'temp'
body: ->
return Template.instance().body
system: (returnClass) ->
if RocketChat.MessageTypes.isSystemMessage(this)
if returnClass
return 'color-info-font-color'
return 'system'
showTranslated: ->
if RocketChat.settings.get('AutoTranslate_Enabled') and this.u?._id isnt Meteor.userId() and !RocketChat.MessageTypes.isSystemMessage(this)
subscription = RocketChat.models.Subscriptions.findOne({ rid: this.rid, 'u._id': Meteor.userId() }, { fields: { autoTranslate: 1, autoTranslateLanguage: 1 } });
language = RocketChat.AutoTranslate.getLanguage(this.rid);
return this.autoTranslateFetching || (subscription?.autoTranslate isnt this.autoTranslateShowInverse && this.translations && this.translations[language]) # || _.find(this.attachments, (attachment) -> attachment.translations && attachment.translations[language] && attachment.author_name isnt Meteor.user().username )
edited: ->
return Template.instance().wasEdited
editTime: ->
if Template.instance().wasEdited
return moment(@editedAt).format(RocketChat.settings.get('Message_DateFormat') + ' ' + RocketChat.settings.get('Message_TimeFormat'))
editedBy: ->
return "" unless Template.instance().wasEdited
# try to return the username of the editor,
# otherwise a special "?" character that will be
# rendered as a special avatar
return @editedBy?.username or "?"
canEdit: ->
hasPermission = RocketChat.authz.hasAtLeastOnePermission('edit-message', this.rid)
isEditAllowed = RocketChat.settings.get 'Message_AllowEditing'
editOwn = this.u?._id is Meteor.userId()
return unless hasPermission or (isEditAllowed and editOwn)
blockEditInMinutes = RocketChat.settings.get 'Message_AllowEditing_BlockEditInMinutes'
if blockEditInMinutes? and blockEditInMinutes isnt 0
msgTs = moment(this.ts) if this.ts?
currentTsDiff = moment().diff(msgTs, 'minutes') if msgTs?
return currentTsDiff < blockEditInMinutes
else
return true
canDelete: ->
hasPermission = RocketChat.authz.hasAtLeastOnePermission('delete-message', this.rid )
isDeleteAllowed = RocketChat.settings.get('Message_AllowDeleting')
deleteOwn = this.u?._id is Meteor.userId()
return unless hasPermission or (isDeleteAllowed and deleteOwn)
blockDeleteInMinutes = RocketChat.settings.get 'Message_AllowDeleting_BlockDeleteInMinutes'
if blockDeleteInMinutes? and blockDeleteInMinutes isnt 0
msgTs = moment(this.ts) if this.ts?
currentTsDiff = moment().diff(msgTs, 'minutes') if msgTs?
return currentTsDiff < blockDeleteInMinutes
else
return true
showEditedStatus: ->
return RocketChat.settings.get 'Message_ShowEditedStatus'
label: ->
if @i18nLabel
return t(@i18nLabel)
else if @label
return @label
hasOembed: ->
return false unless this.urls?.length > 0 and Template.oembedBaseWidget? and RocketChat.settings.get 'API_Embed'
return false unless this.u?.username not in RocketChat.settings.get('API_EmbedDisabledFor')?.split(',').map (username) -> username.trim()
return true
reactions: ->
msgReactions = []
userUsername = Meteor.user()?.username
for emoji, reaction of @reactions
total = reaction.usernames.length
usernames = '@' + reaction.usernames.slice(0, 15).join(', @')
usernames = usernames.replace('@'+userUsername, t('You').toLowerCase())
if total > 15
usernames = usernames + ' ' + t('And_more', { length: total - 15 }).toLowerCase()
else
usernames = usernames.replace(/,([^,]+)$/, ' '+t('and')+'$1')
if usernames[0] isnt '@'
usernames = usernames[0].toUpperCase() + usernames.substr(1)
msgReactions.push
emoji: emoji
count: reaction.usernames.length
usernames: usernames
reaction: ' ' + t('Reacted_with').toLowerCase() + ' ' + emoji
userReacted: reaction.usernames.indexOf(userUsername) > -1
return msgReactions
markUserReaction: (reaction) ->
if reaction.userReacted
return {
class: 'selected'
}
hideReactions: ->
return 'hidden' if _.isEmpty(@reactions)
actionLinks: ->
# remove 'method_id' and 'params' properties
return _.map(@actionLinks, (actionLink, key) -> _.extend({ id: key }, _.omit(actionLink, 'method_id', 'params')))
hideActionLinks: ->
return 'hidden' if _.isEmpty(@actionLinks)
injectIndex: (data, index) ->
data.index = index
return
hideCog: ->
subscription = RocketChat.models.Subscriptions.findOne({ rid: this.rid });
return 'hidden' if not subscription?
hideUsernames: ->
prefs = Meteor.user()?.settings?.preferences
return if prefs?.hideUsernames
Template.message.onCreated ->
msg = Template.currentData()
@wasEdited = msg.editedAt? and not RocketChat.MessageTypes.isSystemMessage(msg)
@body = do ->
isSystemMessage = RocketChat.MessageTypes.isSystemMessage(msg)
messageType = RocketChat.MessageTypes.getType(msg)
if messageType?.render?
msg = messageType.render(msg)
else if messageType?.template?
# render template
else if messageType?.message?
if messageType.data?(msg)?
msg = TAPi18n.__(messageType.message, messageType.data(msg))
else
msg = TAPi18n.__(messageType.message)
else
if msg.u?.username is RocketChat.settings.get('Chatops_Username')
msg.html = msg.msg
msg = RocketChat.callbacks.run 'renderMentions', msg
# console.log JSON.stringify message
msg = msg.html
else
msg = renderMessageBody msg
if isSystemMessage
msg.html = RocketChat.Markdown.parse msg.html
return msg
Template.message.onViewRendered = (context) ->
view = this
this._domrange.onAttached (domRange) ->
currentNode = domRange.lastNode()
currentDataset = currentNode.dataset
previousNode = currentNode.previousElementSibling
nextNode = currentNode.nextElementSibling
$currentNode = $(currentNode)
$nextNode = $(nextNode)
unless previousNode?
$currentNode.addClass('new-day').removeClass('sequential')
else if previousNode?.dataset?
previousDataset = previousNode.dataset
previousMessageDate = new Date(parseInt(previousDataset.timestamp))
currentMessageDate = new Date(parseInt(currentDataset.timestamp))
if previousMessageDate.toDateString() isnt currentMessageDate.toDateString()
$currentNode.addClass('new-day').removeClass('sequential')
else
$currentNode.removeClass('new-day')
if previousDataset.groupable is 'false' or currentDataset.groupable is 'false'
$currentNode.removeClass('sequential')
else
if previousDataset.username isnt currentDataset.username or parseInt(currentDataset.timestamp) - parseInt(previousDataset.timestamp) > RocketChat.settings.get('Message_GroupingPeriod') * 1000
$currentNode.removeClass('sequential')
else if not $currentNode.hasClass 'new-day'
$currentNode.addClass('sequential')
if nextNode?.dataset?
nextDataset = nextNode.dataset
if nextDataset.date isnt currentDataset.date
$nextNode.addClass('new-day').removeClass('sequential')
else
$nextNode.removeClass('new-day')
if nextDataset.groupable isnt 'false'
if nextDataset.username isnt currentDataset.username or parseInt(nextDataset.timestamp) - parseInt(currentDataset.timestamp) > RocketChat.settings.get('Message_GroupingPeriod') * 1000
$nextNode.removeClass('sequential')
else if not $nextNode.hasClass 'new-day'
$nextNode.addClass('sequential')
if not nextNode?
templateInstance = if $('#chat-window-' + context.rid)[0] then Blaze.getView($('#chat-window-' + context.rid)[0])?.templateInstance() else null
if currentNode.classList.contains('own') is true
templateInstance?.atBottom = true
else
if templateInstance?.firstNode && templateInstance?.atBottom is false
newMessage = templateInstance?.find(".new-message")
newMessage?.className = "new-message background-primary-action-color color-content-background-color "

@ -1,6 +1,10 @@
/* globals renderEmoji renderMessageBody*/
import moment from 'moment';
Template.message.helpers({
encodeURI(text) {
return encodeURI(text);
},
isBot() {
if (this.bot != null) {
return 'bot';
@ -103,8 +107,8 @@ Template.message.helpers({
}
},
showTranslated() {
if (RocketChat.settings.get('AutoTranslate_Enabled') && ((ref = this.u) != null ? ref._id : void 0) !== Meteor.userId() && !RocketChat.MessageTypes.isSystemMessage(this)) {
subscription = RocketChat.models.Subscriptions.findOne({
if (RocketChat.settings.get('AutoTranslate_Enabled') && this.u && this.u._id !== Meteor.userId() && !RocketChat.MessageTypes.isSystemMessage(this)) {
const subscription = RocketChat.models.Subscriptions.findOne({
rid: this.rid,
'u._id': Meteor.userId()
}, {
@ -114,7 +118,7 @@ Template.message.helpers({
}
});
const language = RocketChat.AutoTranslate.getLanguage(this.rid);
return this.autoTranslateFetching || ((subscription != null ? subscription.autoTranslate : void 0) !== this.autoTranslateShowInverse && this.translations && this.translations[language]);
return this.autoTranslateFetching || subscription && subscription.autoTranslate !== this.autoTranslateShowInverse && this.translations && this.translations[language];
}
},
edited() {
@ -137,15 +141,17 @@ Template.message.helpers({
canEdit() {
const hasPermission = RocketChat.authz.hasAtLeastOnePermission('edit-message', this.rid);
const isEditAllowed = RocketChat.settings.get('Message_AllowEditing');
const editOwn = ((ref = this.u) != null ? ref._id : void 0) === Meteor.userId();
const editOwn = this.u && this.u._id === Meteor.userId();
if (!(hasPermission || (isEditAllowed && editOwn))) {
return;
}
const blockEditInMinutes = RocketChat.settings.get('Message_AllowEditing_BlockEditInMinutes');
if ((blockEditInMinutes != null) && blockEditInMinutes !== 0) {
if (blockEditInMinutes) {
let msgTs;
if (this.ts != null) {
msgTs = moment(this.ts);
}
let currentTsDiff;
if (msgTs != null) {
currentTsDiff = moment().diff(msgTs, 'minutes');
}
@ -155,18 +161,19 @@ Template.message.helpers({
}
},
canDelete() {
let blockDeleteInMinutes, currentTsDiff, deleteOwn, hasPermission, isDeleteAllowed, msgTs, ref;
hasPermission = RocketChat.authz.hasAtLeastOnePermission('delete-message', this.rid);
isDeleteAllowed = RocketChat.settings.get('Message_AllowDeleting');
deleteOwn = ((ref = this.u) != null ? ref._id : void 0) === Meteor.userId();
const hasPermission = RocketChat.authz.hasAtLeastOnePermission('delete-message', this.rid);
const isDeleteAllowed = RocketChat.settings.get('Message_AllowDeleting');
const deleteOwn = this.u && this.u._id === Meteor.userId();
if (!(hasPermission || (isDeleteAllowed && deleteOwn))) {
return;
}
blockDeleteInMinutes = RocketChat.settings.get('Message_AllowDeleting_BlockDeleteInMinutes');
if ((blockDeleteInMinutes != null) && blockDeleteInMinutes !== 0) {
const blockDeleteInMinutes = RocketChat.settings.get('Message_AllowDeleting_BlockDeleteInMinutes');
if (blockDeleteInMinutes) {
let msgTs;
if (this.ts != null) {
msgTs = moment(this.ts);
}
let currentTsDiff;
if (msgTs != null) {
currentTsDiff = moment().diff(msgTs, 'minutes');
}
@ -186,27 +193,20 @@ Template.message.helpers({
}
},
hasOembed() {
let ref, ref1, ref2, ref3;
if (!(((ref = this.urls) != null ? ref.length : void 0) > 0 && (Template.oembedBaseWidget != null) && RocketChat.settings.get('API_Embed'))) {
if (!(this.urls && this.urls.length > 0 && Template.oembedBaseWidget != null && RocketChat.settings.get('API_Embed'))) {
return false;
}
if (ref1 = (ref2 = this.u) != null ? ref2.username : void 0, indexOf.call((ref3 = RocketChat.settings.get('API_EmbedDisabledFor')) != null ? ref3.split(',').map(function(username) {
return username.trim();
}) : void 0, ref1) >= 0) {
if (!(RocketChat.settings.get('API_EmbedDisabledFor')||'').split(',').map(username => username.trim()).includes(this.u && this.u.username)) {
return false;
}
return true;
},
reactions() {
let emoji, msgReactions, reaction, ref, total, userUsername, usernames;
msgReactions = [];
userUsername = Meteor.user().username;
ref = this.reactions;
for (emoji in ref) {
reaction = ref[emoji];
total = reaction.usernames.length;
usernames = `@${ reaction.usernames.slice(0, 15).join(', @') }`;
usernames = usernames.replace(`@${ userUsername }`, t('You').toLowerCase());
const userUsername = Meteor.user().username;
return Object.keys(this.reactions||{}).map(emoji => {
const reaction = this.reactions[emoji];
const total = reaction.usernames.length;
let usernames = `@${ reaction.usernames.slice(0, 15).join(', @') }`.replace(`@${ userUsername }`, t('You').toLowerCase());
if (total > 15) {
usernames = `${ usernames } ${ t('And_more', {
length: total - 15
@ -217,15 +217,14 @@ Template.message.helpers({
if (usernames[0] !== '@') {
usernames = usernames[0].toUpperCase() + usernames.substr(1);
}
msgReactions.push({
return {
emoji,
count: reaction.usernames.length,
usernames,
reaction: ` ${ t('Reacted_with').toLowerCase() } ${ emoji }`,
userReacted: reaction.usernames.indexOf(userUsername) > -1
});
}
return msgReactions;
};
});
},
markUserReaction(reaction) {
if (reaction.userReacted) {
@ -256,73 +255,63 @@ Template.message.helpers({
data.index = index;
},
hideCog() {
let subscription;
subscription = RocketChat.models.Subscriptions.findOne({
const subscription = RocketChat.models.Subscriptions.findOne({
rid: this.rid
});
if (subscription == null) {
return 'hidden';
}
},
hideUsernames() {
let prefs, ref, ref1;
prefs = (ref = Meteor.user()) != null ? (ref1 = ref.settings) != null ? ref1.preferences : void 0 : void 0;
if (prefs != null ? prefs.hideUsernames : void 0) {
}
}
});
Template.message.onCreated(function() {
let msg;
msg = Template.currentData();
let msg = Template.currentData();
this.wasEdited = (msg.editedAt != null) && !RocketChat.MessageTypes.isSystemMessage(msg);
return this.body = (function() {
let isSystemMessage, messageType, ref;
isSystemMessage = RocketChat.MessageTypes.isSystemMessage(msg);
messageType = RocketChat.MessageTypes.getType(msg);
if ((messageType != null ? messageType.render : void 0) != null) {
msg = messageType.render(msg);
} else if ((messageType != null ? messageType.template : void 0) != null) {
} else if ((messageType != null ? messageType.message : void 0) != null) {
if ((typeof messageType.data === 'function' ? messageType.data(msg) : void 0) != null) {
return this.body = (() => {
const isSystemMessage = RocketChat.MessageTypes.isSystemMessage(msg);
const messageType = RocketChat.MessageTypes.getType(msg)||{};
if (messageType.render) {
msg = messageType.render(msg);
} else if (messageType.template) {
// render template
} else if (messageType.message) {
if (typeof messageType.data === 'function' && messageType.data(msg)) {
msg = TAPi18n.__(messageType.message, messageType.data(msg));
} else {
msg = TAPi18n.__(messageType.message);
}
} else if (((ref = msg.u) != null ? ref.username : void 0) === RocketChat.settings.get('Chatops_Username')) {
} else if (msg.u && msg.u.username === RocketChat.settings.get('Chatops_Username')) {
msg.html = msg.msg;
msg = RocketChat.callbacks.run('renderMentions', msg);
// console.log JSON.stringify message
msg = msg.html;
} else {
msg = renderMessageBody(msg);
}
if (isSystemMessage) {
return RocketChat.Markdown(msg);
} else {
return msg;
msg.html = RocketChat.Markdown.parse(msg.html);
}
}());
return msg;
})();
});
Template.message.onViewRendered = function(context) {
let view;
view = this;
return this._domrange.onAttached(function(domRange) {
let $currentNode, $nextNode, currentDataset, currentMessageDate, currentNode, newMessage, nextDataset, nextNode, previousDataset, previousMessageDate, previousNode, ref, templateInstance;
currentNode = domRange.lastNode();
currentDataset = currentNode.dataset;
previousNode = currentNode.previousElementSibling;
nextNode = currentNode.nextElementSibling;
$currentNode = $(currentNode);
$nextNode = $(nextNode);
const currentNode = domRange.lastNode();
const currentDataset = currentNode.dataset;
const previousNode = currentNode.previousElementSibling;
const nextNode = currentNode.nextElementSibling;
const $currentNode = $(currentNode);
const $nextNode = $(nextNode);
if (previousNode == null) {
$currentNode.addClass('new-day').removeClass('sequential');
} else if ((previousNode != null ? previousNode.dataset : void 0) != null) {
previousDataset = previousNode.dataset;
previousMessageDate = new Date(parseInt(previousDataset.timestamp));
currentMessageDate = new Date(parseInt(currentDataset.timestamp));
} else if (previousNode.dataset) {
const previousDataset = previousNode.dataset;
const previousMessageDate = new Date(parseInt(previousDataset.timestamp));
const currentMessageDate = new Date(parseInt(currentDataset.timestamp));
if (previousMessageDate.toDateString() !== currentMessageDate.toDateString()) {
$currentNode.addClass('new-day').removeClass('sequential');
} else {
@ -336,8 +325,8 @@ Template.message.onViewRendered = function(context) {
$currentNode.addClass('sequential');
}
}
if ((nextNode != null ? nextNode.dataset : void 0) != null) {
nextDataset = nextNode.dataset;
if (nextNode && nextNode.dataset) {
const nextDataset = nextNode.dataset;
if (nextDataset.date !== currentDataset.date) {
$nextNode.addClass('new-day').removeClass('sequential');
} else {
@ -352,12 +341,17 @@ Template.message.onViewRendered = function(context) {
}
}
if (nextNode == null) {
templateInstance = $(`#chat-window-${ context.rid }`)[0] ? (ref = Blaze.getView($(`#chat-window-${ context.rid }`)[0])) != null ? ref.templateInstance() : void 0 : null;
const [el] = $(`#chat-window-${ context.rid }`);
const view = el && Blaze.getView(el);
const templateInstance = view && view.templateInstance();
if (!templateInstance) {
return;
}
if (currentNode.classList.contains('own') === true) {
return templateInstance != null ? templateInstance.atBottom = true : void 0;
} else if ((templateInstance != null ? templateInstance.firstNode : void 0) && (templateInstance != null ? templateInstance.atBottom : void 0) === false) {
newMessage = templateInstance != null ? templateInstance.find('.new-message') : void 0;
return newMessage != null ? newMessage.className = 'new-message background-primary-action-color color-content-background-color ' : void 0;
return (templateInstance.atBottom = true);
} else if (templateInstance.firstNode && templateInstance.atBottom === false) {
const newMessage = templateInstance.find('.new-message');
return newMessage && (newMessage.className = 'new-message background-primary-action-color color-content-background-color ');
}
}
});

@ -1,391 +0,0 @@
import toastr from 'toastr'
import mime from 'mime-type/with-db'
import moment from 'moment'
import {VRecDialog} from 'meteor/rocketchat:ui-vrecord'
katexSyntax = ->
if RocketChat.katex.katex_enabled()
return "$$KaTeX$$" if RocketChat.katex.dollar_syntax_enabled()
return "\\[KaTeX\\]" if RocketChat.katex.parenthesis_syntax_enabled()
return false
Template.messageBox.helpers
roomName: ->
roomData = Session.get('roomData' + this._id)
return '' unless roomData
if roomData.t is 'd'
return ChatSubscription.findOne({ rid: this._id }, { fields: { name: 1 } })?.name
else
return roomData.name
showMarkdown: ->
return RocketChat.Markdown
showMarkdownCode: ->
return RocketChat.MarkdownCode
showKatex: ->
return RocketChat.katex
katexSyntax: ->
return katexSyntax()
showFormattingTips: ->
return RocketChat.settings.get('Message_ShowFormattingTips') and (RocketChat.Markdown or RocketChat.MarkdownCode or katexSyntax())
canJoin: ->
return Meteor.userId()? and RocketChat.roomTypes.verifyShowJoinLink @_id
joinCodeRequired: ->
return Session.get('roomData' + this._id)?.joinCodeRequired
subscribed: ->
return RocketChat.roomTypes.verifyCanSendMessage @_id
allowedToSend: ->
if RocketChat.roomTypes.readOnly @_id, Meteor.user()
return false
if RocketChat.roomTypes.archived @_id
return false
roomData = Session.get('roomData' + this._id)
if roomData?.t is 'd'
subscription = ChatSubscription.findOne({ rid: this._id }, { fields: { archived: 1, blocked: 1, blocker: 1 } })
if subscription and (subscription.archived or subscription.blocked or subscription.blocker)
return false
return true
isBlockedOrBlocker: ->
roomData = Session.get('roomData' + this._id)
if roomData?.t is 'd'
subscription = ChatSubscription.findOne({ rid: this._id }, { fields: { blocked: 1, blocker: 1 } })
if subscription and (subscription.blocked or subscription.blocker)
return true
getPopupConfig: ->
template = Template.instance()
return {
getInput: ->
return template.find('.input-message')
}
usersTyping: ->
users = MsgTyping.get @_id
if users.length is 0
return
if users.length is 1
return {
multi: false
selfTyping: MsgTyping.selfTyping.get()
users: users[0]
}
# usernames = _.map messages, (message) -> return message.u.username
last = users.pop()
if users.length > 4
last = t('others')
# else
usernames = users.join(', ')
usernames = [usernames, last]
return {
multi: true
selfTyping: MsgTyping.selfTyping.get()
users: usernames.join " #{t 'and'} "
}
groupAttachHidden: ->
return 'hidden' if RocketChat.settings.get('Message_Attachments_GroupAttach')
fileUploadEnabled: ->
return RocketChat.settings.get('FileUpload_Enabled')
fileUploadAllowedMediaTypes: ->
return RocketChat.settings.get('FileUpload_MediaTypeWhiteList')
showFileUpload: ->
if (RocketChat.settings.get('FileUpload_Enabled'))
roomData = Session.get('roomData' + this._id)
if roomData?.t is 'd'
return RocketChat.settings.get('FileUpload_Enabled_Direct')
else
return true
else
return RocketChat.settings.get('FileUpload_Enabled')
showMic: ->
return Template.instance().showMicButton.get()
showVRec: ->
return Template.instance().showVideoRec.get()
showSend: ->
if not Template.instance().isMessageFieldEmpty.get()
return 'show-send'
showLocation: ->
return RocketChat.Geolocation.get() isnt false
notSubscribedTpl: ->
return RocketChat.roomTypes.getNotSubscribedTpl @_id
showSandstorm: ->
return Meteor.settings.public.sandstorm && !Meteor.isCordova
anonymousRead: ->
return not Meteor.userId()? and RocketChat.settings.get('Accounts_AllowAnonymousRead') is true
anonymousWrite: ->
return not Meteor.userId()? and RocketChat.settings.get('Accounts_AllowAnonymousRead') is true and RocketChat.settings.get('Accounts_AllowAnonymousWrite') is true
firefoxPasteUpload = (fn) ->
user = navigator.userAgent.match(/Firefox\/(\d+)\.\d/)
if !user or user[1] > 49
return fn
return (event, instance) ->
if (event.originalEvent.ctrlKey or event.originalEvent.metaKey) and (event.keyCode == 86)
textarea = instance.find("textarea")
selectionStart = textarea.selectionStart
selectionEnd = textarea.selectionEnd
contentEditableDiv = instance.find('#msg_contenteditable')
contentEditableDiv.focus()
Meteor.setTimeout ->
pastedImg = contentEditableDiv.querySelector 'img'
textareaContent = textarea.value
startContent = textareaContent.substring(0, selectionStart)
endContent = textareaContent.substring(selectionEnd)
restoreSelection = (pastedText) ->
textarea.value = startContent + pastedText + endContent
textarea.selectionStart = selectionStart + pastedText.length
textarea.selectionEnd = textarea.selectionStart
contentEditableDiv.innerHTML = '' if pastedImg
textarea.focus
return if (!pastedImg || contentEditableDiv.innerHTML.length > 0)
[].slice.call(contentEditableDiv.querySelectorAll("br")).forEach (el) ->
contentEditableDiv.replaceChild(new Text("\n") , el)
restoreSelection(contentEditableDiv.innerText)
imageSrc = pastedImg.getAttribute("src")
if imageSrc.match(/^data:image/)
fetch(imageSrc)
.then((img)->
return img.blob())
.then (blob)->
fileUpload [{
file: blob
name: 'Clipboard'
}]
, 150
fn?.apply @, arguments
Template.messageBox.events
'click .join': (event) ->
event.stopPropagation()
event.preventDefault()
Meteor.call 'joinRoom', @_id, Template.instance().$('[name=joinCode]').val(), (err) =>
if err?
toastr.error t(err.reason)
if RocketChat.authz.hasAllPermission('preview-c-room') is false and RoomHistoryManager.getRoom(@_id).loaded is 0
RoomManager.getOpenedRoomByRid(@_id).streamActive = false
RoomManager.getOpenedRoomByRid(@_id).ready = false
RoomHistoryManager.getRoom(@_id).loaded = undefined
RoomManager.computation.invalidate()
'click .register': (event) ->
event.stopPropagation()
event.preventDefault()
Session.set('forceLogin', true)
'click .register-anonymous': (event) ->
event.stopPropagation()
event.preventDefault()
Meteor.call 'registerUser', {}, (error, loginData) ->
if loginData && loginData.token
Meteor.loginWithToken loginData.token
'focus .input-message': (event, instance) ->
KonchatNotification.removeRoomNotification @_id
chatMessages[@_id].input = instance.find('.input-message')
'click .send-button': (event, instance) ->
input = instance.find('.input-message')
chatMessages[@_id].send(@_id, input, =>
# fixes https://github.com/RocketChat/Rocket.Chat/issues/3037
# at this point, the input is cleared and ready for autogrow
input.updateAutogrow()
instance.isMessageFieldEmpty.set(chatMessages[@_id].isEmpty())
)
input.focus()
'keyup .input-message': (event, instance) ->
chatMessages[@_id].keyup(@_id, event, instance)
instance.isMessageFieldEmpty.set(chatMessages[@_id].isEmpty())
'paste .input-message': (e, instance) ->
Meteor.setTimeout ->
input = instance.find('.input-message')
input.updateAutogrow?()
, 50
if not e.originalEvent.clipboardData?
return
items = e.originalEvent.clipboardData.items
files = []
for item in items
if item.kind is 'file' and item.type.indexOf('image/') isnt -1
e.preventDefault()
files.push
file: item.getAsFile()
name: 'Clipboard - ' + moment().format(RocketChat.settings.get('Message_TimeAndDateFormat'))
if files.length
fileUpload files
else
instance.isMessageFieldEmpty.set(false)
'keydown .input-message': firefoxPasteUpload((event, instance) ->
chatMessages[@_id].keydown(@_id, event, Template.instance()))
'input .input-message': (event) ->
chatMessages[@_id].valueChanged(@_id, event, Template.instance())
'propertychange .input-message': (event) ->
if event.originalEvent.propertyName is 'value'
chatMessages[@_id].valueChanged(@_id, event, Template.instance())
"click .editing-commands-cancel > button": (e) ->
chatMessages[@_id].clearEditing()
"click .editing-commands-save > button": (e) ->
chatMessages[@_id].send(@_id, chatMessages[@_id].input)
'change .message-form input[type=file]': (event, template) ->
e = event.originalEvent or event
files = e.target.files
if not files or files.length is 0
files = e.dataTransfer?.files or []
filesToUpload = []
for file in files
# `file.type = mime.lookup(file.name)` does not work.
Object.defineProperty(file, 'type', { value: mime.lookup(file.name) })
filesToUpload.push
file: file
name: file.name
fileUpload filesToUpload
"click .message-buttons.share": (e, t) ->
t.$('.share-items').toggleClass('hidden')
t.$('.message-buttons.share').toggleClass('active')
'click .message-form .message-buttons.location': (event, instance) ->
roomId = @_id
position = RocketChat.Geolocation.get()
latitude = position.coords.latitude
longitude = position.coords.longitude
text = """
<div class="location-preview">
<img style="height: 250px; width: 250px;" src="https://maps.googleapis.com/maps/api/staticmap?zoom=14&size=250x250&markers=color:gray%7Clabel:%7C#{latitude},#{longitude}&key=#{RocketChat.settings.get('MapView_GMapsAPIKey')}" />
</div>
"""
swal
title: t('Share_Location_Title')
text: text
showCancelButton: true
closeOnConfirm: true
closeOnCancel: true
html: true
, (isConfirm) ->
if isConfirm isnt true
return
Meteor.call "sendMessage",
_id: Random.id()
rid: roomId
msg: ""
location:
type: 'Point'
coordinates: [ longitude, latitude ]
'click .message-form .mic': (e, t) ->
AudioRecorder.start ->
t.$('.stop-mic').removeClass('hidden')
t.$('.mic').addClass('hidden')
'click .message-form .video-button': (e, t) ->
if VRecDialog.opened
VRecDialog.close()
else
VRecDialog.open(e.currentTarget)
'click .message-form .stop-mic': (e, t) ->
AudioRecorder.stop (blob) ->
fileUpload [{
file: blob
type: 'audio'
name: TAPi18n.__('Audio record') + '.wav'
}]
t.$('.stop-mic').addClass('hidden')
t.$('.mic').removeClass('hidden')
'click .sandstorm-offer': (e, t) ->
roomId = @_id
RocketChat.Sandstorm.request "uiView", (err, data) =>
if err or !data.token
console.error err
return
Meteor.call "sandstormClaimRequest", data.token, data.descriptor, (err, viewInfo) =>
if err
console.error err
return
Meteor.call "sendMessage", {
_id: Random.id()
rid: roomId
msg: ""
urls: [{ url: "grain://sandstorm", sandstormViewInfo: viewInfo }]
}
Template.messageBox.onCreated ->
@isMessageFieldEmpty = new ReactiveVar true
@showMicButton = new ReactiveVar false
@showVideoRec = new ReactiveVar false
@autorun =>
videoRegex = /video\/webm|video\/\*/i
videoEnabled = !RocketChat.settings.get("FileUpload_MediaTypeWhiteList") || RocketChat.settings.get("FileUpload_MediaTypeWhiteList").match(videoRegex)
if RocketChat.settings.get('Message_VideoRecorderEnabled') and (navigator.getUserMedia? or navigator.webkitGetUserMedia?) and videoEnabled and RocketChat.settings.get('FileUpload_Enabled')
@showVideoRec.set true
else
@showVideoRec.set false
wavRegex = /audio\/wav|audio\/\*/i
wavEnabled = !RocketChat.settings.get("FileUpload_MediaTypeWhiteList") || RocketChat.settings.get("FileUpload_MediaTypeWhiteList").match(wavRegex)
if RocketChat.settings.get('Message_AudioRecorderEnabled') and (navigator.getUserMedia? or navigator.webkitGetUserMedia?) and wavEnabled and RocketChat.settings.get('FileUpload_Enabled')
@showMicButton.set true
else
@showMicButton.set false
Meteor.startup ->
RocketChat.Geolocation = new ReactiveVar false
Tracker.autorun ->
if RocketChat.settings.get('MapView_Enabled') is true and RocketChat.settings.get('MapView_GMapsAPIKey')?.length and navigator.geolocation?.getCurrentPosition?
success = (position) =>
RocketChat.Geolocation.set position
error = (error) =>
console.log 'Error getting your geolocation', error
RocketChat.Geolocation.set false
options =
enableHighAccuracy: true
maximumAge: 0
timeout: 10000
navigator.geolocation.watchPosition success, error
else
RocketChat.Geolocation.set false

@ -274,7 +274,7 @@ Template.messageBox.events({
if (e.originalEvent.clipboardData == null) {
return;
}
const items = e.originalEvent.clipboardData.items;
const items = [...e.originalEvent.clipboardData.items];
const files = items.map(item => {
if (item.kind === 'file' && item.type.indexOf('image/') !== -1) {
e.preventDefault();
@ -283,7 +283,7 @@ Template.messageBox.events({
name: `Clipboard - ${ moment().format(RocketChat.settings.get('Message_TimeAndDateFormat')) }`
};
}
}).filter();
}).filter(e => e);
if (files.length) {
return fileUpload(files);
} else {
@ -313,7 +313,7 @@ Template.messageBox.events({
if (!files || files.length === 0) {
files = (e.dataTransfer && e.dataTransfer.files) || [];
}
const filesToUpload = files.map(file => {
const filesToUpload = [...files].map(file => {
// `file.type = mime.lookup(file.name)` does not work.
Object.defineProperty(file, 'type', {
value: mime.lookup(file.name)

@ -1,282 +0,0 @@
# This is not supposed to be a complete list
# it is just to improve readability in this file
keys = {
TAB: 9
ENTER: 13
ESC: 27
ARROW_LEFT: 37
ARROW_UP: 38
ARROW_RIGHT: 39
ARROW_DOWN: 40
}
getCursorPosition = (input) ->
if not input? then return
if input.selectionStart?
return input.selectionStart
else if document.selection?
input.focus()
sel = document.selection.createRange()
selLen = document.selection.createRange().text.length
sel.moveStart('character', - input.value.length)
return sel.text.length - selLen
setCursorPosition = (input, caretPos) ->
if not input? then return
if input.selectionStart?
input.focus()
return input.setSelectionRange(caretPos, caretPos)
else if document.selection?
range = input.createTextRange()
range.move('character', caretPos)
range.select()
val = (v, d) ->
return if v? then v else d
Template.messagePopup.onCreated ->
template = this
template.textFilter = new ReactiveVar ''
template.textFilterDelay = val(template.data.textFilterDelay, 0)
template.open = val(template.data.open, new ReactiveVar(false))
template.hasData = new ReactiveVar false
template.value = new ReactiveVar
template.trigger = val(template.data.trigger, '')
template.triggerAnywhere = val(template.data.triggerAnywhere, true)
template.closeOnEsc = val(template.data.closeOnEsc, true)
template.blurOnSelectItem = val(template.data.blurOnSelectItem, false)
template.prefix = val(template.data.prefix, template.trigger)
template.suffix = val(template.data.suffix, '')
if template.triggerAnywhere is true
template.matchSelectorRegex = val(template.data.matchSelectorRegex, new RegExp "(?:^| )#{template.trigger}[^\\s]*$")
else
template.matchSelectorRegex = val(template.data.matchSelectorRegex, new RegExp "(?:^)#{template.trigger}[^\\s]*$")
template.selectorRegex = val(template.data.selectorRegex, new RegExp "#{template.trigger}([^\\s]*)$")
template.replaceRegex = val(template.data.replaceRegex, new RegExp "#{template.trigger}[^\\s]*$")
template.getValue = val template.data.getValue, (_id) -> return _id
template.up = =>
current = template.find('.popup-item.selected')
previous = $(current).prev('.popup-item')[0] or template.find('.popup-item:last-child')
if previous?
current.className = current.className.replace /\sselected/, ''
previous.className += ' selected'
template.value.set previous.getAttribute('data-id')
template.down = =>
current = template.find('.popup-item.selected')
next = $(current).next('.popup-item')[0] or template.find('.popup-item')
if next?.classList.contains('popup-item')
current.className = current.className.replace /\sselected/, ''
next.className += ' selected'
template.value.set next.getAttribute('data-id')
template.verifySelection = =>
current = template.find('.popup-item.selected')
if not current?
first = template.find('.popup-item')
if first?
first.className += ' selected'
template.value.set first.getAttribute('data-id')
else
template.value.set undefined
template.onInputKeydown = (event) =>
if template.open.curValue isnt true or template.hasData.curValue isnt true
return
if event.which in [keys.ENTER, keys.TAB]
if template.blurOnSelectItem is true
template.input.blur()
else
template.open.set false
template.enterValue()
if template.data.cleanOnEnter
template.input.value = ''
event.preventDefault()
event.stopPropagation()
return
if event.which is keys.ARROW_UP
template.up()
event.preventDefault()
event.stopPropagation()
return
if event.which is keys.ARROW_DOWN
template.down()
event.preventDefault()
event.stopPropagation()
return
template.setTextFilter = _.debounce (value) ->
template.textFilter.set(value)
, template.textFilterDelay
template.onInputKeyup = (event) =>
if template.closeOnEsc is true and template.open.curValue is true and event.which is keys.ESC
template.open.set false
event.preventDefault()
event.stopPropagation()
return
value = template.input.value
value = value.substr 0, getCursorPosition(template.input)
if template.matchSelectorRegex.test value
template.setTextFilter value.match(template.selectorRegex)[1]
template.open.set true
else
template.open.set false
if template.open.curValue isnt true
return
if event.which not in [keys.ARROW_UP, keys.ARROW_DOWN]
Meteor.defer =>
template.verifySelection()
template.onFocus = (event) =>
template.clickingItem = false;
if template.open.curValue is true
return
value = template.input.value
value = value.substr 0, getCursorPosition(template.input)
if template.matchSelectorRegex.test value
template.setTextFilter value.match(template.selectorRegex)[1]
template.open.set true
Meteor.defer =>
template.verifySelection()
else
template.open.set false
template.onBlur = (event) =>
if template.open.curValue is false
return
if template.clickingItem is true
return
template.open.set false
template.enterValue = ->
if not template.value.curValue? then return
value = template.input.value
caret = getCursorPosition(template.input)
firstPartValue = value.substr 0, caret
lastPartValue = value.substr caret
getValue = this.getValue(template.value.curValue, template.data.collection, template.records.get(), firstPartValue)
if not getValue
return
firstPartValue = firstPartValue.replace(template.selectorRegex, template.prefix + getValue + template.suffix)
template.input.value = firstPartValue + lastPartValue
setCursorPosition template.input, firstPartValue.length
template.records = new ReactiveVar []
Tracker.autorun ->
if template.data.collection.findOne?
template.data.collection.find().count()
filter = template.textFilter.get()
if filter?
filterCallback = (result) =>
template.hasData.set result?.length > 0
template.records.set result
Meteor.defer =>
template.verifySelection()
result = template.data.getFilter(template.data.collection, filter, filterCallback)
if result?
filterCallback result
Template.messagePopup.onRendered ->
if this.data.getInput?
this.input = this.data.getInput?()
else if this.data.input
this.input = this.parentTemplate().find(this.data.input)
if not this.input?
console.error 'Input not found for popup'
$(this.input).on 'keyup', this.onInputKeyup.bind this
$(this.input).on 'keydown', this.onInputKeydown.bind this
$(this.input).on 'focus', this.onFocus.bind this
$(this.input).on 'blur', this.onBlur.bind this
Template.messagePopup.onDestroyed ->
$(this.input).off 'keyup', this.onInputKeyup
$(this.input).off 'keydown', this.onInputKeydown
$(this.input).off 'focus', this.onFocus
$(this.input).off 'blur', this.onBlur
Template.messagePopup.events
'mouseenter .popup-item': (e) ->
if e.currentTarget.className.indexOf('selected') > -1
return
template = Template.instance()
current = template.find('.popup-item.selected')
if current?
current.className = current.className.replace /\sselected/, ''
e.currentTarget.className += ' selected'
template.value.set this._id
'mousedown .popup-item, touchstart .popup-item': (e) ->
template = Template.instance()
template.clickingItem = true;
'mouseup .popup-item, touchend .popup-item': (e) ->
template = Template.instance()
template.clickingItem = false;
template.value.set this._id
template.enterValue()
template.open.set false
toolbarSearch.clear();
Template.messagePopup.helpers
isOpen: ->
Template.instance().open.get() and ((Template.instance().hasData.get() or Template.instance().data.emptyTemplate?) or not Template.instance().parentTemplate(1).subscriptionsReady())
data: ->
template = Template.instance()
return template.records.get()

@ -1,235 +0,0 @@
@filteredUsersMemory = new Mongo.Collection null
Meteor.startup ->
Tracker.autorun ->
if not Meteor.user()? or not Session.get('openedRoom')?
return
filteredUsersMemory.remove({})
messageUsers = RocketChat.models.Messages.find({rid: Session.get('openedRoom'), 'u.username': {$ne: Meteor.user().username}}, {fields: {'u.username': 1, 'u.name': 1, ts: 1}, sort: {ts: -1}}).fetch()
uniqueMessageUsersControl = {}
messageUsers.forEach (messageUser) ->
if not uniqueMessageUsersControl[messageUser.u.username]?
uniqueMessageUsersControl[messageUser.u.username] = true
filteredUsersMemory.upsert messageUser.u.username,
_id: messageUser.u.username
username: messageUser.u.username
name: messageUser.u.name
status: Session.get('user_' + messageUser.u.username + '_status') or 'offline'
ts: messageUser.ts
getUsersFromServer = (filter, records, cb) =>
messageUsers = _.pluck(records, 'username')
Meteor.call 'spotlight', filter, messageUsers, { users: true }, (err, results) ->
if err?
return console.error err
if results.users.length > 0
for result in results.users
if records.length < 5
records.push
_id: result.username
username: result.username
status: 'offline'
sort: 3
records = _.sortBy(records, 'sort')
cb(records)
getRoomsFromServer = (filter, records, cb) =>
Meteor.call 'spotlight', filter, null, { rooms: true }, (err, results) ->
if err?
return console.error err
if results.rooms.length > 0
for room in results.rooms
if records.length < 5
records.push room
cb(records)
getUsersFromServerDelayed = _.throttle getUsersFromServer, 500
getRoomsFromServerDelayed = _.throttle getRoomsFromServer, 500
Template.messagePopupConfig.helpers
popupUserConfig: ->
self = this
template = Template.instance()
config =
title: t('People')
collection: filteredUsersMemory
template: 'messagePopupUser'
getInput: self.getInput
textFilterDelay: 200
trigger: '@'
suffix: ' '
getFilter: (collection, filter, cb) ->
exp = new RegExp("#{RegExp.escape filter}", 'i')
# Get users from messages
items = filteredUsersMemory.find({ts: {$exists: true}, $or: [{username: exp}, {name: exp}]}, {limit: 5, sort: {ts: -1}}).fetch()
# Get online users
if items.length < 5 and filter?.trim() isnt ''
messageUsers = _.pluck(items, 'username')
Meteor.users.find({$and: [{$or:[{username: exp}, {name: exp}]}, {username: {$nin: [Meteor.user()?.username].concat(messageUsers)}}]}, {limit: 5 - messageUsers.length}).fetch().forEach (item) ->
items.push
_id: item.username
username: item.username
name: item.name
status: item.status
sort: 1
# # Get users of room
# if items.length < 5 and filter?.trim() isnt ''
# messageUsers = _.pluck(items, 'username')
# Tracker.nonreactive ->
# roomUsernames = RocketChat.models.Rooms.findOne(Session.get('openedRoom')).usernames
# for roomUsername in roomUsernames
# if messageUsers.indexOf(roomUsername) is -1 and exp.test(roomUsername)
# items.push
# _id: roomUsername
# username: roomUsername
# status: Session.get('user_' + roomUsername + '_status') or 'offline'
# sort: 2
# if items.length >= 5
# break
# Get users from db
if items.length < 5 and filter?.trim() isnt ''
getUsersFromServerDelayed filter, items, cb
all =
_id: 'all'
username: 'all'
system: true
name: t 'Notify_all_in_this_room'
compatibility: 'channel group'
sort: 4
exp = new RegExp("(^|\\s)#{RegExp.escape filter}", 'i')
if exp.test(all.username) or exp.test(all.compatibility)
items.push all
here =
_id: 'here'
username: 'here'
system: true
name: t 'Notify_active_in_this_room'
compatibility: 'channel group'
sort: 4
if exp.test(here.username) or exp.test(here.compatibility)
items.push here
return items
getValue: (_id) ->
return _id
return config
popupChannelConfig: ->
self = this
template = Template.instance()
config =
title: t('Channels')
collection: RocketChat.models.Subscriptions
trigger: '#'
suffix: ' '
template: 'messagePopupChannel'
getInput: self.getInput
getFilter: (collection, filter, cb) ->
exp = new RegExp(filter, 'i')
records = collection.find({name: exp, t: {$in: ['c', 'p']}}, {limit: 5, sort: {ls: -1}}).fetch()
if records.length < 5 and filter?.trim() isnt ''
getRoomsFromServerDelayed filter, records, cb
return records
getValue: (_id, collection, records) ->
return _.findWhere(records, {_id: _id})?.name
return config
popupSlashCommandsConfig: ->
self = this
template = Template.instance()
config =
title: t('Commands')
collection: RocketChat.slashCommands.commands
trigger: '/'
suffix: ' '
triggerAnywhere: false
template: 'messagePopupSlashCommand'
getInput: self.getInput
getFilter: (collection, filter) ->
commands = []
for command, item of collection
if command.indexOf(filter) > -1
commands.push
_id: command
params: if item.params then TAPi18n.__ item.params else ''
description: TAPi18n.__ item.description
commands = commands.sort (a, b) ->
return a._id > b._id
commands = commands[0..10]
return commands
return config
emojiEnabled: ->
return RocketChat.emoji?
popupEmojiConfig: ->
if RocketChat.emoji?
self = this
template = Template.instance()
config =
title: t('Emoji')
collection: RocketChat.emoji.list
template: 'messagePopupEmoji'
trigger: ':'
prefix: ''
suffix: ' '
getInput: self.getInput
getFilter: (collection, filter, cb) ->
results = []
key = ':' + filter
if RocketChat.emoji.packages.emojione?.asciiList[key] or filter.length < 2
return []
regExp = new RegExp('^' + RegExp.escape(key), 'i')
for key, value of collection
if results.length > 10
break
if regExp.test(key)
results.push
_id: key
data: value
results.sort (a, b) ->
if a._id < b._id
return -1
if a._id > b._id
return 1
return 0
return results
return config

@ -1,4 +0,0 @@
Template.messagePopupEmoji.helpers
value: ->
length = this.data.length
return this.data[length - 1]

@ -15,7 +15,6 @@ Package.onUse(function(api) {
'mongo',
'ecmascript',
'templating',
'coffeescript',
'underscore',
'tracker',
'rocketchat:lib',
@ -33,7 +32,7 @@ Package.onUse(function(api) {
api.addFiles('client/popup/messagePopupSlashCommand.html', 'client');
api.addFiles('client/popup/messagePopupUser.html', 'client');
api.addFiles('client/message.coffee', 'client');
api.addFiles('client/message.js', 'client');
api.addFiles('client/messageBox.js', 'client');
api.addFiles('client/popup/messagePopup.js', 'client');
api.addFiles('client/popup/messagePopupChannel.js', 'client');

Loading…
Cancel
Save