Add new mod links and tooling (#4009)

* Add new mod links and tooling

* Adjust mod page copy, test
This commit is contained in:
Ben Halpern 2019-09-12 00:11:01 -04:00 committed by GitHub
parent 01a60b1ee9
commit d9634150d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 206 additions and 63 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -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();

View file

@ -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';
}
}

View file

@ -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{

View file

@ -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;
}
}

View file

@ -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,

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -23,6 +23,9 @@
<img class="nav-emoji" src="<%= asset_path("emoji/emoji-one-bookmark.png") %>" alt="reading list icon" /> Reading List
<span id="reading-list-count"></span>
</a>
<a class="sidebar-nav-link trusted-visible-block" href="<%= mod_path %>">
<img class="nav-emoji" src="<%= asset_path("emoji/emoji-one-sloth.png") %>" alt="moderation icon" /> Moderation
</a>
<a class="sidebar-nav-link pro-visible-block" href="<%= history_path %>">
<img class="nav-emoji" src="<%= asset_path("emoji/emoji-one-filmstrip.png") %>" alt="history icon" /> History
</a>

View file

@ -65,6 +65,18 @@
<div>
<%= pluralize Article.published.cached_tagged_with(@tag).size, "Post" %> Published
</div>
<% if user_signed_in? %>
<span style="display:none;" id="tag-edit-button" data-tag="<%= @tag_model.name %>">
<a class="cta mod-action-button" href="/t/<%= @tag_model.name %>/edit" style="color:<%= @tag_model.text_color_hex %>;background-color:<%= @tag_model.bg_color_hex %>">
EDIT
</a>
</span>
<span style="display:none;" id="tag-mod-button" data-tag="<%= @tag_model.name %>">
<a class="cta mod-action-button" href="/t/<%= @tag_model.name %>/edit" style="color:<%= @tag_model.text_color_hex %>;background-color:<%= @tag_model.bg_color_hex %>">
MODERATE
</a>
</span>
<% end %>
</div>
</div>
</div>

View file

@ -0,0 +1,13 @@
<h2>Add a reaction</h2>
<button class="reaction-button <%= Reaction.cached_any_reactions_for?(moderatable, current_user, "like") ? "reacted" : "" %>" data-reactable-id="<%= moderatable.id %>" data-category="like" data-reactable-type="Article">
<%= image_tag("favorite-heart-outline-button.svg", alt: "Thumbs down emoji") %>
<img class="reacted-emoji" src="<%= asset_path("emoji/emoji-one-heart.png") %>" alt="Thumbs down emoji" />
</button>
<button class="reaction-button <%= Reaction.cached_any_reactions_for?(moderatable, current_user, "thumbsdown") ? "reacted" : "" %>" data-reactable-id="<%= moderatable.id %>" data-category="thumbsdown" data-reactable-type="Article">
<%= image_tag("emoji/emoji-one-thumbs-down-gray.png", alt: "Thumbs down emoji") %>
<img class="reacted-emoji" src="<%= asset_path("emoji/emoji-one-thumbs-down.png") %>" alt="Thumbs down emoji" />
</button>
<button class="reaction-button <%= Reaction.cached_any_reactions_for?(moderatable, current_user, "vomit") ? "reacted" : "" %>" data-reactable-id="<%= moderatable.id %>" data-category="vomit" data-reactable-type="Article">
<%= image_tag("emoji/emoji-one-nausea-face-gray.png", alt: "Nausea face emoji") %>
<img class="reacted-emoji" src="<%= asset_path("emoji/emoji-one-nausea-face.png") %>" alt="Nausea down emoji" />
</button>

View file

@ -1,3 +1,5 @@
<% title "Moderate" %>
<% if current_user&.trusted %>
<style>
.level-rating-button {
@ -12,54 +14,83 @@
.level-rating-button.selected {
background: #9bebff;
}
.tag-mod-form {
border-top: 1px solid rgba(255, 255, 255, 0.5);
margin: 40px auto;
}
.tag-mod-form {
border-top: 1px solid rgba(255, 255, 255, 0.5);
margin: 40px auto;
}
.tag-mod-form ul {
text-align: left;
font-size: 15px;
}
.tag-mod-form ul {
text-align: left;
font-size: 15px;
}
.tag-mod-form form {
width: 500px;
margin: auto;
max-width: 90%;
}
.tag-mod-form form {
width: 500px;
margin: auto;
max-width: 90%;
}
.tag-mod-form form input {
width: 100%;
padding: 5px;
font-size: 20px;
margin-bottom: 5px;
border: 1px solid #888;
}
.tag-mod-form form input {
width: 100%;
padding: 5px;
font-size: 20px;
margin-bottom: 5px;
border: 1px solid #888;
}
.tag-mod-form form input[type="submit"] {
background: #0045ff;
color: white;
font-weight: bold;
padding: 10px;
margin: auto;
display: block;
}
.tag-mod-form form input[type="submit"] {
background: #0045ff;
color: white;
font-weight: bold;
padding: 10px;
margin: auto;
display: block;
}
.tag-mod-form form textarea {
box-sizing: border-box;
width: 100%;
padding: 5px;
font-size: 17px;
height: 100px;
border: 1px solid #888;
}
.tag-mod-form form textarea {
box-sizing: border-box;
width: 100%;
padding: 5px;
font-size: 17px;
height: 100px;
border: 1px solid #888;
}
.reaction-button {
position: relative;
width: 40px;
height: 40px;
background: white;
border-color: white;
margin: 20px 40px;
border-radius: 200px;
border: 0px;
}
.reaction-button img {
position: absolute;
top: 0;
width: 40px;
left: 0;
}
.reaction-button .reacted-emoji {
display: none;
}
.reaction-button.reacted .reacted-emoji {
display: block;
}
</style>
<br /><br /><br /><br />
<center>
<h1>Mod Convenience Dashboard</h1>
<h2>Add "Experience Level" ratings</h2>
<h5>More mod tools will be added as they are created.</h5>
<h1>Mod Dashboard (beta)</h1>
<% if current_user.moderator_for_tags.any? %>
<a href="/mod" style="display: inline-block; font-size: 1.3em; padding: 3px 8px; border-radius: 3px; <%= "background: #c7ffe0" if params[:tag].blank? %>">all recent</a>
<% current_user.moderator_for_tags.each do |tag| %>
| <a href="/mod?tag=<%= tag %>" style="display: inline-block; font-size: 1.3em; padding: 3px 8px; border-radius: 3px; <%= "background: #c7ffe0" if params[:tag] == tag %>">#<%= tag %></a>
<% end %>
<% end %>
</center>
<div class="container">
<% @articles.each do |article| %>
@ -80,7 +111,8 @@
<%= HTML_Truncator.truncate(
article.processed_html,
200,
ellipsis: '<a class="comment-read-more" href="' + article.path + '">... Read Entire Post</a>').html_safe %>
ellipsis: '<a class="comment-read-more" href="' + article.path + '">... Read Entire Post</a>').html_safe
%>
</div>
<center>
<% if article.last_buffered.nil? %>
@ -88,17 +120,13 @@
<%= form_for(BufferUpdate.new) do |f| %>
<h2>Suggest a Tweet</h2>
<%= 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 %>
</div>
<p style="width: 90%;margin:50px auto;text-align:left">
<br /><br />Text will be used in the body of a tweet linking to this post.
<br /><br />Tweet suggestion can be a TLDR of the post, an interesting quote from the post, or bullet points from topics covered in the post.
</p>
<% end %>
<div style="padding: 10px 0px 20px">
<h2>Experience Level Target:</h2>
<h2>Suggest experience level</h2>
<% 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 %>
</div>
<h2><a href="<%= article.path %>/mod">Full Moderation</a></h2>
<%= render "moderations/reactions", moderatable: article %>
<h2><a href="<%= article.path %>/mod">Full Moderation for Post</a></h2>
<br /><br /> <br /><br />
</center>
</div>
@ -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)
</script>

View file

@ -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"

View file

@ -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

View file

@ -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