From 69ab9b1437f349bd59fd8fc36402e53163bf9192 Mon Sep 17 00:00:00 2001 From: forceofcalm Date: Wed, 20 May 2026 13:57:34 -0400 Subject: [PATCH 01/28] close... --- app/controllers/comments_controller.rb | 64 +++++++++-- app/views/comments/_comment_form.html.erb | 1 + app/views/comments/preview.html.erb | 123 +++++++++++++++++++++ config/locales/views/en.yml | 6 + config/routes.rb | 1 + public/stylesheets/site/2.0/08-actions.css | 2 +- 6 files changed, 185 insertions(+), 12 deletions(-) create mode 100644 app/views/comments/preview.html.erb diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 05b7ce921ad..17f0016d518 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -1,29 +1,29 @@ class CommentsController < ApplicationController before_action :load_commentable, - only: [:index, :new, :create, :edit, :update, :show_comments, + only: [:index, :new, :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, :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, :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, :create, :preview, :add_comment_reply] + before_action :check_parent_comment_permissions, only: [:new, :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, :create, :preview, :add_comment_reply] + before_action :check_hidden_by_admin, only: [:new, :create, :preview, :add_comment_reply] + before_action :check_not_replying_to_spam, only: [:new, :create, :preview, :add_comment_reply] + before_action :check_guest_replies_preference, only: [:new, :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] @@ -36,7 +36,7 @@ class CommentsController < ApplicationController 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 @@ -365,7 +365,19 @@ def new flash[:error] = ts("What did you want to comment on?") redirect_back_or_to root_path else - @comment = Comment.new + # Allow pre-populating from preview or edit forms + # Handle both flat params (from GET) and nested params (from POST) + 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 = Comment.new(comment_attrs.compact) + else + @comment = Comment.new + end @controller_name = params[:controller_name] if params[:controller_name] @name = case @commentable.class.name @@ -393,9 +405,39 @@ def edit end end + # GET /comments/preview + def preview + if @commentable.nil? + flash[:error] = ts("What did you want to comment on?") + redirect_back_or_to root_path + return + end + + @comment = Comment.new(comment_params) + @comment.commentable = Comment.commentable_object(@commentable) + + # Set parent and unreviewed status for nested comments + @comment.set_parent_and_unreviewed + + # Validate to catch errors early + unless @comment.valid? + render :new, locals: { show_errors: true } + return + end + + @preview_mode = true + render :preview + end + # POST /comments # POST /comments.xml def create + # Check if this is a preview request + if params[:preview_button] + preview + return + end + if @commentable.nil? flash[:error] = ts("What did you want to comment on?") redirect_back_or_to root_path 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..dea89b884d9 --- /dev/null +++ b/app/views/comments/preview.html.erb @@ -0,0 +1,123 @@ + +

<%= t(".page_heading") %>

+<%# error_messages_for :comment %> + + + +
+ +
+ + + +
+ <%# Back to Edit - submit form with all data to repopulate the form %> + <%= form_with url: new_comment_path, method: :get, local: true, html: { id: "back_to_edit_form" } do |f| %> + <%# Use hidden_field_tag for all fields to ensure flat params %> + <%= hidden_field_tag :comment_content, @comment.comment_content %> + + <% if @comment.pseud_id %> + <%= hidden_field_tag :pseud_id, @comment.pseud_id %> + <% elsif @comment.name %> + <%= hidden_field_tag :name, @comment.name %> + <%= hidden_field_tag :email, @comment.email %> + <% end %> + + <%# Preserve commentable context - use hidden_field_tag for non-nested params %> + <% if @commentable.is_a?(Work) %> + <%= hidden_field_tag :work_id, @commentable.id %> + <% elsif @commentable.is_a?(Chapter) %> + <%= hidden_field_tag :chapter_id, @commentable.id %> + <% elsif @commentable.is_a?(AdminPost) %> + <%= hidden_field_tag :admin_post_id, @commentable.id %> + <% elsif @commentable.is_a?(Tag) %> + <%= hidden_field_tag :tag_id, @commentable.name %> + <% elsif @commentable.is_a?(Comment) %> + <%= hidden_field_tag :comment_id, @commentable.id %> + <% end %> + + <%# Preserve contextual params %> + <% if params[:view_full_work] %> + <%= hidden_field_tag :view_full_work, params[:view_full_work] %> + <% end %> + <% if params[:page] %> + <%= hidden_field_tag :page, params[:page] %> + <% end %> + <% if controller.controller_name == "inbox" && params[:filters] %> + <%= hidden_field_tag "filters[read]", params[:filters][:read] %> + <%= hidden_field_tag "filters[replied_to]", params[:filters][:replied_to] %> + <%= hidden_field_tag "filters[date]", params[:filters][:date] %> + <% end %> + <%= hidden_field_tag :controller_name, @controller_name if @controller_name %> + + <%= submit_tag t(".back_to_edit"), class: "button" %> + <% end %> + + <%= form_for @comment, url: comments_path, method: :post, local: true, html: { id: "post_comment_from_preview" } do |f| %> + <%= f.hidden_field :comment_content %> + + <% if @comment.pseud_id %> + <%= f.hidden_field :pseud_id %> + <% elsif @comment.name %> + <%= f.hidden_field :name %> + <%= f.hidden_field :email %> + <% end %> + + <%# Preserve commentable context - use hidden_field_tag for non-nested params %> + <% if @commentable.is_a?(Work) %> + <%= hidden_field_tag :work_id, @commentable.id %> + <% elsif @commentable.is_a?(Chapter) %> + <%= hidden_field_tag :chapter_id, @commentable.id %> + <% elsif @commentable.is_a?(AdminPost) %> + <%= hidden_field_tag :admin_post_id, @commentable.id %> + <% elsif @commentable.is_a?(Tag) %> + <%= hidden_field_tag :tag_id, @commentable.name %> + <% elsif @commentable.is_a?(Comment) %> + <%= hidden_field_tag :comment_id, @commentable.id %> + <% end %> + + <%# Preserve contextual params %> + <% if params[:view_full_work] %> + <%= hidden_field_tag :view_full_work, params[:view_full_work] %> + <% end %> + <% if params[:page] %> + <%= hidden_field_tag :page, params[:page] %> + <% end %> + <% if controller.controller_name == "inbox" && params[:filters] %> + <%= hidden_field_tag "filters[read]", params[:filters][:read] %> + <%= hidden_field_tag "filters[replied_to]", params[:filters][:replied_to] %> + <%= hidden_field_tag "filters[date]", params[:filters][:date] %> + <% end %> + <%= hidden_field_tag :controller_name, @controller_name if @controller_name %> + + <%= f.submit t(".post_comment"), class: "button submit" %> + <% end %> +
+ diff --git a/config/locales/views/en.yml b/config/locales/views/en.yml index 630a3291f93..ae9a5506597 100644 --- a/config/locales/views/en.yml +++ b/config/locales/views/en.yml @@ -874,6 +874,7 @@ en: comment: Comment note: Note legend: Post Comment + preview_button: Preview processing_message: Please wait... commentable: actions: @@ -920,6 +921,11 @@ en: page_heading: Editing comment show: Show update: Update + preview: + back_to_edit: Back to Edit + commenting_on: "Commenting on: %{commentable_link}" + page_heading: Preview Comment + post_comment: Post Comment show: comment_on_html: Comment on %{commentable_link} single_comment: diff --git a/config/routes.rb b/config/routes.rb index 5c5c951551f..79249456fc0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -578,6 +578,7 @@ put :unhide end collection do + post :preview get :hide_comments get :show_comments get :add_comment_reply diff --git a/public/stylesheets/site/2.0/08-actions.css b/public/stylesheets/site/2.0/08-actions.css index d9a2cabd816..4ec046b1a63 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, div.actions.right { text-align: right; } From 7e7d691856cf66e028278aefd1fbb08e4990a571 Mon Sep 17 00:00:00 2001 From: forceofcalm Date: Wed, 20 May 2026 14:21:48 -0400 Subject: [PATCH 02/28] added tests --- .../comments_and_kudos/add_comment.feature | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/features/comments_and_kudos/add_comment.feature b/features/comments_and_kudos/add_comment.feature index d90b2ef166a..6b3dffba879 100644 --- a/features/comments_and_kudos/add_comment.feature +++ b/features/comments_and_kudos/add_comment.feature @@ -457,3 +457,80 @@ Scenario: Guest comments with an email from a banned or suspended user should be And the email to "creator" should contain "edited their reply to your comment on" And the email to "creator" should contain "Go to the thread starting from this comment" And the email to "creator" should be translated + +Scenario: Comment preview + Given the work "The One Where Neal is Awesome" + When I am logged in as "commenter" + And I view the work "The One Where Neal is Awesome" + And I fill in "Comment" with "This is my test comment with bold text" + And I press "Preview" + Then I should see "Preview Comment" + And I should see "Commenting on: The One Where Neal is Awesome" + And I should see "This is my test comment with bold text" + And I should see "commenter" in the comment byline + And I should see a link "Back to Edit" + And I should see a button "Post Comment" + +Scenario: Comment preview persists content when going back to edit + Given the work "The One Where Neal is Awesome" + When I am logged in as "commenter" + And I view the work "The One Where Neal is Awesome" + And I fill in "Comment" with "Initial comment content" + And I press "Preview" + Then I should see "Preview Comment" + When I follow "Back to Edit" + Then I should see "Initial comment content" in the comment field + And I should see the "Preview" button + When I fill in "Comment" with "Initial comment content with more text" + And I press "Preview" + Then I should see "Initial comment content with more text" + When I follow "Back to Edit" + Then I should see "Initial comment content with more text" in the comment field + +Scenario: Comment preview and post + Given the work "The One Where Neal is Awesome" + When I am logged in as "commenter" + And I view the work "The One Where Neal is Awesome" + And I fill in "Comment" with "I really enjoyed this!" + And I press "Preview" + Then I should see "Preview Comment" + And I should see "I really enjoyed this!" + When I press "Post Comment" + Then I should see "Comment created!" + And I should see "I really enjoyed this!" within ".odd" + +Scenario: Comment preview shows sanitized HTML + Given the work "The One Where Neal is Awesome" + When I am logged in as "commenter" + And I view the work "The One Where Neal is Awesome" + And I fill in "Comment" with "Safe HTML: bold, italic,

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 "Back to Edit" + When I follow "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 the anonymous work "The One Where Neal is Awesome" + When I am logged in as "commenter" + 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" From 16d19e7fed842457a3382149b378e07ff66471ba Mon Sep 17 00:00:00 2001 From: forceofcalm Date: Wed, 20 May 2026 17:20:13 -0400 Subject: [PATCH 03/28] some progress on code cleanup --- app/controllers/comments_controller.rb | 39 +++++++++++-- app/views/comments/preview.html.erb | 79 +++----------------------- 2 files changed, 40 insertions(+), 78 deletions(-) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 17f0016d518..7443072831a 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -365,8 +365,6 @@ def new flash[:error] = ts("What did you want to comment on?") redirect_back_or_to root_path else - # Allow pre-populating from preview or edit forms - # Handle both flat params (from GET) and nested params (from POST) if params[:comment_content].present? || params[:comment].present? comment_attrs = { comment_content: params[:comment_content] || params.dig(:comment, :comment_content), @@ -416,23 +414,52 @@ def preview @comment = Comment.new(comment_params) @comment.commentable = Comment.commentable_object(@commentable) - # Set parent and unreviewed status for nested comments @comment.set_parent_and_unreviewed - # Validate to catch errors early unless @comment.valid? render :new, locals: { show_errors: true } return end @preview_mode = true + @context_params = build_context_params render :preview end + private + + def build_context_params + params = { comment_content: @comment.comment_content } + params[:pseud_id] = @comment.pseud_id if @comment.pseud_id + params[:name] = @comment.name if @comment.name + params[:email] = @comment.email if @comment.email + params[:view_full_work] = request.parameters[:view_full_work] if request.parameters[:view_full_work] + params[:page] = request.parameters[:page] if request.parameters[:page] + params[:controller_name] = request.parameters[:controller_name] if request.parameters[:controller_name] + + case @commentable + when Work + params[:work_id] = @commentable.id + when Chapter + params[:chapter_id] = @commentable.id + when AdminPost + params[:admin_post_id] = @commentable.id + when Tag + params[:tag_id] = @commentable.name + when Comment + params[:comment_id] = @commentable.id + end + + if controller_name == "inbox" && request.parameters[:filters] + params[:filters] = request.parameters[:filters] + end + + params + end + # POST /comments # POST /comments.xml def create - # Check if this is a preview request if params[:preview_button] preview return @@ -728,7 +755,7 @@ def delete_comment redirect_to_comment(@comment, options) end format.js - end + end# Allow pre-populating from preview or edit forms end def cancel_comment_delete diff --git a/app/views/comments/preview.html.erb b/app/views/comments/preview.html.erb index dea89b884d9..c6fac90ac95 100644 --- a/app/views/comments/preview.html.erb +++ b/app/views/comments/preview.html.erb @@ -1,7 +1,4 @@ -

<%= t(".page_heading") %>

-<%# error_messages_for :comment %> -
@@ -20,12 +17,12 @@
<% if !@comment.pseud.nil? %> <% if @comment.by_anonymous_creator? %> - + <% else %> <%= icon_display(@comment.pseud.user, @comment.pseud) %> <% end %> <% else %> - + <% end %>
@@ -36,49 +33,9 @@
- +
- <%# Back to Edit - submit form with all data to repopulate the form %> - <%= form_with url: new_comment_path, method: :get, local: true, html: { id: "back_to_edit_form" } do |f| %> - <%# Use hidden_field_tag for all fields to ensure flat params %> - <%= hidden_field_tag :comment_content, @comment.comment_content %> - - <% if @comment.pseud_id %> - <%= hidden_field_tag :pseud_id, @comment.pseud_id %> - <% elsif @comment.name %> - <%= hidden_field_tag :name, @comment.name %> - <%= hidden_field_tag :email, @comment.email %> - <% end %> - - <%# Preserve commentable context - use hidden_field_tag for non-nested params %> - <% if @commentable.is_a?(Work) %> - <%= hidden_field_tag :work_id, @commentable.id %> - <% elsif @commentable.is_a?(Chapter) %> - <%= hidden_field_tag :chapter_id, @commentable.id %> - <% elsif @commentable.is_a?(AdminPost) %> - <%= hidden_field_tag :admin_post_id, @commentable.id %> - <% elsif @commentable.is_a?(Tag) %> - <%= hidden_field_tag :tag_id, @commentable.name %> - <% elsif @commentable.is_a?(Comment) %> - <%= hidden_field_tag :comment_id, @commentable.id %> - <% end %> - - <%# Preserve contextual params %> - <% if params[:view_full_work] %> - <%= hidden_field_tag :view_full_work, params[:view_full_work] %> - <% end %> - <% if params[:page] %> - <%= hidden_field_tag :page, params[:page] %> - <% end %> - <% if controller.controller_name == "inbox" && params[:filters] %> - <%= hidden_field_tag "filters[read]", params[:filters][:read] %> - <%= hidden_field_tag "filters[replied_to]", params[:filters][:replied_to] %> - <%= hidden_field_tag "filters[date]", params[:filters][:date] %> - <% end %> - <%= hidden_field_tag :controller_name, @controller_name if @controller_name %> - - <%= submit_tag t(".back_to_edit"), class: "button" %> - <% end %> + <%= link_to t(".back_to_edit"), new_comment_path(@context_params), class: "button" %> <%= form_for @comment, url: comments_path, method: :post, local: true, html: { id: "post_comment_from_preview" } do |f| %> <%= f.hidden_field :comment_content %> @@ -90,32 +47,10 @@ <%= f.hidden_field :email %> <% end %> - <%# Preserve commentable context - use hidden_field_tag for non-nested params %> - <% if @commentable.is_a?(Work) %> - <%= hidden_field_tag :work_id, @commentable.id %> - <% elsif @commentable.is_a?(Chapter) %> - <%= hidden_field_tag :chapter_id, @commentable.id %> - <% elsif @commentable.is_a?(AdminPost) %> - <%= hidden_field_tag :admin_post_id, @commentable.id %> - <% elsif @commentable.is_a?(Tag) %> - <%= hidden_field_tag :tag_id, @commentable.name %> - <% elsif @commentable.is_a?(Comment) %> - <%= hidden_field_tag :comment_id, @commentable.id %> - <% end %> - - <%# Preserve contextual params %> - <% if params[:view_full_work] %> - <%= hidden_field_tag :view_full_work, params[:view_full_work] %> - <% end %> - <% if params[:page] %> - <%= hidden_field_tag :page, params[:page] %> - <% end %> - <% if controller.controller_name == "inbox" && params[:filters] %> - <%= hidden_field_tag "filters[read]", params[:filters][:read] %> - <%= hidden_field_tag "filters[replied_to]", params[:filters][:replied_to] %> - <%= hidden_field_tag "filters[date]", params[:filters][:date] %> + <% @context_params.each do |key, value| %> + <% next if key.in?(:comment_content, :pseud_id, :name, :email) %> + <%= hidden_field_tag key, value %> <% end %> - <%= hidden_field_tag :controller_name, @controller_name if @controller_name %> <%= f.submit t(".post_comment"), class: "button submit" %> <% end %> From e1b9efac367562f66ea06a5c3108518dd4a9b9b4 Mon Sep 17 00:00:00 2001 From: forceofcalm Date: Wed, 20 May 2026 18:12:48 -0400 Subject: [PATCH 04/28] maybe better? --- app/controllers/comments_controller.rb | 60 +++++++++++++------------- app/views/comments/preview.html.erb | 12 ++++-- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 7443072831a..86fbe67aec6 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -426,37 +426,6 @@ def preview render :preview end - private - - def build_context_params - params = { comment_content: @comment.comment_content } - params[:pseud_id] = @comment.pseud_id if @comment.pseud_id - params[:name] = @comment.name if @comment.name - params[:email] = @comment.email if @comment.email - params[:view_full_work] = request.parameters[:view_full_work] if request.parameters[:view_full_work] - params[:page] = request.parameters[:page] if request.parameters[:page] - params[:controller_name] = request.parameters[:controller_name] if request.parameters[:controller_name] - - case @commentable - when Work - params[:work_id] = @commentable.id - when Chapter - params[:chapter_id] = @commentable.id - when AdminPost - params[:admin_post_id] = @commentable.id - when Tag - params[:tag_id] = @commentable.name - when Comment - params[:comment_id] = @commentable.id - end - - if controller_name == "inbox" && request.parameters[:filters] - params[:filters] = request.parameters[:filters] - end - - params - end - # POST /comments # POST /comments.xml def create @@ -833,6 +802,35 @@ def permission_to_modify_frozen_status private + def build_context_params + context = { comment_content: @comment.comment_content } + context[:pseud_id] = @comment.pseud_id if @comment.pseud_id + context[:name] = @comment.name if @comment.name + context[:email] = @comment.email if @comment.email + context[:view_full_work] = params[:view_full_work] if params[:view_full_work] + context[:page] = params[:page] if params[:page] + context[:controller_name] = params[:controller_name] if params[:controller_name] + + case @commentable + when Work + context[:work_id] = @commentable.id + when Chapter + context[:chapter_id] = @commentable.id + when AdminPost + context[:admin_post_id] = @commentable.id + when Tag + context[:tag_id] = @commentable.name + when Comment + context[:comment_id] = @commentable.id + end + + if controller_name == "inbox" && params[:filters] + context[:filters] = filter_params.to_h + end + + context + end + def comment_params params.require(:comment).permit( :pseud_id, :comment_content, :name, :email, :edited_at diff --git a/app/views/comments/preview.html.erb b/app/views/comments/preview.html.erb index c6fac90ac95..e7488364821 100644 --- a/app/views/comments/preview.html.erb +++ b/app/views/comments/preview.html.erb @@ -39,7 +39,7 @@ <%= form_for @comment, url: comments_path, method: :post, local: true, html: { id: "post_comment_from_preview" } do |f| %> <%= f.hidden_field :comment_content %> - + <% if @comment.pseud_id %> <%= f.hidden_field :pseud_id %> <% elsif @comment.name %> @@ -48,8 +48,14 @@ <% end %> <% @context_params.each do |key, value| %> - <% next if key.in?(:comment_content, :pseud_id, :name, :email) %> - <%= hidden_field_tag key, value %> + <% next if key.in?([:comment_content, :pseud_id, :name, :email]) %> + <% if value.is_a?(Hash) %> + <% value.each do |sub_key, sub_value| %> + <%= hidden_field_tag "#{key}[#{sub_key}]", sub_value %> + <% end %> + <% else %> + <%= hidden_field_tag key, value %> + <% end %> <% end %> <%= f.submit t(".post_comment"), class: "button submit" %> From db9f657c0a09204c0dd6763259d0e827dc163dd6 Mon Sep 17 00:00:00 2001 From: forceofcalm Date: Wed, 20 May 2026 18:34:42 -0400 Subject: [PATCH 05/28] stray comment... --- app/controllers/comments_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 86fbe67aec6..c4d108f390d 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -724,7 +724,7 @@ def delete_comment redirect_to_comment(@comment, options) end format.js - end# Allow pre-populating from preview or edit forms + end end def cancel_comment_delete From ed8d5366088ef2362d9d1af4b088e7e111facc8d Mon Sep 17 00:00:00 2001 From: forceofcalm Date: Fri, 22 May 2026 00:22:10 -0400 Subject: [PATCH 06/28] matching template to rest of codebase --- app/views/comments/preview.html.erb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/comments/preview.html.erb b/app/views/comments/preview.html.erb index e7488364821..8804da3c443 100644 --- a/app/views/comments/preview.html.erb +++ b/app/views/comments/preview.html.erb @@ -2,7 +2,7 @@
-
    +
    1. - <% if !@comment.pseud.nil? %> + <% if @comment.pseud %> <% if @comment.by_anonymous_creator? %> <% else %> @@ -29,12 +29,12 @@ <%= raw sanitize_field(@comment, :comment_content) %>
    2. -
+
-
+
    <%= link_to t(".back_to_edit"), new_comment_path(@context_params), class: "button" %> <%= form_for @comment, url: comments_path, method: :post, local: true, html: { id: "post_comment_from_preview" } do |f| %> @@ -60,5 +60,5 @@ <%= f.submit t(".post_comment"), class: "button submit" %> <% end %> -
+ From 9fc1ade215859346d598ae3f181b42669863107f Mon Sep 17 00:00:00 2001 From: forceofcalm Date: Fri, 22 May 2026 15:51:12 -0400 Subject: [PATCH 07/28] removed unused key --- config/locales/views/en.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/config/locales/views/en.yml b/config/locales/views/en.yml index ae9a5506597..26f2a5953e8 100644 --- a/config/locales/views/en.yml +++ b/config/locales/views/en.yml @@ -923,7 +923,6 @@ en: update: Update preview: back_to_edit: Back to Edit - commenting_on: "Commenting on: %{commentable_link}" page_heading: Preview Comment post_comment: Post Comment show: From 3700d671597ca5fcc16acf1bcb6a096f09d62a85 Mon Sep 17 00:00:00 2001 From: forceofcalm Date: Fri, 22 May 2026 15:59:48 -0400 Subject: [PATCH 08/28] lol jk readded the key and updated template and tests --- app/views/comments/preview.html.erb | 1 + config/locales/views/en.yml | 231 +++++++++++---------- features/step_definitions/comment_steps.rb | 28 +++ 3 files changed, 145 insertions(+), 115 deletions(-) diff --git a/app/views/comments/preview.html.erb b/app/views/comments/preview.html.erb index 8804da3c443..bf356fbf435 100644 --- a/app/views/comments/preview.html.erb +++ b/app/views/comments/preview.html.erb @@ -1,4 +1,5 @@

<%= t(".page_heading") %>

+

<%= t(".commenting_on", commentable_link: link_to_comment_ultimate_parent(@comment)) %>

diff --git a/config/locales/views/en.yml b/config/locales/views/en.yml index 26f2a5953e8..ef719b4baf4 100644 --- a/config/locales/views/en.yml +++ b/config/locales/views/en.yml @@ -6,7 +6,7 @@ en: new: do_not_spam: delay: it may take several months for us to get to your report - paragraph_html: "%{split_bold} Our volunteer team is small, so %{delay_link}." + paragraph_html: '%{split_bold} Our volunteer team is small, so %{delay_link}.' split: Please only report one user at a time, and do not submit multiple reports about the same user. form: comment: @@ -185,7 +185,7 @@ en: creations: Creations no_creations: This user has no works or comments. page_heading: Works and Comments by %{user} (%{id}) - page_title: "%{login} - User Creations" + page_title: '%{login} - User Creations' history: heading: User History table: @@ -244,7 +244,7 @@ en: troubleshoot: Troubleshoot note: To fix common errors with this user's Subscriptions and Stats pages, and to reindex the user and their works and bookmarks, choose "Troubleshoot." page_heading: 'User: %{login} (%{id})' - page_title: "%{login} - User Administration" + page_title: '%{login} - User Administration' status: form: admin_action: @@ -303,7 +303,7 @@ en: blacklist: emails_found: one: one email found - other: "%{count} emails found" + other: '%{count} emails found' blacklisted_emails: create: browser_title: Create Banned Email @@ -360,7 +360,7 @@ en: passwords: edit: describedby: - password_length: "%{minimum} to %{maximum} characters" + password_length: '%{minimum} to %{maximum} characters' label: confirmation: Confirm new password password: New password @@ -418,8 +418,8 @@ en: disable_support_form: Turn Off Support Form performance_and_misc: Performance and Misc queue_status: - one: "%{count} person is scheduled to be sent an invitation at %{time}." - other: "%{count} people are scheduled to be sent invitations at %{time}." + one: '%{count} person is scheduled to be sent an invitation at %{time}.' + other: '%{count} people are scheduled to be sent invitations at %{time}.' queue_status_help: "(The automatic task that invites people from the queue runs at most every hour. Don't be alarmed if the time you see is within the last hour. Do be alarmed if the time you see is more than one hour in the past and the queue is supposed to be active.)" update: Update update: @@ -682,7 +682,7 @@ en: fandom: Fandom %{allowed_fandom_count} challenge_assignments: assignment_blurb: - header_draft_html: "%{title_link} (Draft)" + header_draft_html: '%{title_link} (Draft)' stats: creation: posted: 'Posted:' @@ -912,7 +912,7 @@ en: draft: Sorry, you can't comment on a draft. hidden: Sorry, you can't add or edit comments on a hidden work. unrevealed: Sorry, you can't add or edit comments on an unrevealed work. - previous_chapter_html: "%{back_arrow_html} Previous Chapter" + previous_chapter_html: '%{back_arrow_html} Previous Chapter' confirm_delete: confirm_button: Yes, delete! delete_confirmation: Are you sure you want to delete this comment? @@ -923,6 +923,7 @@ en: update: Update preview: back_to_edit: Back to Edit + commenting_on: 'Commenting on: %{commentable_link}' page_heading: Preview Comment post_comment: Post Comment show: @@ -942,8 +943,8 @@ en: comment: drop by the Archive and comment end_notes: End Notes inspired_by: - restricted_html: "[Restricted Work] by %{creator_link}" - revealed_html: "%{work_link} by %{creator_link}" + restricted_html: '[Restricted Work] by %{creator_link}' + revealed_html: '%{work_link} by %{creator_link}' title: Works inspired by this one unrevealed: A work in an unrevealed collection please_comment_html: Please %{comment_link} to let the creator know if you enjoyed their work! @@ -978,7 +979,7 @@ en: series_list_html: Part %{position} of %{series_link} stats: 'Stats:' summary: Summary - tag_type: "%{tag_type}:" + tag_type: '%{tag_type}:' translated_to: restricted_html: 'Translation into %{language} available: [Restricted Work] by %{creator_link}' revealed_html: 'Translation into %{language} available: %{work_link} by %{creator_link}' @@ -1090,7 +1091,7 @@ en: abuse: contact: contact our Policy and Abuse team reports_html: For reports of violations of the Terms of Service such as harassment, spam, or plagiarism, or if you believe your account has been hacked, please %{contact_link} instead. We cannot act on these reports or provide information about Policy and Abuse cases. - do_not_spam_html: "We respond to every report we receive, but we are a small volunteer team. For this reason, we ask you to not submit multiple reports regarding any one issue, or encourage other people to report the same issue, unless there is additional information to offer." + do_not_spam_html: 'We respond to every report we receive, but we are a small volunteer team. For this reason, we ask you to not submit multiple reports regarding any one issue, or encourage other people to report the same issue, unless there is additional information to offer.' form: comment: description: Please be as specific as possible, including error messages and/or links @@ -1117,7 +1118,7 @@ en: landmark: reference: Reference Links page_title: Support and Feedback - languages_html: "We can answer Support inquiries in %{list_html}. Please allow for additional delay for responses in any language other than English." + languages_html: 'We can answer Support inquiries in %{list_html}. Please allow for additional delay for responses in any language other than English.' navigation: faqs: FAQs & Tutorials known_issues: Known Issues @@ -1137,7 +1138,7 @@ en: tag_changes: Requests to canonize or change tags work_problems: Works labeled with the wrong language or duplicate works status: - bluesky: "@status.archiveofourown.org" + bluesky: '@status.archiveofourown.org' current_html: For current updates on AO3 performance or downtime, please check our %{status_page_link} and follow %{bluesky_link} on Bluesky or %{tumblr_link} on Tumblr. status_page: status page tumblr: ao3org @@ -1238,26 +1239,26 @@ en: description: 'When you enter HTML into the archive, we do some cleanup on it to make sure that it is safe (so spammers and hackers cannot upload badness) and try and do some basic formatting both for your convenience and for accessibility reasons. Here are the formatting steps we take:' example: blockquote: - description_html: "<blockquote>%{text_blockquote}</blockquote>" + description_html: '<blockquote>%{text_blockquote}</blockquote>' text: To quote a block of text - chapter_title: "

Chapter title

" + chapter_title: '

Chapter title

' cite: description_html: Try cite to cite <cite>%{phrase_or_title_cite}</cite> phrase_or_title: a phrase or title em: - description_html: "<em>%{rodney_italic}</em> McKay" + description_html: '<em>%{rodney_italic}</em> McKay' rodney: Rodney - footnote_title: "

Footnote title
" + footnote_title: '
Footnote title
' inline_quote: description_html: Use q to <q>%{phrase_inline_quote}</q> phrase: To quote a phrase - scene_title: "

Scene title

" + scene_title: '

Scene title

' strong: description_html: I will <strong>%{never_bold}</strong> understand you! never: never - subtitle_h2: "

Subtitle

" - subtitle_h5: "

Subtitle
" - title: "

Title

" + subtitle_h2: '

Subtitle

' + subtitle_h5: '

Subtitle
' + title: '

Title

' forgotten_open_tags: If you have forgotten to close a formatting tag, it will be closed at the end of the paragraph for you. good_html_note_html: Good HTML means HTML that labels what the text is supposed to be -- so if you have paragraphs, they should be inside paragraph tags, not just separated with break tags. If you have emphasized text, it should be inside em tags. If you have a list of items, each item should be inside list tags. If you %{negation_italic} have a list of items, you shouldn't have list tags. :) (We are putting together a set of more detailed helpful references about this, but this is the basic idea.) header: How Do We Format Your HTML? @@ -1375,9 +1376,9 @@ en: reverse_underscore: Start with fandom, then with author, then title, with underscores instead of dashes. page_heading: Work Title Format rte: - description_html: 'The exact behavior of the Rich Text Editor (%{rte_abbreviation}) depends on your device, browser, and operating system as well as the source you''re pasting from. However, starting with a well-formatted document will help you get the most out of the %{rte_abbreviation_no_title}. Here are some general tips to ensure that as much of your formatting as possible will be retained:' + description_html: "The exact behavior of the Rich Text Editor (%{rte_abbreviation}) depends on your device, browser, and operating system as well as the source you're pasting from. However, starting with a well-formatted document will help you get the most out of the %{rte_abbreviation_no_title}. Here are some general tips to ensure that as much of your formatting as possible will be retained:" enter_once: - description_html: "%{intro_sentence_bold_html} Pressing %{enter_for_twice_key} twice will insert a blank paragraph, creating additional, and likely unwanted, space between paragraphs when you paste into the %{rte_abbreviation}. The Archive uses top and bottom margins to create the appearance of a blank line between paragraphs; you can use the paragraph formatting options in your text editor to create a similar effect without adding extra %{p_tag_code} tags." + description_html: '%{intro_sentence_bold_html} Pressing %{enter_for_twice_key} twice will insert a blank paragraph, creating additional, and likely unwanted, space between paragraphs when you paste into the %{rte_abbreviation}. The Archive uses top and bottom margins to create the appearance of a blank line between paragraphs; you can use the paragraph formatting options in your text editor to create a similar effect without adding extra %{p_tag_code} tags.' enter_for_once: Enter enter_for_twice: Enter intro_sentence_html: Press %{enter_for_once_key} %{once_italic} between paragraphs. @@ -1492,7 +1493,7 @@ en: header: You can create new skins for the Archive using our wizard, or by writing your own CSS (cascading style sheets) code numeric_values: description: - em_unit_html: 'PS: we highly encourage learning about and using %{em_code}, which lets you set things relative to the viewer''s current font size! It will make your layouts much more flexible and responsive to different browser/font settings.' + em_unit_html: "PS: we highly encourage learning about and using %{em_code}, which lets you set things relative to the viewer's current font size! It will make your layouts much more flexible and responsive to different browser/font settings." precision_html: 'You can specify numeric values up to two decimal places, either as percentages or in %{various_units_link}: %{units_list_code}' units_list: cm, em, ex, in, mm, pc, pt, px various_units: various units @@ -1505,7 +1506,7 @@ en: header: Scale numeric_value: numeric value skin_examples: - description_html: "%{public_skins_link} are visible and you can read their code and copy them to edit for your own use." + description_html: '%{public_skins_link} are visible and you can read their code and copy them to edit for your own use.' header: Look at other public skins for examples public_skins: All approved public skins skins_parents: @@ -1606,7 +1607,7 @@ en: description: This is for content with adult themes (sex, violence, etc.) that isn't as graphic as explicit-rated content. header: Mature (Adult!) more_info: - html: "(For more information, see the %{ratings_and_warnings_tos_section_link}.)" + html: '(For more information, see the %{ratings_and_warnings_tos_section_link}.)' ratings_and_warnings_tos_section: Ratings and Warnings section of the %{app_name} Terms of Service not_rated: description: For searching, screening, and other Archive functions, this may get treated the same way as explicit-rated content. In reality, it could be anything from porn to completely family-friendly stuff. Choose this rating if you prefer not to rate your content (because you don't like ratings, because you're trying to avoid spoilers, etc.). @@ -1675,14 +1676,14 @@ en: otw_long: Organization for Transformative Works major_projects: fanlore: Fanlore - fanlore_details_html: "%{fanlore_link}, a fandom wiki devoted to preserving the history of transformative fanworks and the fandoms from which they have arisen." + fanlore_details_html: '%{fanlore_link}, a fandom wiki devoted to preserving the history of transformative fanworks and the fandoms from which they have arisen.' legal_advocacy: Legal Advocacy - legal_details_html: "%{legal_advocacy_link} committed to protecting and defending fanworks from commercial exploitation and legal challenge." + legal_details_html: '%{legal_advocacy_link} committed to protecting and defending fanworks from commercial exploitation and legal challenge.' open_doors: Open Doors - open_doors_details_html: "%{open_doors_link}, which offers shelter to at-risk fannish projects." + open_doors_details_html: '%{open_doors_link}, which offers shelter to at-risk fannish projects.' title: 'Our other major projects include:' twc: Transformative Works and Cultures - twc_details_html: "%{twc_link}, a peer-reviewed academic journal that seeks to promote scholarship on fanworks and practices." + twc_details_html: '%{twc_link}, a peer-reviewed academic journal that seeks to promote scholarship on fanworks and practices.' more_info: communications_team: Communications team faq_page: FAQ page @@ -1695,7 +1696,7 @@ en: heading: II.C. Commercial Promotion not_allowed: Promotion, solicitation, and advertisement of commercial products or activities are not allowed. tos_faq: FAQ for Section II.C - tos_faq_in_parens_html: "(%{tos_faq_link})" + tos_faq_in_parens_html: '(%{tos_faq_link})' tos_faq_link_label: Commercial Promotion FAQ content_policy: Content Policy content_policy_heading: II. Content Policy @@ -1704,20 +1705,20 @@ en: heading: II.D. Copyright Infringement not_allowed: Reproductions of large excerpts of copyrighted works are not allowed without the consent of the copyright owner. This includes stories, artwork, songs, poems, transcripts, and other copyrighted material. Crediting the original creator does not give you the right to upload, podfic, or translate someone else's work without permission, regardless of whether the source is a fanwork or a professionally published work. tos_faq: FAQ for Section II.D - tos_faq_in_parens_html: "(%{tos_faq_link})" + tos_faq_in_parens_html: '(%{tos_faq_link})' tos_faq_link_label: Copyright Infringement and Plagiarism FAQ transformative_fanworks: transformative fanworks transformative_works_legal_html: We believe that %{transformative_fanworks_link} are legal. Complaints about the mere existence of fanworks that mention trademarks or are based on copyrighted material will not be pursued. effective: 'Effective: November 19, 2024' fanworks: bookmarks: Bookmarks - bookmarks_only_fanworks_html: "%{bookmarks_link} must only be created for fanworks. You may create %{external_bookmarks_link} for fanworks hosted on third-party sites." + bookmarks_only_fanworks_html: '%{bookmarks_link} must only be created for fanworks. You may create %{external_bookmarks_link} for fanworks hosted on third-party sites.' external_bookmarks: external bookmarks heading: II.B. Fanworks must_be_fanworks_html: Works must be fanworks. Posting a Work that primarily consists of %{non_fanwork_content_link} is not allowed. non_fanwork_content: non-fanwork Content tos_faq: FAQ for Section II.B - tos_faq_in_parens_html: "(%{tos_faq_link})" + tos_faq_in_parens_html: '(%{tos_faq_link})' tos_faq_link_label: Fanworks and Non-Fanwork Content FAQ harassment: advocating_harm: @@ -1739,7 +1740,7 @@ en: text: Creating RPF never constitutes harassment in and of itself. Posting works where someone dies, is subjected to slurs, or is otherwise harmed as part of the plot is usually not a violation of the Harassment Policy. However, deliberately posting such Content in a manner designed to be seen by the subject of the work, such as by gifting them the work, may result in a judgment of harassment. threatening_versus_annoying_html: In general, threatening Content will be considered harassment, while Content that is merely rude or annoying will be allowed. Not everyone agrees about what is offensive and unacceptable. Users are encouraged to use tools such as %{blocking_link}, %{muting_link}, and %{filtering_link} to control their own environment on AO3. tos_faq: FAQ for Section II.H - tos_faq_in_parens_html: "(%{tos_faq_link})" + tos_faq_in_parens_html: '(%{tos_faq_link})' tos_faq_link_label: Harassment FAQ illegal_inappropriate_content: automated_spam_check_html: We may use automated means to filter out spam. If you submit Content that is erroneously caught in a spam filter, please %{contact_ao3_administrators_link}. @@ -1752,7 +1753,7 @@ en: spamming_behavior: Spamming behavior is prohibited. Repeated identical or nearly identical posts in multiple places may be considered spam regardless of commercial content. technical_integrity: technical integrity tos_faq: FAQ for Section II.K - tos_faq_in_parens_html: "(%{tos_faq_link})" + tos_faq_in_parens_html: '(%{tos_faq_link})' tos_faq_link_label: Offensive Content vs Illegal Content FAQ violates_us_law_html: If you encounter Content that you believe violates a specific law of the United States, you can %{report_it_to_us_link}. impersonation: @@ -1760,7 +1761,7 @@ en: heading: II.G. Impersonation html: You may not impersonate or misrepresent your affiliation with any person, entity, or %{function_link}. Fiction clearly marked as such is not considered impersonation. tos_faq: FAQ for Section II.G - tos_faq_in_parens_html: "(%{tos_faq_link})" + tos_faq_in_parens_html: '(%{tos_faq_link})' tos_faq_link_label: Fannish Identities and Impersonation FAQ intro: all_content_must_comply_html: All %{content_link} on AO3 must comply with our Content Policy. @@ -1769,7 +1770,7 @@ en: maximum_inclusiveness: maximum inclusiveness of fanwork content our_goal_html: Our goal is %{maximum_inclusiveness_link}. We want to provide a safe and permanent home for fanworks, including works that might be at risk on other sites due to being deemed immoral, explicit, or otherwise objectionable. Users should always observe and heed the ratings and warnings provided before accessing works on AO3. report_it_to_us: report it to us - review_before_posting_html: "%{all_content_must_comply_bold} Please review this page carefully before you begin posting. Answers to common questions about the Content Policy can be found in the %{tos_faq_link}." + review_before_posting_html: '%{all_content_must_comply_bold} Please review this page carefully before you begin posting. Answers to common questions about the Content Policy can be found in the %{tos_faq_link}.' tos_faq: TOS FAQ we_do_not_prescreen: We do not prescreen content on AO3. you_can_report_html: If you encounter content that you believe violates our Content Policy, you can %{report_it_to_us_link}. %{we_do_not_prescreen_bold} @@ -1788,13 +1789,13 @@ en: tags_applied_automatically_html: Some tags may be automatically applied to Works. In addition, AO3 administrators may determine that tags in a mandatory tag field on a Work are inaccurate or insufficient. In such cases, AO3 administrators may remove inaccurate tags; add non-specific tags to the Work; add specific tags to the Work in fields where non-specific tags are %{not_available_link}; require the creator to appropriately adjust the Work's tags; hide the Work; or take other appropriate action to address the matter. Refer to the %{tos_faq_link} for details about when AO3 administrators may enforce the presence or removal of tags in mandatory fields. tos_faq: TOS FAQ tos_faq_endnote: FAQ for Section II.J - tos_faq_in_parens_html: "(%{tos_faq_link})" + tos_faq_in_parens_html: '(%{tos_faq_link})' tos_faq_link_label: Ratings and Archive Warnings FAQ offensive_content: heading: II.A. Offensive Content removal_not_just_offensiveness: Unless it violates some other policy, we will not remove Content for offensiveness. tos_faq: FAQ for Section II.A - tos_faq_in_parens_html: "(%{tos_faq_link})" + tos_faq_in_parens_html: '(%{tos_faq_link})' tos_faq_link_label: Offensive Content vs Illegal Content FAQ page_content_landmark: Main Text page_heading: Content Policy @@ -1810,14 +1811,14 @@ en: sharing_sufficient_information: sharing information sufficient to identify someone else's legal or professional identity or their physical location (e.g. phone numbers, email addresses, residential addresses, or hotel room numbers); and special_categories_of_personal_data: Special Categories of Personal Data tos_faq: FAQ for Section II.F - tos_faq_in_parens_html: "(%{tos_faq_link})" + tos_faq_in_parens_html: '(%{tos_faq_link})' tos_faq_link_label: Fannish Identities and Impersonation FAQ plagiarism: heading: II.E. Plagiarism html: Plagiarism is the use of someone else's words, or %{their_expressions_of_their_ideas_link}, without attribution. Minor alterations (such as replacing names, substituting synonyms, or rearranging a few words) are insufficient to make a work your own. Plagiarism is not allowed. Deliberately creating a work using the same general idea as another work is not plagiarism, but citation may be appropriate. their_expressions_of_their_ideas: their expressions of their ideas tos_faq: FAQ for Section II.E - tos_faq_in_parens_html: "(%{tos_faq_link})" + tos_faq_in_parens_html: '(%{tos_faq_link})' tos_faq_link_label: Copyright Infringement and Plagiarism FAQ privacy_policy: Privacy Policy terms_of_service: Terms of Service @@ -1828,7 +1829,7 @@ en: heading: II.I. User Icons text: User icons must be appropriate for general audiences. They must not depict genital nudity, explicit sexual activity, hate symbols, or imagery that promotes, advocates, or causes harm. tos_faq: FAQ for Section II.I - tos_faq_in_parens_html: "(%{tos_faq_link})" + tos_faq_in_parens_html: '(%{tos_faq_link})' tos_faq_link_label: Usernames, Icons, and Profiles FAQ diversity_statement: archive_description_html: This archive is a permanent, panfandom place for fanworks, built by fans for fans. Whichever way you use the Archive, you're part of this, powering it, shaping it through your use and %{your_feedback_link}. @@ -1843,7 +1844,7 @@ en: html: This work is licensed under a %{creative_commons_by_sa_link}. image_alt: Creative Commons License some_essential_parts: some essential parts - still_missing_html: 'We know that there are %{some_essential_parts_link} that are still missing to make the Archive truly panfandom: the ability to host fanworks other than text, an interface in languages other than English, and more ways for you to connect with each other, to name just a few. But with your support, we''ll get there.' + still_missing_html: "We know that there are %{some_essential_parts_link} that are still missing to make the Archive truly panfandom: the ability to host fanworks other than text, an interface in languages other than English, and more ways for you to connect with each other, to name just a few. But with your support, we'll get there." terms_of_service: Terms of Service we_build_for: We are building this archive for you. Come be a part of it. welcome_header: You are welcome at the Archive of Our Own. @@ -1884,7 +1885,7 @@ en: title: Is it later already? note: Some works you've marked for later. social: - bluesky: "@status.archiveofourown.org on Bluesky" + bluesky: '@status.archiveofourown.org on Bluesky' heading: Follow us note_html: Follow AO3 on Bluesky or Tumblr for status updates, and don't forget to check out the %{other_outlets_link} for updates on our other projects! other_outlets: Organization for Transformative Works' news outlets @@ -2147,17 +2148,17 @@ en: privacy_policy: Privacy Policy entirety_of_agreement: entirety_of_agreement: 'Entirety of agreement:' - html: "%{entirety_of_agreement} These Terms of Service constitute the entire agreement between you and the Organization for Transformative Works (OTW) and govern your use of AO3. They replace all prior agreements between you and the OTW concerning your use of AO3. They do not govern your use of any other OTW sites and/or projects, all of which are covered under separate agreements." + html: '%{entirety_of_agreement} These Terms of Service constitute the entire agreement between you and the Organization for Transformative Works (OTW) and govern your use of AO3. They replace all prior agreements between you and the OTW concerning your use of AO3. They do not govern your use of any other OTW sites and/or projects, all of which are covered under separate agreements.' heading: I.A. General terms jurisdiction: - html: "%{jurisdiction} The AO3 TOS, the relationship between you and the OTW, and all disputes arising out of or related to them shall be governed by the laws of the United States and specifically %{the_state_of_new_york_link}, without regard to any conflict-of-law provisions. You and the OTW agree to submit to the personal and exclusive jurisdiction of the courts located within New York County (Manhattan), New York, and to waive any objection to the laying of venue there." + html: '%{jurisdiction} The AO3 TOS, the relationship between you and the OTW, and all disputes arising out of or related to them shall be governed by the laws of the United States and specifically %{the_state_of_new_york_link}, without regard to any conflict-of-law provisions. You and the OTW agree to submit to the personal and exclusive jurisdiction of the courts located within New York County (Manhattan), New York, and to waive any objection to the laying of venue there.' jurisdiction: 'Jurisdiction:' the_state_of_new_york: the State of New York limitation_on_claims: - html: "%{limitation_on_claims} You agree that, regardless of any statute or law to the contrary, any claim or cause of action arising out of or related to any use of AO3 or its TOS must be filed within one (1) year after such claim or cause of action arose or be forever barred." + html: '%{limitation_on_claims} You agree that, regardless of any statute or law to the contrary, any claim or cause of action arising out of or related to any use of AO3 or its TOS must be filed within one (1) year after such claim or cause of action arose or be forever barred.' limitation_on_claims: 'Limitation on claims:' no_assignment: - html: "%{no_assignment} These TOS are personal to you. You may not assign or transfer your rights or obligations under these TOS to any other person or entity, and any attempted assignment or transfer is void." + html: '%{no_assignment} These TOS are personal to you. You may not assign or transfer your rights or obligations under these TOS to any other person or entity, and any attempted assignment or transfer is void.' no_assignment: 'No assignment:' non_severability: html: "%{non_severability} The OTW's failure to enforce any part of the TOS will not waive the OTW's ability to enforce it. Any waiver with regard to a specific instance shall not constitute a waiver of any other breaches of the TOS, even with regard to the same user. If any provision of the TOS is found by a court of competent jurisdiction to be invalid, you agree that the court should give effect to the party's intentions as reflected in the provision, and that all other provisions of the TOS remain in full force and effect." @@ -2250,7 +2251,7 @@ en: tos_navigation: content: Content Policy privacy: Privacy Policy - top: "%{arrow} Top" + top: '%{arrow} Top' tos: Terms of Service tos_faq: TOS FAQ tos_toc: @@ -2332,7 +2333,7 @@ en: page_heading: Invitation Requests queue_disabled: closed: New invitation requests are currently closed. - html: "%{closed_bold} For more information, please check the %{news_link}." + html: '%{closed_bold} For more information, please check the %{news_link}.' news: '"Invitations" tag on AO3 News' waiting_list_count: one: There is %{count} person remaining on the waiting list. @@ -2345,10 +2346,10 @@ en: details_html: To get a free Archive of Our Own account, you need an Invitation. By submitting your email address to our invitation queue, you confirm that you are at least 13 years old, and if you're in a country whose residents/citizens have to be of an age older than 13 to consent, you are old enough to consent to the processing of your personal data without our obtaining written permission from a parent or legal guardian. We will use the email address you submit only to send you an Invitation and to process/manage your account activation. Please don't request an Invitation unless you've read our %{tos_link}, including the %{content_policy_link} and %{privacy_policy_link}, and agree to abide by those Terms. frequency: one: hour - other: "%{count} hours" + other: '%{count} hours' invitation_number: - one: "%{count} invitation" - other: "%{count} invitations" + one: '%{count} invitation' + other: '%{count} invitations' invitation_send_rate: We are sending out %{queue_invitation_number} every %{queue_frequency}. page_heading: Invitation Requests privacy_policy: Privacy Policy @@ -2385,11 +2386,11 @@ en: status: frequency: one: hour - other: "%{count} hours" + other: '%{count} hours' heading: Invitation Request Status invitation_number: - one: "%{count} invitation" - other: "%{count} invitations" + one: '%{count} invitation' + other: '%{count} invitations' invitation_send_rate: We are sending out %{queue_invitation_number} every %{queue_frequency}. search: Look me up waiting_list: @@ -2397,12 +2398,12 @@ en: other: There are currently %{count} people on the waiting list. kudos: guest_header: - one: "%{count} guest has also left kudos" - other: "%{count} guests have also left kudos" + one: '%{count} guest has also left kudos' + other: '%{count} guests have also left kudos' user_links: more_link: - one: "%{count} more user" - other: "%{count} more users" + one: '%{count} more user' + other: '%{count} more users' languages: form: abuse_support_available: Abuse support available @@ -2415,15 +2416,15 @@ en: update: Update Language support_available: Support available index: - language_code_format_html: "(%{code})" + language_code_format_html: '(%{code})' navigation: add: Add a Language edit: Edit suggest: Suggest a Language page_heading: Work Languages works_count: - one: "%{formatted_count} work" - other: "%{formatted_count} works" + one: '%{formatted_count} work' + other: '%{formatted_count} works' layouts: banner: hide: hide banner @@ -2449,7 +2450,7 @@ en: gpl_2_0_or_later: GPL-2.0-or-later header: Development known_issues: Known Issues - license_details_html: "%{license_link} by the %{otw_link}" + license_details_html: '%{license_link} by the %{otw_link}' view_license: View License landmark: Footer otw: @@ -2568,7 +2569,7 @@ en: aria_label: nav: Pagination next: Next → - prev: "← Previous" + prev: '← Previous' potential_matches: in_progress: cancel: Cancel Potential Match Generation @@ -2663,8 +2664,8 @@ en: update: Update pseud_list: more_pseuds: - one: "%{count} more pseud" - other: "%{count} more pseuds" + one: '%{count} more pseud' + other: '%{count} more pseuds' show: delete_account_confirmation: This will permanently delete your account and cannot be undone. Are you sure? delete_my_account: Delete My Account @@ -2696,11 +2697,11 @@ en: confirm_delete: Are you sure? default_pseud: Default Pseud delete_html: Delete%{landmark_span} - delete_landmark_text: " %{pseud}" + delete_landmark_text: ' %{pseud}' edit_html: Edit%{landmark_span} - edit_landmark_text: " %{pseud}" + edit_landmark_text: ' %{pseud}' orphan_html: Orphan Works%{landmark_span} - orphan_landmark_text: " by %{pseud}" + orphan_landmark_text: ' by %{pseud}' user_actions: User Actions pseuds_form: cannot_change_matching_pseud_html: You cannot change the pseud that matches your username. However, you can %{change_username_link} instead. @@ -2739,16 +2740,16 @@ en: page_heading: "%{login}'s Related Works" series: series_module: - hidden_by_admin_alt: "(Hidden by Admin)" + hidden_by_admin_alt: '(Hidden by Admin)' hidden_by_administrator: Hidden by Administrator landmark: fandom: Fandom summary: Summary restricted: Restricted - restricted_alt: "(Restricted)" - series_by_html: "%{series_title_link} by %{byline}" + restricted_alt: '(Restricted)' + series_by_html: '%{series_title_link} by %{byline}' series_order: - draft_work_title: "%{title} (DRAFT)" + draft_work_title: '%{title} (DRAFT)' skins: confirm_delete: confirm_html: Are you sure you want to delete the skin "%{skin_title}"? @@ -2813,13 +2814,13 @@ en: series: My Series Subscriptions users: My User Subscriptions works: My Work Subscriptions - series: "(Series)" - work: "(Work)" + series: '(Series)' + work: '(Work)' tag_sets: nomination_review_heading: already_approved_fandoms: Already Approved Fandoms - left_to_review: "%{type} (%{count} left to review)" - paginated: "%{from} - %{to} of %{total} %{type}" + left_to_review: '%{type} (%{count} left to review)' + paginated: '%{from} - %{to} of %{total} %{type}' tag_wranglers: index: filters: @@ -2828,42 +2829,42 @@ en: only_media: Only media only_wrangler: Only wrangler show: - last_wrangled_html: "%{wrangler_login} last wrangled at %{time}." + last_wrangled_html: '%{wrangler_login} last wrangled at %{time}.' tags_wrangled_csv: Tags Wrangled (CSV) tag_wranglings: index: general_resources: ao3_catch_and_release: AO3_Catch_and_Release - ao3_catch_and_release_html: "%{ao3_catch_and_release_link} - For more information, please see the %{ao3_wrangling_project_faq_link}" + ao3_catch_and_release_html: '%{ao3_catch_and_release_link} - For more information, please see the %{ao3_wrangling_project_faq_link}' ao3_wrangling_project_faq: Tag Wrangling AO3 Project FAQ current_wrangling_limitations: Current Wrangling Limitations - current_wrangling_limitations_html: "%{current_wrangling_limitations_link} - Details about current wrangling limitations and policies" + current_wrangling_limitations_html: '%{current_wrangling_limitations_link} - Details about current wrangling limitations and policies' fandom_and_megafandom_directory: Directory of Fandom & Megafandom Wiki Pages - fandom_and_megafandom_directory_html: "%{fandom_and_megafandom_directory_link} - All currently available fandom & megafandom wiki pages" + fandom_and_megafandom_directory_html: '%{fandom_and_megafandom_directory_link} - All currently available fandom & megafandom wiki pages' fandoms_in_need_of_cowranglers: Fandoms in need of Cowranglers - fandoms_in_need_of_cowranglers_html: "%{fandoms_in_need_of_cowranglers_link} - Interested in helping out a fandom in need?" + fandoms_in_need_of_cowranglers_html: '%{fandoms_in_need_of_cowranglers_link} - Interested in helping out a fandom in need?' heading: 'General Wrangling Resources:' otw_slack_chat: OTW Slack Chat - otw_slack_chat_html: "%{otw_slack_chat_link} - For more information please see the %{tag_wrangling_slack_tutorial_link}" + otw_slack_chat_html: '%{otw_slack_chat_link} - For more information please see the %{tag_wrangling_slack_tutorial_link}' tag_wranglers_mailing_list: Tag Wranglers Mailing List Conversations - tag_wranglers_mailing_list_html: "%{tag_wranglers_mailing_list_link} - Message Archive for the Tagwranglers Mailing List" + tag_wranglers_mailing_list_html: '%{tag_wranglers_mailing_list_link} - Message Archive for the Tagwranglers Mailing List' tag_wrangling_bugs: Tag Wrangling Bugs & Troubleshooting - tag_wrangling_bugs_html: "%{tag_wrangling_bugs_link} - Having technical issues while wrangling? Check here for solutions!" + tag_wrangling_bugs_html: '%{tag_wrangling_bugs_link} - Having technical issues while wrangling? Check here for solutions!' tag_wrangling_committee: Tag Wrangling Committee - tag_wrangling_committee_html: "%{tag_wrangling_committee_link} - Full collection of wiki links & policies" + tag_wrangling_committee_html: '%{tag_wrangling_committee_link} - Full collection of wiki links & policies' tag_wrangling_communication_policy: Tag Wrangling Communication Policy - tag_wrangling_communication_policy_html: "%{tag_wrangling_communication_policy_link} - Includes instructions for leaving tag comments and chat rules" + tag_wrangling_communication_policy_html: '%{tag_wrangling_communication_policy_link} - Includes instructions for leaving tag comments and chat rules' tag_wrangling_faq: Tag Wrangling FAQ - tag_wrangling_faq_html: "%{tag_wrangling_faq_link} - Check here to find answers to frequently asked questions" + tag_wrangling_faq_html: '%{tag_wrangling_faq_link} - Check here to find answers to frequently asked questions' tag_wrangling_moodle: Tag Wrangling Moodle - tag_wrangling_moodle_html: "%{tag_wrangling_moodle_link} - Additional modular training and support for all wranglers" + tag_wrangling_moodle_html: '%{tag_wrangling_moodle_link} - Additional modular training and support for all wranglers' tag_wrangling_slack_tutorial: Tag Wrangling Slack Tutorial tag_wrangling_training: Tag Wrangling Training - tag_wrangling_training_html: "%{tag_wrangling_training_link} - New wranglers, start here!" + tag_wrangling_training_html: '%{tag_wrangling_training_link} - New wranglers, start here!' unassigned_fandoms: Unassigned Fandoms - unassigned_fandoms_html: "%{unassigned_fandoms_link} - A list of all current unassigned fandoms" + unassigned_fandoms_html: '%{unassigned_fandoms_link} - A list of all current unassigned fandoms' vacations_and_retiring: Vacation and Retiring from Wrangling - vacations_and_retiring_html: "%{vacations_and_retiring_link} - Unable to wrangle for more than 2 weeks? Read about options for taking a break from wrangling!" + vacations_and_retiring_html: '%{vacations_and_retiring_link} - Unable to wrangle for more than 2 weeks? Read about options for taking a break from wrangling!' manage: comments: Comments (%{count}) edit: Edit @@ -2875,8 +2876,8 @@ en: landmark: Notes question_for_chair: Question for a Chair! question_for_supervisor: Question for a Supervisor! - tw_wranglers_quietroom_slack: "#tw-wranglers-quietroom" - tw_wranglers_slack: "#tw-wranglers" + tw_wranglers_quietroom_slack: '#tw-wranglers-quietroom' + tw_wranglers_slack: '#tw-wranglers' wrangle_to_guidelines_html: Wrangle tags in accordance with the %{wrangling_guidelines_link}. wrangling_guidelines: wrangling guidelines page_heading: Tag Wrangling @@ -2894,7 +2895,7 @@ en: merger: Mergers relationship: Relationships subtag: SubTags - tag_type_and_count: "%{tag_type} (%{count})" + tag_type_and_count: '%{tag_type} (%{count})' unsorted_tags: Unsorted Tags (%{count}) use_type: bookmarks: Bookmarks @@ -2904,7 +2905,7 @@ en: private_bookmarks: Private Bookmarks taggings_count: Taggings Count works: Works - use_type_and_count: "%{use_type} (%{count})" + use_type_and_count: '%{use_type} (%{count})' wranglers: Wranglers wrangling_home: Wrangling Home wrangling_tools: Wrangling Tools @@ -2996,7 +2997,7 @@ en: ticket_id: Ticket ID change_email: caution: - check_spam_html: "%{must_confirm_bold} Please check your spam folder or %{contact_support_link} if you do not receive the confirmation email." + check_spam_html: '%{must_confirm_bold} Please check your spam folder or %{contact_support_link} if you do not receive the confirmation email.' confirm_by: If you don't confirm your request by %{date}, your email address will not be changed. contact_support: contact Support must_confirm: You must use the link in the confirmation email in order to finish confirming your email change. @@ -3022,8 +3023,8 @@ en: old_password: Old password page_heading: Change Password password_length: - one: "%{minimum} to %{count} character" - other: "%{minimum} to %{count} characters" + one: '%{minimum} to %{count} character' + other: '%{minimum} to %{count} characters' submit: Submit change_username: account_faq: Account FAQ @@ -3045,7 +3046,7 @@ en: password: Password submit: Change Username submit_landmark: Submit - username_requirements: "%{minimum} to %{maximum} characters (A-Z, a-z, _, 0-9 only), no spaces, cannot begin or end with underscore (_)" + username_requirements: '%{minimum} to %{maximum} characters (A-Z, a-z, _, 0-9 only), no spaces, cannot begin or end with underscore (_)' confirm_change_email: are_you_sure_html: Are you sure you want to change your email address to %{unconfirmed_email}? A confirmation email will be sent to this address. cancel: Cancel @@ -3106,8 +3107,8 @@ en: new_password: New password page_heading: Change Password password_length: - one: "%{minimum} to %{count} character" - other: "%{minimum} to %{count} characters" + one: '%{minimum} to %{count} character' + other: '%{minimum} to %{count} characters' submit: Submit new: email_html: Email address @@ -3137,10 +3138,10 @@ en: confirm_password: Confirm password confirm_password_validation: Please enter the same password in both fields. password: Password - password_requirements: "%{minimum} to %{maximum} characters" + password_requirements: '%{minimum} to %{maximum} characters' password_validation: Please enter a password! (At least %{minimum} characters long, please.) username: Username - username_requirements: "%{minimum} to %{maximum} characters (A-Z, a-z, _, 0-9 only), no spaces, cannot begin or end with underscore (_)" + username_requirements: '%{minimum} to %{maximum} characters (A-Z, a-z, _, 0-9 only), no spaces, cannot begin or end with underscore (_)' username_validation: You need a username! (At least %{minimum} letters long, please.) valid_email: Valid email sessions: @@ -3262,7 +3263,7 @@ en: edit_tags_and_language_html: Edit Work Tags and Language for %{title} edit_tags_html: Edit Work Tags for %{title} import: - draft_work_title_html: "%{work_link} (Draft)" + draft_work_title_html: '%{work_link} (Draft)' page_heading: Imported Works success: We were able to successfully upload the following works. index: @@ -3320,7 +3321,7 @@ en: show: unposted_deletion_notice_html: This work is a draft and has not been posted. The draft will be scheduled for deletion on %{deletion_date}. show_multiple: - draft: " (Draft)" + draft: ' (Draft)' no_works: You have no works or drafts to edit. skin: select: Select work skin @@ -3332,8 +3333,8 @@ en: works_translation_link_help_title: Translation link work_approved_children: inspired_by: - restricted_html: "[Restricted Work] by %{creator_link} (Log in to access.)" - revealed_html: "%{work_link} by %{creator_link}" + restricted_html: '[Restricted Work] by %{creator_link} (Log in to access.)' + revealed_html: '%{work_link} by %{creator_link}' title: Works inspired by this one unrevealed: A work in an unrevealed collection work_form_associations_language: @@ -3358,11 +3359,11 @@ en: revealed_html: Inspired by %{work_link} by %{creator_link} unrevealed: Inspired by a work in an unrevealed collection jump: - endnotes_and_related_works_html: "(See the end of the work for %{endnotes_link} and %{related_works_link}.)" - endnotes_html: "(See the end of the work for %{endnotes_link}.)" + endnotes_and_related_works_html: '(See the end of the work for %{endnotes_link} and %{related_works_link}.)' + endnotes_html: '(See the end of the work for %{endnotes_link}.)' more_notes: more notes notes: notes - related_works_html: "(See the end of the work for %{related_works_link}.)" + related_works_html: '(See the end of the work for %{related_works_link}.)' translated_to: restricted_html: 'Translation into %{language} available: [Restricted Work] by %{creator_link} (Log in to access.)' revealed_html: 'Translation into %{language} available: %{work_link} by %{creator_link}' diff --git a/features/step_definitions/comment_steps.rb b/features/step_definitions/comment_steps.rb index 22d1cc413a1..2c93362f5e2 100644 --- a/features/step_definitions/comment_steps.rb +++ b/features/step_definitions/comment_steps.rb @@ -410,3 +410,31 @@ 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 a link {string}' do |text| + expect(page).to have_link(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 + +Given 'the anonymous work {string}' do |title| + FactoryBot.create(:no_authors, title: title) +end From f1c857f6829b95b1aaaf35b1145b793a0f038ec2 Mon Sep 17 00:00:00 2001 From: forceofcalm Date: Fri, 22 May 2026 16:10:41 -0400 Subject: [PATCH 09/28] i think?? --- app/views/comments/preview.html.erb | 2 +- features/step_definitions/comment_steps.rb | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/app/views/comments/preview.html.erb b/app/views/comments/preview.html.erb index bf356fbf435..5481de466f0 100644 --- a/app/views/comments/preview.html.erb +++ b/app/views/comments/preview.html.erb @@ -7,7 +7,7 @@