The communications platform that puts data protection first.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
Rocket.Chat/app/ui-utils/client/lib/MessageAction.js

351 lines
8.3 KiB

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',
});
});