implement story merging

closes #137
This commit is contained in:
joshua stein 2014-04-08 17:51:12 -05:00
parent 9d2253a010
commit 73b8df5eb7
10 changed files with 113 additions and 13 deletions

View file

@ -487,6 +487,15 @@ li .domain {
vertical-align: middle;
}
.merge {
background: url(data:image/png;base64,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) no-repeat;
width: 11px;
height: 14px;
padding-right: 2px;
display: inline-block;
vertical-align: middle;
}
li .byline {
color: #888;
font-size: 8.5pt;
@ -695,7 +704,8 @@ div#story_box button#story_fetch_title {
padding-bottom: 1px;
}
div#story_box input#story_title,
div#story_box input#story_moderation_reason {
div#story_box input#story_moderation_reason,
div#story_box input#story_merge_story_short_id {
width: 600px;
}
div#story_box #story_tags_a {

View file

@ -174,7 +174,7 @@ private
end
def _find_stories(how)
stories = Story.where(:is_expired => false)
stories = Story.unmerged.where(:is_expired => false)
if @user
hidden_arel = Vote.arel_table.where(
@ -186,7 +186,7 @@ private
).project(
Vote.arel_table[:story_id]
)
if how[:hidden]
stories = stories.where(Story.arel_table[:id].in(hidden_arel))
elsif !how[:by_user]
@ -218,7 +218,7 @@ private
else
filtered_tag_ids = tags_filtered_by_cookie.map{|t| t.id }
end
if filtered_tag_ids.any?
stories = stories.where(
Story.arel_table[:id].not_in(

View file

@ -52,6 +52,10 @@ class StoriesController < ApplicationController
end
@title = "Edit Story"
if @story.merged_into_story
@story.merge_story_short_id = @story.merged_into_story.short_id
end
end
def fetch_url_title
@ -110,6 +114,11 @@ class StoriesController < ApplicationController
def show
@story = Story.where(:short_id => params[:id]).first!
if @story.merged_into_story
flash[:success] = "\"#{@story.title}\" has been merged into this story."
return redirect_to @story.merged_into_story.comments_url
end
if @story.can_be_seen_by_user?(@user)
@title = @story.title
else
@ -118,7 +127,8 @@ class StoriesController < ApplicationController
@short_url = @story.short_id_url
@comments = @story.comments.includes(:user).arrange_for_user(@user)
@comments = @story.merged_comments.includes(:user,
:story).arrange_for_user(@user)
if params[:comment_short_id]
@comments.each do |c,x|
@ -247,13 +257,13 @@ private
def story_params
p = params.require(:story).permit(
:title, :url, :description, :moderation_reason, :seen_previous,
:tags_a => [],
:merge_story_short_id, :tags_a => [],
)
if @user.is_moderator?
p
else
p.except(:moderation_reason)
p.except(:moderation_reason, :merge_story_short_id)
end
end

View file

@ -1,11 +1,19 @@
class Story < ActiveRecord::Base
belongs_to :user
belongs_to :merged_into_story,
:class_name => "Story",
:foreign_key => "merged_story_id"
has_many :merged_stories,
:class_name => "Story",
:foreign_key => "merged_story_id"
has_many :taggings,
:autosave => true
has_many :comments,
:inverse_of => :story
has_many :tags, :through => :taggings
scope :unmerged, -> { where(:merged_story_id => nil) }
validates_length_of :title, :in => 3..150
validates_length_of :description, :maximum => (64 * 1024)
validates_presence_of :user_id
@ -20,12 +28,13 @@ class Story < ActiveRecord::Base
attr_accessor :vote, :already_posted_story, :fetched_content, :previewing,
:seen_previous
attr_accessor :editor, :moderation_reason
attr_accessor :editor, :moderation_reason, :merge_story_short_id
before_validation :assign_short_id_and_upvote,
:on => :create
before_save :log_moderation
after_create :mark_submitter, :record_initial_upvote
after_save :update_merged_into_story_comments
validate do
if self.url.present?
@ -284,8 +293,18 @@ class Story < ActiveRecord::Base
elsif all_changes["is_expired"] && !self.is_expired?
m.action = "undeleted story"
else
m.action = all_changes.map{|k,v| "changed #{k} from #{v[0].inspect} " <<
"to #{v[1].inspect}" }.join(", ")
m.action = all_changes.map{|k,v|
if k == "merged_story_id"
if v[1]
"merged into #{self.merged_into_story.short_id} " <<
"(#{self.merged_into_story.title})"
else
"unmerged from another story"
end
else
"changed #{k} from #{v[0].inspect} to #{v[1].inspect}"
end
}.join(", ")
end
m.reason = self.moderation_reason
@ -302,6 +321,22 @@ class Story < ActiveRecord::Base
Keystore.increment_value_for("user:#{self.user_id}:stories_submitted")
end
def merge_into_story!(story)
self.merged_story_id = story.id
self.save!
end
def merged_comments
# TODO: make this a normal has_many?
Comment.where(:story_id => Story.select(:id).
where(:merged_story_id => self.id) + [ self.id ])
end
def merge_story_short_id=(sid)
self.merged_story_id = sid.present??
Story.where(:short_id => sid).first.id : nil
end
def recalculate_hotness!
update_column :hotness, calculated_hotness
end
@ -375,12 +410,18 @@ class Story < ActiveRecord::Base
end
def update_comments_count!
comments = self.comments.arrange_for_user(nil)
comments = self.merged_comments.arrange_for_user(nil)
# calculate count after removing deleted comments and threads
self.update_column :comments_count, comments.count{|c| !c.is_gone? }
end
def update_merged_into_story_comments
if self.merged_into_story
self.merged_into_story.update_comments_count!
end
end
def url=(u)
# strip out stupid google analytics parameters
if u && (m = u.match(/\A([^\?]+)\?(.+)\z/))

View file

@ -23,6 +23,9 @@ class="comment <%= comment.current_vote ? (comment.current_vote[:vote] == 1 ?
<% end %>
<div class="details">
<div class="byline">
<% if defined?(was_merged) && was_merged %>
<span class="merge"></span>
<% end %>
<% if comment.previewing %>
<a><%= comment.user.username %></a>
previewed

View file

@ -36,6 +36,25 @@ class="story <%= story.vote == 1 ? "upvoted" : "" %> <%= story.vote == -1 ?
<% end %>
</span>
<span class="domain"><%= story.domain %></span>
<% if defined?(single_story) && single_story %>
<% story.merged_stories.each do |ms| %>
<br>
<span class="merge"></span>
<span class="link">
<a href="<%= ms.url_or_comments_url %>"><%= ms.title %></a>
</span>
<span class="tags">
<% ms.taggings.sort_by{|t| t.tag.tag }.sort_by{|t|
t.tag.tag == "pdf" ? -1 : 0 }.each do |tagging| %>
<a href="<%= tag_url(tagging.tag.tag) %>"
class="<%= tagging.tag.css_class %>"
title="<%= tagging.tag.description %>"><%= tagging.tag.tag %></a>
<% end %>
</span>
<span class="domain"><%= ms.domain %></span>
<% end %>
<% end %>
<% end %>
<div class="byline">
<% if story.previewing %>

View file

@ -10,6 +10,13 @@
<% if @user.is_moderator? && (@story.user_id != @user.id) %>
<div class="box">
<div class="boxline">
<%= f.label :merge_story_short_id, "Merge Into:",
:class => "required" %>
<%= f.text_field :merge_story_short_id, :autocomplete => "off",
:placeholder => "Short id of story into which this story " <<
"be merged" %>
</div>
<div class="boxline">
<%= f.label :moderation_reason, "Mod Reason:",
:class => "required" %>

View file

@ -24,7 +24,9 @@
<% while subtree %>
<% if (comment = subtree.shift) %>
<li>
<%= render "comments/comment", :comment => comment %>
<%= render "comments/comment", :comment => comment,
:show_story => (comment.story_id != @story.id),
:was_merged => (comment.story_id != @story.id) %>
<% if (children = comments_by_parent[comment.id]) %>
<% ancestors << subtree %>

View file

@ -0,0 +1,6 @@
class AddStoryMerging < ActiveRecord::Migration
def change
add_column :stories, :merged_story_id, :integer
add_index "stories", [ "merged_story_id" ]
end
end

View file

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20140221164400) do
ActiveRecord::Schema.define(version: 20140408160306) do
create_table "comments", force: true do |t|
t.datetime "created_at", null: false
@ -103,10 +103,12 @@ ActiveRecord::Schema.define(version: 20140221164400) do
t.text "markeddown_description", limit: 16777215
t.text "story_cache", limit: 16777215
t.integer "comments_count", default: 0, null: false
t.integer "merged_story_id"
end
add_index "stories", ["hotness"], name: "hotness_idx", using: :btree
add_index "stories", ["is_expired", "is_moderated"], name: "is_idxes", using: :btree
add_index "stories", ["merged_story_id"], name: "index_stories_on_merged_story_id", using: :btree
add_index "stories", ["short_id"], name: "unique_short_id", unique: true, using: :btree
add_index "stories", ["url"], name: "url", length: {"url"=>191}, using: :btree