Rewrite : Message Thread metrics (#20051)

pull/20117/head
Guilherme Gazzo 4 years ago committed by GitHub
parent 201a8fb7bf
commit 5f79f37bbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      app/ui-message/client/message.html
  2. 32
      app/ui-message/client/message.js
  3. 20
      app/ui-utils/client/lib/messageContext.js
  4. 2
      app/ui/client/views/app/room.html
  5. 3
      client/adapters.js
  6. 30
      client/components/Message/Message.stories.js
  7. 60
      client/components/Message/Metrics/Thread.tsx
  8. 0
      client/components/Message/NotificationStatus.js
  9. 15
      client/components/Message/helpers/followSyle.js
  10. 29
      client/components/Message/index.tsx
  11. 1
      client/main.js
  12. 2
      client/views/room/components/Message.js
  13. 17
      client/views/room/contextualBar/Threads/components/Message.js

@ -141,26 +141,8 @@
</div>
{{/if}}
{{#if $and settings.showReplyButton msg.tcount}}
<div class="message-thread">
<button class="js-open-thread rc-button rc-button--primary rc-button--small" data-rid={{roomId}}>
{{> icon icon="thread"}}
{{_ 'Reply'}}
</button>
<span class='reply-counter'>{{_ i18nReplyCounter counter=msg.tcount }}</span>
<span class="discussion-reply-lm">{{ formatDateAndTime msg.tlm}}</span>
{{# if unread }}
<div title="{{_ 'Unread' }}" class="message-unread {{class}}"></div>
{{/if}}
{{# if following }}
<div role="button" class="js-unfollow-thread" title="{{_ "Following"}}">
{{> icon icon="bell" block="thread-icons"}}
</div>
{{else}}
<div role="button" class="js-follow-thread" title="{{_ "Not_following"}}">
{{> icon icon="bell-off" block="thread-icons"}}
</div>
{{/if}}
</div>
{{> ThreadReply counter=msg.tcount following=following lm=msg.tlm rid=msg.rid mid=msg._id unread=unread mention=mention all=all openThread=actions.openThread }}
{{/if}}
{{#with readReceipt}}

@ -54,6 +54,19 @@ const renderBody = (msg, settings) => {
};
Template.message.helpers({
unread() {
const { msg, subscription } = this;
return subscription?.tunread?.includes(msg._id);
},
mention() {
const { msg, subscription } = this;
return subscription.tunreadUser?.includes(msg._id);
},
all() {
const { msg, subscription } = this;
return subscription.tunreadGroup?.includes(msg._id);
},
following() {
const { msg, u } = this;
return msg.replies && msg.replies.indexOf(u._id) > -1;
@ -218,25 +231,6 @@ Template.message.helpers({
return 'system';
}
},
unread() {
const { msg, subscription } = this;
if (!subscription?.tunread?.includes(msg._id)) {
return false;
}
const badgeClass = (() => {
if (subscription.tunreadUser?.includes(msg._id)) {
return 'badge--user-mentions';
}
if (subscription.tunreadGroup?.includes(msg._id)) {
return 'badge--group-mentions';
}
})();
return {
class: badgeClass,
};
},
showTranslated() {
const { msg, subscription, settings, u } = this;
if (settings.AutoTranslate_Enabled && msg.u && msg.u._id !== u._id && !MessageTypes.isSystemMessage(msg)) {

@ -1,5 +1,6 @@
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Subscriptions, Rooms, Users } from '../../../models/client';
import { hasPermission } from '../../../authorization/client';
@ -12,6 +13,20 @@ const fields = { name: 1, username: 1, 'settings.preferences.showMessageInMainTh
export function messageContext({ rid } = Template.instance()) {
const uid = Meteor.userId();
const user = Users.findOne({ _id: uid }, { fields }) || {};
const openThread = (e) => {
const { rid, mid, tmid } = e.currentTarget.dataset;
const room = Rooms.findOne({ _id: rid });
FlowRouter.go(FlowRouter.getRouteName(), {
rid,
name: room.name,
tab: 'thread',
context: tmid || mid,
}, {
jump: tmid && tmid !== mid && mid && mid,
});
};
return {
u: user,
room: Rooms.findOne({ _id: rid }, {
@ -31,6 +46,11 @@ export function messageContext({ rid } = Template.instance()) {
tunreadGroup: 1,
},
}),
actions: {
openThread() {
return openThread;
},
},
settings: {
translateLanguage: AutoTranslate.getLanguage(rid),
showMessageInMainThread: getUserPreference(user, 'showMessageInMainThread'),

@ -119,7 +119,7 @@
{{/if}}
{{# with messageContext}}
{{#each msg in messagesHistory}}{{> message showRoles=true index=@index shouldCollapseReplies=false msg=msg room=room subscription=subscription settings=settings u=u}}{{/each}}
{{#each msg in messagesHistory}}{{> message showRoles=true index=@index shouldCollapseReplies=false msg=msg room=room actions=actions subscription=subscription settings=settings u=u}}{{/each}}
{{/with}}
{{#if hasMoreNext}}

@ -0,0 +1,3 @@
const { createTemplateForComponent } = require('./reactAdapters');
createTemplateForComponent('ThreadReply', () => import('./components/Message/Metrics/Thread'));

@ -0,0 +1,30 @@
import React from 'react';
import Metrics, { Reply, Content } from '.';
export default {
title: 'components/Message/Metrics',
component: Metrics,
};
export const Default = () => <Content>
<Reply/>
<Metrics>
<Metrics.Item>
<Metrics.Item.Icon name='thread'/>
<Metrics.Item.Label>3</Metrics.Item.Label>
</Metrics.Item>
<Metrics.Item>
<Metrics.Item.Icon name='user'/>
<Metrics.Item.Label>3</Metrics.Item.Label>
</Metrics.Item>
<Metrics.Item>
<Metrics.Item.Icon name='clock'/>
<Metrics.Item.Label>sunday</Metrics.Item.Label>
</Metrics.Item>
<Metrics.Item>
<Metrics.Item.Icon name='bell'/>
<Metrics.Item.Label>3</Metrics.Item.Label>
</Metrics.Item>
</Metrics>
</Content>;

@ -0,0 +1,60 @@
import React, { useCallback, FC } from 'react';
import { useEndpoint } from '../../../contexts/ServerContext';
import { useTranslation } from '../../../contexts/TranslationContext';
import { useTimeAgo } from '../../../hooks/useTimeAgo';
import * as NotificationStatus from '../NotificationStatus';
import { followStyle, anchor } from '../helpers/followSyle';
import Metrics, { Reply, Content } from '..';
type ThreadReplyOptions = {
unread: boolean;
mention: boolean;
all: boolean; lm:
Date;
mid: string;
rid: string;
counter: number;
participants: number;
following: boolean;
openThread: () => any;
};
const ReplyBlock: FC<ThreadReplyOptions> = ({ unread, mention, all, rid, mid, counter, participants, following, lm, openThread }) => {
const t = useTranslation();
const followMessage = useEndpoint('POST', 'chat.followMessage');
const unfollowMessage = useEndpoint('POST', 'chat.unfollowMessage');
const format = useTimeAgo();
const handleFollow = useCallback(() => (following ? unfollowMessage({ mid }) : followMessage({ mid })), [followMessage, following, mid, unfollowMessage]);
return <Content className={followStyle}>
<Reply data-rid={rid} data-mid={mid} onClick={openThread} />
<Metrics>
<Metrics.Item title={t('Replies')}>
<Metrics.Item.Icon name='thread'/>
<Metrics.Item.Label>{counter}</Metrics.Item.Label>
</Metrics.Item>
{ participants && <Metrics.Item title={t('Participants')}>
<Metrics.Item.Icon name='user'/>
<Metrics.Item.Label>{participants}</Metrics.Item.Label>
</Metrics.Item> }
<Metrics.Item>
<Metrics.Item.Icon name='clock'/>
<Metrics.Item.Label>{format(lm)}</Metrics.Item.Label>
</Metrics.Item>
<Metrics.Item className={!following ? anchor : undefined} title={t(following ? 'Following' : 'Not_following')} data-rid={rid} onClick={handleFollow}>
<Metrics.Following name={following ? 'bell' : 'bell-off'}/>
<Metrics.Item.Label>{
(mention && <NotificationStatus.Me t={t} />)
|| (all && <NotificationStatus.All t={t} />)
|| (unread && <NotificationStatus.Unread t={t} />)}
</Metrics.Item.Label>
</Metrics.Item>
</Metrics>
</Content>;
};
export default ReplyBlock;

@ -0,0 +1,15 @@
import { css } from '@rocket.chat/css-in-js';
export const anchor = 'rcx-contextual-message__follow';
export const followStyle = css`
& .${ anchor } {
opacity: 0;
}
.${ anchor }:focus,
&:hover .${ anchor },
&:focus .${ anchor } {
opacity: 1;
}
`;

@ -0,0 +1,29 @@
import React, { FC } from 'react';
import { ActionButton, Box, BoxProps, Button, ButtonProps, Icon } from '@rocket.chat/fuselage';
import { useTranslation } from '../../contexts/TranslationContext';
export const Content: FC<BoxProps> = (props) => <Box display='flex' mb='x4' mi='neg-x4' {...props} />;
const ContentItem: FC = (props) => <Box display='flex' mb='x4' mi='x4' {...props} />;
export const Reply: FC<ButtonProps> = (props) => {
const t = useTranslation();
return <ContentItem><Button {...props} small primary>{t('Reply')}</Button></ContentItem>;
};
type IconProps = { name: 'thread' | 'user' | 'clock' | 'discussion' };
type FollowingProps = { name: 'bell' | 'bell-off' };
const MetricsItemIcon: FC<IconProps> = (props) => <Icon size='x20' {...props} />;
const MetricsItemLabel: FC<BoxProps> = (props) => <Box mis='x4'{...props} />;
const MetricsItem: FC<BoxProps> & { Icon: FC<IconProps>; Label: FC<BoxProps> } = (props) => <Box display='flex' justifyContent='center' alignItems='center' fontScale='micro' color='info' mi='x4'{...props} />;
const Metrics: FC<BoxProps> & { Item: FC<BoxProps> & { Icon: FC<IconProps>; Label: FC }; Following: FC<FollowingProps> } = (props) => <ContentItem><Box display='flex' mi='neg-x4' {...props} /></ContentItem>;
const MetricsFollowing: FC<FollowingProps> = ({ name }) => <ActionButton color='info' small ghost icon={name} />;
Metrics.Item = MetricsItem;
Metrics.Following = MetricsFollowing;
MetricsItem.Label = MetricsItemLabel;
MetricsItem.Icon = MetricsItemIcon;
export default Metrics;

@ -21,3 +21,4 @@ import './startup';
import './views/admin';
import './views/login';
import './views/room/adapters';
import './adapters';

@ -48,7 +48,7 @@ function isIterable(obj) {
}
export function Message({ className, ...props }) {
return <Box rcx-contextual-message pi='x20' pb='x16' pbs='x16' display='flex' {...props} className={[...isIterable(className) ? className : [className]].filter(Boolean)}/>;
return <Box rcx-message pi='x20' pb='x16' pbs='x16' display='flex' {...props} className={[...isIterable(className) ? className : [className]].filter(Boolean)}/>;
}
export default Message;

@ -1,11 +1,11 @@
import React from 'react';
import { Box, Button, Icon } from '@rocket.chat/fuselage';
import { css } from '@rocket.chat/css-in-js';
import UserAvatar from '../../../../../components/avatar/UserAvatar';
import RawText from '../../../../../components/RawText';
import * as MessageTemplate from '../../../components/Message';
import * as NotificationStatus from '../../../components/NotificationStatus';
import * as NotificationStatus from '../../../../../components/Message/NotificationStatus';
import { followStyle, anchor } from '../../../../../components/Message/helpers/followSyle';
function isIterable(obj) {
// checks for null and undefined
@ -15,17 +15,6 @@ function isIterable(obj) {
return typeof obj[Symbol.iterator] === 'function';
}
const followStyle = css`
& > .rcx-message__container > .rcx-contextual-message__follow {
opacity: 0;
}
.rcx-contextual-message__follow:focus,
&:hover > .rcx-message__container > .rcx-contextual-message__follow,
&:focus > .rcx-message__container > .rcx-contextual-message__follow {
opacity: 1;
}
`;
export default React.memo(function Message({ _id, msg, following, username, name = username, ts, replies, participants, handleFollowButton, unread, mention, all, t = (e) => e, formatDate = (e) => e, tlm, className = [], ...props }) {
const button = !following ? 'bell-off' : 'bell';
const actionLabel = t(!following ? 'Not_Following' : 'Following');
@ -47,7 +36,7 @@ export default React.memo(function Message({ _id, msg, following, username, name
</Box>
</MessageTemplate.Container>
<MessageTemplate.Container alignItems='center'>
<Button rcx-contextual-message__follow small square flexShrink={0} ghost data-following={following} data-id={_id} onClick={handleFollowButton} title={actionLabel} aria-label={actionLabel}><Icon name={button} size='x20'/></Button>
<Button className={anchor} small square flexShrink={0} ghost data-following={following} data-id={_id} onClick={handleFollowButton} title={actionLabel} aria-label={actionLabel}><Icon name={button} size='x20'/></Button>
{
(mention && <NotificationStatus.Me t={t} mb='x24'/>)
|| (all && <NotificationStatus.All t={t} mb='x24'/>)

Loading…
Cancel
Save