From 8cbb367b4336d301a8388091855e16344ccd2eed Mon Sep 17 00:00:00 2001 From: Samuel Borges Date: Sun, 7 Mar 2021 23:27:20 -0300 Subject: [PATCH] adds the funcionality to quote only a fragment of a message by selecting the desired part Co-authored-by: Mikhaelle Bueno --- app/ui-message/client/message.html | 4 +- app/ui-message/client/message.js | 13 +++++ .../messageBox/messageBoxReplyPreview.js | 10 +++- app/ui-utils/client/lib/MessageAction.js | 54 ++++++++++++++++++- app/ui-utils/client/lib/prependReplies.js | 17 +++++- .../Message/Attachments/QuoteAttachment.tsx | 5 ++ .../Message/helpers/getUrlParamsQuote.ts | 15 ++++++ packages/rocketchat-i18n/i18n/en.i18n.json | 1 + packages/rocketchat-i18n/i18n/pt-BR.i18n.json | 1 + 9 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 client/components/Message/helpers/getUrlParamsQuote.ts diff --git a/app/ui-message/client/message.html b/app/ui-message/client/message.html index d207d9c6df56..d7bf6786319d 100644 --- a/app/ui-message/client/message.html +++ b/app/ui-message/client/message.html @@ -90,7 +90,7 @@ {{_ "Message_Ignored"}} {{/if}} -
+
{{#if isSnippet}}
{{_ "Snippet_name"}}: {{snippetName}}
{{/if}} @@ -121,7 +121,7 @@ {{#if msg.drid}} {{> DiscussionMetric count=msg.dcount drid=msg.drid lm=msg.dlm openDiscussion=actions.openDiscussion }} {{/if}} - + {{#if $and settings.showReplyButton msg.tcount}} {{> ThreadMetric counter=msg.tcount following=following lm=msg.tlm rid=msg.rid mid=msg._id unread=unread mention=mention all=all openThread=actions.openThread }} {{/if}} diff --git a/app/ui-message/client/message.js b/app/ui-message/client/message.js index d7e20e547f24..e9b33bebaf6e 100644 --- a/app/ui-message/client/message.js +++ b/app/ui-message/client/message.js @@ -53,6 +53,19 @@ const renderBody = (msg, settings) => { return msg; }; +Template.message.events({ + 'mousedown #selectable'() { + document.getSelection().empty(); + MessageAction.setSelection(); + }, + 'mouseup #selectable'() { + const selected = document.getSelection().toString(); + if (selected.length) { + MessageAction.setSelection(selected); + } + }, +}); + Template.message.helpers({ unread() { const { msg, subscription } = this; diff --git a/app/ui-message/client/messageBox/messageBoxReplyPreview.js b/app/ui-message/client/messageBox/messageBoxReplyPreview.js index a99e60250950..4786f82fb00c 100644 --- a/app/ui-message/client/messageBox/messageBoxReplyPreview.js +++ b/app/ui-message/client/messageBox/messageBoxReplyPreview.js @@ -2,10 +2,18 @@ import { Template } from 'meteor/templating'; import './messageBoxReplyPreview.html'; +const getMessageTextPreview = ({ msg, start, end }) => { + if (start || end) { + return `[...] ${ msg.slice(start, end) } [...]`; + } + return msg; +}; + Template.messageBoxReplyPreview.helpers({ attachments() { const { replyMessageData } = this; - return [{ text: replyMessageData.msg, author_name: replyMessageData.u.username }]; + const message = getMessageTextPreview(replyMessageData); + return [{ text: message, author_name: replyMessageData.u.username }]; }, }); diff --git a/app/ui-utils/client/lib/MessageAction.js b/app/ui-utils/client/lib/MessageAction.js index 6e87cc6a5831..896dbf2b22fd 100644 --- a/app/ui-utils/client/lib/MessageAction.js +++ b/app/ui-utils/client/lib/MessageAction.js @@ -49,6 +49,15 @@ export const MessageAction = new class { constructor() { this.buttons = new ReactiveVar({}); + this.selection = ''; + } + + setSelection(selection = '') { + this.selection = selection; + } + + getSelection() { + return this.selection; } addButton(config) { @@ -150,7 +159,13 @@ export const MessageAction = new class { Meteor.startup(async function() { const { chatMessages } = await import('../../../ui'); + const flushStartEnd = (msg) => { + delete msg.start; + delete msg.end; + }; + const getChatMessagesFrom = (msg) => { + flushStartEnd(msg); const { rid = Session.get('openedRoom'), tmid = msg._id } = msg; return chatMessages[`${ rid }-${ tmid }`] || chatMessages[rid]; @@ -181,6 +196,43 @@ Meteor.startup(async function() { group: 'menu', }); + MessageAction.addButton({ + id: 'quote-message-fragment', + icon: 'quote', + label: 'Quote_fragment', + context: ['message', 'message-mobile', 'threads'], + action() { + const { msg: message } = messageArgs(this); + const { input } = getChatMessagesFrom(message); + const $input = $(input); + const substring = MessageAction.getSelection(); + + message.start = message.msg.indexOf(substring); + message.end = message.start + substring.length; + + let messages = $input.data('reply') || []; + messages = addMessageToList(messages, message, substring); + + $input + .focus() + .data('mention-user', false) + .data('reply', messages) + .trigger('dataChange'); + + MessageAction.setSelection(); + }, + condition({ msg: message, subscription }) { + const substring = MessageAction.getSelection(); + if (subscription == null || !substring || message.msg.indexOf(substring) === -1) { + return false; + } + + return true; + }, + order: -4, + group: ['message', 'menu'], + }); + MessageAction.addButton({ id: 'quote-message', icon: 'quote', @@ -193,7 +245,7 @@ Meteor.startup(async function() { let messages = $input.data('reply') || []; - messages = addMessageToList(messages, message, input); + messages = addMessageToList(messages, message); $input .focus() diff --git a/app/ui-utils/client/lib/prependReplies.js b/app/ui-utils/client/lib/prependReplies.js index eb9b3a8fd5ea..d622506c46d5 100644 --- a/app/ui-utils/client/lib/prependReplies.js +++ b/app/ui-utils/client/lib/prependReplies.js @@ -3,14 +3,27 @@ import { Meteor } from 'meteor/meteor'; import { MessageAction } from './MessageAction'; import { Rooms, Users } from '../../../models/client'; +const getStartEnd = (start, end) => { + let parameters = ''; + if (start !== undefined) { + parameters += `&start=${ start }`; + } + if (end !== undefined) { + parameters += `&end=${ end }`; + } + return parameters; +}; + export const prependReplies = async (msg, replies = [], mention = false) => { const { username } = Users.findOne({ _id: Meteor.userId() }, { fields: { username: 1 } }); - const chunks = await Promise.all(replies.map(async ({ _id, rid, u }) => { + const chunks = await Promise.all(replies.map(async ({ _id, rid, u, start, end }) => { const permalink = await MessageAction.getPermaLink(_id); const room = Rooms.findOne(rid, { fields: { t: 1 } }); - let chunk = `[ ](${ permalink })`; + const query_params = getStartEnd(start, end); + + let chunk = `[ ](${ permalink }${ query_params })`; if (room.t === 'd' && u.username !== username && mention) { chunk += ` @${ u.username }`; } diff --git a/client/components/Message/Attachments/QuoteAttachment.tsx b/client/components/Message/Attachments/QuoteAttachment.tsx index 09c7585aec8f..1c7a266af57a 100644 --- a/client/components/Message/Attachments/QuoteAttachment.tsx +++ b/client/components/Message/Attachments/QuoteAttachment.tsx @@ -6,6 +6,7 @@ import colors from '@rocket.chat/fuselage-tokens/colors'; import { Attachment, AttachmentPropsBase } from './Attachment'; import { useTimeAgo } from '../../../hooks/useTimeAgo'; import MarkdownText from '../../MarkdownText'; +import { findSlice } from '../helpers/getUrlParamsQuote'; import Attachments from '.'; @@ -31,6 +32,10 @@ const hover = css` export const QuoteAttachment: FC = ({ author_icon: url, author_name: name, author_link: authorLink, message_link: messageLink, ts, text, attachments }) => { const format = useTimeAgo(); + const slice = findSlice(messageLink, text); + if (slice) { + text = `[...] ${ slice } [...]`; + } return <> diff --git a/client/components/Message/helpers/getUrlParamsQuote.ts b/client/components/Message/helpers/getUrlParamsQuote.ts new file mode 100644 index 000000000000..46babc11fadc --- /dev/null +++ b/client/components/Message/helpers/getUrlParamsQuote.ts @@ -0,0 +1,15 @@ +export function findSlice(messageUrl: string|undefined, string: string): string { + if (messageUrl !== undefined) { + const url = new URL(messageUrl); + const params = new URLSearchParams(url.search); + + const start = params.get('start'); + const end = params.get('end'); + + return string.slice( + start !== null ? +start : undefined, + end !== null ? +end : undefined, + ); + } + return ''; +} diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 130f642252cf..ff3d2fbc86f9 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -3135,6 +3135,7 @@ "Queue": "Queue", "quote": "quote", "Quote": "Quote", + "Quote_fragment": "Quote Fragment", "Random": "Random", "RD Station": "RD Station", "RDStation_Token": "RD Station Token", diff --git a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index 6a15a31588e6..a5c478af0081 100644 --- a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -2689,6 +2689,7 @@ "Queue": "Fila", "quote": "citação", "Quote": "Citar", + "Quote_fragment": "Citar Fragmento", "Random": "Aleatória", "RD Station": "RD Station", "RDStation_Token": "RD Station Token",