diff --git a/packages/rocketchat-ui-message/client/message.coffee b/packages/rocketchat-ui-message/client/message.coffee
deleted file mode 100644
index 2684dfd9c0a3..000000000000
--- a/packages/rocketchat-ui-message/client/message.coffee
+++ /dev/null
@@ -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 "
diff --git a/packages/rocketchat-ui-message/client/message.js b/packages/rocketchat-ui-message/client/message.js
new file mode 100644
index 000000000000..ae7997d58303
--- /dev/null
+++ b/packages/rocketchat-ui-message/client/message.js
@@ -0,0 +1,358 @@
+/* globals renderEmoji renderMessageBody*/
+import moment from 'moment';
+
+Template.message.helpers({
+ encodeURI(text) {
+ return encodeURI(text);
+ },
+ isBot() {
+ if (this.bot != null) {
+ return 'bot';
+ }
+ },
+ roleTags() {
+ const user = Meteor.user();
+ // test user -> settings -> preferences -> hideRoles
+ if (!RocketChat.settings.get('UI_DisplayRoles') || ['settings', 'preferences', 'hideRoles'].reduce((obj, field) => typeof obj !== 'undefined' && obj[field], user)) {
+ return [];
+ }
+
+ if (!this.u || !this.u._id) {
+ return [];
+ }
+ /* globals UserRoles RoomRoles */
+ const userRoles = UserRoles.findOne(this.u._id);
+ const roomRoles = RoomRoles.findOne({
+ 'u._id': this.u._id,
+ rid: this.rid
+ });
+ const roles = [...(userRoles && userRoles.roles) || [], ...(roomRoles && roomRoles.roles) || []];
+ return RocketChat.models.Roles.find({
+ _id: {
+ $in: roles
+ },
+ description: {
+ $exists: 1,
+ $ne: ''
+ }
+ }, {
+ fields: {
+ description: 1
+ }
+ });
+ },
+ isGroupable() {
+ if (this.groupable === false) {
+ return 'false';
+ }
+ },
+ isSequential() {
+ if (this.groupable !== false) {
+ return 'sequential';
+ }
+ },
+ avatarFromUsername() {
+ if ((this.avatar != null) && this.avatar[0] === '@') {
+ return this.avatar.replace(/^@/, '');
+ }
+ },
+ getEmoji(emoji) {
+ return renderEmoji(emoji);
+ },
+ getName() {
+ if (this.alias) {
+ return this.alias;
+ }
+ if (!this.u) {
+ return '';
+ }
+ return (RocketChat.settings.get('UI_Use_Real_Name') && this.u.name) || this.u.username;
+ },
+ showUsername() {
+ return this.alias || RocketChat.settings.get('UI_Use_Real_Name') && this.u && this.u.name;
+ },
+ own() {
+ if (this.u && this.u._id === Meteor.userId()) {
+ return 'own';
+ }
+ },
+ timestamp() {
+ return +this.ts;
+ },
+ chatops() {
+ if (this.u && this.u.username === RocketChat.settings.get('Chatops_Username')) {
+ return 'chatops-message';
+ }
+ },
+ time() {
+ return moment(this.ts).format(RocketChat.settings.get('Message_TimeFormat'));
+ },
+ date() {
+ return moment(this.ts).format(RocketChat.settings.get('Message_DateFormat'));
+ },
+ isTemp() {
+ if (this.temp === 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') && this.u && this.u._id !== Meteor.userId() && !RocketChat.MessageTypes.isSystemMessage(this)) {
+ const subscription = RocketChat.models.Subscriptions.findOne({
+ rid: this.rid,
+ 'u._id': Meteor.userId()
+ }, {
+ fields: {
+ autoTranslate: 1,
+ autoTranslateLanguage: 1
+ }
+ });
+ const language = RocketChat.AutoTranslate.getLanguage(this.rid);
+ return this.autoTranslateFetching || subscription && subscription.autoTranslate !== this.autoTranslateShowInverse && this.translations && this.translations[language];
+ }
+ },
+ edited() {
+ return Template.instance().wasEdited;
+ },
+ editTime() {
+ if (Template.instance().wasEdited) {
+ return moment(this.editedAt).format(`${ RocketChat.settings.get('Message_DateFormat') } ${ RocketChat.settings.get('Message_TimeFormat') }`);
+ }
+ },
+ editedBy() {
+ if (!Template.instance().wasEdited) {
+ return '';
+ }
+ // try to return the username of the editor,
+ // otherwise a special "?" character that will be
+ // rendered as a special avatar
+ return (this.editedBy && this.editedBy.username) || '?';
+ },
+ canEdit() {
+ const hasPermission = RocketChat.authz.hasAtLeastOnePermission('edit-message', this.rid);
+ const isEditAllowed = RocketChat.settings.get('Message_AllowEditing');
+ const editOwn = this.u && this.u._id === Meteor.userId();
+ if (!(hasPermission || (isEditAllowed && editOwn))) {
+ return;
+ }
+ const blockEditInMinutes = RocketChat.settings.get('Message_AllowEditing_BlockEditInMinutes');
+ if (blockEditInMinutes) {
+ let msgTs;
+ if (this.ts != null) {
+ msgTs = moment(this.ts);
+ }
+ let currentTsDiff;
+ if (msgTs != null) {
+ currentTsDiff = moment().diff(msgTs, 'minutes');
+ }
+ return currentTsDiff < blockEditInMinutes;
+ } else {
+ return true;
+ }
+ },
+ canDelete() {
+ 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;
+ }
+ 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');
+ }
+ return currentTsDiff < blockDeleteInMinutes;
+ } else {
+ return true;
+ }
+ },
+ showEditedStatus() {
+ return RocketChat.settings.get('Message_ShowEditedStatus');
+ },
+ label() {
+ if (this.i18nLabel) {
+ return t(this.i18nLabel);
+ } else if (this.label) {
+ return this.label;
+ }
+ },
+ hasOembed() {
+ if (!(this.urls && this.urls.length > 0 && Template.oembedBaseWidget != null && RocketChat.settings.get('API_Embed'))) {
+ return false;
+ }
+ if (!(RocketChat.settings.get('API_EmbedDisabledFor')||'').split(',').map(username => username.trim()).includes(this.u && this.u.username)) {
+ return false;
+ }
+ return true;
+ },
+ reactions() {
+ 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
+ }).toLowerCase() }`;
+ } else {
+ usernames = usernames.replace(/,([^,]+)$/, ` ${ t('and') }$1`);
+ }
+ if (usernames[0] !== '@') {
+ usernames = usernames[0].toUpperCase() + usernames.substr(1);
+ }
+ return {
+ emoji,
+ count: reaction.usernames.length,
+ usernames,
+ reaction: ` ${ t('Reacted_with').toLowerCase() } ${ emoji }`,
+ userReacted: reaction.usernames.indexOf(userUsername) > -1
+ };
+ });
+ },
+ markUserReaction(reaction) {
+ if (reaction.userReacted) {
+ return {
+ 'class': 'selected'
+ };
+ }
+ },
+ hideReactions() {
+ if (_.isEmpty(this.reactions)) {
+ return 'hidden';
+ }
+ },
+ actionLinks() {
+ // remove 'method_id' and 'params' properties
+ return _.map(this.actionLinks, function(actionLink, key) {
+ return _.extend({
+ id: key
+ }, _.omit(actionLink, 'method_id', 'params'));
+ });
+ },
+ hideActionLinks() {
+ if (_.isEmpty(this.actionLinks)) {
+ return 'hidden';
+ }
+ },
+ injectIndex(data, index) {
+ data.index = index;
+ },
+ hideCog() {
+ const subscription = RocketChat.models.Subscriptions.findOne({
+ rid: this.rid
+ });
+ if (subscription == null) {
+ return 'hidden';
+ }
+ }
+});
+
+Template.message.onCreated(function() {
+ let msg = Template.currentData();
+
+ this.wasEdited = (msg.editedAt != null) && !RocketChat.MessageTypes.isSystemMessage(msg);
+
+ 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 (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) {
+ msg.html = RocketChat.Markdown.parse(msg.html);
+ }
+ return msg;
+ })();
+});
+
+Template.message.onViewRendered = function(context) {
+ return this._domrange.onAttached(function(domRange) {
+ 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.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 {
+ $currentNode.removeClass('new-day');
+ }
+ if (previousDataset.groupable === 'false' || currentDataset.groupable === 'false') {
+ $currentNode.removeClass('sequential');
+ } else if (previousDataset.username !== currentDataset.username || parseInt(currentDataset.timestamp) - parseInt(previousDataset.timestamp) > RocketChat.settings.get('Message_GroupingPeriod') * 1000) {
+ $currentNode.removeClass('sequential');
+ } else if (!$currentNode.hasClass('new-day')) {
+ $currentNode.addClass('sequential');
+ }
+ }
+ if (nextNode && nextNode.dataset) {
+ const nextDataset = nextNode.dataset;
+ if (nextDataset.date !== currentDataset.date) {
+ $nextNode.addClass('new-day').removeClass('sequential');
+ } else {
+ $nextNode.removeClass('new-day');
+ }
+ if (nextDataset.groupable !== 'false') {
+ if (nextDataset.username !== currentDataset.username || parseInt(nextDataset.timestamp) - parseInt(currentDataset.timestamp) > RocketChat.settings.get('Message_GroupingPeriod') * 1000) {
+ $nextNode.removeClass('sequential');
+ } else if (!$nextNode.hasClass('new-day')) {
+ $nextNode.addClass('sequential');
+ }
+ }
+ }
+ if (nextNode == 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.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 ');
+ }
+ }
+ });
+};
diff --git a/packages/rocketchat-ui-message/client/messageBox.coffee b/packages/rocketchat-ui-message/client/messageBox.coffee
deleted file mode 100644
index 2587fda0c301..000000000000
--- a/packages/rocketchat-ui-message/client/messageBox.coffee
+++ /dev/null
@@ -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 = """
-
-
-
- """
-
- 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
diff --git a/packages/rocketchat-ui-message/client/messageBox.js b/packages/rocketchat-ui-message/client/messageBox.js
new file mode 100644
index 000000000000..add30a122142
--- /dev/null
+++ b/packages/rocketchat-ui-message/client/messageBox.js
@@ -0,0 +1,454 @@
+/* globals fileUpload AudioRecorder KonchatNotification chatMessages */
+import toastr from 'toastr';
+
+import mime from 'mime-type/with-db';
+
+import moment from 'moment';
+
+import {VRecDialog} from 'meteor/rocketchat:ui-vrecord';
+
+function katexSyntax() {
+ if (RocketChat.katex.katex_enabled()) {
+ if (RocketChat.katex.dollar_syntax_enabled()) {
+ return '$$KaTeX$$';
+ }
+ if (RocketChat.katex.parenthesis_syntax_enabled()) {
+ return '\\[KaTeX\\]';
+ }
+ }
+ return false;
+}
+
+Template.messageBox.helpers({
+ roomName() {
+ const roomData = Session.get(`roomData${ this._id }`);
+ if (!roomData) {
+ return '';
+ }
+ if (roomData.t === 'd') {
+ const chat = ChatSubscription.findOne({
+ rid: this._id
+ }, {
+ fields: {
+ name: 1
+ }
+ });
+ return chat && chat.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') && (RocketChat.Markdown || RocketChat.MarkdownCode || katexSyntax());
+ },
+ canJoin() {
+ return RocketChat.roomTypes.verifyShowJoinLink(this._id);
+ },
+ joinCodeRequired() {
+ const code = Session.get(`roomData${ this._id }`);
+ return code && code.joinCodeRequired;
+ },
+ subscribed() {
+ return RocketChat.roomTypes.verifyCanSendMessage(this._id);
+ },
+ allowedToSend() {
+ if (RocketChat.roomTypes.readOnly(this._id, Meteor.user())) {
+ return false;
+ }
+ if (RocketChat.roomTypes.archived(this._id)) {
+ return false;
+ }
+ const roomData = Session.get(`roomData${ this._id }`);
+ if (roomData && roomData.t === 'd') {
+ const subscription = ChatSubscription.findOne({
+ rid: this._id
+ }, {
+ fields: {
+ archived: 1,
+ blocked: 1,
+ blocker: 1
+ }
+ });
+ if (subscription && (subscription.archived || subscription.blocked || subscription.blocker)) {
+ return false;
+ }
+ }
+ return true;
+ },
+ isBlockedOrBlocker() {
+ const roomData = Session.get(`roomData${ this._id }`);
+ if (roomData && roomData.t === 'd') {
+ const subscription = ChatSubscription.findOne({
+ rid: this._id
+ }, {
+ fields: {
+ blocked: 1,
+ blocker: 1
+ }
+ });
+ if (subscription && (subscription.blocked || subscription.blocker)) {
+ return true;
+ }
+ }
+ },
+ getPopupConfig() {
+ const template = Template.instance();
+ return {
+ getInput() {
+ return template.find('.input-message');
+ }
+ };
+ },
+ /* globals MsgTyping*/
+ usersTyping() {
+ const users = MsgTyping.get(this._id);
+ if (users.length === 0) {
+ return;
+ }
+ if (users.length === 1) {
+ return {
+ multi: false,
+ selfTyping: MsgTyping.selfTyping.get(),
+ users: users[0]
+ };
+ }
+ let last = users.pop();
+ if (users.length > 4) {
+ last = t('others');
+ }
+ let usernames = users.join(', ');
+ usernames = [usernames, last];
+ return {
+ multi: true,
+ selfTyping: MsgTyping.selfTyping.get(),
+ users: usernames.join(` ${ t('and') } `)
+ };
+ },
+ groupAttachHidden() {
+ if (RocketChat.settings.get('Message_Attachments_GroupAttach')) {
+ return 'hidden';
+ }
+ },
+ fileUploadEnabled() {
+ return RocketChat.settings.get('FileUpload_Enabled');
+ },
+ fileUploadAllowedMediaTypes() {
+ return RocketChat.settings.get('FileUpload_MediaTypeWhiteList');
+ },
+ showFileUpload() {
+ let roomData;
+ if (RocketChat.settings.get('FileUpload_Enabled')) {
+ roomData = Session.get(`roomData${ this._id }`);
+ if (roomData && roomData.t === '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 (!Template.instance().isMessageFieldEmpty.get()) {
+ return 'show-send';
+ }
+ },
+ showLocation() {
+ return RocketChat.Geolocation.get() !== false;
+ },
+ notSubscribedTpl() {
+ return RocketChat.roomTypes.getNotSubscribedTpl(this._id);
+ },
+ showSandstorm() {
+ return Meteor.settings['public'].sandstorm && !Meteor.isCordova;
+ }
+});
+
+function firefoxPasteUpload(fn) {
+ const user = navigator.userAgent.match(/Firefox\/(\d+)\.\d/);
+ if (!user || user[1] > 49) {
+ return fn;
+ }
+ return function(event, instance) {
+ if ((event.originalEvent.ctrlKey || event.originalEvent.metaKey) && (event.keyCode === 86)) {
+ const textarea = instance.find('textarea');
+ const {selectionStart, selectionEnd} = textarea;
+ const contentEditableDiv = instance.find('#msg_contenteditable');
+ contentEditableDiv.focus();
+ Meteor.setTimeout(function() {
+ const pastedImg = contentEditableDiv.querySelector('img');
+ const textareaContent = textarea.value;
+ const startContent = textareaContent.substring(0, selectionStart);
+ const endContent = textareaContent.substring(selectionEnd);
+ const restoreSelection = function(pastedText) {
+ textarea.value = startContent + pastedText + endContent;
+ textarea.selectionStart = selectionStart + pastedText.length;
+ return textarea.selectionEnd = textarea.selectionStart;
+ };
+ if (pastedImg) {
+ contentEditableDiv.innerHTML = '';
+ }
+ textarea.focus;
+ if (!pastedImg || contentEditableDiv.innerHTML.length > 0) {
+ return [].slice.call(contentEditableDiv.querySelectorAll('br')).forEach(function(el) {
+ contentEditableDiv.replaceChild(new Text('\n'), el);
+ return restoreSelection(contentEditableDiv.innerText);
+ });
+ }
+ const imageSrc = pastedImg.getAttribute('src');
+ if (imageSrc.match(/^data:image/)) {
+ return fetch(imageSrc).then(function(img) {
+ return img.blob();
+ }).then(function(blob) {
+ return fileUpload([
+ {
+ file: blob,
+ name: 'Clipboard'
+ }
+ ]);
+ });
+ }
+ }, 150);
+ }
+ return fn && fn.apply(this, arguments);
+ };
+}
+
+Template.messageBox.events({
+ 'click .join'(event) {
+ event.stopPropagation();
+ event.preventDefault();
+ Meteor.call('joinRoom', this._id, Template.instance().$('[name=joinCode]').val(), (err) => {
+ if (err != null) {
+ toastr.error(t(err.reason));
+ }
+ if (RocketChat.authz.hasAllPermission('preview-c-room') === false && RoomHistoryManager.getRoom(this._id).loaded === 0) {
+ RoomManager.getOpenedRoomByRid(this._id).streamActive = false;
+ RoomManager.getOpenedRoomByRid(this._id).ready = false;
+ RoomHistoryManager.getRoom(this._id).loaded = null;
+ RoomManager.computation.invalidate();
+ }
+ });
+ },
+ 'focus .input-message'(event, instance) {
+ KonchatNotification.removeRoomNotification(this._id);
+ chatMessages[this._id].input = instance.find('.input-message');
+ },
+ 'click .send-button'(event, instance) {
+ const input = instance.find('.input-message');
+ chatMessages[this._id].send(this._id, input, () => {
+ // fixes https://github.com/RocketChat/Rocket.Chat/issues/3037
+ // at this point, the input is cleared and ready for autogrow
+ input.updateAutogrow();
+ return instance.isMessageFieldEmpty.set(chatMessages[this._id].isEmpty());
+ });
+ return input.focus();
+ },
+ 'keyup .input-message'(event, instance) {
+ chatMessages[this._id].keyup(this._id, event, instance);
+ return instance.isMessageFieldEmpty.set(chatMessages[this._id].isEmpty());
+ },
+ 'paste .input-message'(e, instance) {
+ Meteor.setTimeout(function() {
+ const input = instance.find('.input-message');
+ return typeof input.updateAutogrow === 'function' && input.updateAutogrow();
+ }, 50);
+ if (e.originalEvent.clipboardData == null) {
+ return;
+ }
+ const items = [...e.originalEvent.clipboardData.items];
+ const files = items.map(item => {
+ if (item.kind === 'file' && item.type.indexOf('image/') !== -1) {
+ e.preventDefault();
+ return {
+ file: item.getAsFile(),
+ name: `Clipboard - ${ moment().format(RocketChat.settings.get('Message_TimeAndDateFormat')) }`
+ };
+ }
+ }).filter(e => e);
+ if (files.length) {
+ return fileUpload(files);
+ } else {
+ return instance.isMessageFieldEmpty.set(false);
+ }
+ },
+ 'keydown .input-message': firefoxPasteUpload(function(event) {
+ return chatMessages[this._id].keydown(this._id, event, Template.instance());
+ }),
+ 'input .input-message'(event) {
+ return chatMessages[this._id].valueChanged(this._id, event, Template.instance());
+ },
+ 'propertychange .input-message'(event) {
+ if (event.originalEvent.propertyName === 'value') {
+ return chatMessages[this._id].valueChanged(this._id, event, Template.instance());
+ }
+ },
+ 'click .editing-commands-cancel > button'() {
+ return chatMessages[this._id].clearEditing();
+ },
+ 'click .editing-commands-save > button'() {
+ return chatMessages[this._id].send(this._id, chatMessages[this._id].input);
+ },
+ 'change .message-form input[type=file]'(event) {
+ const e = event.originalEvent || event;
+ let files = e.target.files;
+ if (!files || files.length === 0) {
+ files = (e.dataTransfer && e.dataTransfer.files) || [];
+ }
+ const filesToUpload = [...files].map(file => {
+ // `file.type = mime.lookup(file.name)` does not work.
+ Object.defineProperty(file, 'type', {
+ value: mime.lookup(file.name)
+ });
+ return {
+ file,
+ name: file.name
+ };
+ });
+ return fileUpload(filesToUpload);
+ },
+ 'click .message-buttons.share'(e, t) {
+ t.$('.share-items').toggleClass('hidden');
+ return t.$('.message-buttons.share').toggleClass('active');
+ },
+ 'click .message-form .message-buttons.location'() {
+ const roomId = this._id;
+ const position = RocketChat.Geolocation.get();
+ const latitude = position.coords.latitude;
+ const longitude = position.coords.longitude;
+ const text = `\n
\n
`;
+ return swal({
+ title: t('Share_Location_Title'),
+ text,
+ showCancelButton: true,
+ closeOnConfirm: true,
+ closeOnCancel: true,
+ html: true
+ }, function(isConfirm) {
+ if (isConfirm !== true) {
+ return;
+ }
+ return Meteor.call('sendMessage', {
+ _id: Random.id(),
+ rid: roomId,
+ msg: '',
+ location: {
+ type: 'Point',
+ coordinates: [longitude, latitude]
+ }
+ });
+ });
+ },
+ 'click .message-form .mic'(e, t) {
+ return AudioRecorder.start(function() {
+ t.$('.stop-mic').removeClass('hidden');
+ return t.$('.mic').addClass('hidden');
+ });
+ },
+ 'click .message-form .video-button'(e) {
+ return VRecDialog.opened ? VRecDialog.close() : VRecDialog.open(e.currentTarget);
+ },
+ 'click .message-form .stop-mic'(e, t) {
+ AudioRecorder.stop(function(blob) {
+ return fileUpload([
+ {
+ file: blob,
+ type: 'audio',
+ name: `${ TAPi18n.__('Audio record') }.wav`
+ }
+ ]);
+ });
+ t.$('.stop-mic').addClass('hidden');
+ return t.$('.mic').removeClass('hidden');
+ },
+ 'click .sandstorm-offer'() {
+ const roomId = this._id;
+ return RocketChat.Sandstorm.request('uiView', (err, data) => {
+ if (err || !data.token) {
+ console.error(err);
+ return;
+ }
+ return Meteor.call('sandstormClaimRequest', data.token, data.descriptor, function(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(function() {
+ this.isMessageFieldEmpty = new ReactiveVar(true);
+ this.showMicButton = new ReactiveVar(false);
+ this.showVideoRec = new ReactiveVar(false);
+ return this.autorun(() => {
+ const videoRegex = /video\/webm|video\/\*/i;
+ const videoEnabled = !RocketChat.settings.get('FileUpload_MediaTypeWhiteList') || RocketChat.settings.get('FileUpload_MediaTypeWhiteList').match(videoRegex);
+ if (RocketChat.settings.get('Message_VideoRecorderEnabled') && ((navigator.getUserMedia != null) || (navigator.webkitGetUserMedia != null)) && videoEnabled && RocketChat.settings.get('FileUpload_Enabled')) {
+ this.showVideoRec.set(true);
+ } else {
+ this.showVideoRec.set(false);
+ }
+ const wavRegex = /audio\/wav|audio\/\*/i;
+ const wavEnabled = !RocketChat.settings.get('FileUpload_MediaTypeWhiteList') || RocketChat.settings.get('FileUpload_MediaTypeWhiteList').match(wavRegex);
+ if (RocketChat.settings.get('Message_AudioRecorderEnabled') && ((navigator.getUserMedia != null) || (navigator.webkitGetUserMedia != null)) && wavEnabled && RocketChat.settings.get('FileUpload_Enabled')) {
+ return this.showMicButton.set(true);
+ } else {
+ return this.showMicButton.set(false);
+ }
+ });
+});
+
+Meteor.startup(function() {
+ RocketChat.Geolocation = new ReactiveVar(false);
+ return Tracker.autorun(function() {
+ const MapView_GMapsAPIKey = RocketChat.settings.get('MapView_GMapsAPIKey');
+ if (RocketChat.settings.get('MapView_Enabled') === true && MapView_GMapsAPIKey && MapView_GMapsAPIKey.length && navigator.geolocation && navigator.geolocation.getCurrentPosition) {
+ const success = (position) => {
+ return RocketChat.Geolocation.set(position);
+ };
+ const error = (error) => {
+ console.log('Error getting your geolocation', error);
+ return RocketChat.Geolocation.set(false);
+ };
+ const options = {
+ enableHighAccuracy: true,
+ maximumAge: 0,
+ timeout: 10000
+ };
+ return navigator.geolocation.watchPosition(success, error, options);
+ } else {
+ return RocketChat.Geolocation.set(false);
+ }
+ });
+});
diff --git a/packages/rocketchat-ui-message/client/popup/messagePopup.coffee b/packages/rocketchat-ui-message/client/popup/messagePopup.coffee
deleted file mode 100644
index c76fcf1b1711..000000000000
--- a/packages/rocketchat-ui-message/client/popup/messagePopup.coffee
+++ /dev/null
@@ -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()
diff --git a/packages/rocketchat-ui-message/client/popup/messagePopup.js b/packages/rocketchat-ui-message/client/popup/messagePopup.js
new file mode 100644
index 000000000000..57a160bc13bb
--- /dev/null
+++ b/packages/rocketchat-ui-message/client/popup/messagePopup.js
@@ -0,0 +1,285 @@
+/* globals toolbarSearch */
+// This is not supposed to be a complete list
+// it is just to improve readability in this file
+const keys = {
+ TAB: 9,
+ ENTER: 13,
+ ESC: 27,
+ ARROW_LEFT: 37,
+ ARROW_UP: 38,
+ ARROW_RIGHT: 39,
+ ARROW_DOWN: 40
+};
+
+function getCursorPosition(input) {
+ if (input == null) {
+ return;
+ }
+ if (input.selectionStart != null) {
+ return input.selectionStart;
+ } else if (document.selection != null) {
+ input.focus();
+ const sel = document.selection.createRange();
+ const selLen = document.selection.createRange().text.length;
+ sel.moveStart('character', -input.value.length);
+ return sel.text.length - selLen;
+ }
+}
+
+function setCursorPosition(input, caretPos) {
+ if (input == null) {
+ return;
+ }
+ if (input.selectionStart != null) {
+ input.focus();
+ return input.setSelectionRange(caretPos, caretPos);
+ } else if (document.selection != null) {
+ const range = input.createTextRange();
+ range.move('character', caretPos);
+ return range.select();
+ }
+}
+
+function val(v, d) {
+ if (v != null) {
+ return v;
+ } else {
+ return d;
+ }
+}
+
+Template.messagePopup.onCreated(function() {
+ const 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 === 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, function(_id) {
+ return _id;
+ });
+ template.up = () => {
+ const current = template.find('.popup-item.selected');
+ const previous = $(current).prev('.popup-item')[0] || template.find('.popup-item:last-child');
+ if (previous != null) {
+ current.className = current.className.replace(/\sselected/, '');
+ previous.className += ' selected';
+ return template.value.set(previous.getAttribute('data-id'));
+ }
+ };
+ template.down = () => {
+ const current = template.find('.popup-item.selected');
+ const next = $(current).next('.popup-item')[0] || template.find('.popup-item');
+ if (next && next.classList.contains('popup-item')) {
+ current.className = current.className.replace(/\sselected/, '');
+ next.className += ' selected';
+ return template.value.set(next.getAttribute('data-id'));
+ }
+ };
+ template.verifySelection = () => {
+ const current = template.find('.popup-item.selected');
+ if (current == null) {
+ const first = template.find('.popup-item');
+ if (first != null) {
+ first.className += ' selected';
+ return template.value.set(first.getAttribute('data-id'));
+ } else {
+ return template.value.set(null);
+ }
+ }
+ };
+ template.onInputKeydown = (event) => {
+ if (template.open.curValue !== true || template.hasData.curValue !== true) {
+ return;
+ }
+ if (event.which === keys.ENTER || event.which === keys.TAB) {
+ if (template.blurOnSelectItem === 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 === keys.ARROW_UP) {
+ template.up();
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
+ if (event.which === keys.ARROW_DOWN) {
+ template.down();
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ };
+
+ template.setTextFilter = _.debounce(function(value) {
+ return template.textFilter.set(value);
+ }, template.textFilterDelay);
+
+ template.onInputKeyup = (event) => {
+ if (template.closeOnEsc === true && template.open.curValue === true && event.which === keys.ESC) {
+ template.open.set(false);
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
+ const value = template.input.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 !== true) {
+ return;
+ }
+ if (event.which !== keys.ARROW_UP && event.which !== keys.ARROW_DOWN) {
+ return Meteor.defer(function() {
+ template.verifySelection();
+ });
+ }
+ };
+ template.onFocus = () => {
+ template.clickingItem = false;
+ if (template.open.curValue === true) {
+ return;
+ }
+ const value = template.input.value.substr(0, getCursorPosition(template.input));
+ if (template.matchSelectorRegex.test(value)) {
+ template.setTextFilter(value.match(template.selectorRegex)[1]);
+ template.open.set(true);
+ return Meteor.defer(function() {
+ return template.verifySelection();
+ });
+ } else {
+ return template.open.set(false);
+ }
+ };
+
+ template.onBlur = () => {
+ if (template.open.curValue === false) {
+ return;
+ }
+ if (template.clickingItem === true) {
+ return;
+ }
+ return template.open.set(false);
+ };
+
+ template.enterValue = function() {
+ if (template.value.curValue == null) {
+ return;
+ }
+ const value = template.input.value;
+ const caret = getCursorPosition(template.input);
+ let firstPartValue = value.substr(0, caret);
+ const lastPartValue = value.substr(caret);
+ const getValue = this.getValue(template.value.curValue, template.data.collection, template.records.get(), firstPartValue);
+ if (!getValue) {
+ return;
+ }
+ firstPartValue = firstPartValue.replace(template.selectorRegex, template.prefix + getValue + template.suffix);
+ template.input.value = firstPartValue + lastPartValue;
+ return setCursorPosition(template.input, firstPartValue.length);
+ };
+ template.records = new ReactiveVar([]);
+ Tracker.autorun(function() {
+ if (template.data.collection.findOne != null) {
+ template.data.collection.find().count();
+ }
+ const filter = template.textFilter.get();
+ if (filter != null) {
+ const filterCallback = (result) => {
+ template.hasData.set(result && result.length > 0);
+ template.records.set(result);
+ return Meteor.defer(function() {
+ return template.verifySelection();
+ });
+ };
+ const result = template.data.getFilter(template.data.collection, filter, filterCallback);
+ if (result != null) {
+ return filterCallback(result);
+ }
+ }
+ });
+});
+
+Template.messagePopup.onRendered(function() {
+ if (this.data.getInput != null) {
+ this.input = typeof this.data.getInput === 'function' && this.data.getInput();
+ } else if (this.data.input) {
+ this.input = this.parentTemplate().find(this.data.input);
+ }
+ if (this.input == null) {
+ 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));
+ return $(this.input).on('blur', this.onBlur.bind(this));
+});
+
+Template.messagePopup.onDestroyed(function() {
+ $(this.input).off('keyup', this.onInputKeyup);
+ $(this.input).off('keydown', this.onInputKeydown);
+ $(this.input).off('focus', this.onFocus);
+ return $(this.input).off('blur', this.onBlur);
+});
+
+Template.messagePopup.events({
+ 'mouseenter .popup-item'(e) {
+ if (e.currentTarget.className.indexOf('selected') > -1) {
+ return;
+ }
+ const template = Template.instance();
+ const current = template.find('.popup-item.selected');
+ if (current != null) {
+ current.className = current.className.replace(/\sselected/, '');
+ }
+ e.currentTarget.className += ' selected';
+ return template.value.set(this._id);
+ },
+ 'mousedown .popup-item, touchstart .popup-item'() {
+ const template = Template.instance();
+ return template.clickingItem = true;
+ },
+ 'mouseup .popup-item, touchend .popup-item'() {
+ const template = Template.instance();
+ template.clickingItem = false;
+ template.value.set(this._id);
+ template.enterValue();
+ template.open.set(false);
+ return toolbarSearch.clear();
+ }
+});
+
+Template.messagePopup.helpers({
+ isOpen() {
+ return Template.instance().open.get() && ((Template.instance().hasData.get() || (Template.instance().data.emptyTemplate != null)) || !Template.instance().parentTemplate(1).subscriptionsReady());
+ },
+ data() {
+ const template = Template.instance();
+ return template.records.get();
+ }
+});
diff --git a/packages/rocketchat-ui-message/client/popup/messagePopupConfig.coffee b/packages/rocketchat-ui-message/client/popup/messagePopupConfig.coffee
deleted file mode 100644
index a5bc17cc33fc..000000000000
--- a/packages/rocketchat-ui-message/client/popup/messagePopupConfig.coffee
+++ /dev/null
@@ -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
diff --git a/packages/rocketchat-ui-message/client/popup/messagePopupConfig.js b/packages/rocketchat-ui-message/client/popup/messagePopupConfig.js
new file mode 100644
index 000000000000..797e77fa9ca4
--- /dev/null
+++ b/packages/rocketchat-ui-message/client/popup/messagePopupConfig.js
@@ -0,0 +1,295 @@
+/* globals filteredUsersMemory */
+filteredUsersMemory = new Mongo.Collection(null);
+
+Meteor.startup(function() {
+ Tracker.autorun(function() {
+ if (Meteor.user() == null || Session.get('openedRoom') == null) {
+ return;
+ }
+ filteredUsersMemory.remove({});
+ const 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();
+ const uniqueMessageUsersControl = {};
+ return messageUsers.forEach(function(messageUser) {
+ if (uniqueMessageUsersControl[messageUser.u.username] == null) {
+ uniqueMessageUsersControl[messageUser.u.username] = true;
+ return 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`) || 'offline',
+ ts: messageUser.ts
+ });
+ }
+ });
+ });
+});
+
+const getUsersFromServer = (filter, records, cb) => {
+ const messageUsers = _.pluck(records, 'username');
+ return Meteor.call('spotlight', filter, messageUsers, {
+ users: true
+ }, function(err, results) {
+ if (err != null) {
+ return console.error(err);
+ }
+ if (results.users.length > 0) {
+ results.users.forEach(result => {
+ if (records.length < 5) {
+ records.push({
+ _id: result.username,
+ username: result.username,
+ status: 'offline',
+ sort: 3
+ });
+ }
+ });
+ records = _.sortBy(records, 'sort');
+ return cb(records);
+ }
+ });
+};
+
+const getRoomsFromServer = (filter, records, cb) => {
+ return Meteor.call('spotlight', filter, null, {
+ rooms: true
+ }, function(err, results) {
+ if (err != null) {
+ return console.error(err);
+ }
+ if (results.rooms.length > 0) {
+ results.rooms.forEach(room => {
+ if (records.length < 5) {
+ records.push(room);
+ }
+ });
+ return cb(records);
+ }
+ });
+};
+
+const getUsersFromServerDelayed = _.throttle(getUsersFromServer, 500);
+
+const getRoomsFromServerDelayed = _.throttle(getRoomsFromServer, 500);
+
+Template.messagePopupConfig.helpers({
+ popupUserConfig() {
+ const self = this;
+ const config = {
+ title: t('People'),
+ collection: filteredUsersMemory,
+ template: 'messagePopupUser',
+ getInput: self.getInput,
+ textFilterDelay: 200,
+ trigger: '@',
+ suffix: ' ',
+ getFilter(collection, filter, cb) {
+ let exp = new RegExp(`${ RegExp.escape(filter) }`, 'i');
+ // Get users from messages
+ const items = filteredUsersMemory.find({
+ ts: {
+ $exists: true
+ },
+ $or: [
+ {
+ username: exp
+ }, {
+ name: exp
+ }
+ ]
+ }, {
+ limit: 5,
+ sort: {
+ ts: -1
+ }
+ }).fetch();
+ // Get online users
+ if (items.length < 5 && filter && filter.trim() !== '') {
+ const messageUsers = _.pluck(items, 'username');
+ const user = Meteor.user();
+ items.push(...Meteor.users.find({
+ $and: [
+ {
+ $or: [
+ {
+ username: exp
+ }, {
+ name: exp
+ }
+ ]
+ }, {
+ username: {
+ $nin: [(user && user.username), ...messageUsers]
+ }
+ }
+ ]
+ }, {
+ limit: 5 - messageUsers.length
+ }).fetch().map(function(item) {
+ return {
+ _id: item.username,
+ username: item.username,
+ name: item.name,
+ status: item.status,
+ sort: 1
+ };
+ }));
+ }
+ // Get users from db
+ if (items.length < 5 && filter && filter.trim() !== '') {
+ getUsersFromServerDelayed(filter, items, cb);
+ }
+ const 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) || exp.test(all.compatibility)) {
+ items.push(all);
+ }
+ const here = {
+ _id: 'here',
+ username: 'here',
+ system: true,
+ name: t('Notify_active_in_this_room'),
+ compatibility: 'channel group',
+ sort: 4
+ };
+ if (exp.test(here.username) || exp.test(here.compatibility)) {
+ items.push(here);
+ }
+ return items;
+ },
+ getValue(_id) {
+ return _id;
+ }
+ };
+ return config;
+ },
+ popupChannelConfig() {
+ const self = this;
+ const config = {
+ title: t('Channels'),
+ collection: RocketChat.models.Subscriptions,
+ trigger: '#',
+ suffix: ' ',
+ template: 'messagePopupChannel',
+ getInput: self.getInput,
+ getFilter(collection, filter, cb) {
+ const exp = new RegExp(filter, 'i');
+ const records = collection.find({
+ name: exp,
+ t: {
+ $in: ['c', 'p']
+ }
+ }, {
+ limit: 5,
+ sort: {
+ ls: -1
+ }
+ }).fetch();
+
+ if (records.length < 5 && filter && filter.trim() !== '') {
+ getRoomsFromServerDelayed(filter, records, cb);
+ }
+ return records;
+ },
+ getValue(_id, collection, records) {
+ const record = _.findWhere(records, {
+ _id
+ });
+ return record && record.name;
+ }
+ };
+ return config;
+ },
+ popupSlashCommandsConfig() {
+ const self = this;
+ const config = {
+ title: t('Commands'),
+ collection: RocketChat.slashCommands.commands,
+ trigger: '/',
+ suffix: ' ',
+ triggerAnywhere: false,
+ template: 'messagePopupSlashCommand',
+ getInput: self.getInput,
+ getFilter(collection, filter) {
+ return Object.keys(collection).map(command => {
+ const item = collection[command];
+ return {
+ _id: command,
+ params: item.params ? TAPi18n.__(item.params) : '',
+ description: TAPi18n.__(item.description)
+ };
+ })
+ .filter(command => command._id.indexOf(filter) > -1)
+ .sort(function(a, b) {
+ return a._id > b._id;
+ })
+ .slice(0, 11);
+ }
+ };
+ return config;
+ },
+ emojiEnabled() {
+ return RocketChat.emoji != null;
+ },
+ popupEmojiConfig() {
+ if (RocketChat.emoji != null) {
+ const self = this;
+ return {
+ title: t('Emoji'),
+ collection: RocketChat.emoji.list,
+ template: 'messagePopupEmoji',
+ trigger: ':',
+ prefix: '',
+ suffix: ' ',
+ getInput: self.getInput,
+ getFilter(collection, filter) {
+ const key = `:${ filter }`;
+
+ if (!RocketChat.emoji.packages.emojione || RocketChat.emoji.packages.emojione.asciiList[key] || filter.length < 2) {
+ return [];
+ }
+
+ const regExp = new RegExp(`^${ RegExp.escape(key) }`, 'i');
+ return Object.keys(collection).map(key => {
+ const value = collection[key];
+ return {
+ _id: key,
+ data: value
+ };
+ })
+ .filter(obj => regExp.test(obj._id))
+ .slice(0, 10)
+ .sort(function(a, b) {
+ if (a._id < b._id) {
+ return -1;
+ }
+ if (a._id > b._id) {
+ return 1;
+ }
+ return 0;
+ });
+ }
+ };
+ }
+ }
+});
diff --git a/packages/rocketchat-ui-message/client/popup/messagePopupEmoji.coffee b/packages/rocketchat-ui-message/client/popup/messagePopupEmoji.coffee
deleted file mode 100644
index d50a31f2c236..000000000000
--- a/packages/rocketchat-ui-message/client/popup/messagePopupEmoji.coffee
+++ /dev/null
@@ -1,4 +0,0 @@
-Template.messagePopupEmoji.helpers
- value: ->
- length = this.data.length
- return this.data[length - 1]
diff --git a/packages/rocketchat-ui-message/client/popup/messagePopupEmoji.js b/packages/rocketchat-ui-message/client/popup/messagePopupEmoji.js
new file mode 100644
index 000000000000..934067a235e7
--- /dev/null
+++ b/packages/rocketchat-ui-message/client/popup/messagePopupEmoji.js
@@ -0,0 +1,6 @@
+Template.messagePopupEmoji.helpers({
+ value() {
+ const length = this.data.length;
+ return this.data[length - 1];
+ }
+});
diff --git a/packages/rocketchat-ui-message/package.js b/packages/rocketchat-ui-message/package.js
index 4d26d912832f..bcf2b9e3ddfd 100644
--- a/packages/rocketchat-ui-message/package.js
+++ b/packages/rocketchat-ui-message/package.js
@@ -15,7 +15,6 @@ Package.onUse(function(api) {
'mongo',
'ecmascript',
'templating',
- 'coffeescript',
'underscore',
'tracker',
'rocketchat:lib',
@@ -33,12 +32,12 @@ 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/messageBox.coffee', 'client');
- api.addFiles('client/popup/messagePopup.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');
- api.addFiles('client/popup/messagePopupConfig.coffee', 'client');
- api.addFiles('client/popup/messagePopupEmoji.coffee', 'client');
+ api.addFiles('client/popup/messagePopupConfig.js', 'client');
+ api.addFiles('client/popup/messagePopupEmoji.js', 'client');
api.addFiles('client/renderMessageBody.js', 'client');