From d9634150d8dd68b166dc3ebd7711adab7f6667e6 Mon Sep 17 00:00:00 2001 From: Ben Halpern Date: Thu, 12 Sep 2019 00:11:01 -0400 Subject: [PATCH] Add new mod links and tooling (#4009) * Add new mod links and tooling * Adjust mod page copy, test --- app/assets/images/emoji/emoji-one-sloth.png | Bin 0 -> 11736 bytes app/assets/javascripts/initializePage.js.erb | 3 +- .../initializeAllTagEditButtons.js | 12 ++ app/assets/stylesheets/scaffolds.scss | 6 +- app/assets/stylesheets/sidebar-data.scss | 7 + app/controllers/async_info_controller.rb | 1 + app/controllers/moderations_controller.rb | 2 +- app/decorators/user_decorator.rb | 2 +- app/labor/assign_tag_moderator.rb | 2 + app/models/reaction.rb | 5 +- app/models/user.rb | 7 + app/views/articles/_sidebar_nav.html.erb | 3 + app/views/articles/tags/_sidebar.html.erb | 12 ++ app/views/moderations/_reactions.html.erb | 13 ++ app/views/moderations/index.html.erb | 155 ++++++++++++------ config/routes.rb | 2 +- spec/models/user_spec.rb | 35 +++- spec/requests/moderations_spec.rb | 2 +- 18 files changed, 206 insertions(+), 63 deletions(-) create mode 100644 app/assets/images/emoji/emoji-one-sloth.png create mode 100644 app/assets/javascripts/initializers/initializeAllTagEditButtons.js create mode 100644 app/views/moderations/_reactions.html.erb diff --git a/app/assets/images/emoji/emoji-one-sloth.png b/app/assets/images/emoji/emoji-one-sloth.png new file mode 100644 index 0000000000000000000000000000000000000000..55b75d6decf4dc2925fed3d7f881e462f23f8c18 GIT binary patch literal 11736 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 literal 0 HcmV?d00001 diff --git a/app/assets/javascripts/initializePage.js.erb b/app/assets/javascripts/initializePage.js.erb index b8708e26c..c80cd47da 100644 --- a/app/assets/javascripts/initializePage.js.erb +++ b/app/assets/javascripts/initializePage.js.erb @@ -12,8 +12,9 @@ function callInitalizers(){ if (document.getElementsByTagName('body')[0].getAttribute('data-loaded') == "true") { clearInterval(waitingForDataLoad); if (document.getElementsByTagName('body')[0].getAttribute('data-user-status') == "logged-in") { - initializeAllChatButtons(); initializeBaseUserData(); + initializeAllChatButtons(); + initializeAllTagEditButtons(); } initializeAllFollowButts(); initializeReadingListIcons(); diff --git a/app/assets/javascripts/initializers/initializeAllTagEditButtons.js b/app/assets/javascripts/initializers/initializeAllTagEditButtons.js new file mode 100644 index 000000000..ddb27f812 --- /dev/null +++ b/app/assets/javascripts/initializers/initializeAllTagEditButtons.js @@ -0,0 +1,12 @@ +function initializeAllTagEditButtons() { + var tagEditButton = document.getElementById('tag-edit-button'); + var user = userData(); + if ( + user && + tagEditButton && + user.moderator_for_tags.indexOf(tagEditButton.dataset.tag) > -1 + ) { + tagEditButton.style.display = 'inline-block'; + document.getElementById('tag-mod-button').style.display = 'inline-block'; + } +} diff --git a/app/assets/stylesheets/scaffolds.scss b/app/assets/stylesheets/scaffolds.scss index 92a99bebb..d5f0a6c17 100644 --- a/app/assets/stylesheets/scaffolds.scss +++ b/app/assets/stylesheets/scaffolds.scss @@ -214,12 +214,12 @@ body.comic-sans-article-body { } } -.pro-visible-block { +.pro-visible-block, .trusted-visible-block { display: none !important; } -body.pro-status-true .pro-visible-block { - display: block !important;; +body.pro-status-true .pro-visible-block, body.trusted-status-true .trusted-visible-block { + display: block !important; } .delete-confirm{ diff --git a/app/assets/stylesheets/sidebar-data.scss b/app/assets/stylesheets/sidebar-data.scss index 201a457d6..29ae460e8 100644 --- a/app/assets/stylesheets/sidebar-data.scss +++ b/app/assets/stylesheets/sidebar-data.scss @@ -13,4 +13,11 @@ $dark-medium-gray ); font-family: $monospace; + .mod-action-button { + padding: 2px 9px; + font-size: 1.2em; + display: block; + margin-top: 5px; + border-radius: 3px; + } } \ No newline at end of file diff --git a/app/controllers/async_info_controller.rb b/app/controllers/async_info_controller.rb index 4414b5189..41f40d598 100644 --- a/app/controllers/async_info_controller.rb +++ b/app/controllers/async_info_controller.rb @@ -49,6 +49,7 @@ class AsyncInfoController < ApplicationController number_of_comments: @user.comments.count, display_sponsors: @user.display_sponsors, trusted: @user.trusted, + moderator_for_tags: @user.moderator_for_tags, experience_level: @user.experience_level, preferred_languages_array: @user.preferred_languages_array, config_body_class: @user.config_body_class, diff --git a/app/controllers/moderations_controller.rb b/app/controllers/moderations_controller.rb index 3316c1cb7..d5ece75eb 100644 --- a/app/controllers/moderations_controller.rb +++ b/app/controllers/moderations_controller.rb @@ -8,7 +8,7 @@ class ModerationsController < ApplicationController @articles = Article.published. where("rating_votes_count < 3"). where("score > -5"). - order("hotness_score DESC").limit(100) + order("hotness_score DESC").limit(50) @articles = @articles.cached_tagged_with(params[:tag]) if params[:tag].present? @rating_votes = RatingVote.where(article: @articles, user: current_user) diff --git a/app/decorators/user_decorator.rb b/app/decorators/user_decorator.rb index 6c16a37a4..aff0e385e 100644 --- a/app/decorators/user_decorator.rb +++ b/app/decorators/user_decorator.rb @@ -34,7 +34,7 @@ class UserDecorator < ApplicationDecorator def config_body_class body_class = "" body_class += config_theme.tr("_", "-") - body_class = body_class + " " + config_font.tr("_", "-") + "-article-body" + " pro-status-#{pro?}" + body_class = body_class + " " + config_font.tr("_", "-") + "-article-body" + " pro-status-#{pro?} trusted-status-#{trusted}" body_class end diff --git a/app/labor/assign_tag_moderator.rb b/app/labor/assign_tag_moderator.rb index 3b8ada559..6b6ea503e 100644 --- a/app/labor/assign_tag_moderator.rb +++ b/app/labor/assign_tag_moderator.rb @@ -17,6 +17,7 @@ module AssignTagModerator def self.add_tag_mod_role(user, tag) user.update(email_tag_mod_newsletter: true) if user.email_tag_mod_newsletter == false user.add_role(:tag_moderator, tag) + Rails.cache.delete("user-#{user.id}/tag_moderators_list") MailchimpBot.new(user).manage_tag_moderator_list end @@ -34,6 +35,7 @@ module AssignTagModerator def self.remove_tag_moderator(user, tag) user.remove_role(:tag_moderator, tag) user.update(email_tag_mod_newsletter: false) if user.email_tag_mod_newsletter == true + Rails.cache.delete("user-#{user.id}/tag_moderators_list") MailchimpBot.new(user).manage_tag_moderator_list end end diff --git a/app/models/reaction.rb b/app/models/reaction.rb index 3b44567b9..396bd0be4 100644 --- a/app/models/reaction.rb +++ b/app/models/reaction.rb @@ -58,9 +58,10 @@ class Reaction < ApplicationRecord end def cached_any_reactions_for?(reactable, user, category) - cache_name = "any_reactions_for-#{reactable.class.name}-#{reactable.id}-#{user.updated_at}-#{category}" + class_name = reactable.class.name == "ArticleDecorator" ? "Article" : reactable.class.name + cache_name = "any_reactions_for-#{class_name}-#{reactable.id}-#{user.updated_at}-#{category}" Rails.cache.fetch(cache_name, expires_in: 24.hours) do - Reaction.where(reactable_id: reactable.id, reactable_type: reactable.class.name, user: user, category: category).any? + Reaction.where(reactable_id: reactable.id, reactable_type: class_name, user: user, category: category).any? end end end diff --git a/app/models/user.rb b/app/models/user.rb index 8ed5d58e5..9f4e5a0b3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -336,6 +336,13 @@ class User < ApplicationRecord end end + def moderator_for_tags + Rails.cache.fetch("user-#{id}/tag_moderators_list", expires_in: 200.hours) do + tag_ids = roles.where(name: "tag_moderator").pluck(:resource_id) + Tag.where(id: tag_ids).pluck(:name) + end + end + def scholar valid_pass = workshop_expiration.nil? || workshop_expiration > Time.current has_role?(:workshop_pass) && valid_pass diff --git a/app/views/articles/_sidebar_nav.html.erb b/app/views/articles/_sidebar_nav.html.erb index b68072a05..c3711ea9a 100644 --- a/app/views/articles/_sidebar_nav.html.erb +++ b/app/views/articles/_sidebar_nav.html.erb @@ -23,6 +23,9 @@ " alt="reading list icon" /> Reading List + + " alt="moderation icon" /> Moderation + " alt="history icon" /> History diff --git a/app/views/articles/tags/_sidebar.html.erb b/app/views/articles/tags/_sidebar.html.erb index 95e3143d5..95e4efb99 100644 --- a/app/views/articles/tags/_sidebar.html.erb +++ b/app/views/articles/tags/_sidebar.html.erb @@ -65,6 +65,18 @@
<%= pluralize Article.published.cached_tagged_with(@tag).size, "Post" %> Published
+ <% if user_signed_in? %> + + + <% end %> diff --git a/app/views/moderations/_reactions.html.erb b/app/views/moderations/_reactions.html.erb new file mode 100644 index 000000000..63618b3e5 --- /dev/null +++ b/app/views/moderations/_reactions.html.erb @@ -0,0 +1,13 @@ +

Add a reaction

+ + + diff --git a/app/views/moderations/index.html.erb b/app/views/moderations/index.html.erb index d8e9c2265..44b11fee0 100644 --- a/app/views/moderations/index.html.erb +++ b/app/views/moderations/index.html.erb @@ -1,3 +1,5 @@ +<% title "Moderate" %> + <% if current_user&.trusted %>



-

Mod Convenience Dashboard

-

Add "Experience Level" ratings

-
More mod tools will be added as they are created.
+

Mod Dashboard (beta)

+ <% if current_user.moderator_for_tags.any? %> + ">all recent + <% current_user.moderator_for_tags.each do |tag| %> + | ">#<%= tag %> + <% end %> + <% end %>
<% @articles.each do |article| %> @@ -80,7 +111,8 @@ <%= HTML_Truncator.truncate( article.processed_html, 200, - ellipsis: '... Read Entire Post').html_safe %> + ellipsis: '... Read Entire Post').html_safe + %>
<% if article.last_buffered.nil? %> @@ -88,17 +120,13 @@ <%= form_for(BufferUpdate.new) do |f| %>

Suggest a Tweet

<%= f.hidden_field :article_id, value: article.id %> - <%= f.text_area :body_text, maxlength: 220 %> + <%= f.text_area :body_text, maxlength: 220, placeholder: "Short summary, bullet points, etc." %> <%= f.submit "Share Tweet Suggestion" %> <% end %> -

-

Text will be used in the body of a tweet linking to this post. -

Tweet suggestion can be a TLDR of the post, an interesting quote from the post, or bullet points from topics covered in the post. -

<% end %>
-

Experience Level Target:

+

Suggest experience level

<% 10.times do |i| %> <%= form_for(RatingVote.new, html: { style: "display: inline-block" }) do |f| %> <%= f.hidden_field :article_id, value: article.id %> @@ -108,7 +136,8 @@ <% end %> <% end %>
-

Full Moderation

+ <%= render "moderations/reactions", moderatable: article %> +

Full Moderation for Post





@@ -153,4 +182,36 @@ }); } } + setTimeout(function () { + var butts = document.getElementsByClassName('reaction-button'); + for (var i = 0; i < butts.length; i++) { + var butt = butts[i]; + butt.onclick = function (event) { + event.preventDefault(); + var thisButt = this; + thisButt.classList.add('reacted'); + + function successCb(response) { + if (response.result === 'create') { + thisButt.classList.add('reacted'); + } else { + thisButt.classList.remove('reacted'); + } + } + + var formData = new FormData(); + formData.append('reactable_type', thisButt.dataset.reactableType); + formData.append('category', thisButt.dataset.category); + formData.append('reactable_id', thisButt.dataset.reactableId); + + getCsrfToken() + .then(sendFetch('reaction-creation', formData)) + .then(function (response) { + if (response.status === 200) { + response.json().then(successCb); + } + }); + }; + } + }, 200) diff --git a/config/routes.rb b/config/routes.rb index d2c241967..472708f44 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -285,7 +285,7 @@ Rails.application.routes.draw do get "/stories/warm_comments/:username/:slug" => "stories#warm_comments" get "/freestickers" => "giveaways#new" get "/shop", to: redirect("https://shop.dev.to/") - get "/mod" => "moderations#index" + get "/mod" => "moderations#index", as: :mod post "/fallback_activity_recorder" => "ga_events#create" diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 6960680db..f8c524902 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -481,6 +481,24 @@ RSpec.describe User, type: :model do expect(user.all_follows.size).to eq(2) end + describe "#moderator_for_tags" do + let(:tag1) { create(:tag) } + let(:tag2) { create(:tag) } + let(:tag3) { create(:tag) } + + it "lists tags user moderates" do + user.add_role(:tag_moderator, tag1) + user.add_role(:tag_moderator, tag2) + expect(user.moderator_for_tags).to include(tag1.name) + expect(user.moderator_for_tags).to include(tag2.name) + expect(user.moderator_for_tags).not_to include(tag3.name) + end + + it "returns empty array if no tags moderated" do + expect(user.moderator_for_tags).to eq([]) + end + end + describe "#followed_articles" do let(:user2) { create(:user) } let(:user3) { create(:user) } @@ -534,32 +552,37 @@ RSpec.describe User, type: :model do end it "creates proper body class with defaults" do - expect(user.decorate.config_body_class).to eq("default default-article-body pro-status-#{user.pro?}") + expect(user.decorate.config_body_class).to eq("default default-article-body pro-status-#{user.pro?} trusted-status-#{user.trusted}") end it "creates proper body class with sans serif config" do user.config_font = "sans_serif" - expect(user.decorate.config_body_class).to eq("default sans-serif-article-body pro-status-#{user.pro?}") + expect(user.decorate.config_body_class).to eq("default sans-serif-article-body pro-status-#{user.pro?} trusted-status-#{user.trusted}") end it "creates proper body class with night theme" do user.config_theme = "night_theme" - expect(user.decorate.config_body_class).to eq("night-theme default-article-body pro-status-#{user.pro?}") + expect(user.decorate.config_body_class).to eq("night-theme default-article-body pro-status-#{user.pro?} trusted-status-#{user.trusted}") end it "creates proper body class with pink theme" do user.config_theme = "pink_theme" - expect(user.decorate.config_body_class).to eq("pink-theme default-article-body pro-status-#{user.pro?}") + expect(user.decorate.config_body_class).to eq("pink-theme default-article-body pro-status-#{user.pro?} trusted-status-#{user.trusted}") end it "creates proper body class with minimal light theme" do user.config_theme = "minimal_light_theme" - expect(user.decorate.config_body_class).to eq("minimal-light-theme default-article-body pro-status-#{user.pro?}") + expect(user.decorate.config_body_class).to eq("minimal-light-theme default-article-body pro-status-#{user.pro?} trusted-status-#{user.trusted}") end it "creates proper body class with pro user" do user.add_role(:pro) - expect(user.decorate.config_body_class).to eq("default default-article-body pro-status-#{user.pro?}") + expect(user.decorate.config_body_class).to eq("default default-article-body pro-status-#{user.pro?} trusted-status-#{user.trusted}") + end + + it "creates proper body class with trusted user" do + user.add_role(:trusted) + expect(user.decorate.config_body_class).to eq("default default-article-body pro-status-#{user.pro?} trusted-status-#{user.trusted}") end it "inserts into mailchimp" do diff --git a/spec/requests/moderations_spec.rb b/spec/requests/moderations_spec.rb index c936d04f8..ee6dac6d1 100644 --- a/spec/requests/moderations_spec.rb +++ b/spec/requests/moderations_spec.rb @@ -60,7 +60,7 @@ RSpec.describe "Moderations", type: :request do it "grants access to /mod index with articles" do create(:article, published: true) get "/mod" - expect(response.body).to include("Experience Level Target") + expect(response.body).to include("Suggest experience level") end end end