From 4b0deaf891c7e2264116f24209f0372e942f20e0 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 5 Apr 2017 15:23:05 -0300 Subject: [PATCH 1/2] conversion --- .../rocketchat-ui-message/client/message.js | 364 ++++++++++++++ .../client/messageBox.js | 454 ++++++++++++++++++ .../client/popup/messagePopup.js | 285 +++++++++++ .../client/popup/messagePopupConfig.js | 295 ++++++++++++ .../client/popup/messagePopupEmoji.js | 6 + packages/rocketchat-ui-message/package.js | 8 +- 6 files changed, 1408 insertions(+), 4 deletions(-) create mode 100644 packages/rocketchat-ui-message/client/message.js create mode 100644 packages/rocketchat-ui-message/client/messageBox.js create mode 100644 packages/rocketchat-ui-message/client/popup/messagePopup.js create mode 100644 packages/rocketchat-ui-message/client/popup/messagePopupConfig.js create mode 100644 packages/rocketchat-ui-message/client/popup/messagePopupEmoji.js diff --git a/packages/rocketchat-ui-message/client/message.js b/packages/rocketchat-ui-message/client/message.js new file mode 100644 index 000000000000..9129677d94f6 --- /dev/null +++ b/packages/rocketchat-ui-message/client/message.js @@ -0,0 +1,364 @@ +import moment from 'moment'; + +Template.message.helpers({ + 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') && ((ref = this.u) != null ? ref._id : void 0) !== Meteor.userId() && !RocketChat.MessageTypes.isSystemMessage(this)) { + 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 != null ? subscription.autoTranslate : void 0) !== 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 = ((ref = this.u) != null ? ref._id : void 0) === Meteor.userId(); + if (!(hasPermission || (isEditAllowed && editOwn))) { + return; + } + const blockEditInMinutes = RocketChat.settings.get('Message_AllowEditing_BlockEditInMinutes'); + if ((blockEditInMinutes != null) && blockEditInMinutes !== 0) { + if (this.ts != null) { + msgTs = moment(this.ts); + } + if (msgTs != null) { + currentTsDiff = moment().diff(msgTs, 'minutes'); + } + return currentTsDiff < blockEditInMinutes; + } else { + return true; + } + }, + 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(); + if (!(hasPermission || (isDeleteAllowed && deleteOwn))) { + return; + } + blockDeleteInMinutes = RocketChat.settings.get('Message_AllowDeleting_BlockDeleteInMinutes'); + if ((blockDeleteInMinutes != null) && blockDeleteInMinutes !== 0) { + if (this.ts != null) { + msgTs = moment(this.ts); + } + 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() { + let ref, ref1, ref2, ref3; + if (!(((ref = this.urls) != null ? ref.length : void 0) > 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) { + 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()); + 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); + } + msgReactions.push({ + 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) { + 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() { + let subscription; + 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(); + 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) { + 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')) { + msg.html = msg.msg; + msg = RocketChat.callbacks.run('renderMentions', msg); + msg = msg.html; + } else { + msg = renderMessageBody(msg); + } + if (isSystemMessage) { + return RocketChat.Markdown(msg); + } else { + 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); + 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)); + 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 != null ? nextNode.dataset : void 0) != null) { + 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) { + templateInstance = $(`#chat-window-${ context.rid }`)[0] ? (ref = Blaze.getView($(`#chat-window-${ context.rid }`)[0])) != null ? ref.templateInstance() : void 0 : null; + 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; + } + } + }); +}; diff --git a/packages/rocketchat-ui-message/client/messageBox.js b/packages/rocketchat-ui-message/client/messageBox.js new file mode 100644 index 000000000000..f882b3bc0f56 --- /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(); + 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.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.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.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..881c8687a13f 100644 --- a/packages/rocketchat-ui-message/package.js +++ b/packages/rocketchat-ui-message/package.js @@ -34,11 +34,11 @@ Package.onUse(function(api) { 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/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'); From b3f07535b9c42b35ae7b8538b22b4112a91211fd Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 8 May 2017 16:15:57 -0300 Subject: [PATCH 2/2] conversion --- .../client/message.coffee | 254 ------------ .../rocketchat-ui-message/client/message.js | 142 +++---- .../client/messageBox.coffee | 391 ------------------ .../client/messageBox.js | 6 +- .../client/popup/messagePopup.coffee | 282 ------------- .../client/popup/messagePopupConfig.coffee | 235 ----------- .../client/popup/messagePopupEmoji.coffee | 4 - packages/rocketchat-ui-message/package.js | 3 +- 8 files changed, 72 insertions(+), 1245 deletions(-) delete mode 100644 packages/rocketchat-ui-message/client/message.coffee delete mode 100644 packages/rocketchat-ui-message/client/messageBox.coffee delete mode 100644 packages/rocketchat-ui-message/client/popup/messagePopup.coffee delete mode 100644 packages/rocketchat-ui-message/client/popup/messagePopupConfig.coffee delete mode 100644 packages/rocketchat-ui-message/client/popup/messagePopupEmoji.coffee 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 index 9129677d94f6..ae7997d58303 100644 --- a/packages/rocketchat-ui-message/client/message.js +++ b/packages/rocketchat-ui-message/client/message.js @@ -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 '); } } }); 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 index f882b3bc0f56..add30a122142 100644 --- a/packages/rocketchat-ui-message/client/messageBox.js +++ b/packages/rocketchat-ui-message/client/messageBox.js @@ -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) 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/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/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/package.js b/packages/rocketchat-ui-message/package.js index 881c8687a13f..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,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');