Add new mod links and tooling (#4009)
* Add new mod links and tooling * Adjust mod page copy, test
This commit is contained in:
parent
01a60b1ee9
commit
d9634150d8
18 changed files with 206 additions and 63 deletions
BIN
app/assets/images/emoji/emoji-one-sloth.png
Normal file
BIN
app/assets/images/emoji/emoji-one-sloth.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
13
app/views/moderations/_reactions.html.erb
Normal file
13
app/views/moderations/_reactions.html.erb
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue