Skip to content

Commit

Permalink
start on automated story title and tagging suggestions
Browse files Browse the repository at this point in the history
Rather than keep "poorly titled" and "poorly tagged" as reasons for
flagging, make the user do the work of suggesting new ones.

At some point, suggested taggings will flip to real taggings once
they reach a certain count (to be determined later).  This also has
to take into account tagging sets that don't contain current tags,
for when they need to be removed.

For titles, I'm not yet sure how to handle this in an automated
fashion except for the (probably rare) case of multiple users
submitting the same exact thing, but at least collect them for now.

Issue #207
  • Loading branch information
jcs committed Oct 15, 2015
1 parent 700c63b commit e940601
Show file tree
Hide file tree
Showing 16 changed files with 233 additions and 87 deletions.
2 changes: 1 addition & 1 deletion app/assets/stylesheets/application.css
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -823,7 +823,7 @@ div#story_box #story_tags_a {
div#story_box textarea { div#story_box textarea {
width: 600px; width: 600px;
} }
div#story_box div.markdown_help_toggler { div#story_box div.actions {
margin-left: 7em; margin-left: 7em;
width: 610px; width: 610px;
} }
Expand Down
2 changes: 1 addition & 1 deletion app/assets/stylesheets/mobile.css
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@
div#story_box button, div#story_box button,
div#story_box textarea, div#story_box textarea,
div#story_box #story_tags_a, div#story_box #story_tags_a,
div.markdown_help_toggler { div.actions {
margin: 0 !important; margin: 0 !important;
width: 100% !important; width: 100% !important;
} }
Expand Down
39 changes: 36 additions & 3 deletions app/controllers/stories_controller.rb
Original file line number Original file line Diff line number Diff line change
@@ -1,12 +1,11 @@
class StoriesController < ApplicationController class StoriesController < ApplicationController
before_filter :require_logged_in_user_or_400, before_filter :require_logged_in_user_or_400,
:only => [ :upvote, :downvote, :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, :suggest ]

before_filter :find_user_story, :only => [ :destroy, :edit, :undelete, before_filter :find_user_story, :only => [ :destroy, :edit, :undelete,
:update ] :update ]
before_filter :find_story!, :only => [ :suggest, :submit_suggestions ]


def create def create
@title = "Submit Story" @title = "Submit Story"
Expand Down Expand Up @@ -169,6 +168,33 @@ def show
end end
end end


def suggest
if (st = @story.suggested_taggings.where(:user_id => @user.id)).any?
@story.tags_a = st.map{|st| st.tag.tag }
end
if tt = @story.suggested_titles.where(:user_id => @user.id).first
@story.title = tt.title
end
end

def submit_suggestions
ostory = @story.dup

@story.title = params[:story][:title]
if @story.valid?
if @story.title != ostory.title
@story.save_suggested_title_for_user!(@story.title, @user)
end
if @story.tags_a.sort != params[:story][:tags_a].sort
@story.save_suggested_tags_a_for_user!(params[:story][:tags_a], @user)
end
flash[:success] = "Your suggested changes have been noted."
redirect_to ostory.comments_path
else
render :action => "suggest"
end
end

def undelete def undelete
if !(@story.is_editable_by_user?(@user) && if !(@story.is_editable_by_user?(@user) &&
@story.is_undeletable_by_user?(@user)) @story.is_undeletable_by_user?(@user))
Expand Down Expand Up @@ -291,6 +317,13 @@ def find_story
story story
end end


def find_story!
@story = find_story
if !@story
raise ActiveRecord::RecordNotFound
end
end

def find_user_story def find_user_story
if @user.is_moderator? if @user.is_moderator?
@story = Story.where(:short_id => params[:story_id] || params[:id]).first @story = Story.where(:short_id => params[:story_id] || params[:id]).first
Expand Down
46 changes: 45 additions & 1 deletion app/models/story.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ class Story < ActiveRecord::Base
:foreign_key => "merged_story_id" :foreign_key => "merged_story_id"
has_many :taggings, has_many :taggings,
:autosave => true :autosave => true
has_many :suggested_taggings
has_many :suggested_titles
has_many :comments, has_many :comments,
:inverse_of => :story :inverse_of => :story
has_many :tags, :through => :taggings has_many :tags, :through => :taggings
Expand Down Expand Up @@ -478,7 +480,8 @@ def tagging_changes


@_tags_a = [] @_tags_a = []
def tags_a def tags_a
@_tags_a ||= self.taggings.map{|t| t.tag.tag } @_tags_a ||= self.taggings.reject{|t| t.marked_for_destruction?
}.map{|t| t.tag.tag }
end end


def tags_a=(new_tag_names_a) def tags_a=(new_tag_names_a)
Expand All @@ -501,6 +504,47 @@ def tags_a=(new_tag_names_a)
end end
end end


def save_suggested_tags_a_for_user!(new_tag_names_a, user)
st = self.suggested_taggings.where(:user_id => user.id)

st.each do |tagging|
if !new_tag_names_a.include?(tagging.tag.tag)
tagging.destroy
end
end

st.reload

new_tag_names_a.each do |tag_name|
# XXX: AR bug? st.exists?(:tag => tag_name) does not work
if tag_name.to_s != "" && !st.map{|x| x.tag.tag }.include?(tag_name)
if (t = Tag.active.where(:tag => tag_name).first) &&
t.valid_for?(user)
tg = self.suggested_taggings.build
tg.user_id = user.id
tg.tag_id = t.id
tg.save!

st.reload
else
next
end
end
end

# TODO: promote suggested tags to real one when count reaches something
end

def save_suggested_title_for_user!(title, user)
st = self.suggested_titles.where(:user_id => user.id).first
if !st
st = self.suggested_titles.build
st.user_id = user.id
end
st.title = title
st.save!
end

def title=(t) def title=(t)
# change unicode whitespace characters into real spaces # change unicode whitespace characters into real spaces
self[:title] = t.strip self[:title] = t.strip
Expand Down
5 changes: 5 additions & 0 deletions app/models/suggested_tagging.rb
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,5 @@
class SuggestedTagging < ActiveRecord::Base
belongs_to :tag
belongs_to :story
belongs_to :user
end
4 changes: 4 additions & 0 deletions app/models/suggested_title.rb
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,4 @@
class SuggestedTitle < ActiveRecord::Base
belongs_to :story
belongs_to :user
end
2 changes: 0 additions & 2 deletions app/models/vote.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ class Vote < ActiveRecord::Base
STORY_REASONS = { STORY_REASONS = {
"O" => "Off-topic", "O" => "Off-topic",
"A" => "Already Posted", "A" => "Already Posted",
"T" => "Poorly Tagged",
"L" => "Poorly Titled",
"S" => "Spam", "S" => "Spam",
"" => "Cancel", "" => "Cancel",
} }
Expand Down
158 changes: 82 additions & 76 deletions app/views/stories/_form.html.erb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -25,19 +25,21 @@
<% end %> <% end %>


<div class="box"> <div class="box">
<div class="boxline"> <% unless defined?(suggesting) %>
<% if f.object.url_is_editable_by_user?(@user) %> <div class="boxline">
<%= f.label :url, "URL:", :class => "required" %> <% if f.object.url_is_editable_by_user?(@user) %>
<%= f.text_field :url, :autocomplete => "off" %> <%= f.label :url, "URL:", :class => "required" %>
<%= button_tag "Fetch Title", :id => "story_fetch_title", <%= f.text_field :url, :autocomplete => "off" %>
:type => "button" %> <%= button_tag "Fetch Title", :id => "story_fetch_title",
<% elsif !f.object.new_record? && !f.object.url.blank? %> :type => "button" %>
<%= f.label :url, "URL:", :class => "required" %> <% elsif !f.object.new_record? && !f.object.url.blank? %>
<div class="d"> <%= f.label :url, "URL:", :class => "required" %>
<a href="<%= f.object.url %>"><%= f.object.url %></a> <div class="d">
<a href="<%= f.object.url %>"><%= f.object.url %></a>
</div>
<% end %>
</div> </div>
<% end %> <% end %>
</div>


<div class="boxline"> <div class="boxline">
<%= f.label :title, "Title:", :class => "required" %> <%= f.label :title, "Title:", :class => "required" %>
Expand All @@ -63,80 +65,84 @@
f.object.tags_a), {}, { :multiple => true } %> f.object.tags_a), {}, { :multiple => true } %>
</div> </div>


<div class="boxline"> <% unless defined?(suggesting) %>
<%= f.label :description, "Text:", :class => "required" %> <div class="boxline">
<%= f.text_area :description, :rows => 15, <%= f.label :description, "Text:", :class => "required" %>
:placeholder => "Optional when submitting a URL; please see guidelines", <%= f.text_area :description, :rows => 15,
:autocomplete => "off" %> :placeholder => "Optional when submitting a URL; please see guidelines",
</div> :autocomplete => "off" %>
</div>


<div class="boxline markdown_help_toggler"> <div class="boxline actions markdown_help_toggler">
<a href="#" id="story_guidelines_toggler"> <a href="#" id="story_guidelines_toggler">
Story submission guidelines Story submission guidelines
</a> </a>
<div id="story_guidelines" style="<%= show_guidelines?? "" : <div id="story_guidelines" style="<%= show_guidelines?? "" :
"display: none;" %>"> "display: none;" %>">
<div style="float: right;"> <div style="float: right;">
<a href="javascript:window.location=%22<%= Rails.application.root_url %>stories/new?url=%22+encodeURIComponent(document.location)+%22&title=%22+encodeURIComponent(document.title)" <a href="javascript:window.location=%22<%= Rails.application.root_url %>stories/new?url=%22+encodeURIComponent(document.location)+%22&title=%22+encodeURIComponent(document.title)"
style="border: 1px solid #ddd; padding: 0.5em; background-color: style="border: 1px solid #ddd; padding: 0.5em; background-color:
#f8f8f8; line-height: 1.5em; margin-left: 1em;">Submit to #f8f8f8; line-height: 1.5em; margin-left: 1em;">Submit to
<%= Rails.application.name %></a> <%= Rails.application.name %></a>
</div> </div>
<ul> <ul>


<li><p> <li><p>
To be able to easily submit a page you're viewing in your browser To be able to easily submit a page you're viewing in your browser
to <%= Rails.application.name %>, drag the bookmarklet to the right to <%= Rails.application.name %>, drag the bookmarklet to the right
to your bookmark bar. You'll be taken to this page with the viewed to your bookmark bar. You'll be taken to this page with the viewed
page's URL and title. page's URL and title.
</p></li> </p></li>


<li><p> <li><p>
When submitting a URL, the text field is optional and should only When submitting a URL, the text field is optional and should only
be used when additional context or explanation of the URL is be used when additional context or explanation of the URL is
needed. Commentary or opinion should be reserved for a comment, needed. Commentary or opinion should be reserved for a comment,
so that it can be voted on separately from the story. so that it can be voted on separately from the story.
</p></li> </p></li>


<li><p> <li><p>
Do not editorialize story titles, but when the original story's Do not editorialize story titles, but when the original story's
title has no context or is unclear, please change it. <strong>Please title has no context or is unclear, please change it. <strong>Please
remove extraneous components from titles such as the name of the remove extraneous components from titles such as the name of the
site or section.</strong> site or section.</strong>
</p></li> </p></li>


<li><p> <li><p>
If no tags clearly apply to the story you are submitting, chances If no tags clearly apply to the story you are submitting, chances
are it does not belong here. Do not overreach with tags if they are it does not belong here. Do not overreach with tags if they
are not the primary focus of the story. are not the primary focus of the story.
</p></li> </p></li>


<li><p> <li><p>
When the story being submitted is more than a year or so old, When the story being submitted is more than a year or so old,
please add the year the story was written to the post title in please add the year the story was written to the post title in
parentheses. parentheses.
</p></li> </p></li>


</ul> </ul>
</div>
</div> </div>
</div> <% end %>
</div> </div>
<div class="box"> <% unless defined?(suggesting) %>
<div class="boxline"> <div class="box">
<%= f.label :user_is_author, "Author:", :class => "required" %> <div class="boxline">
<%= f.check_box :user_is_author %> <%= f.label :user_is_author, "Author:", :class => "required" %>
<%= f.label :user_is_author, <%= f.check_box :user_is_author %>
(f.object.id && f.object.user_id != @user.id ? "Submitter is" : "I am") + <%= f.label :user_is_author,
" the author of the story at this URL (or this text)", (f.object.id && f.object.user_id != @user.id ? "Submitter is" : "I am") +
:class => "normal" %> " the author of the story at this URL (or this text)",
:class => "normal" %>
</div>
</div> </div>
</div>


<script> <script>
$(document).ready(function() { $(document).ready(function() {
$("#story_fetch_title").click(function() { $("#story_fetch_title").click(function() {
Lobsters.fetchURLTitle($(this), $("#story_url"), $("#story_title")); Lobsters.fetchURLTitle($(this), $("#story_url"), $("#story_title"));
return false; return false;
});
}); });
}); </script>
</script> <% end %>
3 changes: 3 additions & 0 deletions app/views/stories/_listdetail.html.erb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ class="story <%= story.vote && story.vote[:vote] == 1 ? "upvoted" : "" %>
:confirm => "Are you sure you want to delete this story?" } %> :confirm => "Are you sure you want to delete this story?" } %>
<% end %> <% end %>
<% end %> <% end %>
<% elsif @user %>
| <%= link_to "suggest", story_suggest_path(story.short_id),
:class => "suggester" %>
<% end %> <% end %>
<% if !story.is_gone? && @user %> <% if !story.is_gone? && @user %>
<% if @user && story.vote && story.vote[:vote] == -1 %> <% if @user && story.vote && story.vote[:vote] == -1 %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/stories/edit.html.erb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
<p></p> <p></p>


<div class="box"> <div class="box">
<div class="boxline markdown_help_toggler"> <div class="boxline actions markdown_help_toggler">
<div class="markdown_help_label"> <div class="markdown_help_label">
Markdown formatting available Markdown formatting available
</div> </div>
Expand Down
2 changes: 1 addition & 1 deletion app/views/stories/new.html.erb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<p></p> <p></p>


<div class="box"> <div class="box">
<div class="boxline markdown_help_toggler"> <div class="boxline actions markdown_help_toggler">
<div class="markdown_help_label"> <div class="markdown_help_label">
Markdown formatting available Markdown formatting available
</div> </div>
Expand Down
Loading

0 comments on commit e940601

Please sign in to comment.