parent
9d2253a010
commit
73b8df5eb7
|
@ -487,6 +487,15 @@ li .domain {
|
||||||
vertical-align: middle;
|
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 {
|
li .byline {
|
||||||
color: #888;
|
color: #888;
|
||||||
font-size: 8.5pt;
|
font-size: 8.5pt;
|
||||||
|
@ -695,7 +704,8 @@ div#story_box button#story_fetch_title {
|
||||||
padding-bottom: 1px;
|
padding-bottom: 1px;
|
||||||
}
|
}
|
||||||
div#story_box input#story_title,
|
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;
|
width: 600px;
|
||||||
}
|
}
|
||||||
div#story_box #story_tags_a {
|
div#story_box #story_tags_a {
|
||||||
|
|
|
@ -174,7 +174,7 @@ private
|
||||||
end
|
end
|
||||||
|
|
||||||
def _find_stories(how)
|
def _find_stories(how)
|
||||||
stories = Story.where(:is_expired => false)
|
stories = Story.unmerged.where(:is_expired => false)
|
||||||
|
|
||||||
if @user
|
if @user
|
||||||
hidden_arel = Vote.arel_table.where(
|
hidden_arel = Vote.arel_table.where(
|
||||||
|
|
|
@ -52,6 +52,10 @@ class StoriesController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
@title = "Edit Story"
|
@title = "Edit Story"
|
||||||
|
|
||||||
|
if @story.merged_into_story
|
||||||
|
@story.merge_story_short_id = @story.merged_into_story.short_id
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_url_title
|
def fetch_url_title
|
||||||
|
@ -110,6 +114,11 @@ class StoriesController < ApplicationController
|
||||||
def show
|
def show
|
||||||
@story = Story.where(:short_id => params[:id]).first!
|
@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)
|
if @story.can_be_seen_by_user?(@user)
|
||||||
@title = @story.title
|
@title = @story.title
|
||||||
else
|
else
|
||||||
|
@ -118,7 +127,8 @@ class StoriesController < ApplicationController
|
||||||
|
|
||||||
@short_url = @story.short_id_url
|
@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]
|
if params[:comment_short_id]
|
||||||
@comments.each do |c,x|
|
@comments.each do |c,x|
|
||||||
|
@ -247,13 +257,13 @@ private
|
||||||
def story_params
|
def story_params
|
||||||
p = params.require(:story).permit(
|
p = params.require(:story).permit(
|
||||||
:title, :url, :description, :moderation_reason, :seen_previous,
|
:title, :url, :description, :moderation_reason, :seen_previous,
|
||||||
:tags_a => [],
|
:merge_story_short_id, :tags_a => [],
|
||||||
)
|
)
|
||||||
|
|
||||||
if @user.is_moderator?
|
if @user.is_moderator?
|
||||||
p
|
p
|
||||||
else
|
else
|
||||||
p.except(:moderation_reason)
|
p.except(:moderation_reason, :merge_story_short_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
class Story < ActiveRecord::Base
|
class Story < ActiveRecord::Base
|
||||||
belongs_to :user
|
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,
|
has_many :taggings,
|
||||||
:autosave => true
|
:autosave => true
|
||||||
has_many :comments,
|
has_many :comments,
|
||||||
:inverse_of => :story
|
:inverse_of => :story
|
||||||
has_many :tags, :through => :taggings
|
has_many :tags, :through => :taggings
|
||||||
|
|
||||||
|
scope :unmerged, -> { where(:merged_story_id => nil) }
|
||||||
|
|
||||||
validates_length_of :title, :in => 3..150
|
validates_length_of :title, :in => 3..150
|
||||||
validates_length_of :description, :maximum => (64 * 1024)
|
validates_length_of :description, :maximum => (64 * 1024)
|
||||||
validates_presence_of :user_id
|
validates_presence_of :user_id
|
||||||
|
@ -20,12 +28,13 @@ class Story < ActiveRecord::Base
|
||||||
|
|
||||||
attr_accessor :vote, :already_posted_story, :fetched_content, :previewing,
|
attr_accessor :vote, :already_posted_story, :fetched_content, :previewing,
|
||||||
:seen_previous
|
:seen_previous
|
||||||
attr_accessor :editor, :moderation_reason
|
attr_accessor :editor, :moderation_reason, :merge_story_short_id
|
||||||
|
|
||||||
before_validation :assign_short_id_and_upvote,
|
before_validation :assign_short_id_and_upvote,
|
||||||
:on => :create
|
:on => :create
|
||||||
before_save :log_moderation
|
before_save :log_moderation
|
||||||
after_create :mark_submitter, :record_initial_upvote
|
after_create :mark_submitter, :record_initial_upvote
|
||||||
|
after_save :update_merged_into_story_comments
|
||||||
|
|
||||||
validate do
|
validate do
|
||||||
if self.url.present?
|
if self.url.present?
|
||||||
|
@ -284,8 +293,18 @@ class Story < ActiveRecord::Base
|
||||||
elsif all_changes["is_expired"] && !self.is_expired?
|
elsif all_changes["is_expired"] && !self.is_expired?
|
||||||
m.action = "undeleted story"
|
m.action = "undeleted story"
|
||||||
else
|
else
|
||||||
m.action = all_changes.map{|k,v| "changed #{k} from #{v[0].inspect} " <<
|
m.action = all_changes.map{|k,v|
|
||||||
"to #{v[1].inspect}" }.join(", ")
|
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
|
end
|
||||||
|
|
||||||
m.reason = self.moderation_reason
|
m.reason = self.moderation_reason
|
||||||
|
@ -302,6 +321,22 @@ class Story < ActiveRecord::Base
|
||||||
Keystore.increment_value_for("user:#{self.user_id}:stories_submitted")
|
Keystore.increment_value_for("user:#{self.user_id}:stories_submitted")
|
||||||
end
|
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!
|
def recalculate_hotness!
|
||||||
update_column :hotness, calculated_hotness
|
update_column :hotness, calculated_hotness
|
||||||
end
|
end
|
||||||
|
@ -375,12 +410,18 @@ class Story < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_comments_count!
|
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
|
# calculate count after removing deleted comments and threads
|
||||||
self.update_column :comments_count, comments.count{|c| !c.is_gone? }
|
self.update_column :comments_count, comments.count{|c| !c.is_gone? }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_merged_into_story_comments
|
||||||
|
if self.merged_into_story
|
||||||
|
self.merged_into_story.update_comments_count!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def url=(u)
|
def url=(u)
|
||||||
# strip out stupid google analytics parameters
|
# strip out stupid google analytics parameters
|
||||||
if u && (m = u.match(/\A([^\?]+)\?(.+)\z/))
|
if u && (m = u.match(/\A([^\?]+)\?(.+)\z/))
|
||||||
|
|
|
@ -23,6 +23,9 @@ class="comment <%= comment.current_vote ? (comment.current_vote[:vote] == 1 ?
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="details">
|
<div class="details">
|
||||||
<div class="byline">
|
<div class="byline">
|
||||||
|
<% if defined?(was_merged) && was_merged %>
|
||||||
|
<span class="merge"></span>
|
||||||
|
<% end %>
|
||||||
<% if comment.previewing %>
|
<% if comment.previewing %>
|
||||||
<a><%= comment.user.username %></a>
|
<a><%= comment.user.username %></a>
|
||||||
previewed
|
previewed
|
||||||
|
|
|
@ -36,6 +36,25 @@ class="story <%= story.vote == 1 ? "upvoted" : "" %> <%= story.vote == -1 ?
|
||||||
<% end %>
|
<% end %>
|
||||||
</span>
|
</span>
|
||||||
<span class="domain"><%= story.domain %></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 %>
|
<% end %>
|
||||||
<div class="byline">
|
<div class="byline">
|
||||||
<% if story.previewing %>
|
<% if story.previewing %>
|
||||||
|
|
|
@ -10,6 +10,13 @@
|
||||||
|
|
||||||
<% if @user.is_moderator? && (@story.user_id != @user.id) %>
|
<% if @user.is_moderator? && (@story.user_id != @user.id) %>
|
||||||
<div class="box">
|
<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">
|
<div class="boxline">
|
||||||
<%= f.label :moderation_reason, "Mod Reason:",
|
<%= f.label :moderation_reason, "Mod Reason:",
|
||||||
:class => "required" %>
|
:class => "required" %>
|
||||||
|
|
|
@ -24,7 +24,9 @@
|
||||||
<% while subtree %>
|
<% while subtree %>
|
||||||
<% if (comment = subtree.shift) %>
|
<% if (comment = subtree.shift) %>
|
||||||
<li>
|
<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]) %>
|
<% if (children = comments_by_parent[comment.id]) %>
|
||||||
<% ancestors << subtree %>
|
<% ancestors << subtree %>
|
||||||
|
|
6
db/migrate/20140408160306_add_story_merging.rb
Normal file
6
db/migrate/20140408160306_add_story_merging.rb
Normal 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
|
|
@ -11,7 +11,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# 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|
|
create_table "comments", force: true do |t|
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
|
@ -103,10 +103,12 @@ ActiveRecord::Schema.define(version: 20140221164400) do
|
||||||
t.text "markeddown_description", limit: 16777215
|
t.text "markeddown_description", limit: 16777215
|
||||||
t.text "story_cache", limit: 16777215
|
t.text "story_cache", limit: 16777215
|
||||||
t.integer "comments_count", default: 0, null: false
|
t.integer "comments_count", default: 0, null: false
|
||||||
|
t.integer "merged_story_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index "stories", ["hotness"], name: "hotness_idx", using: :btree
|
add_index "stories", ["hotness"], name: "hotness_idx", using: :btree
|
||||||
add_index "stories", ["is_expired", "is_moderated"], name: "is_idxes", 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", ["short_id"], name: "unique_short_id", unique: true, using: :btree
|
||||||
add_index "stories", ["url"], name: "url", length: {"url"=>191}, using: :btree
|
add_index "stories", ["url"], name: "url", length: {"url"=>191}, using: :btree
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue