diff --git a/app/assets/stylesheets/chat.scss b/app/assets/stylesheets/chat.scss index b4fc9c4d7..e1a64d109 100644 --- a/app/assets/stylesheets/chat.scss +++ b/app/assets/stylesheets/chat.scss @@ -121,6 +121,21 @@ box-shadow: 3px 3px 0px $dark-purple; } +.chatchanneltabindicator{ + display: inline-block; + background: white; + border: 2px solid $light-medium-gray; + padding: 5px; + border-radius: 100px; + vertical-align: -2px; + margin-right: 3px; +} + +.chatchanneltabindicator--new{ + background: $yellow; + border: 2px solid $black; +} + @keyframes example { // animation-name: example; // animation-duration: 4s; @@ -157,11 +172,11 @@ } .chatmessagebody__profileimage { - height: 18px; - width: 18px; - margin-right: 3px; + height: 23px; + width: 23px; + margin-right: 6px; border-radius: 20px; - vertical-align: bottom; + vertical-align: -5px; } .chatmessagebody__username { diff --git a/app/controllers/chat_channels_controller.rb b/app/controllers/chat_channels_controller.rb index 5ad220d9e..e06f8cc65 100644 --- a/app/controllers/chat_channels_controller.rb +++ b/app/controllers/chat_channels_controller.rb @@ -12,6 +12,13 @@ class ChatChannelsController < ApplicationController end end + def open + @chat_channel = ChatChannel.find(params[:id]) + raise unless @chat_channel.has_member?(current_user) + @chat_channel.chat_channel_memberships.where(user_id: current_user.id).first.touch(:last_opened_at) + render json: { status: "success", channel: params[:id] }, status: 200 + end + def moderate @chat_channel = ChatChannel.find(params[:id]) authorize @chat_channel diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 2ca805262..71dc1329e 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -43,7 +43,12 @@ class PagesController < ApplicationController end def chat - @chat_channels = current_user.chat_channels.order("last_message_at DESC") + @chat_channels = current_user.chat_channels. + order("last_message_at DESC"). + includes(:chat_channel_memberships) + @chat_channels.each do |channel| + channel.current_user = current_user + end slug = if params[:slug] && params[:slug].start_with?("@") [current_user.username, params[:slug].gsub("@", "")].sort.join("/") else diff --git a/app/javascript/chat/actions.js b/app/javascript/chat/actions.js index a401540cf..317650759 100644 --- a/app/javascript/chat/actions.js +++ b/app/javascript/chat/actions.js @@ -30,6 +30,22 @@ export function sendMessage(activeChannelId, message, successCb, failureCb) { .catch(failureCb); } +export function sendOpen(activeChannelId, successCb, failureCb) { + fetch(`/chat_channels/${activeChannelId}/open`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'X-CSRF-Token': window.csrfToken, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}), + credentials: 'same-origin', + }) + .then(response => response.json()) + .then(successCb) + .catch(failureCb); +} + export function conductModeration( activeChannelId, message, diff --git a/app/javascript/chat/channels.jsx b/app/javascript/chat/channels.jsx index 42aa2d5b7..8babc03fb 100644 --- a/app/javascript/chat/channels.jsx +++ b/app/javascript/chat/channels.jsx @@ -8,6 +8,7 @@ const Channels = ({ activeChannelId, chatChannels, handleSwitchChannel }) => { ? 'chatchanneltab--active' : 'chatchanneltab--inactive'; const name = channel.channel_type === "direct" ? '@'+channel.slug.replace(`${window.currentUser.username}/`, '').replace(`/${window.currentUser.username}`, '') : channel.channel_name + const newMessagesIndicatorClass = new Date(channel.last_opened_at) < new Date(channel.last_message_at) ? "chatchanneltabindicator--new" : "chatchanneltabindicator--old" return ( ); diff --git a/app/javascript/chat/chat.jsx b/app/javascript/chat/chat.jsx index e9c4ca772..fe0d48eb3 100644 --- a/app/javascript/chat/chat.jsx +++ b/app/javascript/chat/chat.jsx @@ -1,6 +1,6 @@ import { h, Component } from 'preact'; import PropTypes from 'prop-types'; -import { conductModeration, getAllMessages, sendMessage } from './actions'; +import { conductModeration, getAllMessages, sendMessage, sendOpen } from './actions'; import { hideMessages, scrollToBottom, setupObserver } from './util'; import Alert from './alert'; import Channels from './channels'; @@ -54,6 +54,11 @@ export default class Chat extends Component { channelCleared: this.clearChannel, redactUserMessages: this.redactUserMessages, }); + sendOpen( + this.state.activeChannelId, + this.handleChannelOpenSuccess, + null, + ); }; observerCallback = entries => { @@ -83,8 +88,22 @@ export default class Chat extends Component { this.state.activeChannelId === receivedChatChannelId ? { showAlert: this.state.scrolled } : {}; + const newChannelsObj = this.state.chatChannels.map(channel => { + if (receivedChatChannelId === channel["id"]){ + channel["last_message_at"] = new Date(); + } + return channel; + }); + if (receivedChatChannelId === this.state.activeChannelId) { + sendOpen( + receivedChatChannelId, + this.handleChannelOpenSuccess, + null, + ); + } this.setState({ ...newShowAlert, + chatChannels: newChannelsObj, messages: { ...this.state.messages, [receivedChatChannelId]: newMessages, @@ -148,6 +167,11 @@ export default class Chat extends Component { showAlert: false, }); window.history.replaceState(null, null, "/chat/"+e.target.dataset.channelName); + sendOpen( + e.target.dataset.channelId, + this.handleChannelOpenSuccess, + null, + ); }; handleSubmitOnClick = e => { @@ -165,6 +189,16 @@ export default class Chat extends Component { } }; + handleChannelOpenSuccess = response => { + const newChannelsObj = this.state.chatChannels.map(channel => { + if (parseInt(response.channel) === channel["id"]){ + channel["last_opened_at"] = new Date(); + } + return channel; + }); + this.setState({ chatChannels: newChannelsObj }); + }; + handleFailure = err => { console.error(err); }; @@ -217,6 +251,8 @@ export default class Chat extends Component { ); + + render() { return (