diff --git a/app/assets/javascripts/initializers/initNotifications.js b/app/assets/javascripts/initializers/initNotifications.js index 9c587873e..b30a85874 100644 --- a/app/assets/javascripts/initializers/initNotifications.js +++ b/app/assets/javascripts/initializers/initNotifications.js @@ -23,7 +23,7 @@ function markNotificationsAsRead() { xmlhttp.setRequestHeader('X-CSRF-Token', csrfToken); xmlhttp.send(); } - }, 120); + }, 250); } function fetchNotificationsCount() { diff --git a/app/assets/stylesheets/chat.scss b/app/assets/stylesheets/chat.scss index e1a64d109..319c07d77 100644 --- a/app/assets/stylesheets/chat.scss +++ b/app/assets/stylesheets/chat.scss @@ -1,14 +1,28 @@ @import 'variables'; // High level class +.chat-page-header{ + background: $green; + text-align: center; + padding: 5px; + margin-top: -25px; + border-radius: 3px; + margin-bottom: 5px; + font-size:0.8em; + font-weight: bold; +} + .chat { display: flex; height: inherit; } .chat__channels { - width: 160px; + width: 80px; height: inherit; + @media screen and ( min-width: 550px ){ + width: 160px; + } } .chat__channels--hidden { @@ -89,9 +103,10 @@ width: inherit; } .chatchanneltabbutton{ - width: 92%; + width: 93%; border: none; background: transparent; + padding:0; &:hover { .chatchanneltab--inactive { border: 1px solid $outline-color; @@ -101,19 +116,22 @@ } .chatchanneltab { display:inline-block; - width: 95%; + width: 90%; margin-bottom: 5px; - border-left: 8px solid transparent; background: inherit; text-align: start; - font-size: 14px; + font-size: 11px; font-weight: 500; padding: 8px; border: 1px solid transparent; border-radius: 3px; white-space: nowrap; overflow: hidden; - text-overflow: ellipsis;} + text-overflow: ellipsis; + @media screen and ( min-width: 550px ){ + font-size: 13px; + } +} .chatchanneltab--active { background: white; @@ -125,9 +143,9 @@ display: inline-block; background: white; border: 2px solid $light-medium-gray; - padding: 5px; + padding: 0.3em; border-radius: 100px; - vertical-align: -2px; + vertical-align: -0.05em; margin-right: 3px; } @@ -136,18 +154,6 @@ border: 2px solid $black; } -@keyframes example { - // animation-name: example; - // animation-duration: 4s; - // animation-iteration-count: infinite; - from { - border-left: 8px solid transparent; - } - to { - border-left: 8px solid #ffb6c1; - } -} - .chatchannels__channelslist { padding: 10px 0; flex-grow: 1; diff --git a/app/controllers/chat_channels_controller.rb b/app/controllers/chat_channels_controller.rb index e06f8cc65..f39ad60ac 100644 --- a/app/controllers/chat_channels_controller.rb +++ b/app/controllers/chat_channels_controller.rb @@ -1,6 +1,15 @@ class ChatChannelsController < ApplicationController before_action :authenticate_user!, only: [:moderate] + def index + if params[:state] == "unopened" + render_unopened_json_response + return + else + render_channels_html + end + end + def show @chat_channel = ChatChannel.includes(:messages).find_by(id: params[:id]) if @chat_channel @@ -15,7 +24,8 @@ class ChatChannelsController < ApplicationController 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) + membership = @chat_channel.chat_channel_memberships.where(user_id: current_user.id).first + membership.update(last_opened_at: 1.seconds.from_now, has_unopened_messages: false) render json: { status: "success", channel: params[:id] }, status: 200 end @@ -55,4 +65,30 @@ class ChatChannelsController < ApplicationController def chat_channel_params params.require(:chat_channel).permit(:command) end + + def render_unopened_json_response + if current_user.has_role?(:super_admin) || Rails.env.development? + @chat_channels_memberships = current_user. + chat_channel_memberships. + where(has_unopened_messages: true).order("updated_at DESC") + else + @chat_channels_memberships = [] + end + render "index.json" + end + + def render_channels_html + @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 + params[:slug] + end + @active_channel = ChatChannel.find_by_slug(slug) || @chat_channels.first + end end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 71dc1329e..2da56fba0 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -41,22 +41,7 @@ class PagesController < ApplicationController def live @chat_channels = [ChatChannel.find_by_channel_name("Workshop")].to_json end - - def chat - @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 - params[:slug] - end - @active_channel = ChatChannel.find_by_slug(slug) || @chat_channels.first - end - + private # helpers def latest_published_welcome_thread diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 50d7bb04d..a19b07cfb 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -19,7 +19,7 @@ module ApplicationHelper controller_name == "registrations" || controller_name == "users" || controller_name == "pages" || - + controller_name == "chat_channels" || controller_name == "dashboards"|| controller_name == "moderations"|| controller_name == "videos"|| diff --git a/app/javascript/chat/channels.jsx b/app/javascript/chat/channels.jsx index 8babc03fb..96e77083a 100644 --- a/app/javascript/chat/channels.jsx +++ b/app/javascript/chat/channels.jsx @@ -9,19 +9,22 @@ const Channels = ({ activeChannelId, chatChannels, handleSwitchChannel }) => { : '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" + const modififedSlug = channel.channel_type === "direct" ? name : channel.slug; return ( ); diff --git a/app/javascript/chat/chat.jsx b/app/javascript/chat/chat.jsx index 78eb4d914..bef48eb82 100644 --- a/app/javascript/chat/chat.jsx +++ b/app/javascript/chat/chat.jsx @@ -162,12 +162,15 @@ export default class Chat extends Component { handleSwitchChannel = e => { e.preventDefault(); this.setState({ - activeChannelId: e.target.dataset.channelId, + activeChannelId: parseInt(e.target.dataset.channelId), scrolled: false, showAlert: false, }); - window.history.replaceState(null, null, "/chat/"+e.target.dataset.channelName); + window.history.replaceState(null, null, "/chat/"+e.target.dataset.channelSlug); document.getElementById("messageform").focus(); + if (window.ga && ga.create) { + ga('send', 'pageview', location.pathname + location.search); + } sendOpen( e.target.dataset.channelId, this.handleChannelOpenSuccess, @@ -258,7 +261,9 @@ export default class Chat extends Component { return (
{this.renderChatChannels()} -
{this.renderActiveChatChannel()}
+
+ {this.renderActiveChatChannel()} +
); } diff --git a/app/javascript/packs/pack.js b/app/javascript/packs/pack.js index 362048a2d..c00b9f22b 100644 --- a/app/javascript/packs/pack.js +++ b/app/javascript/packs/pack.js @@ -1,6 +1,7 @@ import { h, render } from 'preact'; import Onboarding from '../src/Onboarding'; import { getUserData } from '../src/utils/getUserData'; +import getUnopenedChannels from '../src/utils/getUnopenedChannels'; HTMLDocument.prototype.ready = new Promise(resolve => { if (document.readyState !== 'loading') { @@ -22,12 +23,13 @@ function renderPage() { if (shouldShowOnboarding()) { setTimeout(() => { render(, document.getElementById('top-bar')); - }, 500); + }, 580); } } document.ready.then( getUserData().then(() => { renderPage(); + getUnopenedChannels(); }), ); diff --git a/app/javascript/src/components/UnopenedChannelNotice.jsx b/app/javascript/src/components/UnopenedChannelNotice.jsx new file mode 100644 index 000000000..c5d934149 --- /dev/null +++ b/app/javascript/src/components/UnopenedChannelNotice.jsx @@ -0,0 +1,10 @@ +import { h } from 'preact'; + +const UnopenedChannelNotice = () => { + return ( +
+
+ ); +}; + +export default UnopenedChannelNotice; diff --git a/app/javascript/src/utils/getUnopenedChannels.jsx b/app/javascript/src/utils/getUnopenedChannels.jsx new file mode 100644 index 000000000..55ef753ad --- /dev/null +++ b/app/javascript/src/utils/getUnopenedChannels.jsx @@ -0,0 +1,74 @@ +import { h, render, Component } from 'preact'; + +class UnopenedChannelNotice extends Component { + constructor(props) { + super(props); + this.state = { visible: true } + this.handleClick = this.handleClick.bind(this); + } + + handleClick() { + this.setState({visible: false}) + } + render() { + if (this.state.visible) { + const channels = this.props.channels.map(channel => { + return {channel.adjusted_slug} + }); + return ( +
+ 💌 New Message from {channels} + (beta testing) +
+ ); + } + } +} + +export default function getUnopenedChannels(user, successCb) { + if (location.pathname.startsWith("/chat")) { + return + } + fetch('/chat_channels?state=unopened', { + method: 'GET', + credentials: 'same-origin', + }) + .then(response => response.json()) + .then(json => { + if (json.length > 0) { + render(, document.getElementById('message-notice')); + } else { + render(null, document.getElementById('message-notice')); + } + }) + .catch(error => { + console.log(error); + }); +} diff --git a/app/models/article.rb b/app/models/article.rb index f7f5d9814..340af6b15 100644 --- a/app/models/article.rb +++ b/app/models/article.rb @@ -332,6 +332,14 @@ class Article < ApplicationRecord end end + def async_score_calc + update_column(:hotness_score, BlackBox.article_hotness_score(self)) + update_column(:spaminess_rating, BlackBox.calculate_spaminess(self)) + index! if published && tag_list.exclude?("hiring") + end + handle_asynchronously :async_score_calc + + private # def send_to_moderator @@ -454,13 +462,6 @@ class Article < ApplicationRecord self.spaminess_rating = 0 if new_record? end - def async_score_calc - update_column(:hotness_score, BlackBox.article_hotness_score(self)) - update_column(:spaminess_rating, BlackBox.calculate_spaminess(self)) - index! if published && tag_list.exclude?("hiring") - end - handle_asynchronously :async_score_calc - def async_bust CacheBuster.new.bust_article(self) HTTParty.get GeneratedImage.new(self).social_image if published diff --git a/app/models/chat_channel.rb b/app/models/chat_channel.rb index bef214970..1202df171 100644 --- a/app/models/chat_channel.rb +++ b/app/models/chat_channel.rb @@ -44,4 +44,13 @@ class ChatChannel < ApplicationRecord ChatChannelMembership.create!(user_id: user.id, chat_channel_id: id) end end + + def adjusted_slug(user = nil) + user ||= current_user + if channel_type == "direct" + "@"+slug.gsub("/#{user.username}","").gsub("#{user.username}/","") + else + slug + end + end end diff --git a/app/models/message.rb b/app/models/message.rb index 9613f6732..b3e112a8c 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -8,6 +8,7 @@ class Message < ApplicationRecord before_validation :evaluate_markdown before_validation :evaluate_channel_permission after_create :update_chat_channel_last_message_at + after_create :update_all_has_unopened_messages_statuses def preferred_user_color color_options = [user.bg_color_hex || "#000000", user.text_color_hex || "#000000"] @@ -20,6 +21,16 @@ class Message < ApplicationRecord chat_channel.touch(:last_message_at) end + def update_all_has_unopened_messages_statuses + chat_channel. + chat_channel_memberships. + where("last_opened_at < ?", 1.seconds.ago). + where. + not(user_id: user_id). + update_all(has_unopened_messages: true) + end + # handle_asynchronously :update_all_has_unopened_messages_statuses + def evaluate_markdown self.message_html = message_markdown end diff --git a/app/models/reaction.rb b/app/models/reaction.rb index 9aac613f1..a77e4bb13 100644 --- a/app/models/reaction.rb +++ b/app/models/reaction.rb @@ -71,6 +71,7 @@ class Reaction < ApplicationRecord def update_reactable if reactable_type == "Article" + reactable.async_score_calc reactable.index! CacheBuster.new.bust "/reactions/logged_out_reaction_counts?article_id=#{reactable_id}" elsif reactable_type == "Comment" diff --git a/app/views/pages/chat.html.erb b/app/views/chat_channels/index.html.erb similarity index 89% rename from app/views/pages/chat.html.erb rename to app/views/chat_channels/index.html.erb index 37b99ebd4..e470bb91e 100644 --- a/app/views/pages/chat.html.erb +++ b/app/views/chat_channels/index.html.erb @@ -18,8 +18,8 @@ height: calc(100vh - 115px); } -
+
DEV Chat is Beta —— Post any thoughts in the "Meta" channel
100.hours) do %> <%= render "layouts/top_bar" unless is_internal_navigation? %> <% end %> +
<%= yield %> diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 618dd86a2..ebd2c7048 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -25,6 +25,8 @@ Rails.application.config.assets.precompile += %w( favicon.ico ) Rails.application.config.assets.precompile += %w( minimal.css ) Rails.application.config.assets.precompile += %w( s3_direct_upload.css ) Rails.application.config.assets.precompile += %w( base.js ) +Rails.application.config.assets.precompile += %w( pack.js ) +Rails.application.config.assets.precompile += %w( chat.js ) Rails.application.config.assets.precompile += %w( s3_direct_upload.js ) Rails.application.config.assets.precompile += %w( lib/xss.js ) diff --git a/config/routes.rb b/config/routes.rb index fb3ae813a..de7a3b807 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -70,7 +70,7 @@ Rails.application.routes.draw do end resources :messages, only: [:create] - resources :chat_channels, only: [:show] + resources :chat_channels, only: [:index, :show] resources :articles, only: [:update,:create,:destroy] resources :comments, only:[:create,:update,:destroy] resources :users, only:[:update] @@ -97,8 +97,11 @@ Rails.application.routes.draw do get "/notifications/:username" => "notifications#index" patch "/onboarding_update" => "users#onboarding_update" get "email_subscriptions/unsubscribe" - post "chat_channels/:id/moderate" => "chat_channels#moderate" - post "chat_channels/:id/open" => "chat_channels#open" + post "/chat_channels/:id/moderate" => "chat_channels#moderate" + post "/chat_channels/:id/open" => "chat_channels#open" + get "/chat" => "chat_channels#index" + get "/chat/:slug" => "chat_channels#index" + # resources :users get "/social_previews/article/:id" => "social_previews#article" @@ -154,10 +157,6 @@ Rails.application.routes.draw do get "/infiniteloop" => "pages#infinite_loop" get "/faq" => "pages#faq" get "/live" => "pages#live" - get "/chat" => "pages#chat" - get "/chat/:slug" => "pages#chat" - get "/m" => "pages#chat" - get "/m/:slug" => "pages#chat" get "/swagnets" => "pages#swagnets" get "/welcome" => "pages#welcome" get "/💸", to: redirect("t/hiring") diff --git a/db/migrate/20180606155327_add_new_messages_to_chat_memberships.rb b/db/migrate/20180606155327_add_new_messages_to_chat_memberships.rb new file mode 100644 index 000000000..82e657eb9 --- /dev/null +++ b/db/migrate/20180606155327_add_new_messages_to_chat_memberships.rb @@ -0,0 +1,5 @@ +class AddNewMessagesToChatMemberships < ActiveRecord::Migration[5.1] + def change + add_column :chat_channel_memberships, :has_unopened_messages, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 7a1d11658..ec6e2db66 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180604200603) do +ActiveRecord::Schema.define(version: 20180606155327) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -165,6 +165,7 @@ ActiveRecord::Schema.define(version: 20180604200603) do create_table "chat_channel_memberships", force: :cascade do |t| t.bigint "chat_channel_id", null: false t.datetime "created_at", null: false + t.boolean "has_unopened_messages", default: false t.datetime "last_opened_at", default: "2017-01-01 05:00:00" t.datetime "updated_at", null: false t.bigint "user_id", null: false