Merge pull request #1196 from Retrospring/feature/turbo-comments
Move comments to a turbo frame
This commit is contained in:
commit
3bd45c8e96
|
@ -14,10 +14,12 @@ class Ajax::CommentController < AjaxController
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
comments = Comment.where(answer:).includes([{ user: :profile }, :smiles])
|
||||||
|
|
||||||
@response[:status] = :okay
|
@response[:status] = :okay
|
||||||
@response[:message] = t(".success")
|
@response[:message] = t(".success")
|
||||||
@response[:success] = true
|
@response[:success] = true
|
||||||
@response[:render] = render_to_string(partial: 'answerbox/comments', locals: { a: answer })
|
@response[:render] = render_to_string(partial: "answerbox/comments", locals: { a: answer, comments: })
|
||||||
@response[:count] = answer.comment_count
|
@response[:count] = answer.comment_count
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CommentController < ApplicationController
|
||||||
|
def index
|
||||||
|
answer = Answer.find(params[:id])
|
||||||
|
@comments = Comment.where(answer:).includes([{ user: :profile }, :smiles])
|
||||||
|
|
||||||
|
render "index", locals: { a: answer }
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ReactionController < ApplicationController
|
||||||
|
def index
|
||||||
|
answer = Answer.includes([smiles: { user: :profile }]).find(params[:id])
|
||||||
|
|
||||||
|
render "index", locals: { a: answer }
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,8 +1,8 @@
|
||||||
- if a.comments.all.count.zero?
|
- if comments.all.count.zero?
|
||||||
= t(".none")
|
= t(".none")
|
||||||
- else
|
- else
|
||||||
%ul.comment__container
|
%ul.comment__container
|
||||||
- a.comments.order(:created_at).each do |comment|
|
- comments.order(:created_at).each do |comment|
|
||||||
%li.comment{ data: { comment_id: comment.id } }
|
%li.comment{ data: { comment_id: comment.id } }
|
||||||
%div{ style: "height: 0; width: 0" }= render "modal/comment_smiles", comment: comment
|
%div{ style: "height: 0; width: 0" }= render "modal/comment_smiles", comment: comment
|
||||||
.d-flex
|
.d-flex
|
||||||
|
@ -24,20 +24,3 @@
|
||||||
%button.btn.btn-link.btn-sm.dropdown-toggle{ data: { bs_toggle: :dropdown }, aria: { expanded: false } }
|
%button.btn.btn-link.btn-sm.dropdown-toggle{ data: { bs_toggle: :dropdown }, aria: { expanded: false } }
|
||||||
%span.caret
|
%span.caret
|
||||||
= render "actions/comment", comment: comment, answer: a
|
= render "actions/comment", comment: comment, answer: a
|
||||||
- if user_signed_in?
|
|
||||||
%button.d-none{ name: "ab-open-and-comment", data: { a_id: a.id, selection_hotkey: "c" } }
|
|
||||||
.comment__compose-wrapper{
|
|
||||||
name: "ab-comment-new-group",
|
|
||||||
data: { a_id: a.id, controller: "character-count", character_count_max_value: 512 }
|
|
||||||
}
|
|
||||||
.form-group.has-feedback.comment__input-group.input-group
|
|
||||||
%textarea.form-control.comment__input{ type: :text, placeholder: t(".placeholder"), name: "ab-comment-new", data: { a_id: a.id, "character-count-target": "input" } }
|
|
||||||
.comment__submit-wrapper
|
|
||||||
%button.btn.btn-primary{
|
|
||||||
type: :button,
|
|
||||||
name: "ab-comment-new-submit",
|
|
||||||
title: t(".action"),
|
|
||||||
data: { a_id: a.id, "character-count-target": "action" }
|
|
||||||
}
|
|
||||||
%i.fa.fa-paper-plane-o
|
|
||||||
%span.text-muted.form-control-feedback.comment__character-count{ id: "ab-comment-charcount-#{a.id}", data: { "character-count-target": "counter" } } 512
|
|
||||||
|
|
|
@ -35,5 +35,28 @@
|
||||||
.col-md-6.d-md-flex.answerbox__actions
|
.col-md-6.d-md-flex.answerbox__actions
|
||||||
= render "answerbox/actions", a:, display_all:, subscribed_answer_ids:
|
= render "answerbox/actions", a:, display_all:, subscribed_answer_ids:
|
||||||
.card-footer{ id: "ab-comments-section-#{a.id}", class: display_all.nil? ? "d-none" : nil }
|
.card-footer{ id: "ab-comments-section-#{a.id}", class: display_all.nil? ? "d-none" : nil }
|
||||||
%div{ id: "ab-smiles-#{a.id}" }= render "answerbox/smiles", a: a
|
= turbo_frame_tag("ab-reactions-list-#{a.id}", src: reactions_path(a.question, a), loading: :lazy) do
|
||||||
%div{ id: "ab-comments-#{a.id}" }= render "answerbox/comments", a: a
|
.d-flex.smiles
|
||||||
|
.flex-shrink-0.me-1
|
||||||
|
%i.fa.fa-smile-o
|
||||||
|
= turbo_frame_tag("ab-comments-list-#{a.id}", src: comments_path(a.question, a), loading: :lazy) do
|
||||||
|
.d-flex.justify-content-center
|
||||||
|
.spinner-border{ role: :status }
|
||||||
|
.visually-hidden= t("voc.loading")
|
||||||
|
- if user_signed_in?
|
||||||
|
%button.d-none{ name: "ab-open-and-comment", data: { a_id: a.id, selection_hotkey: "c" } }
|
||||||
|
.comment__compose-wrapper{
|
||||||
|
name: "ab-comment-new-group",
|
||||||
|
data: { a_id: a.id, controller: "character-count", character_count_max_value: 512 }
|
||||||
|
}
|
||||||
|
.form-group.has-feedback.comment__input-group.input-group
|
||||||
|
%textarea.form-control.comment__input{ type: :text, placeholder: t(".comments.placeholder"), name: "ab-comment-new", data: { a_id: a.id, "character-count-target": "input" } }
|
||||||
|
.comment__submit-wrapper
|
||||||
|
%button.btn.btn-primary{
|
||||||
|
type: :button,
|
||||||
|
name: "ab-comment-new-submit",
|
||||||
|
title: t(".comments.action"),
|
||||||
|
data: { a_id: a.id, "character-count-target": "action" }
|
||||||
|
}
|
||||||
|
%i.fa.fa-paper-plane-o
|
||||||
|
%span.text-muted.form-control-feedback.comment__character-count{ id: "ab-comment-charcount-#{a.id}", data: { "character-count-target": "counter" } } 512
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
= turbo_frame_tag "ab-comments-list-#{a.id}" do
|
||||||
|
%div{ id: "ab-comments-#{a.id}" }= render "answerbox/comments", a:, comments: @comments
|
|
@ -0,0 +1,2 @@
|
||||||
|
= turbo_frame_tag "ab-reactions-list-#{a.id}" do
|
||||||
|
%div{ id: "ab-smiles-#{a.id}" }= render "answerbox/smiles", a:
|
|
@ -117,18 +117,18 @@ en:
|
||||||
actions:
|
actions:
|
||||||
share:
|
share:
|
||||||
title: "Share"
|
title: "Share"
|
||||||
comments:
|
|
||||||
none: "There are no comments yet."
|
|
||||||
placeholder: "Comment..."
|
|
||||||
action: "Post comment"
|
|
||||||
smiles:
|
smiles:
|
||||||
none: "No one smiled this yet."
|
none: "No one smiled this yet."
|
||||||
|
comments:
|
||||||
|
none: "There are no comments yet."
|
||||||
application:
|
application:
|
||||||
answerbox:
|
answerbox:
|
||||||
read: "Read the entire answer"
|
read: "Read the entire answer"
|
||||||
answered: "%{hide} %{user}" # resolves into "Answered by %{user}"
|
answered: "%{hide} %{user}" # resolves into "Answered by %{user}"
|
||||||
hide: "Answered by"
|
hide: "Answered by"
|
||||||
pinned: "Pinned"
|
pinned: "Pinned"
|
||||||
|
comments:
|
||||||
|
placeholder: "Comment…"
|
||||||
questionbox:
|
questionbox:
|
||||||
title: "Ask something!"
|
title: "Ask something!"
|
||||||
placeholder: "Type your question here…"
|
placeholder: "Type your question here…"
|
||||||
|
|
|
@ -12,6 +12,7 @@ en:
|
||||||
follow: "Follow"
|
follow: "Follow"
|
||||||
format_markdown: "Styling with Markdown is supported"
|
format_markdown: "Styling with Markdown is supported"
|
||||||
load: "Load more"
|
load: "Load more"
|
||||||
|
loading: "Loading…"
|
||||||
login: "Sign in"
|
login: "Sign in"
|
||||||
logout: "Sign out"
|
logout: "Sign out"
|
||||||
mute: "Mute"
|
mute: "Mute"
|
||||||
|
|
|
@ -153,6 +153,8 @@ Rails.application.routes.draw do
|
||||||
get "/@:username/a/:id", to: "answer#show", as: :answer
|
get "/@:username/a/:id", to: "answer#show", as: :answer
|
||||||
post "/@:username/a/:id/pin", to: "answer#pin", as: :pin_answer
|
post "/@:username/a/:id/pin", to: "answer#pin", as: :pin_answer
|
||||||
delete "/@:username/a/:id/pin", to: "answer#unpin", as: :unpin_answer
|
delete "/@:username/a/:id/pin", to: "answer#unpin", as: :unpin_answer
|
||||||
|
get "/@:username/a/:id/comments", to: "comment#index", as: :comments
|
||||||
|
get "/@:username/a/:id/reactions", to: "reaction#index", as: :reactions
|
||||||
get "/@:username/q/:id", to: "question#show", as: :question
|
get "/@:username/q/:id", to: "question#show", as: :question
|
||||||
get "/@:username/followers", to: "user#followers", as: :show_user_followers
|
get "/@:username/followers", to: "user#followers", as: :show_user_followers
|
||||||
get "/@:username/followings", to: "user#followings", as: :show_user_followings
|
get "/@:username/followings", to: "user#followings", as: :show_user_followings
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
describe CommentController, type: :controller do
|
||||||
|
describe "#index" do
|
||||||
|
shared_examples_for "succeeds" do
|
||||||
|
it "returns the correct response" do
|
||||||
|
subject
|
||||||
|
expect(response).to have_rendered("comment/index")
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
expect(assigns(:comments)).to eq(comments)
|
||||||
|
expect(assigns(:comments)).to_not include(unrelated_comment)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { get :index, params: { username: answer_author.screen_name, id: answer.id } }
|
||||||
|
|
||||||
|
let(:answer_author) { FactoryBot.create(:user) }
|
||||||
|
let(:answer) { FactoryBot.create(:answer, user: answer_author) }
|
||||||
|
let(:commenter) { FactoryBot.create(:user) }
|
||||||
|
let!(:comments) { FactoryBot.create_list(:comment, num_comments, answer:, user: commenter) }
|
||||||
|
let!(:unrelated_comment) do
|
||||||
|
FactoryBot.create(:comment,
|
||||||
|
answer: FactoryBot.create(:answer, user: FactoryBot.create(:user)),
|
||||||
|
user: commenter,)
|
||||||
|
end
|
||||||
|
|
||||||
|
[0, 1, 5, 30].each do |num_comments|
|
||||||
|
context "#{num_comments} comments" do
|
||||||
|
let(:num_comments) { num_comments }
|
||||||
|
|
||||||
|
include_examples "succeeds"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,34 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
describe ReactionController, type: :controller do
|
||||||
|
describe "#index" do
|
||||||
|
shared_examples_for "succeeds" do
|
||||||
|
it "returns the correct response" do
|
||||||
|
subject
|
||||||
|
expect(response).to have_rendered("reaction/index")
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { get :index, params: { username: answer_author.screen_name, id: answer.id } }
|
||||||
|
|
||||||
|
let(:answer_author) { FactoryBot.create(:user) }
|
||||||
|
let(:answer) { FactoryBot.create(:answer, user: answer_author) }
|
||||||
|
let!(:reactees) { FactoryBot.create_list(:user, num_comments) }
|
||||||
|
|
||||||
|
[0, 1, 5, 30].each do |num_comments|
|
||||||
|
context "#{num_comments} reactions" do
|
||||||
|
let(:num_comments) { num_comments }
|
||||||
|
|
||||||
|
before do
|
||||||
|
reactees.each { _1.smile(answer) }
|
||||||
|
end
|
||||||
|
|
||||||
|
include_examples "succeeds"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,101 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
describe "answerbox/_comments.html.haml", type: :view do
|
||||||
|
subject(:rendered) do
|
||||||
|
render partial: "answerbox/comments", locals: {
|
||||||
|
comments:, a:,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:a) { FactoryBot.create(:answer, user: FactoryBot.create(:user)) }
|
||||||
|
let(:comments) { Comment.all }
|
||||||
|
|
||||||
|
context "no comments" do
|
||||||
|
it "shows an empty list" do
|
||||||
|
expect(rendered).to eq("There are no comments yet.\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "comments are present" do
|
||||||
|
let!(:expected_comments) { FactoryBot.create_list(:comment, 5, answer: a, user: FactoryBot.create(:user)) }
|
||||||
|
|
||||||
|
it "shows a list of comments" do
|
||||||
|
html = Nokogiri::HTML.parse(rendered)
|
||||||
|
selector = %(li.comment .comment__content)
|
||||||
|
comment_elements = html.css(selector)
|
||||||
|
expect(comment_elements.size).to eq(5)
|
||||||
|
expect(comment_elements.map(&:text).map(&:strip)).to eq(expected_comments.map(&:content))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "containing your own comment" do
|
||||||
|
let(:user) { FactoryBot.create(:user) }
|
||||||
|
let!(:comment) { FactoryBot.create(:comment, user:, answer: a) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sign_in user
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows the delete option" do
|
||||||
|
html = Nokogiri::HTML.parse(rendered)
|
||||||
|
selector = %(li.comment[data-comment-id="#{comment.id}"] .btn-group a[data-action="ab-comment-destroy"])
|
||||||
|
element = html.css(selector)
|
||||||
|
expect(element).to_not be_nil
|
||||||
|
expect(element.text.strip).to eq("Delete")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "containing someone else's comment" do
|
||||||
|
let(:user) { FactoryBot.create(:user) }
|
||||||
|
let!(:comment) { FactoryBot.create(:comment, user: FactoryBot.create(:user), answer: a) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sign_in user
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not show the delete option" do
|
||||||
|
html = Nokogiri::HTML.parse(rendered)
|
||||||
|
selector = %(li.comment[data-comment-id="#{comment.id}"] .btn-group a[data-action="ab-comment-destroy"])
|
||||||
|
expect(html.css(selector)).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "containing a comment with smiles" do
|
||||||
|
let(:comment_author) { FactoryBot.create(:user) }
|
||||||
|
let(:comment) { FactoryBot.create(:comment, answer: a, user: comment_author) }
|
||||||
|
let(:other_comment) { FactoryBot.create(:comment, answer: a, user: comment_author) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
5.times do
|
||||||
|
user = FactoryBot.create(:user)
|
||||||
|
user.smile comment
|
||||||
|
end
|
||||||
|
|
||||||
|
User.last.smile other_comment
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows the correct number of smiles" do
|
||||||
|
html = Nokogiri::HTML.parse(rendered)
|
||||||
|
selector = %(li.comment[data-comment-id="#{comment.id}"] button[data-action="smile"]>span)
|
||||||
|
expect(html.css(selector).text).to eq("5")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "containing a comment you've smiled" do
|
||||||
|
let(:user) { FactoryBot.create(:user) }
|
||||||
|
let!(:comment) { FactoryBot.create(:comment, user: FactoryBot.create(:user), answer: a) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sign_in user
|
||||||
|
user.smile comment
|
||||||
|
end
|
||||||
|
|
||||||
|
it "displays the comment as smiled" do
|
||||||
|
html = Nokogiri::HTML.parse(rendered)
|
||||||
|
selector = %(li.comment[data-comment-id="#{comment.id}"] button[data-action="unsmile"])
|
||||||
|
expect(html.css(selector)).to_not be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,28 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
describe "answerbox/_smiles.html.haml", type: :view do
|
||||||
|
subject(:rendered) do
|
||||||
|
render partial: "answerbox/smiles", locals: { a: }
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:a) { FactoryBot.create(:answer, user: FactoryBot.create(:user)) }
|
||||||
|
|
||||||
|
context "no reactions" do
|
||||||
|
it "shows an empty list" do
|
||||||
|
expect(rendered).to match("No one smiled this yet.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "reactions are present" do
|
||||||
|
let!(:reactions) { FactoryBot.create_list(:smile, 5, parent: a) }
|
||||||
|
|
||||||
|
it "shows a list of users" do
|
||||||
|
html = Nokogiri::HTML.parse(rendered)
|
||||||
|
selector = %(.smiles a)
|
||||||
|
reaction_elements = html.css(selector)
|
||||||
|
expect(reaction_elements.size).to eq(5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue