diff --git a/app/assets/stylesheets/chat.scss b/app/assets/stylesheets/chat.scss index 2d24038f5..ca2c554b1 100644 --- a/app/assets/stylesheets/chat.scss +++ b/app/assets/stylesheets/chat.scss @@ -798,11 +798,12 @@ } .chatchanneltabbutton { - width: 93%; + width: calc(95% - 2px); border: none; background: transparent; padding: 3px 0px; margin-bottom: -5px 0; + box-sizing: border-box; @include themeable(color, theme-color, $black); @@ -817,7 +818,7 @@ .chatchanneltab { display: inline-block; - width: 90%; + width: 100%; margin-bottom: 5px; background: inherit; text-align: start; @@ -829,6 +830,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + box-sizing: border-box; @media screen and (min-width: 550px) { font-size: 13px; diff --git a/app/assets/stylesheets/scaffolds.scss b/app/assets/stylesheets/scaffolds.scss index bfc3873bc..f8cf553a2 100644 --- a/app/assets/stylesheets/scaffolds.scss +++ b/app/assets/stylesheets/scaffolds.scss @@ -207,6 +207,10 @@ body.night-theme, body.ten-x-hacker-theme { .partner-image-light-mode { display: none !important; } + + .not-dark-theme-text-compatible { + @include themeable-important(color, theme-secondary-color, white); + } } // Alternate base fonts diff --git a/app/controllers/chat_channels_controller.rb b/app/controllers/chat_channels_controller.rb index 86a95c9fe..c92a3a1af 100644 --- a/app/controllers/chat_channels_controller.rb +++ b/app/controllers/chat_channels_controller.rb @@ -7,6 +7,9 @@ class ChatChannelsController < ApplicationController if params[:state] == "unopened" authorize ChatChannel render_unopened_json_response + elsif params[:state] == "unopened_ids" + authorize ChatChannel + render_unopened_ids_response elsif params[:state] == "pending" authorize ChatChannel render_pending_json_response @@ -134,6 +137,12 @@ class ChatChannelsController < ApplicationController render "index.json" end + def render_unopened_ids_response + @unopened_ids = ChatChannelMembership.where(user_id: session_current_user_id).includes(:chat_channel). + where(has_unopened_messages: true).pluck(:chat_channel_id) + render json: { unopened_ids: @unopened_ids } + end + def render_channels_html return unless current_user diff --git a/app/javascript/chat/__tests__/__snapshots__/message.test.jsx.snap b/app/javascript/chat/__tests__/__snapshots__/message.test.jsx.snap index db8d9b527..772bf7766 100644 --- a/app/javascript/chat/__tests__/__snapshots__/message.test.jsx.snap +++ b/app/javascript/chat/__tests__/__snapshots__/message.test.jsx.snap @@ -33,7 +33,7 @@ exports[` should render and test snapshot 1`] = ` class="message__info" > ( incomingVideoCallChannelIds={[]} // no incoming calls activeChannelId={12345} chatChannels={chatChannels} + unopenedChannelIds={[]} handleSwitchChannel={fakeSwitchChannel} channelsLoaded filterQuery="" diff --git a/app/javascript/chat/actions.js b/app/javascript/chat/actions.js index f492a456d..780b18158 100644 --- a/app/javascript/chat/actions.js +++ b/app/javascript/chat/actions.js @@ -137,6 +137,16 @@ export function getChannels( }); } +export function getUnopenedChannelIds(successCb) { + fetch('/chat_channels?state=unopened_ids', { + credentials: 'same-origin', + }) + .then(response => response.json()) + .then(json => { + successCb(json.unopened_ids); + }); +} + export function getTwilioToken(videoChannelName, successCb, failureCb) { fetch(`/twilio_tokens/${videoChannelName}`, { Accept: 'application/json', diff --git a/app/javascript/chat/channels.jsx b/app/javascript/chat/channels.jsx index c9b88098f..17abc35e4 100644 --- a/app/javascript/chat/channels.jsx +++ b/app/javascript/chat/channels.jsx @@ -6,6 +6,7 @@ import ConfigImage from 'images/three-dots.svg'; const Channels = ({ activeChannelId, chatChannels, + unopenedChannelIds, handleSwitchChannel, expanded, filterQuery, @@ -14,10 +15,7 @@ const Channels = ({ }) => { const channels = chatChannels.map(channel => { const isActive = parseInt(activeChannelId, 10) === channel.chat_channel_id; - const lastOpened = channel.last_opened_at; - const isUnopened = - new Date(channel.channel_last_message_at) > new Date(lastOpened) && - channel.channel_messages_count > 0; + const isUnopened = !isActive && unopenedChannelIds.includes(channel.chat_channel_id) let newMessagesIndicator = isUnopened ? 'new' : 'old'; if (incomingVideoCallChannelIds.indexOf(channel.chat_channel_id) > -1) { newMessagesIndicator = 'video'; @@ -114,6 +112,7 @@ const Channels = ({ Channels.propTypes = { activeChannelId: PropTypes.number.isRequired, chatChannels: PropTypes.arrayOf(PropTypes.objectOf()).isRequired, + unopenedChannelIds: PropTypes.arrayOf().isRequired, handleSwitchChannel: PropTypes.func.isRequired, expanded: PropTypes.bool.isRequired, filterQuery: PropTypes.string.isRequired, diff --git a/app/javascript/chat/chat.jsx b/app/javascript/chat/chat.jsx index 0e2bd1b2f..30293194e 100644 --- a/app/javascript/chat/chat.jsx +++ b/app/javascript/chat/chat.jsx @@ -7,6 +7,7 @@ import { sendMessage, sendOpen, getChannels, + getUnopenedChannelIds, getContent, getChannelInvites, sendChannelInviteAction, @@ -41,6 +42,7 @@ export default class Chat extends Component { scrolled: false, showAlert: false, chatChannels, + unopenedChannelIds: [], filterQuery: '', channelTypeFilter: 'all', channelsLoaded: false, @@ -92,9 +94,6 @@ export default class Chat extends Component { channel => `open-channel-${channel.chat_channel_id}`, ); setupObserver(this.observerCallback); - if (!window.currentUser) { - window.currentUser = JSON.parse(document.body.dataset.user); - } this.subscribePusher( `private-message-notifications-${currentUserId}`, ); @@ -114,6 +113,7 @@ export default class Chat extends Component { filters, this.loadChannels, ); + getUnopenedChannelIds(this.markUnopenedChannelIds) } if (!isMobileDevice) { document.getElementById('messageform').focus(); @@ -237,6 +237,10 @@ export default class Chat extends Component { document.getElementById('chatchannels__channelslist').scrollTop = 0; }; + markUnopenedChannelIds = (ids) => { + this.setState({unopenedChannelIds: ids}) + } + subscribeChannelsToPusher = (channels, channelNameFn) => { channels.forEach(channel => { this.subscribePusher(channelNameFn(channel)); @@ -341,7 +345,7 @@ export default class Chat extends Component { }; receiveNewMessage = message => { - const { messages, activeChannelId, scrolled, chatChannels } = this.state; + const { messages, activeChannelId, scrolled, chatChannels, unopenedChannelIds } = this.state; const receivedChatChannelId = message.chat_channel_id; let newMessages = []; if ( @@ -389,7 +393,16 @@ export default class Chat extends Component { if (receivedChatChannelId === activeChannelId) { sendOpen(receivedChatChannelId, this.handleChannelOpenSuccess, null); + } else { + const newUnopenedChannels = unopenedChannelIds + if (!unopenedChannelIds.includes(receivedChatChannelId)) { + newUnopenedChannels.push(receivedChatChannelId) + } + this.setState({ + unopenedChannelIds: newUnopenedChannels + }) } + this.setState(prevState => ({ ...newShowAlert, chatChannels: newChannelsObj, @@ -545,7 +558,7 @@ export default class Chat extends Component { target = target.parentElement; } this.triggerSwitchChannel( - target.dataset.channelId, + parseInt(target.dataset.channelId, 10), target.dataset.channelSlug, ); }; @@ -581,12 +594,18 @@ export default class Chat extends Component { }; triggerSwitchChannel = (id, slug) => { - const { chatChannels, isMobileDevice } = this.state; + const { chatChannels, isMobileDevice, unopenedChannelIds } = this.state; + const newUnopenedChannelIds = unopenedChannelIds + const index = newUnopenedChannelIds.indexOf(id); + if (index > -1) { + newUnopenedChannelIds.splice(index, 1); + } this.setState({ activeChannel: this.filterForActiveChannel(chatChannels, id), activeChannelId: parseInt(id, 10), scrolled: false, showAlert: false, + unopenedChannelIds: unopenedChannelIds.filter(unopenedId => unopenedId !== id) }); this.setupChannel(id); window.history.replaceState(null, null, `/connect/${slug}`); @@ -838,7 +857,7 @@ export default class Chat extends Component { } return messages[activeChannelId].map(message => ( @@ -997,6 +1018,10 @@ export default class Chat extends Component { messageOffset, } = this.state; + if (!messages[activeChannelId]) { + return; + } + const jumpbackButton = document.getElementById('jumpback_button'); if (this.scroller) { diff --git a/app/javascript/chat/message.jsx b/app/javascript/chat/message.jsx index 0813ad722..02a5d8afe 100644 --- a/app/javascript/chat/message.jsx +++ b/app/javascript/chat/message.jsx @@ -59,7 +59,7 @@ const Message = ({ >
- +