Merge pull request #104 from srgpqt/master

fix DOM structure when replying to or updating comments
This commit is contained in:
joshua stein 2014-01-20 17:26:33 -08:00
commit c764814ee5
12 changed files with 196 additions and 203 deletions

View file

@ -32,7 +32,7 @@ var _Lobsters = Class.extend({
if (!Lobsters.curUser)
return Lobsters.bounceToLogin();
var li = $(voterEl).parents("li").first();
var li = $(voterEl).closest(".story, .comment");
if (li.hasClass("downvoted")) {
/* already upvoted, neutralize */
Lobsters.vote(thingType, voterEl, -1, null);
@ -91,7 +91,7 @@ var _Lobsters = Class.extend({
if (!Lobsters.curUser)
return Lobsters.bounceToLogin();
var li = $(voterEl).parents("li").first();
var li = $(voterEl).closest(".story, .comment");
var scoreDiv = li.find("div.score").get(0);
var score = parseInt(scoreDiv.innerHTML);
var action = "";
@ -136,19 +136,24 @@ var _Lobsters = Class.extend({
Lobsters.commentDownvoteReasons[reason].toLowerCase() + ")");
$.post("/" + (thingType == "story" ? "stories" : thingType + "s") + "/" +
$(voterEl).parents("li").first().attr("data-shortid") + "/" +
li.attr("data-shortid") + "/" +
action, { reason: reason });
},
postComment: function(form) {
$(form).parent().load($(form).attr("action"), $(form).serializeArray());
$.post($(form).attr("action"), $(form).serializeArray(), function(data) {
$(form).closest('.comment').replaceWith($.parseHTML(data));
});
},
previewComment: function(form) {
$(form).parent().load($(form).attr("action").replace(/(post|update)/,
"preview"), $(form).serializeArray());
var params = $(form).serializeArray();
params.push({"name": "preview", "value": "true"});
$.post($(form).attr("action"), params, function(data) {
$(form).closest('.comment').replaceWith($.parseHTML(data));
});
},
previewStory: function(form) {
$("#inside").load("/stories/preview", $(form).serializeArray());
},
@ -221,11 +226,11 @@ var _Lobsters = Class.extend({
var Lobsters = new _Lobsters();
$(document).ready(function() {
$("li.comment a.downvoter").click(function() {
$(".comment a.downvoter").click(function() {
Lobsters.downvoteComment(this);
return false;
});
$("li.comment a.upvoter").click(function() {
$(".comment a.upvoter").click(function() {
Lobsters.upvoteComment(this);
return false;
});
@ -249,38 +254,33 @@ $(document).ready(function() {
return false;
}
var box = $(this).parents("li.comment").first().find("div.comment_reply").
first();
box.html($("#comment_form").clone());
box.find("ol").remove();
box.find("button.comment-preview").after("&nbsp;\n&nbsp;<button class=\"comment-cancel\" name=\"button\" type=\"button\">Cancel</button>");
box.find("textarea").focus();
var el = $("<input type=\"hidden\" " +
"name=\"parent_comment_short_id\" value=\"" +
$(this).parents("li.comment").first().attr("data-shortid") + "\">");
box.find("form").append(el);
var comment = $(this).closest(".comment");
var replies = comment.nextAll(".comments").first();
$.get("/comments/" + comment.attr("data-shortid") + "/reply",
function(data) {
var reply = $($.parseHTML(data));
replies.prepend(reply);
reply.find("textarea").focus();
});
return false;
});
$(document).on("click", "button.comment-cancel", function() {
$(this).parents("div.comment_reply form").remove();
$(this).closest(".comment").remove();
});
$(document).on("click", "a.comment_editor", function() {
var li = $(this).parents("li.comment").first();
li.load("/comments/" + $(li).attr("data-shortid") + "/edit",
{ "edit": 1 });
var comment = $(this).closest(".comment");
$.get("/comments/" + comment.attr("data-shortid") + "/edit",
function(data) {
comment.replaceWith($.parseHTML(data));
});
});
$(document).on("click", "a.comment_deletor", function() {
if (confirm("Are you sure you want to delete this comment?")) {
var li = $(this).parents("li.comment").first();
var li = $(this).closest(".comment");
$.post("/comments/" + $(li).attr("data-shortid") + "/delete",
function(d) {
$(li).replaceWith(d);
@ -290,7 +290,7 @@ $(document).ready(function() {
$(document).on("click", "a.comment_undeletor", function() {
if (confirm("Are you sure you want to undelete this comment?")) {
var li = $(this).parents("li.comment").first();
var li = $(this).closest(".comment");
$.post("/comments/" + $(li).attr("data-shortid") + "/undelete",
function(d) {
$(li).replaceWith(d);

View file

@ -358,10 +358,6 @@ ol.comments.comments1 {
border-left-color: transparent;
}
ol.comments.preview {
margin: 1em 0 0 -15px;
padding: 0;
}
ol.comments.collapsed ol,
ol.comments.collapsed li {
display: none;
@ -406,7 +402,7 @@ div.voters .downvoter {
}
div.voters .upvoter:hover,
li.upvoted div.voters .upvoter {
.upvoted div.voters .upvoter {
border-bottom-color: #ac130d;
}
@ -423,7 +419,7 @@ div.voters .downvoter {
border-top-width: 9px;
}
div.voters .downvoter:hover,
li.downvoted div.voters .downvoter {
.downvoted div.voters .downvoter {
border-top-color: gray;
}
@ -432,17 +428,17 @@ div.voters .downvoter.downvoter_stub {
}
li.story,
li.comment {
.comment {
clear: both;
padding-top: 0.4em;
padding-bottom: 0.4em;
}
li.comment {
.comment {
padding-top: 0.5em;
padding-bottom: 0.5em;
}
li.comment a {
.comment a {
color: #666;
}
@ -453,21 +449,21 @@ li div.details {
padding-top: 0.1em;
}
li.negative {
.negative {
opacity: 0.7;
color: gray !important;
}
li.negative_3 {
.negative_3 {
opacity: 0.4;
}
li.negative_5 {
.negative_5 {
opacity: 0.3;
}
li.negative_7 {
.negative_7 {
opacity: 0.2;
}
li.comment.highlighted {
.comment.highlighted {
background-color: #ffffbf;
}
@ -532,7 +528,7 @@ div.story_content {
}
div.story_content {
margin-bottom: 3em;
margin-bottom: 1em;
}
div.morelink {
@ -618,14 +614,6 @@ div.comment_actions a {
text-decoration: none;
}
div.comment_reply form {
padding-top: 1em;
}
div.comment_reply button.comment-post {
font-weight: bold;
}
a.pagelink {
border: 1px solid #ddd;
background-color: #fbfbfb;
@ -689,6 +677,9 @@ div.markdown_help_label {
cursor: pointer;
}
.comment .preview {
padding-left: 25px;
}
div#story_preview {
margin-top: 2em;
@ -717,10 +708,14 @@ div#story_box div.markdown_help_toggler {
width: 610px;
}
div.comment_form_container, div.comment_reply {
div.comment_form_container {
max-width: 700px;
}
div.comment_form_container form {
margin-left: 15px;
}
ul.root {
list-style-type: none;
margin: 0;

View file

@ -2,7 +2,7 @@ class CommentsController < ApplicationController
COMMENTS_PER_PAGE = 20
before_filter :require_logged_in_user_or_400,
:only => [ :create, :preview, :preview_new, :upvote, :downvote, :unvote ]
:only => [ :create, :preview, :upvote, :downvote, :unvote ]
# for rss feeds, load the user's tag filters if a token is passed
before_filter :find_user_from_rss_token, :only => [ :index ]
@ -20,9 +20,7 @@ class CommentsController < ApplicationController
if params[:parent_comment_short_id].present?
if pc = Comment.where(:story_id => story.id, :short_id =>
params[:parent_comment_short_id]).first
comment.parent_comment_id = pc.id
# needed for carryng along in comment preview form
comment.parent_comment_short_id = params[:parent_comment_short_id]
comment.parent_comment = pc
else
return render :json => { :error => "invalid parent comment",
:status => 400 }
@ -30,7 +28,7 @@ class CommentsController < ApplicationController
end
# prevent double-clicks of the post button
if !params[:preview].present? &&
if params[:preview].blank? &&
(pc = Comment.where(:story_id => story.id, :user_id => @user.id,
:parent_comment_id => comment.parent_comment_id).first)
if (Time.now - pc.created_at) < 5.minutes
@ -42,33 +40,19 @@ class CommentsController < ApplicationController
end
end
if comment.valid? && !params[:preview].present? && comment.save
if comment.valid? && params[:preview].blank? && comment.save
comment.current_vote = { :vote => 1 }
if comment.parent_comment_id
render :partial => "postedreply", :layout => false,
:content_type => "text/html", :locals => { :comment => comment }
else
render :partial => "commentbox", :layout => false,
:content_type => "text/html", :locals => {
:comment => story.comments.build, :show_comment => comment }
end
render :partial => "comments/postedreply", :layout => false,
:content_type => "text/html", :locals => { :comment => comment }
else
comment.previewing = true
comment.upvotes = 1
comment.current_vote = { :vote => 1 }
render :partial => "commentbox", :layout => false,
:content_type => "text/html", :locals => {
:comment => comment, :show_comment => comment }
preview comment
end
end
def preview_new
params[:preview] = true
return create
end
def edit
if !((comment = find_comment) && comment.is_editable_by_user?(@user))
return render :text => "can't find comment", :status => 400
@ -78,6 +62,20 @@ class CommentsController < ApplicationController
:content_type => "text/html", :locals => { :comment => comment }
end
def reply
if !(parent_comment = find_comment)
return render :text => "can't find comment", :status => 400
end
comment = Comment.new
comment.story = parent_comment.story
comment.parent_comment = parent_comment
render :partial => "commentbox", :layout => false,
:content_type => "text/html", :locals => { :comment => comment,
:cancellable => true }
end
def delete
if !((comment = find_comment) && comment.is_deletable_by_user?(@user))
return render :text => "can't find comment", :status => 400
@ -107,36 +105,20 @@ class CommentsController < ApplicationController
comment.comment = params[:comment]
if comment.save
# TODO: render the comment again properly, it's indented wrong
if params[:preview].blank? && comment.save
votes = Vote.comment_votes_by_user_for_comment_ids_hash(@user.id,
[comment.id])
comment.current_vote = votes[comment.id]
render :partial => "postedreply", :layout => false,
render :partial => "comments/comment", :layout => false,
:content_type => "text/html", :locals => { :comment => comment }
else
comment.previewing = true
comment.current_vote = { :vote => 1 }
render :partial => "commentbox", :layout => false,
:content_type => "text/html", :locals => {
:comment => comment, :show_comment => comment }
preview comment
end
end
def preview
if !((comment = find_comment) && comment.is_editable_by_user?(@user))
return render :text => "can't find comment", :status => 400
end
comment.comment = params[:comment]
comment.previewing = true
comment.current_vote = { :vote => 1 }
render :partial => "commentbox", :layout => false,
:content_type => "text/html", :locals => {
:comment => comment, :show_comment => comment }
end
def unvote
if !(comment = find_comment)
return render :text => "can't find comment", :status => 400
@ -229,53 +211,55 @@ class CommentsController < ApplicationController
@cur_url = "/threads"
end
@threads = @showing_user.recent_threads(20).map{|r|
cs = Comment.where(
:thread_id => r
).includes(
:user, :story
).arrange_for_user(
@showing_user
)
thread_ids = @showing_user.recent_threads(20)
if @user && (@showing_user.id == @user.id)
@votes = Vote.comment_votes_by_user_for_story_hash(@user.id,
cs.map{|c| c.story_id }.uniq)
comments = Comment.where(
:thread_id => thread_ids
).includes(
:user, :story
).arrange_for_user(
@showing_user
)
cs.each do |c|
if @votes[c.id]
c.current_vote = @votes[c.id]
end
comments_by_thread_id = comments.group_by(&:thread_id)
@threads = comments_by_thread_id.values_at(*thread_ids).compact
if @user && (@showing_user.id == @user.id)
@votes = Vote.comment_votes_by_user_for_story_hash(@user.id,
comments.map(&:story_id).uniq)
comments.each do |c|
if @votes[c.id]
c.current_vote = @votes[c.id]
end
else
@votes = []
end
cs
}
end
# trim each thread to this user's first response
# XXX: busted
if false
@threads.map!{|th|
th.each do |c|
if c.user_id == @user.id
break
else
th.shift
end
end
th
}
end
@comments = @threads.flatten
#@threads.each do |th|
# th.each do |c|
# if c.user_id == @user.id
# break
# else
# th.shift
# end
# end
#end
end
private
def preview(comment)
comment.previewing = true
comment.is_deleted = false # show normal preview for deleted comments
render :partial => "comments/commentbox", :layout => false,
:content_type => "text/html", :locals => {
:comment => comment, :show_comment => comment }
end
def find_comment
Comment.where(:short_id => params[:comment_id]).first
Comment.where(:short_id => params[:id]).first
end
end

View file

@ -11,8 +11,7 @@ class Comment < ActiveRecord::Base
attr_accessible :comment, :moderation_reason
attr_accessor :parent_comment_short_id, :current_vote, :previewing,
:indent_level, :highlighted
attr_accessor :current_vote, :previewing, :indent_level, :highlighted
before_validation :on => :create do
self.assign_short_id_and_upvote
@ -39,6 +38,10 @@ class Comment < ActiveRecord::Base
errors.add(:base, (m[1] == "T" ? "N" : "n") + "ope" + m[2].to_s)
end
def to_param
self.short_id
end
def self.regenerate_markdown
Comment.record_timestamps = false

View file

@ -1,4 +1,4 @@
<li id="comment_<%= comment.short_id %>" data-shortid="<%= comment.short_id %>"
<div id="comment_<%= comment.short_id %>" data-shortid="<%= comment.short_id %>"
class="comment <%= comment.current_vote ? (comment.current_vote[:vote] == 1 ?
"upvoted" : "downvoted") : "" %>
<%= comment.highlighted ? "highlighted" : "" %>
@ -91,10 +91,8 @@ class="comment <%= comment.current_vote ? (comment.current_vote[:vote] == 1 ?
<%= raw comment.markeddown_comment %>
<% end %>
</div>
<div class="comment_reply"></div>
</div>
</li>
</div>
<% if comment.highlighted %>
<script>

View file

@ -1,14 +1,15 @@
<div class="comment_form_container">
<%= form_tag comment.new_record? ?
"/comments/post_to/#{comment.story.short_id}" :
"/comments/#{comment.short_id}/update", :id => "comment_form" do |f| %>
<div class="comment comment_form_container">
<%= form_for comment do |f| %>
<% if comment.errors.any? %>
<%= errors_for comment %>
<% end %>
<% if comment.parent_comment_short_id.present? %>
<%= hidden_field_tag "story_id",
comment.story.short_id %>
<% if comment.parent_comment %>
<%= hidden_field_tag "parent_comment_short_id",
comment.parent_comment_short_id %>
comment.parent_comment.short_id %>
<% end %>
<div style="width: 100%;">
@ -27,6 +28,12 @@
&nbsp;
<%= button_tag "Preview Comment", :class => "comment-preview",
:type => "button" %>
<% if local_assigns[:cancellable] %>
&nbsp;
<%= button_tag "Cancel", :class => "comment-cancel",
:type => "button" %>
<% end %>
<div style="clear: both;"></div>
@ -37,8 +44,8 @@
<p></p>
<% end %>
<% if defined?(show_comment) && show_comment.valid? %>
<ol class="comments comments1 preview">
<div class="preview">
<%= render "comments/comment", :comment => show_comment %>
</ol>
</div>
<% end %>
</div>

View file

@ -1,3 +1,2 @@
<ol class="comments comments1 preview">
<%= render "comments/comment", :comment => comment %>
</ol>
<%= render "comments/comment", :comment => comment %>
<ol class="comments"></ol>

View file

@ -1,7 +1,7 @@
<ol class="comments comments1">
<% @comments.each do |comment| %>
<%= render "comments/comment", :comment => comment,
:show_story => true %>
<li><%= render "comments/comment", :comment => comment,
:show_story => true %></li>
<% end %>
</ol>

View file

@ -1,18 +1,26 @@
<% indent_level = -1 %>
<% @comments.each_with_index do |comment,x| %>
<% if comment.indent_level > indent_level %>
<ol class="comments comments<%= comment.indent_level %>">
<% elsif comment.indent_level < indent_level %>
<% (indent_level - comment.indent_level).times do %>
</ol>
<% @threads.each do |thread| %>
<ol class="comments comments1">
<% comments_by_parent = thread.group_by(&:parent_comment_id) %>
<% subtree = comments_by_parent[nil] %>
<% ancestors = [] %>
<% while subtree %>
<% if (comment = subtree.shift) %>
<li>
<%= render "comments/comment", :comment => comment,
:show_story => ancestors.empty? %>
<% if (children = comments_by_parent[comment.id]) %>
<% ancestors << subtree %>
<% subtree = children %>
<ol class="comments">
<% else %>
<ol class="comments"></ol></li>
<% end %>
<% elsif (subtree = ancestors.pop) %>
</ol></li>
<% end %>
<% end %>
<%= render :partial => "comments/comment", :locals => {
:comment => comment, :show_story => (comment.indent_level == 1) } %>
<% indent_level = comment.indent_level %>
<% end %>
<% indent_level.times do %>
</ol>
</ol>
<% end %>

View file

@ -62,9 +62,8 @@
<%= render :partial => "stories/listdetail",
:locals => { :story => res } %>
<% elsif res.class == Comment %>
<%= render :partial => "comments/comment",
:locals => { :comment => res,
:show_story => true, :hide_voters => true } %>
<li><%= render "comments/comment", :comment => res,
:show_story => true, :hide_voters => true %></li>
<% end %>
<% end %>
</ol>

View file

@ -9,29 +9,31 @@
<%= raw @story.markeddown_description %>
</div>
<% end %>
<p></p>
<% if @user && !@story.is_gone? && !@story.previewing %>
<%= render "comments/commentbox", :comment => @comment %>
<% end %>
</div>
<% if @comments %>
<% indent_level = -1 %>
<% @comments.each_with_index do |comment,x| %>
<% if comment.indent_level > indent_level %>
<ol class="comments comments<%= comment.indent_level %>">
<% elsif comment.indent_level < indent_level %>
<% (indent_level - comment.indent_level).times do %>
</ol>
<ol class="comments comments1">
<% if @user && !@story.is_gone? && !@story.previewing %>
<li><%= render "comments/commentbox", :comment => @comment %></li>
<% end %>
<% comments_by_parent = @comments.group_by(&:parent_comment_id) %>
<% subtree = comments_by_parent[nil] %>
<% ancestors = [] %>
<% while subtree %>
<% if (comment = subtree.shift) %>
<li>
<%= render "comments/comment", :comment => comment %>
<% if (children = comments_by_parent[comment.id]) %>
<% ancestors << subtree %>
<% subtree = children %>
<ol class="comments">
<% else %>
<ol class="comments"></ol></li>
<% end %>
<% elsif (subtree = ancestors.pop) %>
</ol></li>
<% end %>
<%= render "comments/comment", :comment => comment %>
<% indent_level = comment.indent_level %>
<% end %>
<% indent_level.times do %>
</ol>
<% end %>
<% end %>
</ol>

View file

@ -45,19 +45,17 @@ Lobsters::Application.routes.draw do
post "/stories/preview" => "stories#preview"
resources :comments do
post "upvote"
post "downvote"
post "unvote"
member do
get "reply"
post "upvote"
post "downvote"
post "unvote"
post "edit"
post "preview"
post "update"
post "delete"
post "undelete"
post "delete"
post "undelete"
end
end
get "/comments/page/:page" => "comments#index"
post "/comments/post_to/:story_id" => "comments#create"
post "/comments/preview_to/:story_id" => "comments#preview_new"
get "/messages/sent" => "messages#sent"
resources :messages do