diff --git a/app/discussion/client/public/stylesheets/discussion.css b/app/discussion/client/public/stylesheets/discussion.css index b4dc9737f02a1..14083f8d0686d 100644 --- a/app/discussion/client/public/stylesheets/discussion.css +++ b/app/discussion/client/public/stylesheets/discussion.css @@ -7,17 +7,26 @@ flex-wrap: wrap; } -.discussion-reply-lm { - padding: 4px 8px; +.discussion-reply-lm, +.reply-counter { + padding: 4px; color: var(--color-gray); font-size: 12px; + font-style: italic; flex-grow: 0; flex-shrink: 0; } +.reply-counter { + color: var(--color-dark-light); + + font-weight: 600; + margin-inline-start: 4px; +} + .discussions-list .load-more { text-align: center; text-transform: lowercase; diff --git a/app/models/server/models/Subscriptions.js b/app/models/server/models/Subscriptions.js index 460d6b20bdaa1..c98ebdd695238 100644 --- a/app/models/server/models/Subscriptions.js +++ b/app/models/server/models/Subscriptions.js @@ -1365,16 +1365,19 @@ export class Subscriptions extends Base { // ////////////////////////////////////////////////////////////////// // threads - addUnreadThreadByRoomIdAndUserIds(rid, users, tmid) { + addUnreadThreadByRoomIdAndUserIds(rid, users, tmid, { groupMention = false, userMention = false } = {}) { if (!users) { return; } + return this.update({ 'u._id': { $in: users }, rid, }, { $addToSet: { tunread: tmid, + ...groupMention && { tunreadGroup: tmid }, + ...userMention && { tunreadUser: tmid }, }, }, { multi: true }); } @@ -1386,6 +1389,8 @@ export class Subscriptions extends Base { }, { $pull: { tunread: tmid, + tunreadGroup: tmid, + tunreadUser: tmid, }, }); } diff --git a/app/theme/client/imports/components/header.css b/app/theme/client/imports/components/header.css index e2217d6c715c9..1d70c1acde2de 100644 --- a/app/theme/client/imports/components/header.css +++ b/app/theme/client/imports/components/header.css @@ -18,13 +18,21 @@ color: white; border-radius: calc(4 * var(--badge-font-size)); - background: var(--rc-color-button-primary); + background-color: var(--rc-color-button-primary); box-shadow: 0 0 0 2px #ffffff; font-size: var(--badge-font-size); font-weight: 600; align-items: center; justify-content: center; + + &--user-mentions { + background-color: var(--badge-user-mentions-background); + } + + &--group-mentions { + background-color: var(--badge-group-mentions-background); + } } &__first-icon { diff --git a/app/theme/client/imports/components/sidebar/sidebar-item.css b/app/theme/client/imports/components/sidebar/sidebar-item.css index 17771d9a3081c..a677e655325d0 100644 --- a/app/theme/client/imports/components/sidebar/sidebar-item.css +++ b/app/theme/client/imports/components/sidebar/sidebar-item.css @@ -101,8 +101,8 @@ background-color: var(--sidebar-item-active-background); } - &--unread, - &--mention { + &--unread &__message-top, + &--mention &__message-top { color: var(--sidebar-item-unread-color); font-weight: var(--sidebar-item-unread-font-weight); @@ -274,6 +274,12 @@ font-size: 12px; line-height: normal; + + &--unread { + color: var(--sidebar-item-unread-color); + + font-weight: var(--sidebar-item-unread-font-weight); + } } &__time { diff --git a/app/threads/client/components/ThreadList.js b/app/threads/client/components/ThreadList.js index 6f7a136f86085..13ea603df54ef 100644 --- a/app/threads/client/components/ThreadList.js +++ b/app/threads/client/components/ThreadList.js @@ -14,7 +14,7 @@ import RawText from '../../../../client/components/basic/RawText'; import { useRoute } from '../../../../client/contexts/RouterContext'; import { roomTypes } from '../../../utils/client'; import { call, renderMessageBody } from '../../../ui-utils/client'; -import { useUserId, useUser } from '../../../../client/contexts/UserContext'; +import { useUserId } from '../../../../client/contexts/UserContext'; import { Messages } from '../../../models/client'; import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../../../client/hooks/useEndpointDataExperimental'; import { getConfig } from '../../../ui-utils/client/config'; @@ -51,7 +51,7 @@ const LIST_SIZE = parseInt(getConfig('threadsListSize')) || 25; const filterProps = ({ msg, u, replies, mentions, tcount, ts, _id, tlm, attachments }) => ({ ..._id && { _id }, attachments, mentions, msg, u, replies, tcount, ts: new Date(ts), tlm: new Date(tlm) }); -const subscriptionFields = { tunread: 1 }; +const subscriptionFields = { tunread: 1, tunreadUser: 1, tunreadGroup: 1 }; const roomFields = { t: 1, name: 1 }; export function withData(WrappedComponent) { @@ -128,7 +128,9 @@ export function withData(WrappedComponent) { return { } }; -export function ThreadList({ total = 10, threads = [], room, unread = [], type, setType, loadMoreItems, loading, onClose, error, userId, text, setText }) { +export function ThreadList({ total = 10, threads = [], room, unread = [], unreadUser = [], unreadGroup = [], type, setType, loadMoreItems, loading, onClose, error, userId, text, setText }) { const showRealNames = useSetting('UI_Use_Real_Name'); const threadsRef = useRef(); const t = useTranslation(); - const user = useUser(); - const channelRoute = useRoute(roomTypes.getConfig(room.t).route.name); const onClick = useCallback((e) => { @@ -209,8 +209,8 @@ export function ThreadList({ total = 10, threads = [], room, unread = [], type, username={ thread.u.username } style={style} unread={unread.includes(thread._id)} - mention={thread.mentions && thread.mentions.includes(user.username)} - all={thread.mentions && thread.mentions.includes('all')} + mention={unreadUser.includes(thread._id)} + all={unreadGroup.includes(thread._id)} following={thread.replies && thread.replies.includes(userId)} data-id={thread._id} msg={msg} @@ -218,7 +218,7 @@ export function ThreadList({ total = 10, threads = [], room, unread = [], type, formatDate={formatDate} handleFollowButton={handleFollowButton} onClick={onClick} />; - }), [unread, showRealNames]); + }), [unread, unreadUser, unreadGroup, showRealNames]); const isItemLoaded = useCallback((index) => index < threadsRef.current.length, []); const { ref, contentBoxSize: { inlineSize = 378, blockSize = 750 } = {} } = useResizeObserver(); diff --git a/app/threads/client/components/ThreadListMessage.js b/app/threads/client/components/ThreadListMessage.js index 9b9250315c518..4fe07ce40113e 100644 --- a/app/threads/client/components/ThreadListMessage.js +++ b/app/threads/client/components/ThreadListMessage.js @@ -67,7 +67,7 @@ export default function ThreadListMessage({ _id, msg, following, username, name, - + { (mention && ) || (all && ) diff --git a/app/threads/client/components/hooks/useLocalstorage.js b/app/threads/client/components/hooks/useLocalstorage.js index 59fe97a598d5a..1fa0c81c66ff5 100644 --- a/app/threads/client/components/hooks/useLocalstorage.js +++ b/app/threads/client/components/hooks/useLocalstorage.js @@ -32,7 +32,7 @@ export function useLocalStorage(key, initialValue) { } window.addEventListener('storage', handleEvent); return () => window.removeEventListener('storage', handleEvent); - }, []); + }, [key]); return [storedValue, setValue]; } diff --git a/app/threads/client/flextab/threadlist.js b/app/threads/client/flextab/threadlist.js index 5b72beb019792..ea7653a861e28 100644 --- a/app/threads/client/flextab/threadlist.js +++ b/app/threads/client/flextab/threadlist.js @@ -13,10 +13,24 @@ Meteor.startup(function() { icon: 'thread', template: 'threads', badge: () => { - const subscription = Subscriptions.findOne({ rid: Session.get('openedRoom') }, { fields: { tunread: 1 } }); - if (subscription) { - return subscription.tunread && subscription.tunread.length && { body: subscription.tunread.length > 99 ? '99+' : subscription.tunread.length }; + const subscription = Subscriptions.findOne({ rid: Session.get('openedRoom') }, { fields: { tunread: 1, tunreadUser: 1, tunreadGroup: 1 } }); + if (!subscription?.tunread?.length) { + return; } + + const badgeClass = (() => { + if (subscription.tunreadUser?.length > 0) { + return 'rc-badge--user-mentions'; + } + if (subscription.tunreadGroup?.length > 0) { + return 'rc-badge--group-mentions'; + } + })(); + + return { + body: subscription.tunread.length > 99 ? '99+' : subscription.tunread.length, + class: badgeClass, + }; }, order: 2, }); diff --git a/app/threads/client/threads.css b/app/threads/client/threads.css index 618f2fc2ccac9..d6d81e046fe5f 100644 --- a/app/threads/client/threads.css +++ b/app/threads/client/threads.css @@ -75,6 +75,15 @@ flex-flow: row nowrap; } + &:hover, + &:focus { + .thread-icons { + &--bell-off { + opacity: 1; + } + } + } + .thread-icons { display: block; flex: 0 0 20px; @@ -90,13 +99,21 @@ color: var(--rc-color-alert-message-primary); } + &--bell-off { + opacity: 0; + } + &--bell, &--bell-off { cursor: pointer; - color: #a0a0a0; + color: #6c727a; + + font-size: 1.25rem; + } - font-size: 1rem; + &--large { + font-size: 1.25rem; } } @@ -108,6 +125,13 @@ font-size: 12px; align-items: center; + + .thread-icons { + &--bell, + &--bell-off { + font-size: 1rem; + } + } } .thread-quote__message { diff --git a/app/threads/server/functions.js b/app/threads/server/functions.js index 979f56cd21c96..f9d3d3fff81a0 100644 --- a/app/threads/server/functions.js +++ b/app/threads/server/functions.js @@ -7,7 +7,7 @@ export const reply = ({ tmid }, message, parentMessage, followers) => { return false; } - const { mentionIds } = getMentions(message); + const { toAll, toHere, mentionIds } = getMentions(message); const addToReplies = [ ...new Set([ @@ -21,8 +21,19 @@ export const reply = ({ tmid }, message, parentMessage, followers) => { const replies = Messages.getThreadFollowsByThreadId(tmid); - // doesnt need to update the sender (u._id) subscription, so filter it - Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, replies.filter((userId) => userId !== u._id), tmid); + const repliesFiltered = replies + .filter((userId) => userId !== u._id) + .filter((userId) => !mentionIds.includes(userId)); + + if (toAll || toHere) { + Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, repliesFiltered, tmid, { groupMention: true }); + } else { + Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, repliesFiltered, tmid); + } + + mentionIds.forEach((mentionId) => + Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, [mentionId], tmid, { userMention: true }), + ); }; export const undoReply = ({ tmid }) => { diff --git a/app/ui-flextab/client/flexTabBar.html b/app/ui-flextab/client/flexTabBar.html index 14a35ae8ecfd4..edad99e560d15 100644 --- a/app/ui-flextab/client/flexTabBar.html +++ b/app/ui-flextab/client/flexTabBar.html @@ -45,9 +45,9 @@

{{_ label}}

{{#each buttons}}
- {{#if badge}} - {{badge.body}} - {{/if}} + {{#with badge}} + {{body}} + {{/with}} diff --git a/app/ui-message/client/message.html b/app/ui-message/client/message.html index 12e7269a99989..d572e9e4e0221 100644 --- a/app/ui-message/client/message.html +++ b/app/ui-message/client/message.html @@ -1,5 +1,5 @@