Skip to content

Commit

Permalink
Show story and comment replies, tracking unread ones
Browse files Browse the repository at this point in the history
  • Loading branch information
hmadison authored and pushcx committed Jan 31, 2018
1 parent 925bdc2 commit dd42cca
Show file tree
Hide file tree
Showing 22 changed files with 264 additions and 6 deletions.
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ language: ruby
services:
- mysql
env:
- DATABASE_URL=mysql2://root:@localhost/lobsters_test
global:
- DATABASE_URL=mysql2://root:@localhost/lobsters_dev
- RAILS_ENV=test
before_script:
- ./bin/rails db:create
- ./bin/rails db:schema:load
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ gem "mysql2", ">= 0.3.14"
# uncomment to use PostgreSQL
# gem "pg"

gem 'scenic'
gem 'scenic-mysql'

gem "uglifier", ">= 1.3.0"
gem "jquery-rails", "~> 4.3"
gem "dynamic_form"
Expand Down
7 changes: 7 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ GEM
rspec-support (3.6.0)
ruby-enum (0.7.1)
i18n
scenic (1.4.0)
activerecord (>= 4.0.0)
railties (>= 4.0.0)
scenic-mysql (0.1.0)
scenic (>= 1.3)
sprockets (3.7.1)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
Expand Down Expand Up @@ -175,6 +180,8 @@ DEPENDENCIES
rotp
rqrcode
rspec-rails (~> 3.6)
scenic
scenic-mysql
sqlite3
uglifier (>= 1.3.0)
unicorn
Expand Down
5 changes: 5 additions & 0 deletions app/assets/stylesheets/application.css
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,11 @@ div.comment_form_container textarea {
width: 100%;
}

span.comment_unread {
color: red;
font-weight: 600;
}

/* trees */

.tree,
Expand Down
54 changes: 54 additions & 0 deletions app/controllers/replies_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
class RepliesController < ApplicationController
REPLIES_PER_PAGE = 25

before_action :require_logged_in_user_or_400
after_action :update_read_ribbons

def show
@page = params[:page].to_i
if @page == 0
@page = 1
elsif @page < 0 || @page > (2 ** 32)
raise ActionController::RoutingError.new("page out of bounds")
end

@filter = params[:filter] || 'unread'

case @filter
when 'comments'
@heading = @title = "Your Comment Replies"
@replies = ReplyingComment
.comment_replies_for(@user.id)
.offset((@page - 1) * REPLIES_PER_PAGE)
.limit(REPLIES_PER_PAGE)
when 'stories'
@heading = @title = "Your Story Replies"
@replies = ReplyingComment
.story_replies_for(@user.id)
.offset((@page - 1) * REPLIES_PER_PAGE)
.limit(REPLIES_PER_PAGE)
when 'all'
@heading = @title = "All Your Replies"
@replies = ReplyingComment
.for_user(@user.id)
.offset((@page - 1) * REPLIES_PER_PAGE)
.limit(REPLIES_PER_PAGE)
else
@heading = @title = "Your Unread Replies"
@replies = ReplyingComment.unread_replies_for(@user.id)
end
end

private

def update_read_ribbons
return unless @filter == 'unread'
stories = @replies.pluck(:story_id).uniq

stories.each do |story|
ribbon = ReadRibbon.find_by(user_id: @user.id, story_id: story)
ribbon.updated_at = Time.now
ribbon.save!
end
end
end
10 changes: 10 additions & 0 deletions app/controllers/stories_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class StoriesController < ApplicationController
before_action :find_user_story, :only => [ :destroy, :edit, :undelete,
:update ]
before_action :find_story!, :only => [ :suggest, :submit_suggestions ]
around_action :track_story_reads, only: [ :show ], if: -> { @user.present? }

def create
@title = "Submit Story"
Expand Down Expand Up @@ -292,6 +293,7 @@ def hide
end

HiddenStory.hide_story_for_user(story.id, @user.id)
ReadRibbon.hide_replies_for(story.id, @user.id)

render :plain => "ok"
end
Expand Down Expand Up @@ -407,4 +409,12 @@ def verify_user_can_submit_stories
return redirect_to "/"
end
end

def track_story_reads
story = Story.where(short_id: params[:id]).first!
@ribbon = ReadRibbon.where(user_id: @user.id, story_id: story.id).first_or_create
yield
@ribbon.updated_at = Time.now
@ribbon.save
end
end
19 changes: 18 additions & 1 deletion app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ def right_header_links
@right_header_links = {}

if @user
if (count = @user.unread_replies_count) > 0
@right_header_links.merge!({ "/replies" => {
:class => [ "new_messages" ],
:title => "Replies (#{count})",
} })
else
@right_header_links.merge!({
"/replies" => { :title => "Replies" }
})
end

if (count = @user.unread_message_count) > 0
@right_header_links.merge!({ "/messages" => {
:class => [ "new_messages" ],
Expand Down Expand Up @@ -170,6 +181,12 @@ def time_ago_in_words_label(time, options = {})
ago = "#{years} year#{years == 1 ? "" : "s"} ago"
end

raw(content_tag(:span, ago, :title => time.strftime("%F %T %z")))
span_class = ''

if options[:mark_unread]
span_class += 'comment_unread'
end

raw(content_tag(:span, ago, title: time.strftime("%F %T %z"), class: span_class))
end
end
11 changes: 11 additions & 0 deletions app/helpers/replies_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module RepliesHelper
def link_to_filter(name)
title = name.titleize

if @filter != name
link_to(title, replies_path(filter: name))
else
title
end
end
end
8 changes: 8 additions & 0 deletions app/helpers/stories_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,12 @@ def show_guidelines?

false
end

def is_unread?(comment)
if !@user || !@ribbon
return false
end

(comment.created_at > @ribbon.updated_at) && (comment.user_id != @user.id)
end
end
3 changes: 3 additions & 0 deletions app/models/application_record.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
10 changes: 10 additions & 0 deletions app/models/read_ribbon.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class ReadRibbon < ApplicationRecord
belongs_to :user
belongs_to :story

def self.hide_replies_for(story_id, user_id)
ribbon = find_by(user_id: user_id, story_id: story_id)
ribbon.is_following = false
ribbon.save!
end
end
16 changes: 16 additions & 0 deletions app/models/replying_comment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class ReplyingComment < ApplicationRecord
attribute :is_unread, :boolean

belongs_to :comment

scope :for_user, ->(user_id) { where(user_id: user_id).order(comment_created_at: :desc) }
scope :unread_replies_for, ->(user_id) { for_user(user_id).where(is_unread: true) }
scope :comment_replies_for, ->(user_id) { for_user(user_id).where('parent_comment_id is not null') }
scope :story_replies_for, ->(user_id) { for_user(user_id).where('parent_comment_id is null') }

protected
# This is a view, not a real table
def readonly?
true
end
end
4 changes: 4 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,10 @@ def update_unread_message_count!
self.received_messages.unread.count)
end

def unread_replies_count
ReplyingComment.where(user_id: self.id, is_unread: true).count
end

def votes_for_others
self.votes.joins(:story, :comment).
where("comments.user_id <> votes.user_id AND " <<
Expand Down
5 changes: 4 additions & 1 deletion app/views/comments/_comment.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,11 @@ class="comment <%= comment.current_vote ? (comment.current_vote[:vote] == 1 ?
<% elsif comment.is_from_email? %>
e-mailed
<% end %>
<% if defined?(is_unread) && is_unread %>
<% mark_unread = true %>
<% end %>
<%= time_ago_in_words_label((comment.has_been_edited? ?
comment.updated_at : comment.created_at)) %>
comment.updated_at : comment.created_at), strip_about: true, mark_unread: mark_unread) %>
<% end %>

<% if !comment.previewing %>
Expand Down
1 change: 0 additions & 1 deletion app/views/comments/threads.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

<% @threads.each do |thread| %>
<ol class="comments comments1">
<% comments_by_parent = thread.group_by(&:parent_comment_id) %>
Expand Down
44 changes: 44 additions & 0 deletions app/views/replies/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<div class="box wide">
<div class="legend right">
<%= link_to_filter('unread') %> |
<%= link_to_filter('all') %> |
<%= link_to_filter('comments') %> |
<%= link_to_filter('stories') %>

</div>

<div class="legend">
<%= @heading %>
</div>
</div>

<ol class="comments comments1">
<% @replies.each do |reply| %>
<li class="comments_subtree">
<%= render "comments/comment",
comment: reply.comment,
show_story: true,
is_unread: reply.is_unread,
show_tree_lines: false %>
<ol class="comments"></ol>
</li>
<% end %>
</ol>

<% if @replies.count > RepliesController::REPLIES_PER_PAGE && @filter != 'unread'%>
<div class="morelink">
<% if @page && @page > 1 %>
<a href="/replies<%= @page == 2 ? "" : "/page/#{@page - 1}" %>">&lt;&lt;
Page <%= @page - 1 %></a>
<% end %>

<% if @replies.any? %>
<% if @page && @page > 1 %>
|
<% end %>

<a href="/replies/page/<%= @page + 1 %>">Page
<%= @page + 1 %> &gt;&gt;</a>
<% end %>
</div>
<% end %>
3 changes: 2 additions & 1 deletion app/views/stories/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
:show_story => (comment.story_id != @story.id),
:show_tree_lines => true,
:was_merged => (comment.story_id != @story.id),
:children => children %>
:children => children,
:is_unread => is_unread?(comment) %>

<% if ancestors.present? %>
<div class="comment_siblings_tree_line"></div>
Expand Down
3 changes: 3 additions & 0 deletions config/initializers/senic.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Scenic.configure do |config|
config.database = Scenic::Adapters::Mysql.new
end
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
get "/threads" => "comments#threads"
get "/threads/:user" => "comments#threads"

get "/replies" => "replies#show"
get "/replies/page/:page" => "replies#show"

get "/login" => "login#index"
post "/login" => "login#login"
post "/logout" => "login#logout"
Expand Down
13 changes: 13 additions & 0 deletions db/migrate/20180130235553_add_read_notification_support.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class AddReadNotificationSupport < ActiveRecord::Migration[5.1]
def change
create_table :read_ribbons do |t|
t.boolean :is_following, default: true
t.timestamps
end

add_reference :read_ribbons, :user, index: true
add_reference :read_ribbons, :story, index: true

create_view :replying_comments
end
end
17 changes: 16 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20180124143340) do
ActiveRecord::Schema.define(version: 20180130235553) do

create_table "comments", id: :integer, unsigned: true, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" do |t|
t.datetime "created_at", null: false
Expand Down Expand Up @@ -115,6 +115,16 @@
t.index ["created_at"], name: "index_moderations_on_created_at"
end

create_table "read_ribbons", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
t.boolean "is_following", default: true
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "user_id"
t.bigint "story_id"
t.index ["story_id"], name: "index_read_ribbons_on_story_id"
t.index ["user_id"], name: "index_read_ribbons_on_user_id"
end

create_table "saved_stories", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
Expand Down Expand Up @@ -236,4 +246,9 @@
t.index ["user_id", "story_id"], name: "user_id_story_id"
end


create_view "replying_comments", sql_definition: <<-SQL
select `lobsters_dev`.`read_ribbons`.`user_id` AS `user_id`,`lobsters_dev`.`comments`.`id` AS `comment_id`,`lobsters_dev`.`read_ribbons`.`story_id` AS `story_id`,`lobsters_dev`.`comments`.`parent_comment_id` AS `parent_comment_id`,`lobsters_dev`.`comments`.`created_at` AS `comment_created_at`,`parent_comments`.`user_id` AS `parent_comment_author_id`,`lobsters_dev`.`comments`.`user_id` AS `comment_author_id`,`lobsters_dev`.`stories`.`user_id` AS `story_author_id`,(`lobsters_dev`.`read_ribbons`.`updated_at` < `lobsters_dev`.`comments`.`created_at`) AS `is_unread` from (((`lobsters_dev`.`read_ribbons` join `lobsters_dev`.`comments` on((`lobsters_dev`.`comments`.`story_id` = `lobsters_dev`.`read_ribbons`.`story_id`))) join `lobsters_dev`.`stories` on((`lobsters_dev`.`stories`.`id` = `lobsters_dev`.`comments`.`story_id`))) left join `lobsters_dev`.`comments` `parent_comments` on((`parent_comments`.`id` = `lobsters_dev`.`comments`.`parent_comment_id`))) where ((`lobsters_dev`.`read_ribbons`.`is_following` = 1) and (`lobsters_dev`.`comments`.`user_id` <> `lobsters_dev`.`read_ribbons`.`user_id`) and ((`parent_comments`.`user_id` = `lobsters_dev`.`read_ribbons`.`user_id`) or (isnull(`parent_comments`.`user_id`) and (`lobsters_dev`.`stories`.`user_id` = `lobsters_dev`.`read_ribbons`.`user_id`))) and ((`lobsters_dev`.`comments`.`upvotes` - `lobsters_dev`.`comments`.`downvotes`) < 0) and ((`parent_comments`.`upvotes` - `parent_comments`.`downvotes`) < 0))
SQL

end
Loading

0 comments on commit dd42cca

Please sign in to comment.