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-message/client/messageBox/messageBox.js

516 lines
12 KiB

import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { ReactiveDict } from 'meteor/reactive-dict';
import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import { Tracker } from 'meteor/tracker';
import moment from 'moment';
import { setupAutogrow } from './messageBoxAutogrow';
import {
formattingButtons,
applyFormatting,
} from './messageBoxFormatting';
import { EmojiPicker } from '../../../emoji';
import { Users } from '../../../models';
import { settings } from '../../../settings';
import {
fileUpload,
KonchatNotification,
} from '../../../ui';
import {
messageBox,
popover,
call,
keyCodes,
isRTL,
} from '../../../ui-utils';
import {
t,
roomTypes,
getUserPreference,
} from '../../../utils/client';
import './messageBoxActions';
import './messageBoxReplyPreview';
import './messageBoxTyping';
import './messageBoxAudioMessage';
import './messageBoxNotSubscribed';
import './messageBox.html';
import './messageBoxReadOnly';
Template.messageBox.onCreated(function() {
this.state = new ReactiveDict();
this.popupConfig = new ReactiveVar(null);
this.replyMessageData = new ReactiveVar();
this.isMicrophoneDenied = new ReactiveVar(true);
this.isSendIconVisible = new ReactiveVar(false);
this.set = (value) => {
const { input } = this;
if (!input) {
return;
}
input.value = value;
$(input).trigger('change').trigger('input');
};
this.insertNewLine = () => {
const { input, autogrow } = this;
if (!input) {
return;
}
if (document.selection) {
input.focus();
const sel = document.selection.createRange();
sel.text = '\n';
} else if (input.selectionStart || input.selectionStart === 0) {
const newPosition = input.selectionStart + 1;
const before = input.value.substring(0, input.selectionStart);
const after = input.value.substring(input.selectionEnd, input.value.length);
input.value = `${ before }\n${ after }`;
input.selectionStart = newPosition;
input.selectionEnd = newPosition;
} else {
input.value += '\n';
}
$(input).trigger('change').trigger('input');
input.blur();
input.focus();
autogrow.update();
};
this.send = (event) => {
const { input } = this;
if (!input) {
return;
}
const { autogrow, data: { rid, tmid, onSend, tshow } } = this;
const { value } = input;
this.set('');
if (!onSend) {
return;
}
onSend.call(this.data, event, { rid, tmid, value, tshow }, () => {
autogrow.update();
input.focus();
});
};
});
Template.messageBox.onRendered(function() {
let inputSetup = false;
this.autorun(() => {
const { rid, subscription } = Template.currentData();
const room = Session.get(`roomData${ rid }`);
if (!inputSetup) {
const $input = $(this.find('.js-input-message'));
this.source = $input[0];
if (this.source) {
inputSetup = true;
}
$input.on('dataChange', () => {
const messages = $input.data('reply') || [];
this.replyMessageData.set(messages);
});
}
if (!room) {
return this.state.set({
room: false,
isBlockedOrBlocker: false,
mustJoinWithCode: false,
});
}
const isBlocked = room && room.t === 'd' && subscription && subscription.blocked;
const isBlocker = room && room.t === 'd' && subscription && subscription.blocker;
const isBlockedOrBlocker = isBlocked || isBlocker;
const mustJoinWithCode = !subscription && room.joinCodeRequired;
return this.state.set({
room: false,
isBlockedOrBlocker,
mustJoinWithCode,
});
});
this.autorun(() => {
const { rid, tmid, onInputChanged, onResize } = Template.currentData();
Tracker.afterFlush(() => {
const input = this.find('.js-input-message');
if (this.input === input) {
return;
}
this.input = input;
onInputChanged && onInputChanged(input);
if (input && rid) {
this.popupConfig.set({
rid,
tmid,
getInput: () => input,
});
} else {
this.popupConfig.set(null);
}
if (this.autogrow) {
this.autogrow.destroy();
this.autogrow = null;
}
if (!input) {
return;
}
const shadow = this.find('.js-input-message-shadow');
this.autogrow = setupAutogrow(input, shadow, onResize);
});
});
});
Template.messageBox.onDestroyed(function() {
if (!this.autogrow) {
return;
}
this.autogrow.destroy();
});
Template.messageBox.helpers({
isAnonymousOrMustJoinWithCode() {
const instance = Template.instance();
const { rid } = Template.currentData();
if (!rid) {
return false;
}
const isAnonymous = !Meteor.userId();
return isAnonymous || instance.state.get('mustJoinWithCode');
},
isWritable() {
const { rid, subscription } = Template.currentData();
if (!rid) {
return true;
}
const isBlockedOrBlocker = Template.instance().state.get('isBlockedOrBlocker');
if (isBlockedOrBlocker) {
return false;
}
if (subscription?.onHold) {
return false;
}
const isReadOnly = roomTypes.readOnly(rid, Users.findOne({ _id: Meteor.userId() }, { fields: { username: 1 } }));
const isArchived = roomTypes.archived(rid) || (subscription && subscription.t === 'd' && subscription.archived);
return !isReadOnly && !isArchived;
},
popupConfig() {
return Template.instance().popupConfig.get();
},
input() {
return Template.instance().input;
},
replyMessageData() {
return Template.instance().replyMessageData.get();
},
isEmojiEnabled() {
return getUserPreference(Meteor.userId(), 'useEmojis');
},
maxMessageLength() {
return settings.get('Message_AllowConvertLongMessagesToAttachment') ? null : settings.get('Message_MaxAllowedSize');
},
isSendIconVisible() {
return Template.instance().isSendIconVisible.get();
},
canSend() {
const { rid } = Template.currentData();
if (!rid) {
return true;
}
return roomTypes.verifyCanSendMessage(rid);
},
actions() {
const actionGroups = messageBox.actions.get();
return Object.values(actionGroups)
.reduce((actions, actionGroup) => [...actions, ...actionGroup], []);
},
formattingButtons() {
return formattingButtons.filter(({ condition }) => !condition || condition());
},
isBlockedOrBlocker() {
return Template.instance().state.get('isBlockedOrBlocker');
},
onHold() {
const { rid, subscription } = Template.currentData();
return rid && !!subscription?.onHold;
},
isSubscribed() {
const { subscription } = Template.currentData();
return !!subscription;
},
});
const handleFormattingShortcut = (event, instance) => {
const isMacOS = navigator.platform.indexOf('Mac') !== -1;
const isCmdOrCtrlPressed = (isMacOS && event.metaKey) || (!isMacOS && event.ctrlKey);
if (!isCmdOrCtrlPressed) {
return false;
}
const key = event.key.toLowerCase();
const { pattern } = formattingButtons
.filter(({ condition }) => !condition || condition())
.find(({ command }) => command === key) || {};
if (!pattern) {
return false;
}
const { input } = instance;
applyFormatting(pattern, input);
return true;
};
let sendOnEnter;
let sendOnEnterActive;
Tracker.autorun(() => {
sendOnEnter = getUserPreference(Meteor.userId(), 'sendOnEnter');
sendOnEnterActive = sendOnEnter == null || sendOnEnter === 'normal'
|| (sendOnEnter === 'desktop' && Meteor.Device.isDesktop());
});
const handleSubmit = (event, instance) => {
const { which: keyCode } = event;
const isSubmitKey = keyCode === keyCodes.CARRIAGE_RETURN || keyCode === keyCodes.NEW_LINE;
if (!isSubmitKey) {
return false;
}
const withModifier = event.shiftKey || event.ctrlKey || event.altKey || event.metaKey;
const isSending = (sendOnEnterActive && !withModifier) || (!sendOnEnterActive && withModifier);
if (isSending) {
instance.send(event);
return true;
}
instance.insertNewLine();
return true;
};
Template.messageBox.events({
async 'click .js-join'(event) {
event.stopPropagation();
event.preventDefault();
const joinCodeInput = Template.instance().find('[name=joinCode]');
const joinCode = joinCodeInput && joinCodeInput.value;
await call('joinRoom', this.rid, joinCode);
},
'click .js-emoji-picker'(event, instance) {
event.stopPropagation();
event.preventDefault();
if (!getUserPreference(Meteor.userId(), 'useEmojis')) {
return;
}
if (EmojiPicker.isOpened()) {
EmojiPicker.close();
return;
}
EmojiPicker.open(instance.source, (emoji) => {
const emojiValue = `:${ emoji }: `;
const { input } = instance;
const caretPos = input.selectionStart;
const textAreaTxt = input.value;
input.focus();
if (!document.execCommand || !document.execCommand('insertText', false, emojiValue)) {
instance.set(textAreaTxt.substring(0, caretPos) + emojiValue + textAreaTxt.substring(caretPos));
input.focus();
}
input.selectionStart = caretPos + emojiValue.length;
input.selectionEnd = caretPos + emojiValue.length;
});
},
'focus .js-input-message'() {
KonchatNotification.removeRoomNotification(this.rid);
},
'keydown .js-input-message'(event, instance) {
const isEventHandled = handleFormattingShortcut(event, instance) || handleSubmit(event, instance);
if (isEventHandled) {
event.preventDefault();
event.stopPropagation();
return;
}
const { rid, tmid, onKeyDown } = this;
onKeyDown && onKeyDown.call(this, event, { rid, tmid });
},
'keyup .js-input-message'(event) {
const { rid, tmid, onKeyUp } = this;
onKeyUp && onKeyUp.call(this, event, { rid, tmid });
},
'paste .js-input-message'(event, instance) {
const { rid, tmid } = this;
const { input, autogrow } = instance;
setTimeout(() => autogrow && autogrow.update(), 50);
if (!event.originalEvent.clipboardData) {
return;
}
const items = [...event.originalEvent.clipboardData.items];
if (items.some(({ kind, type }) => kind === 'string' && type === 'text/plain')) {
return;
}
const files = items
.filter((item) => item.kind === 'file' && item.type.indexOf('image/') !== -1)
.map((item) => ({
file: item.getAsFile(),
name: `Clipboard - ${ moment().format(settings.get('Message_TimeAndDateFormat')) }`,
}))
.filter(({ file }) => file !== null);
if (files.length) {
event.preventDefault();
fileUpload(files, input, { rid, tmid });
}
},
'input .js-input-message'(event, instance) {
const { input } = instance;
if (!input) {
return;
}
instance.isSendIconVisible.set(!!input.value);
if (input.value.length > 0) {
input.dir = isRTL(input.value) ? 'rtl' : 'ltr';
}
const { rid, tmid, onValueChanged } = this;
onValueChanged && onValueChanged.call(this, event, { rid, tmid });
},
'propertychange .js-input-message'(event, instance) {
if (event.originalEvent.propertyName !== 'value') {
return;
}
const { input } = instance;
if (!input) {
return;
}
instance.sendIconDisabled.set(!!input.value);
if (input.value.length > 0) {
input.dir = isRTL(input.value) ? 'rtl' : 'ltr';
}
const { rid, tmid, onValueChanged } = this;
onValueChanged && onValueChanged.call(this, event, { rid, tmid });
},
async 'click .js-send'(event, instance) {
instance.send(event);
},
'click .js-action-menu'(event, instance) {
const groups = messageBox.actions.get();
const config = {
popoverClass: 'message-box',
columns: [
{
groups: Object.keys(groups).map((group) => {
const items = [];
groups[group].forEach((item) => {
items.push({
icon: item.icon,
name: t(item.label),
type: 'messagebox-action',
id: item.id,
});
});
return {
title: t(group),
items,
};
}),
},
],
offsetVertical: 10,
direction: 'top-inverted',
currentTarget: event.currentTarget.firstElementChild.firstElementChild,
data: {
rid: this.rid,
tmid: this.tmid,
messageBox: instance.firstNode,
},
activeElement: event.currentTarget,
};
popover.open(config);
},
'click .js-message-actions .js-message-action'(event, instance) {
const { id } = event.currentTarget.dataset;
const actions = messageBox.actions.getById(id);
actions
.filter(({ action }) => !!action)
.forEach(({ action }) => {
action.call(null, {
rid: this.rid,
tmid: this.tmid,
messageBox: instance.firstNode,
event,
});
});
},
'click .js-format'(event, instance) {
event.preventDefault();
event.stopPropagation();
const { id } = event.currentTarget.dataset;
const { pattern } = formattingButtons
.filter(({ condition }) => !condition || condition())
.find(({ label }) => label === id) || {};
if (!pattern) {
return;
}
applyFormatting(pattern, instance.input);
},
});