bring back story downvoting, remove low quality option
story hiding is still here, so hopefully this will result in less bogus story downvotes
This commit is contained in:
parent
48e5a0f130
commit
7f9c227ed0
|
@ -7,12 +7,18 @@
|
||||||
var _Lobsters = Class.extend({
|
var _Lobsters = Class.extend({
|
||||||
curUser: null,
|
curUser: null,
|
||||||
|
|
||||||
|
storyDownvoteReasons: { <%= Vote::STORY_REASONS.map{|k,v|
|
||||||
|
"#{k.inspect}: #{v.inspect}" }.join(", ") %> },
|
||||||
commentDownvoteReasons: { <%= Vote::COMMENT_REASONS.map{|k,v|
|
commentDownvoteReasons: { <%= Vote::COMMENT_REASONS.map{|k,v|
|
||||||
"#{k.inspect}: #{v.inspect}" }.join(", ") %> },
|
"#{k.inspect}: #{v.inspect}" }.join(", ") %> },
|
||||||
|
|
||||||
upvoteStory: function(voterEl) {
|
upvoteStory: function(voterEl) {
|
||||||
Lobsters.vote("story", voterEl, 1);
|
Lobsters.vote("story", voterEl, 1);
|
||||||
},
|
},
|
||||||
|
downvoteStory: function(voterEl) {
|
||||||
|
Lobsters._showDownvoteWhyAt("story", voterEl, function(k) {
|
||||||
|
Lobsters.vote("story", voterEl, -1, k); });
|
||||||
|
},
|
||||||
hideStory: function(hiderEl) {
|
hideStory: function(hiderEl) {
|
||||||
if (!Lobsters.curUser)
|
if (!Lobsters.curUser)
|
||||||
return Lobsters.bounceToLogin();
|
return Lobsters.bounceToLogin();
|
||||||
|
@ -37,10 +43,17 @@ var _Lobsters = Class.extend({
|
||||||
Lobsters.vote("comment", voterEl, 1);
|
Lobsters.vote("comment", voterEl, 1);
|
||||||
},
|
},
|
||||||
downvoteComment: function(voterEl) {
|
downvoteComment: function(voterEl) {
|
||||||
|
Lobsters._showDownvoteWhyAt("comment", voterEl, function(k) {
|
||||||
|
Lobsters.vote("comment", voterEl, -1, k); });
|
||||||
|
},
|
||||||
|
_showDownvoteWhyAt: function(thingType, voterEl, onChooseWhy) {
|
||||||
|
if (!Lobsters.curUser)
|
||||||
|
return Lobsters.bounceToLogin();
|
||||||
|
|
||||||
var li = $(voterEl).closest(".story, .comment");
|
var li = $(voterEl).closest(".story, .comment");
|
||||||
if (li.hasClass("downvoted")) {
|
if (li.hasClass("downvoted")) {
|
||||||
/* already upvoted, neutralize */
|
/* already upvoted, neutralize */
|
||||||
Lobsters.vote("comment", voterEl, -1, null);
|
Lobsters.vote(thingType, voterEl, -1, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +71,13 @@ var _Lobsters = Class.extend({
|
||||||
|
|
||||||
var d = $("<div id=\"downvote_why\"></div>");
|
var d = $("<div id=\"downvote_why\"></div>");
|
||||||
|
|
||||||
$.each(Lobsters.commentDownvoteReasons, function(k, v) {
|
var reasons;
|
||||||
|
if (thingType == "comment")
|
||||||
|
reasons = Lobsters.commentDownvoteReasons;
|
||||||
|
else
|
||||||
|
reasons = Lobsters.storyDownvoteReasons;
|
||||||
|
|
||||||
|
$.each(reasons, function(k, v) {
|
||||||
var a = $("<a href=\"#\"" + (k == "" ? " class=\"cancelreason\"" : "") +
|
var a = $("<a href=\"#\"" + (k == "" ? " class=\"cancelreason\"" : "") +
|
||||||
">" + v + "</a>");
|
">" + v + "</a>");
|
||||||
|
|
||||||
|
@ -67,7 +86,7 @@ var _Lobsters = Class.extend({
|
||||||
$("#downvote_why_shadow").remove();
|
$("#downvote_why_shadow").remove();
|
||||||
|
|
||||||
if (k != "")
|
if (k != "")
|
||||||
Lobsters.vote("comment", voterEl, -1, k);
|
onChooseWhy(k);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
@ -234,6 +253,10 @@ $(document).ready(function() {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("li.story a.downvoter").click(function() {
|
||||||
|
Lobsters.downvoteStory(this);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
$("li.story a.upvoter").click(function() {
|
$("li.story a.upvoter").click(function() {
|
||||||
Lobsters.upvoteStory(this);
|
Lobsters.upvoteStory(this);
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -368,6 +368,7 @@ ol.search_results li.story {
|
||||||
|
|
||||||
div.voters {
|
div.voters {
|
||||||
float: left;
|
float: left;
|
||||||
|
margin-top: -4px;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,8 +424,8 @@ li.story {
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
li.story div.story_liner {
|
li.story div.story_liner {
|
||||||
padding-top: 0.3em;
|
padding-top: 0.4em;
|
||||||
padding-bottom: 0.3em;
|
padding-bottom: 0.4em;
|
||||||
}
|
}
|
||||||
.comment {
|
.comment {
|
||||||
clear: both;
|
clear: both;
|
||||||
|
|
|
@ -58,8 +58,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
div.voters {
|
div.voters {
|
||||||
width: 31px;
|
|
||||||
margin-left: 0.25em;
|
margin-left: 0.25em;
|
||||||
|
margin-top: 0px;
|
||||||
|
width: 31px;
|
||||||
}
|
}
|
||||||
div.voters a.upvoter {
|
div.voters a.upvoter {
|
||||||
margin-top: -5px;
|
margin-top: -5px;
|
||||||
|
@ -133,7 +134,7 @@
|
||||||
color: #333;
|
color: #333;
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 9pt;
|
font-size: 9pt;
|
||||||
margin: -0.5em 0.5em 0 0.5em;
|
margin: 0 0.5em;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
class StoriesController < ApplicationController
|
class StoriesController < ApplicationController
|
||||||
before_filter :require_logged_in_user_or_400,
|
before_filter :require_logged_in_user_or_400,
|
||||||
:only => [ :upvote, :unvote, :hide, :unhide, :preview ]
|
:only => [ :upvote, :downvote, :unvote, :hide, :unhide, :preview ]
|
||||||
|
|
||||||
before_filter :require_logged_in_user, :only => [ :destroy, :create, :edit,
|
before_filter :require_logged_in_user, :only => [ :destroy, :create, :edit,
|
||||||
:fetch_url_title, :new ]
|
:fetch_url_title, :new ]
|
||||||
|
@ -201,6 +201,25 @@ class StoriesController < ApplicationController
|
||||||
render :text => "ok"
|
render :text => "ok"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def downvote
|
||||||
|
if !(story = find_story)
|
||||||
|
return render :text => "can't find story", :status => 400
|
||||||
|
end
|
||||||
|
|
||||||
|
if !Vote::STORY_REASONS[params[:reason]]
|
||||||
|
return render :text => "invalid reason", :status => 400
|
||||||
|
end
|
||||||
|
|
||||||
|
if !@user.can_downvote?(story)
|
||||||
|
return render :text => "not permitted to downvote", :status => 400
|
||||||
|
end
|
||||||
|
|
||||||
|
Vote.vote_thusly_on_story_or_comment_for_user_because(-1, story.id,
|
||||||
|
nil, @user.id, params[:reason])
|
||||||
|
|
||||||
|
render :text => "ok"
|
||||||
|
end
|
||||||
|
|
||||||
def hide
|
def hide
|
||||||
if !(story = find_story)
|
if !(story = find_story)
|
||||||
return render :text => "can't find story", :status => 400
|
return render :text => "can't find story", :status => 400
|
||||||
|
|
|
@ -10,6 +10,8 @@ class Story < ActiveRecord::Base
|
||||||
validates_length_of :description, :maximum => (64 * 1024)
|
validates_length_of :description, :maximum => (64 * 1024)
|
||||||
validates_presence_of :user_id
|
validates_presence_of :user_id
|
||||||
|
|
||||||
|
DOWNVOTABLE_DAYS = 14
|
||||||
|
|
||||||
# after this many minutes old, a story cannot be edited
|
# after this many minutes old, a story cannot be edited
|
||||||
MAX_EDIT_MINS = 30
|
MAX_EDIT_MINS = 30
|
||||||
|
|
||||||
|
@ -117,7 +119,8 @@ class Story < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculated_hotness
|
def calculated_hotness
|
||||||
order = Math.log([ score.abs, 1 ].max, 10)
|
# don't immediately kill stories at 0 by bumping up score by one
|
||||||
|
order = Math.log([ (score + 1).abs, 1 ].max, 10)
|
||||||
if score > 0
|
if score > 0
|
||||||
sign = 1
|
sign = 1
|
||||||
elsif score < 0
|
elsif score < 0
|
||||||
|
@ -230,6 +233,14 @@ class Story < ActiveRecord::Base
|
||||||
:vote => 0).count
|
:vote => 0).count
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def is_downvotable?
|
||||||
|
if self.created_at
|
||||||
|
Time.now - self.created_at <= DOWNVOTABLE_DAYS.days
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def is_editable_by_user?(user)
|
def is_editable_by_user?(user)
|
||||||
if user && user.is_moderator?
|
if user && user.is_moderator?
|
||||||
return true
|
return true
|
||||||
|
@ -401,4 +412,27 @@ class Story < ActiveRecord::Base
|
||||||
def url_or_comments_url
|
def url_or_comments_url
|
||||||
self.url.blank? ? self.comments_url : self.url
|
self.url.blank? ? self.comments_url : self.url
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def vote_summary_for(user)
|
||||||
|
r_counts = {}
|
||||||
|
r_whos = {}
|
||||||
|
Vote.where(:story_id => self.id, :comment_id => nil).each do |v|
|
||||||
|
r_counts[v.reason.to_s] ||= 0
|
||||||
|
r_counts[v.reason.to_s] += v.vote
|
||||||
|
if user && user.is_moderator?
|
||||||
|
r_whos[v.reason.to_s] ||= []
|
||||||
|
r_whos[v.reason.to_s].push v.user.username
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
r_counts.keys.sort.map{|k|
|
||||||
|
if k == ""
|
||||||
|
"+#{r_counts[k]}"
|
||||||
|
else
|
||||||
|
"#{r_counts[k]} " +
|
||||||
|
(Vote::STORY_REASONS[k] || Vote::OLD_STORY_REASONS[k]) +
|
||||||
|
(user && user.is_moderator?? " (#{r_whos[k].join(", ")})" : "")
|
||||||
|
end
|
||||||
|
}.join(", ")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -103,8 +103,12 @@ class User < ActiveRecord::Base
|
||||||
if is_new?
|
if is_new?
|
||||||
return false
|
return false
|
||||||
elsif obj.is_a?(Story)
|
elsif obj.is_a?(Story)
|
||||||
# user can unvote
|
if obj.is_downvotable?
|
||||||
return obj.vote == -1
|
return true
|
||||||
|
elsif obj.vote == -1
|
||||||
|
# user can unvote
|
||||||
|
return true
|
||||||
|
end
|
||||||
elsif obj.is_a?(Comment)
|
elsif obj.is_a?(Comment)
|
||||||
if obj.is_downvotable?
|
if obj.is_downvotable?
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -11,6 +11,18 @@ class Vote < ActiveRecord::Base
|
||||||
"" => "Cancel",
|
"" => "Cancel",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
STORY_REASONS = {
|
||||||
|
"O" => "Off-topic",
|
||||||
|
"A" => "Already Posted",
|
||||||
|
"T" => "Poorly Tagged",
|
||||||
|
"L" => "Poorly Titled",
|
||||||
|
"S" => "Spam",
|
||||||
|
"" => "Cancel",
|
||||||
|
}
|
||||||
|
OLD_STORY_REASONS = {
|
||||||
|
"Q" => "Low Quality",
|
||||||
|
}
|
||||||
|
|
||||||
def self.votes_by_user_for_stories_hash(user, stories)
|
def self.votes_by_user_for_stories_hash(user, stories)
|
||||||
votes = {}
|
votes = {}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,11 @@ class="story <%= story.vote == 1 ? "upvoted" : "" %> <%= story.vote == -1 ?
|
||||||
<%= link_to "", login_url, :class => "upvoter" %>
|
<%= link_to "", login_url, :class => "upvoter" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="score"><%= story.score %></div>
|
<div class="score"><%= story.score %></div>
|
||||||
|
<% if @user && @user.can_downvote?(story) %>
|
||||||
|
<a class="downvoter"></a>
|
||||||
|
<% else %>
|
||||||
|
<span class="downvoter downvoter_stub"></span>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="details">
|
<div class="details">
|
||||||
<span class="link">
|
<span class="link">
|
||||||
|
@ -83,6 +88,12 @@ class="story <%= story.vote == 1 ? "upvoted" : "" %> <%= story.vote == -1 ?
|
||||||
(story.comments_count == 1 ? "" : "s") %></a>
|
(story.comments_count == 1 ? "" : "s") %></a>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<% if defined?(single_story) && single_story %>
|
||||||
|
<% if story.downvotes > 0 %>
|
||||||
|
| <%= story.vote_summary_for(@user).downcase %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -42,6 +42,7 @@ Lobsters::Application.routes.draw do
|
||||||
|
|
||||||
resources :stories do
|
resources :stories do
|
||||||
post "upvote"
|
post "upvote"
|
||||||
|
post "downvote"
|
||||||
post "unvote"
|
post "unvote"
|
||||||
post "undelete"
|
post "undelete"
|
||||||
post "hide"
|
post "hide"
|
||||||
|
|
Loading…
Reference in a new issue