Separate chat channel unopened status from Algolia (#5198) [deploy]

This commit is contained in:
Ben Halpern 2019-12-21 13:05:16 -05:00 committed by GitHub
parent b1a71d0ac1
commit cb3a0c462c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 85 additions and 15 deletions

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -33,7 +33,7 @@ exports[`<Message /> should render and test snapshot 1`] = `
class="message__info"
>
<span
class="chatmessagebody__username"
class="chatmessagebody__username not-dark-theme-text-compatible"
style={
Object {
"color": "#00FFFF",

View file

@ -78,6 +78,7 @@ const getChannels = (mod, chatChannels) => (
incomingVideoCallChannelIds={[]} // no incoming calls
activeChannelId={12345}
chatChannels={chatChannels}
unopenedChannelIds={[]}
handleSwitchChannel={fakeSwitchChannel}
channelsLoaded
filterQuery=""

View file

@ -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',

View file

@ -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,

View file

@ -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 => (
<Message
currentUserId={window.currentUser.id}
currentUserId={currentUserId}
id={message.id}
user={message.username}
userID={message.user_id}
@ -954,6 +973,7 @@ export default class Chat extends Component {
<Channels
activeChannelId={state.activeChannelId}
chatChannels={state.chatChannels}
unopenedChannelIds={state.unopenedChannelIds}
handleSwitchChannel={this.handleSwitchChannel}
channelsLoaded={state.channelsLoaded}
filterQuery={state.filterQuery}
@ -979,6 +999,7 @@ export default class Chat extends Component {
incomingVideoCallChannelIds={state.incomingVideoCallChannelIds}
activeChannelId={state.activeChannelId}
chatChannels={state.chatChannels}
unopenedChannelIds={state.unopenedChannelIds}
handleSwitchChannel={this.handleSwitchChannel}
expanded={state.expanded}
/>
@ -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) {

View file

@ -59,7 +59,7 @@ const Message = ({
>
<div className="message__info__actions">
<div className="message__info">
<span className="chatmessagebody__username" style={spanStyle}>
<span className="chatmessagebody__username not-dark-theme-text-compatible" style={spanStyle}>
<a
className="chatmessagebody__username--link"
href={`/${user}`}

View file

@ -50,6 +50,26 @@ RSpec.describe "ChatChannels", type: :request do
end
end
describe "get /chat_channels?state=unopened_ids" do
it "returns unopened chat channel ids" do
direct_channel.add_users [user]
user.chat_channel_memberships.each { |m| m.update(has_unopened_messages: true) }
sign_in user
get "/chat_channels?state=unopened_ids"
expect(response.body).to include(direct_channel.id.to_s)
expect(response.body).to include("unopened_ids")
end
it "does not return chat channel ids if not signed in" do
direct_channel.add_users [user]
user.chat_channel_memberships.each { |m| m.update(has_unopened_messages: true) }
sign_out user
expect do
get "/chat_channels?state=unopened_ids"
end.to raise_error(Pundit::NotAuthorizedError)
end
end
describe "get /chat_channels?state=pending" do
it "returns pending channels" do
ChatChannelMembership.create(chat_channel_id: invite_channel.id, user_id: user.id, status: "pending")