From 1345334b33f55e1c56b420d98cf24fec5dfbf1ed Mon Sep 17 00:00:00 2001 From: VISHAL DEEPAK Date: Fri, 1 Apr 2022 19:56:18 +0530 Subject: [PATCH] Added a way to sort comments on an article (#16686) * Added a way to sort comments on an article * Sorting of comments now uses crayons-dropwn. Some minor fixes as per comments on PR * Add and fix test cases for sorting comments functionality * Changes in code for sort comments. Code is more aligned with forem's conventions * Added cyperss tests for sort comments on an article. Cleaned up code to better follow forem conventions * Get fresh handle of triggerButton everytime clickOustideListener is clicked. Fix Cypress test cases to reflect the earlier. --- app/assets/stylesheets/views/comments.scss | 21 +++++ app/controllers/stories_controller.rb | 8 +- app/javascript/packs/commentDropdowns.js | 8 +- app/javascript/utilities/dropdownUtils.js | 3 + app/models/comment.rb | 19 ++++- .../articles/_full_comment_area.html.erb | 37 ++++++-- app/views/comments/_sort_option.html.erb | 13 +++ config/locales/views/articles/en.yml | 7 +- config/locales/views/articles/fr.yml | 7 +- .../articleFlows/commentOnArticle.spec.js | 8 +- .../sortCommentsOnArticle.spec.js | 84 +++++++++++++++++++ .../publishingFlows/publishOrSavePost.spec.js | 6 +- spec/models/comment_spec.rb | 34 ++++++++ spec/views/articles_spec.rb | 1 + 14 files changed, 237 insertions(+), 19 deletions(-) create mode 100644 app/views/comments/_sort_option.html.erb create mode 100644 cypress/integration/seededFlows/articleFlows/sortCommentsOnArticle.spec.js diff --git a/app/assets/stylesheets/views/comments.scss b/app/assets/stylesheets/views/comments.scss index 68bdb5c3a..6f4717811 100644 --- a/app/assets/stylesheets/views/comments.scss +++ b/app/assets/stylesheets/views/comments.scss @@ -275,3 +275,24 @@ .moderator-template-button:not(.active) { @extend .crayons-btn--outlined; } + +#comments-sort-dropdown-container { + .comment-sort-option { + &__header { + color: var(--base-90); + font-weight: var(--fw-medium); + } + + .comment-sort-option__header { + .crayons-icon { + position: absolute; + left: var(--su-4); + color: var(--link-branded-color); + } + + &[aria-current='page'] { + font-weight: var(--fw-bold); + } + } + } +} diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb index d823fb867..001ea4594 100644 --- a/app/controllers/stories_controller.rb +++ b/app/controllers/stories_controller.rb @@ -256,7 +256,7 @@ class StoriesController < ApplicationController @discussion_lock = @article.discussion_lock @user = @article.user @organization = @article.organization - + @comments_order = fetch_sort_order if @article.collection @collection = @article.collection @@ -414,4 +414,10 @@ class StoriesController < ApplicationController @user.profile.website_url, ].compact_blank end + + def fetch_sort_order + return params[:comments_sort] if Comment::VALID_SORT_OPTIONS.include? params[:comments_sort] + + "top" + end end diff --git a/app/javascript/packs/commentDropdowns.js b/app/javascript/packs/commentDropdowns.js index a68d2f384..a4cbeb24f 100644 --- a/app/javascript/packs/commentDropdowns.js +++ b/app/javascript/packs/commentDropdowns.js @@ -21,7 +21,7 @@ const handleCopyPermalink = (closeDropdown) => { const initializeArticlePageDropdowns = () => { // Gather all dropdown triggers for comment options and profile previews const dropdownTriggers = document.querySelectorAll( - 'button[id^=comment-dropdown-trigger], button[id^=comment-profile-preview-trigger-]', + 'button[id^=comment-dropdown-trigger], button[id^=comment-profile-preview-trigger-], button[id^=toggle-comments-sort-dropdown]', ); for (const dropdownTrigger of dropdownTriggers) { @@ -58,7 +58,11 @@ const initializeArticlePageDropdowns = () => { '.report-abuse-link-wrapper', ); if (reportAbuseWrapper) { - reportAbuseWrapper.innerHTML = `${locale('core.report_abuse')}`; + reportAbuseWrapper.innerHTML = `${locale( + 'core.report_abuse', + )}`; } // Initialize the "Copy link" functionality diff --git a/app/javascript/utilities/dropdownUtils.js b/app/javascript/utilities/dropdownUtils.js index 4ab8bd402..eb68531d2 100644 --- a/app/javascript/utilities/dropdownUtils.js +++ b/app/javascript/utilities/dropdownUtils.js @@ -165,7 +165,10 @@ export const initializeDropdown = ({ // Close the dropdown if user has clicked outside const clickOutsideListener = ({ target }) => { + // Get fresh handle every time, resulting in more streamlined functionality for cypress + const triggerButton = document.getElementById(triggerElementId); if ( + triggerButton && target !== triggerButton && !dropdownContent.contains(target) && !triggerButton.contains(target) diff --git a/app/models/comment.rb b/app/models/comment.rb index f72dcfbb5..8b96612c9 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -9,6 +9,8 @@ class Comment < ApplicationRecord COMMENTABLE_TYPES = %w[Article PodcastEpisode].freeze + VALID_SORT_OPTIONS = %w[top latest oldest].freeze + URI_REGEXP = %r{ \A (?:https?://)? # optional scheme @@ -87,10 +89,10 @@ class Comment < ApplicationRecord alias touch_by_reaction save - def self.tree_for(commentable, limit = 0) + def self.tree_for(commentable, limit = 0, order = nil) commentable.comments .includes(user: %i[setting profile]) - .arrange(order: "score DESC") + .arrange(order: build_sort_query(order)) .to_a[0..limit - 1] .to_h end @@ -172,6 +174,19 @@ class Comment < ApplicationRecord ancestry && Comment.exists?(id: ancestry) end + def self.build_sort_query(order) + case order + when "latest" + "created_at DESC" + when "oldest" + "created_at ASC" + else + "score DESC" + end + end + + private_class_method :build_sort_query + private def remove_notifications? diff --git a/app/views/articles/_full_comment_area.html.erb b/app/views/articles/_full_comment_area.html.erb index 6c3b1efc6..f7bbd05bb 100644 --- a/app/views/articles/_full_comment_area.html.erb +++ b/app/views/articles/_full_comment_area.html.erb @@ -2,10 +2,37 @@
<% if @article.show_comments %>
-

- <%= t("views.articles.comments.subtitle_html", - num: tag.span(t("views.articles.comments.num", num: @article.comments_count), class: "js-comments-count", data: { comments_count: @article.comments_count })) %> -

+
+

+ <%= t("views.articles.comments.subtitle.#{@comments_order}_html", + num: tag.span(t("views.articles.comments.num", num: @article.comments_count), class: "js-comments-count", data: { comments_count: @article.comments_count })) %> +

+ +
+ + +