diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 05b7ce921ad..95d45de7b61 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -1,42 +1,42 @@ class CommentsController < ApplicationController before_action :load_commentable, - only: [:index, :new, :create, :edit, :update, :show_comments, + only: [:index, :new, :draft, :create, :preview, :edit, :update, :show_comments, :hide_comments, :add_comment_reply, :cancel_comment_reply, :delete_comment, :cancel_comment_delete, :unreviewed, :review_all] - before_action :check_user_status, only: [:new, :create, :edit, :update, :destroy] + before_action :check_user_status, only: [:new, :draft, :create, :preview, :edit, :update, :destroy] before_action :load_comment, only: [:show, :edit, :update, :delete_comment, :destroy, :cancel_comment_edit, :cancel_comment_delete, :review, :approve, :reject, :freeze, :unfreeze, :hide, :unhide] before_action :check_visibility, only: [:show] before_action :check_if_restricted before_action :check_tag_wrangler_access before_action :check_parent_visible before_action :check_modify_parent, - only: [:new, :create, :edit, :update, :add_comment_reply, + only: [:new, :draft, :create, :preview, :edit, :update, :add_comment_reply, :cancel_comment_reply, :cancel_comment_edit] before_action :check_pseud_ownership, only: [:create, :update] before_action :check_ownership, only: [:edit, :update, :cancel_comment_edit] before_action :check_permission_to_edit, only: [:edit, :update] before_action :check_permission_to_delete, only: [:delete_comment, :destroy] - before_action :check_guest_comment_admin_setting, only: [:new, :create, :add_comment_reply] - before_action :check_parent_comment_permissions, only: [:new, :create, :add_comment_reply] + before_action :check_guest_comment_admin_setting, only: [:new, :draft, :create, :preview, :add_comment_reply] + before_action :check_parent_comment_permissions, only: [:new, :draft, :create, :preview, :add_comment_reply] before_action :check_unreviewed, only: [:add_comment_reply] - before_action :check_frozen, only: [:new, :create, :add_comment_reply] - before_action :check_hidden_by_admin, only: [:new, :create, :add_comment_reply] - before_action :check_not_replying_to_spam, only: [:new, :create, :add_comment_reply] - before_action :check_guest_replies_preference, only: [:new, :create, :add_comment_reply] + before_action :check_frozen, only: [:new, :draft, :create, :preview, :add_comment_reply] + before_action :check_hidden_by_admin, only: [:new, :draft, :create, :preview, :add_comment_reply] + before_action :check_not_replying_to_spam, only: [:new, :draft, :create, :preview, :add_comment_reply] + before_action :check_guest_replies_preference, only: [:new, :draft, :create, :preview, :add_comment_reply] before_action :check_permission_to_review, only: [:unreviewed] before_action :check_permission_to_access_single_unreviewed, only: [:show] before_action :check_permission_to_moderate, only: [:approve, :reject] before_action :check_permission_to_modify_frozen_status, only: [:freeze, :unfreeze] before_action :check_permission_to_modify_hidden_status, only: [:hide, :unhide] before_action :check_guest_email_is_from_suspended_or_banned_user, only: [:create] - before_action :admin_logout_required, only: [:new, :create, :add_comment_reply] + before_action :admin_logout_required, only: [:new, :draft, :create, :add_comment_reply] before_action :set_page_subtitle, only: [:index, :new, :show, :unreviewed] include WorksHelper include BlockHelper - before_action :check_blocked, only: [:new, :create, :add_comment_reply, :edit, :update] + before_action :check_blocked, only: [:new, :create, :preview, :add_comment_reply, :edit, :update] def check_blocked parent = find_parent @@ -95,7 +95,7 @@ def check_pseud_ownership return unless params[:comment][:pseud_id] pseud = Pseud.find(params[:comment][:pseud_id]) return if pseud && current_user && current_user.pseuds.include?(pseud) - flash[:error] = ts("You can't comment with that pseud.") + flash[:error] = t("comments.check_pseud_ownership.error") redirect_to root_path end @@ -113,12 +113,12 @@ def check_modify_parent parent = find_parent # No one can create or update comments on something hidden by an admin. if parent.respond_to?(:hidden_by_admin) && parent.hidden_by_admin - flash[:error] = ts("Sorry, you can't add or edit comments on a hidden work.") + flash[:error] = t("comments.commentable.permissions.work.hidden") redirect_to work_path(parent) end # No one can create or update comments on unrevealed works. if parent.respond_to?(:in_unrevealed_collection) && parent.in_unrevealed_collection - flash[:error] = ts("Sorry, you can't add or edit comments on an unrevealed work.") + flash[:error] = t("comments.commentable.permissions.work.unrevealed") redirect_to work_path(parent) end @@ -190,7 +190,7 @@ def check_guest_replies_preference def check_unreviewed return unless @commentable.respond_to?(:unreviewed?) && @commentable.unreviewed? - flash[:error] = ts("Sorry, you cannot reply to an unapproved comment.") + flash[:error] = t("comments.check_unreviewed.error") redirect_to logged_in? ? root_path : new_user_session_path(return_to: request.fullpath) end @@ -218,7 +218,7 @@ def check_not_replying_to_spam def check_permission_to_review parent = find_parent return if logged_in_as_admin? || current_user_owns?(parent) - flash[:error] = ts("Sorry, you don't have permission to see those unreviewed comments.") + flash[:error] = t("comments.check_permission_to_review.error") redirect_to logged_in? ? root_path : new_user_session_path(return_to: request.fullpath) end @@ -226,14 +226,14 @@ def check_permission_to_access_single_unreviewed return unless @comment.unreviewed? parent = find_parent return if logged_in_as_admin? || current_user_owns?(parent) || current_user_owns?(@comment) - flash[:error] = ts("Sorry, that comment is currently in moderation.") + flash[:error] = t("comments.check_permission_to_access_single_unreviewed.error") redirect_to logged_in? ? root_path : new_user_session_path(return_to: request.fullpath) end def check_permission_to_moderate return if logged_in_as_admin? || current_user_owns?(find_parent) - flash[:error] = ts("Sorry, you don't have permission to moderate that comment.") + flash[:error] = t("comments.check_permission_to_moderate.error") redirect_to(logged_in? ? root_path : new_user_session_path(return_to: comment_path(@comment))) end @@ -254,7 +254,7 @@ def check_permission_to_edit flash[:error] = t("comments.check_permission_to_edit.error.frozen") redirect_back_or_to @comment elsif !@comment.count_all_comments.zero? - flash[:error] = ts("Comments with replies cannot be edited") + flash[:error] = t("comments.check_permission_to_edit.error.has_replies") redirect_back_or_to @comment end end @@ -362,27 +362,51 @@ def show # GET /comments/new def new if @commentable.nil? - flash[:error] = ts("What did you want to comment on?") + flash[:error] = t("comments.new.missing_commentable") redirect_back_or_to root_path else - @comment = Comment.new + @comment = build_comment_for_form @controller_name = params[:controller_name] if params[:controller_name] @name = case @commentable.class.name - when /Work/ + when /Work/, /AdminPost/ @commentable.title when /Chapter/ @commentable.work.title when /Tag/ @commentable.name - when /AdminPost/ + when /Comment/ + t("comments.new.previous_comment") + else + @commentable.class.name + end + end + end + + # POST /comments/draft + def draft + if @commentable.nil? + flash[:error] = t("comments.new.missing_commentable") + redirect_back_or_to root_path + else + @comment = build_comment_for_form + @controller_name = params[:controller_name] if params[:controller_name] + @name = + case @commentable.class.name + when /Work/, /AdminPost/ @commentable.title + when /Chapter/ + @commentable.work.title + when /Tag/ + @commentable.name when /Comment/ - ts("Previous Comment") + t("comments.new.previous_comment") else @commentable.class.name end end + + render :new end # GET /comments/1/edit @@ -393,57 +417,108 @@ def edit end end + # GET /comments/preview + def preview + if @commentable.nil? + flash[:error] = t(".missing_commentable") + redirect_back_or_to root_path + return + end + + @comment = Comment.new(comment_params) + @comment.commentable = Comment.commentable_object(@commentable) + + @comment.set_parent_and_unreviewed + + unless @comment.valid? + render :new, locals: { show_errors: true } + return + end + + @preview_mode = true + @form_state = { comment_content: @comment.comment_content } + @form_state[:pseud_id] = @comment.pseud_id if @comment.pseud_id + @form_state[:name] = @comment.name if @comment.name + @form_state[:email] = @comment.email if @comment.email + @form_state[:view_full_work] = params[:view_full_work] if params[:view_full_work] + @form_state[:page] = params[:page] if params[:page] + @form_state[:controller_name] = params[:controller_name] if params[:controller_name] + + case @commentable + when Work + @form_state[:work_id] = @commentable.id + when Chapter + @form_state[:chapter_id] = @commentable.id + when AdminPost + @form_state[:admin_post_id] = @commentable.id + when Tag + @form_state[:tag_id] = @commentable.name + when Comment + @form_state[:comment_id] = @commentable.id + end + + @form_state[:filters] = filter_params.to_h if controller_name == "inbox" && params[:filters] + + render :preview + end + # POST /comments # POST /comments.xml def create - if @commentable.nil? - flash[:error] = ts("What did you want to comment on?") + if params[:preview_button] + preview + return + end + + if @commentable.blank? + flash[:error] = t("comments.create.missing_commentable") redirect_back_or_to root_path - else - @comment = Comment.new(comment_params) - @comment.ip_address = request.remote_ip - @comment.user_agent = request.env["HTTP_USER_AGENT"]&.to(499) - @comment.cloudflare_bot_score = request.env["HTTP_CF_BOT_SCORE"] - @comment.cloudflare_ja3_hash = request.env["HTTP_CF_JA3_HASH"] - @comment.cloudflare_ja4 = request.env["HTTP_CF_JA4"] - @comment.commentable = Comment.commentable_object(@commentable) - @controller_name = params[:controller_name] - - # First, try saving the comment - if @comment.save - flash[:comment_notice] = if @comment.unreviewed? - # i18n-tasks-use t("comments.create.success.moderated.admin_post") - # i18n-tasks-use t("comments.create.success.moderated.work") - t("comments.create.success.moderated.#{@comment.ultimate_parent.model_name.i18n_key}") - else - t("comments.create.success.not_moderated") - end - respond_to do |format| - format.html do - if request.referer&.match(/inbox/) - redirect_to user_inbox_path(current_user, filters: filter_params, page: params[:page]) - elsif request.referer&.match(/new/) || (@comment.unreviewed? && current_user) - # If the referer is the new comment page, go to the comment's page - # instead of reloading the full work. - # If the comment is unreviewed and commenter is logged in, take - # them to the comment's page so they can access the edit and - # delete options for the comment, since unreviewed comments don't - # appear on the commentable. - redirect_to comment_path(@comment) - elsif request.referer == root_url - # replying on the homepage - redirect_to root_path - elsif @comment.unreviewed? - redirect_to_all_comments(@commentable) - else - redirect_to_comment(@comment, { view_full_work: (params[:view_full_work] == "true"), page: params[:page] }) - end + return + end + + @comment = Comment.new(comment_params) + @comment.ip_address = request.remote_ip + @comment.user_agent = request.env["HTTP_USER_AGENT"]&.to(499) + @comment.cloudflare_bot_score = request.env["HTTP_CF_BOT_SCORE"] + @comment.cloudflare_ja3_hash = request.env["HTTP_CF_JA3_HASH"] + @comment.cloudflare_ja4 = request.env["HTTP_CF_JA4"] + @comment.commentable = Comment.commentable_object(@commentable) + @controller_name = params[:controller_name] + + # First, try saving the comment + if @comment.save + flash[:comment_notice] = if @comment.unreviewed? + # i18n-tasks-use t("comments.create.success.moderated.admin_post") + # i18n-tasks-use t("comments.create.success.moderated.work") + t("comments.create.success.moderated.#{@comment.ultimate_parent.model_name.i18n_key}") + else + t("comments.create.success.not_moderated") + end + respond_to do |format| + format.html do + if request.referer&.match(/inbox/) + redirect_to user_inbox_path(current_user, filters: filter_params, page: params[:page]) + elsif request.referer&.match(/new/) || (@comment.unreviewed? && current_user) + # If the referer is the new comment page, go to the comment's page + # instead of reloading the full work. + # If the comment is unreviewed and commenter is logged in, take + # them to the comment's page so they can access the edit and + # delete options for the comment, since unreviewed comments don't + # appear on the commentable. + redirect_to comment_path(@comment) + elsif request.referer == root_url + # replying on the homepage + redirect_to root_path + elsif @comment.unreviewed? + redirect_to_all_comments(@commentable) + else + redirect_to_comment(@comment, { view_full_work: (params[:view_full_work] == "true"), page: params[:page] }) end end - else - flash[:error] = ts("Couldn't save comment!") - render action: "new" end + else + flash[:error] = t("comments.create.error") + render action: "new" end end @@ -452,7 +527,7 @@ def create def update updated_comment_params = comment_params.merge(edited_at: Time.current) if @comment.update(updated_comment_params) - flash[:comment_notice] = ts('Comment was successfully updated.') + flash[:comment_notice] = t("comments.update.success") respond_to do |format| format.html do redirect_to comment_path(@comment) and return if @comment.unreviewed? @@ -476,17 +551,17 @@ def destroy if !@comment.destroy_or_mark_deleted # something went wrong? - flash[:comment_error] = ts("We couldn't delete that comment.") + flash[:comment_error] = t("comments.destroy.error") redirect_to_comment(@comment) elsif unreviewed # go back to the rest of the unreviewed comments - flash[:notice] = ts("Comment deleted.") + flash[:notice] = t("comments.destroy.success") redirect_back_or_to unreviewed_work_comments_path(@comment.commentable) elsif parent_comment - flash[:comment_notice] = ts("Comment deleted.") + flash[:comment_notice] = t("comments.destroy.success") redirect_to_comment(parent_comment) else - flash[:comment_notice] = ts("Comment deleted.") + flash[:comment_notice] = t("comments.destroy.success") redirect_to_all_comments(parent, {show_comments: true}) end end @@ -503,7 +578,7 @@ def review @comment.toggle!(:unreviewed) # mark associated inbox comments as read InboxComment.where(user_id: current_user.id, feedback_comment_id: @comment.id).update_all(read: true) unless logged_in_as_admin? - flash[:notice] = ts("Comment approved.") + flash[:notice] = t("comments.review.success") respond_to do |format| format.html do if params[:approved_from] == "inbox" @@ -764,6 +839,20 @@ def permission_to_modify_frozen_status private + def build_comment_for_form + if params[:comment_content].present? || params[:comment].present? + comment_attrs = { + comment_content: params[:comment_content] || params.dig(:comment, :comment_content), + pseud_id: params[:pseud_id] || params.dig(:comment, :pseud_id), + name: params[:name] || params.dig(:comment, :name), + email: params[:email] || params.dig(:comment, :email) + } + Comment.new(comment_attrs.compact) + else + Comment.new + end + end + def comment_params params.require(:comment).permit( :pseud_id, :comment_content, :name, :email, :edited_at diff --git a/app/views/comments/_comment_form.html.erb b/app/views/comments/_comment_form.html.erb index cc4660bcd8e..a7b22eb55cd 100644 --- a/app/views/comments/_comment_form.html.erb +++ b/app/views/comments/_comment_form.html.erb @@ -102,6 +102,7 @@ maximum_length: ArchiveConfig.COMMENT_MAX, tooLongMessage: t(".comment_too_long", count: ArchiveConfig.COMMENT_MAX) %>
+ <%= f.submit t(".preview_button"), name: "preview_button", id: "comment_preview_for_#{commentable.id}" %> <%= f.submit button_name, id: "comment_submit_for_#{commentable.id}", data: { disable_with: t(".processing_message") } %> <% if controller.controller_name == 'inbox' %> <%= t(".cancel_action") %> diff --git a/app/views/comments/preview.html.erb b/app/views/comments/preview.html.erb new file mode 100644 index 00000000000..771ade1af25 --- /dev/null +++ b/app/views/comments/preview.html.erb @@ -0,0 +1,76 @@ +
<%= t(".commenting_on_html", commentable_link: link_to_comment_ultimate_parent(@comment)) %>
+ + ++ <%= raw sanitize_field(@comment, :comment_content) %> ++
paragraph
" + And I press "Preview" + Then I should see "Preview Comment" + And I should see "bold" + And I should see "italic" + And I should see "paragraph" + +Scenario: Guest comment preview + Given the work "The One Where Neal is Awesome" by "creator" with guest comments enabled + When I view the work "The One Where Neal is Awesome" + And I fill in "Guest name" with "Guest User" + And I fill in "Guest email" with "guest@example.com" + And I fill in "Comment" with "Great work!" + And I press "Preview" + Then I should see "Preview Comment" + And I should see "Guest User" in the comment byline + And I should see "Great work!" + And I should see a button "Back to Edit" + When I press "Back to Edit" + Then I should see "Great work!" in the comment field + And I should see "Guest User" in the guest name field + +Scenario: Comment preview for anonymous work + Given there is a work "The One Where Neal is Awesome" in an anonymous collection "Anonymous Hugs" + When I am logged in as the author of "The One Where Neal is Awesome" + And I view the work "The One Where Neal is Awesome" + And I fill in "Comment" with "Anonymous work comment" + And I press "Preview" + Then I should see "Preview Comment" + And I should see "Anonymous Creator" in the comment byline + And I should see "Anonymous work comment" diff --git a/features/step_definitions/comment_steps.rb b/features/step_definitions/comment_steps.rb index 22d1cc413a1..ec4b4fe4cfc 100644 --- a/features/step_definitions/comment_steps.rb +++ b/features/step_definitions/comment_steps.rb @@ -410,3 +410,23 @@ When "I reply on a new page" do visit find(:link, "Reply")["href"] end + +Then "I should see {string} in the comment byline" do |text| + step %{I should see "#{text}" within ".byline"} +end + +Then "I should see a button {string}" do |text| + expect(page).to have_button(text) +end + +Then "I should see {string} in the comment field" do |text| + expect(find('textarea[name="comment[comment_content]"]').value).to include(text) +end + +Then "I should see the {string} button" do |text| + expect(page).to have_button(text) +end + +Then "I should see {string} in the guest name field" do |text| + expect(find('input[name="comment[name]"]').value).to eq(text) +end diff --git a/public/stylesheets/site/2.0/08-actions.css b/public/stylesheets/site/2.0/08-actions.css index d9a2cabd816..9473824f871 100644 --- a/public/stylesheets/site/2.0/08-actions.css +++ b/public/stylesheets/site/2.0/08-actions.css @@ -60,7 +60,7 @@ input[type="submit"] { white-space: normal; } -p.submit, input.submit, dd.submit { +p.submit, input.submit, dd.submit, ul.actions.right { text-align: right; }