import _ from 'underscore'; import { FlowRouter } from 'meteor/kadira:flow-router'; import moment from 'moment'; import toastr from 'toastr'; import mem from 'mem'; import { Meteor } from 'meteor/meteor'; import { TAPi18n } from 'meteor/tap:i18n'; import { ReactiveVar } from 'meteor/reactive-var'; import { Tracker } from 'meteor/tracker'; import { Session } from 'meteor/session'; import { messageArgs } from './messageArgs'; import { roomTypes, canDeleteMessage } from '../../../utils/client'; import { Messages, Rooms, Subscriptions } from '../../../models/client'; import { hasAtLeastOnePermission } from '../../../authorization/client'; import { modal } from './modal'; const call = (method, ...args) => new Promise((resolve, reject) => { Meteor.call(method, ...args, function(err, data) { if (err) { return reject(err); } resolve(data); }); }); export const addMessageToList = (messagesList, message) => { // checks if the message is not already on the list if (!messagesList.find(({ _id }) => _id === message._id)) { messagesList.push(message); } return messagesList; }; export const MessageAction = new class { /* config expects the following keys (only id is mandatory): id (mandatory) icon: string label: string action: function(event, instance) condition: function(message) order: integer group: string (message or menu) */ constructor() { this.buttons = new ReactiveVar({}); } addButton(config) { if (!config || !config.id) { return false; } if (!config.group) { config.group = 'menu'; } if (config.condition) { config.condition = mem(config.condition, { maxAge: 1000 }); } return Tracker.nonreactive(() => { const btns = this.buttons.get(); btns[config.id] = config; return this.buttons.set(btns); }); } removeButton(id) { return Tracker.nonreactive(() => { const btns = this.buttons.get(); delete btns[id]; return this.buttons.set(btns); }); } updateButton(id, config) { return Tracker.nonreactive(() => { const btns = this.buttons.get(); if (btns[id]) { btns[id] = _.extend(btns[id], config); return this.buttons.set(btns); } }); } getButtonById(id) { const allButtons = this.buttons.get(); return allButtons[id]; } _getButtons = mem(function() { return _.sortBy(_.toArray(this.buttons.get()), 'order'); }, { maxAge: 100 }) getButtons(message, context, group) { let allButtons = this._getButtons(); if (group) { allButtons = allButtons.filter((button) => button.group === group); } if (message) { return allButtons.filter(function(button) { if (button.context == null || button.context.includes(context)) { return button.condition == null || button.condition(message, context); } return false; }); } return allButtons; } resetButtons() { return this.buttons.set({}); } async getPermaLink(msgId) { if (!msgId) { throw new Error('invalid-parameter'); } const msg = Messages.findOne(msgId) || await call('getSingleMessage', msgId); if (!msg) { throw new Error('message-not-found'); } const roomData = Rooms.findOne({ _id: msg.rid, }); if (!roomData) { throw new Error('room-not-found'); } const subData = Subscriptions.findOne({ rid: roomData._id, 'u._id': Meteor.userId() }); const roomURL = roomTypes.getURL(roomData.t, subData || roomData); return `${ roomURL }?msg=${ msgId }`; } }(); Meteor.startup(async function() { const { chatMessages } = await import('../../../ui'); MessageAction.addButton({ id: 'reply-directly', icon: 'reply-directly', label: 'Reply_in_direct_message', context: ['message', 'message-mobile', 'threads'], action() { const { msg } = messageArgs(this); roomTypes.openRouteLink('d', { name: msg.u.username }, { ...FlowRouter.current().queryParams, reply: msg._id, }); }, condition({ subscription, room }) { if (subscription == null) { return false; } if (room.t === 'd') { return false; } return true; }, order: 2, group: 'menu', }); MessageAction.addButton({ id: 'quote-message', icon: 'quote', label: 'Quote', context: ['message', 'message-mobile', 'threads'], action() { const { msg: message } = messageArgs(this); const { input } = chatMessages[message.rid]; const $input = $(input); let messages = $input.data('reply') || []; messages = addMessageToList(messages, message, input); $input .focus() .data('mention-user', false) .data('reply', messages) .trigger('dataChange'); }, condition({ subscription }) { if (subscription == null) { return false; } return true; }, order: 3, group: 'menu', }); MessageAction.addButton({ id: 'permalink', icon: 'permalink', label: 'Get_link', classes: 'clipboard', context: ['message', 'message-mobile', 'threads'], async action(event) { const { msg: message } = messageArgs(this); const permalink = await MessageAction.getPermaLink(message._id); $(event.currentTarget).attr('data-clipboard-text', permalink); toastr.success(TAPi18n.__('Copied')); }, condition({ subscription }) { if (subscription == null) { return false; } return true; }, order: 4, group: 'menu', }); MessageAction.addButton({ id: 'copy', icon: 'copy', label: 'Copy', classes: 'clipboard', context: ['message', 'message-mobile', 'threads'], action(event) { const { msg: { msg } } = messageArgs(this); $(event.currentTarget).attr('data-clipboard-text', msg); toastr.success(TAPi18n.__('Copied')); }, condition({ subscription }) { return !!subscription; }, order: 5, group: 'menu', }); MessageAction.addButton({ id: 'edit-message', icon: 'edit', label: 'Edit', context: ['message', 'message-mobile', 'threads'], action() { const { msg } = messageArgs(this); chatMessages[Session.get('openedRoom')].edit(document.getElementById(msg._id)); }, condition({ msg: message, subscription, settings }) { if (subscription == null) { return false; } const hasPermission = hasAtLeastOnePermission('edit-message', message.rid); const isEditAllowed = settings.Message_AllowEditing; const editOwn = message.u && message.u._id === Meteor.userId(); if (!(hasPermission || (isEditAllowed && editOwn))) { return; } const blockEditInMinutes = settings.Message_AllowEditing_BlockEditInMinutes; if (blockEditInMinutes) { let msgTs; if (message.ts != null) { msgTs = moment(message.ts); } let currentTsDiff; if (msgTs != null) { currentTsDiff = moment().diff(msgTs, 'minutes'); } return currentTsDiff < blockEditInMinutes; } return true; }, order: 6, group: 'menu', }); MessageAction.addButton({ id: 'delete-message', icon: 'trash', label: 'Delete', context: ['message', 'message-mobile', 'threads'], color: 'alert', action() { const { msg: message } = messageArgs(this); chatMessages[Session.get('openedRoom')].confirmDeleteMsg(message); }, condition({ msg: message, subscription }) { if (!subscription) { return false; } return canDeleteMessage({ rid: message.rid, ts: message.ts, uid: message.u._id, }); }, order: 18, group: 'menu', }); MessageAction.addButton({ id: 'report-message', icon: 'report', label: 'Report', context: ['message', 'message-mobile', 'threads'], color: 'alert', action() { const { msg: message } = messageArgs(this); modal.open({ title: TAPi18n.__('Report_this_message_question_mark'), text: message.msg, inputPlaceholder: TAPi18n.__('Why_do_you_want_to_report_question_mark'), type: 'input', showCancelButton: true, confirmButtonColor: '#DD6B55', confirmButtonText: TAPi18n.__('Report_exclamation_mark'), cancelButtonText: TAPi18n.__('Cancel'), closeOnConfirm: false, html: false, }, (inputValue) => { if (inputValue === false) { return false; } if (inputValue === '') { modal.showInputError(TAPi18n.__('You_need_to_write_something')); return false; } Meteor.call('reportMessage', message._id, inputValue); modal.open({ title: TAPi18n.__('Report_sent'), text: TAPi18n.__('Thank_you_exclamation_mark '), type: 'success', timer: 1000, showConfirmButton: false, }); }); }, condition({ subscription }) { return Boolean(subscription); }, order: 17, group: 'menu', }); });