Update chat and modify article score calc indexing (#395)

This commit is contained in:
Ben Halpern 2018-06-06 16:08:12 -04:00 committed by GitHub
parent 851bb4e336
commit ab85216621
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 217 additions and 63 deletions

View file

@ -23,7 +23,7 @@ function markNotificationsAsRead() {
xmlhttp.setRequestHeader('X-CSRF-Token', csrfToken);
xmlhttp.send();
}
}, 120);
}, 250);
}
function fetchNotificationsCount() {

View file

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

View file

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

View file

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

View file

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

View file

@ -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 (
<button
className='chatchanneltabbutton'
onClick={handleSwitchChannel}
data-channel-id={channel.id}
data-channel-name={name}
data-channel-slug={modififedSlug}
>
<span className={`chatchanneltab ${otherClassname}`}
onClick={handleSwitchChannel}
data-channel-id={channel.id}
data-channel-name={name}
data-channel-slug={modififedSlug}
>
<span className={"chatchanneltabindicator " + newMessagesIndicatorClass}></span> {name}
<span
data-channel-slug={modififedSlug}
className={"chatchanneltabindicator " + newMessagesIndicatorClass}
></span> {name}
</span>
</button>
);

View file

@ -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 (
<div className="chat">
{this.renderChatChannels()}
<div className="chat__activechat">{this.renderActiveChatChannel()}</div>
<div className="chat__activechat">
{this.renderActiveChatChannel()}
</div>
</div>
);
}

View file

@ -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(<Onboarding />, document.getElementById('top-bar'));
}, 500);
}, 580);
}
}
document.ready.then(
getUserData().then(() => {
renderPage();
getUnopenedChannels();
}),
);

View file

@ -0,0 +1,10 @@
import { h } from 'preact';
const UnopenedChannelNotice = () => {
return (
<div style={{position: 'absolute'}}>
</div>
);
};
export default UnopenedChannelNotice;

View file

@ -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 <a
href={"/chat/"+channel.adjusted_slug}
style={{
background: "#66e2d5",
color: "black",
padding: "2px 7px",
display: "inline-block",
margin: "3px 6px",
borderRadius: "3px"}}>{channel.adjusted_slug}</a>
});
return (
<div
onClick={this.handleClick}
style={{
position: 'fixed',
zIndex: '200',
top: '44px',
right: 0,
left: 0,
background: '#333333',
borderBottom: '1px solid black',
color: 'white',
fontWeight: 'bold',
textAlign: 'center',
fontSize: '15px',
opacity: '0.94',
padding: '12px 5px 3px'}}>
<span style={{
fontSize: "24px",
verticalAlign: "-4px",
display: "inline-block",
marginRight: "3px"}}>💌</span> New Message from {channels}
<span style={{ color: "#fefa87"}}>(beta testing)</span>
</div>
);
}
}
}
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(<UnopenedChannelNotice channels={json} />, document.getElementById('message-notice'));
} else {
render(null, document.getElementById('message-notice'));
}
})
.catch(error => {
console.log(error);
});
}

View file

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

View file

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

View file

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

View file

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

View file

@ -18,8 +18,8 @@
height: calc(100vh - 115px);
}
</style>
<div class="inner-content">
<div class="chat-page-header">DEV Chat is Beta —— Post any thoughts in the "Meta" channel</div>
<div
id="chat"
class="live-chat"

View file

@ -0,0 +1,3 @@
json.array! @chat_channels_memberships do |membership|
json.adjusted_slug membership.chat_channel.adjusted_slug(current_user)
end

View file

@ -75,6 +75,7 @@
<% cache("application-top-bar--#{user_signed_in?}--#{is_internal_navigation?}", :expires_in => 100.hours) do %>
<%= render "layouts/top_bar" unless is_internal_navigation? %>
<% end %>
<div id="message-notice"></div>
<div id="page-content" class="universal-page-content-wrapper <%= view_class %>" data-current-page="<%= current_page %>">
<div id="page-content-inner">
<%= yield %>

View file

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

View file

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

View file

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

View file

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