* Continued work on improving post view templates

* Added statistics-based estimator for related tag calculator
* Fleshed out IpBan class based on changes to Danbooru 1.xx
This commit is contained in:
albert
2010-04-29 17:32:15 -04:00
parent 3666364469
commit 23656e3fa9
39 changed files with 816 additions and 86 deletions

View File

@@ -8,7 +8,7 @@ group :test do
gem "faker"
end
gem "rails", "3.0.0.beta"
gem "rails", "3.0.0.beta3"
gem "pg"
gem "memcache-client", :require => "memcache"
gem "imagesize", :require => "image_size"

View File

@@ -1,7 +1,27 @@
class CommentsController < ApplicationController
respond_to :html, :xml, :json
def index
end
def update
@comment = Comment.find(params[:id])
@comment.update_attributes(params[:comment])
respond_with(@comment)
end
def create
@comment = Comment.new(params[:comment])
@comment.post_id = params[:comment][:post_id]
@comment.creator_id = @current_user.id
@comment.ip_addr = request.remote_ip
@comment.score = 0
@comment.save
respond_with(@comment) do |format|
format.html do
flash[:notice] = "Comment posted"
redirect_to posts_path(@comment.post)
end
end
end
end

View File

@@ -9,6 +9,10 @@ module ApplicationHelper
content_tag("li", link_to(text, url, options), :class => klass)
end
def format_text(text, options = {})
DText.parse(text)
end
protected
def nav_link_match(controller, url)
url =~ case controller

View File

@@ -1,2 +0,0 @@
module PostHelper
end

View File

@@ -0,0 +1,27 @@
module PostsHelper
def image_dimensions(post, current_user)
if post.is_image?
"(#{post.image_width_for(current_user)}x#{post.image_height_for(current_user)})"
else
""
end
end
def image_dimension_menu(post, current_user)
html = ""
file_size = number_to_human_size(post.file_size)
original_dimensions = post.is_image? ? "(#{post.image_width}x#{post.image_height})" : nil
large_dimensions = post.has_large? ? "(#{post.large_image_width}x#{post.large_image_height})" : nil
medium_dimensions = post.has_medium? ? "(#{post.medium_image_width}x#{post.medium_image_height})" : nil
current_dimensions = "(#{post.image_width_for(current_user)}x#{post.image_height_for(current_user)})"
html << %{<menu type="context" data-user-default="<%= current_user.default_image_size %>">}
html << %{<li>#{file_size} #{current_dimensions}</li>}
html << %{<ul>}
html << %{<li id="original">#{file_size} #{original_dimensions}</li>}
html << %{<li id="medium">#{file_size} #{medium_dimensions}</li>} if medium_dimensions
html << %{<li id="large">#{file_size} #{large_dimensions}</li>} if large_dimensions
html << %{</ul>}
html << %{</menu>}
html.html_safe
end
end

View File

@@ -104,6 +104,10 @@ class AnonymousUser
"Eastern Time (US & Canada)"
end
def default_image_size
"medium"
end
%w(member banned privileged contributor janitor moderator admin).each do |name|
define_method("is_#{name}?") do
false

138
app/logical/d_text.rb Normal file
View File

@@ -0,0 +1,138 @@
require 'cgi'
class DText
def self.parse_inline(str, options = {})
str = str.gsub(/&/, "&amp;")
str.gsub!(/</, "&lt;")
str.gsub!(/>/, "&gt;")
str.gsub!(/\[\[.+?\]\]/m) do |tag|
tag = tag[2..-3]
if tag =~ /^(.+?)\|(.+)$/
tag = $1
name = $2
'<a href="/wiki/show?title=' + CGI.escape(CGI.unescapeHTML(tag.tr(" ", "_").downcase)) + '">' + name + '</a>'
else
'<a href="/wiki/show?title=' + CGI.escape(CGI.unescapeHTML(tag.tr(" ", "_").downcase)) + '">' + tag + '</a>'
end
end
str.gsub!(/\{\{.+?\}\}/m) do |tag|
tag = tag[2..-3]
'<a href="/post/index?tags=' + CGI.escape(CGI.unescapeHTML(tag)) + '">' + tag + '</a>'
end
str.gsub!(/[Pp]ost #(\d+)/, '<a href="/post/show/\1">post #\1</a>')
str.gsub!(/[Ff]orum #(\d+)/, '<a href="/forum/show/\1">forum #\1</a>')
str.gsub!(/[Cc]omment #(\d+)/, '<a href="/comment/show/\1">comment #\1</a>')
str.gsub!(/[Pp]ool #(\d+)/, '<a href="/pool/show/\1">pool #\1</a>')
str.gsub!(/\n/m, "<br>")
str.gsub!(/\[b\](.+?)\[\/b\]/, '<strong>\1</strong>')
str.gsub!(/\[i\](.+?)\[\/i\]/, '<em>\1</em>')
str.gsub!(/\[spoilers?\](.+?)\[\/spoilers?\]/m, '<span class="spoiler">\1</span>')
str.gsub!(/("[^"]+":(http:\/\/|\/)\S+|http:\/\/\S+)/m) do |link|
if link =~ /^"([^"]+)":(.+)$/
text = $1
link = $2
else
text = link
end
if link =~ /([;,.!?\)\]<>])$/
link.chop!
ch = $1
else
ch = ""
end
link.gsub!(/"/, '&quot;')
'<a href="' + link + '">' + text + '</a>' + ch
end
str
end
def self.parse_list(str, options = {})
html = ""
layout = []
nest = 0
str.split(/\n/).each do |line|
if line =~ /^\s*(\*+) (.+)/
nest = $1.size
content = parse_inline($2)
else
content = parse_inline(line)
end
if nest > layout.size
html += "<ul>"
layout << "ul"
end
while nest < layout.size
elist = layout.pop
if elist
html += "</#{elist}>"
end
end
html += "<li>#{content}</li>"
end
while layout.any?
elist = layout.pop
html += "</#{elist}>"
end
html
end
def self.parse(str, options = {})
return "" if str.blank?
# Make sure quote tags are surrounded by newlines
unless options[:inline]
str.gsub!(/\s*\[quote\]\s*/m, "\n\n[quote]\n\n")
str.gsub!(/\s*\[\/quote\]\s*/m, "\n\n[/quote]\n\n")
end
str.gsub!(/(?:\r?\n){3,}/, "\n\n")
str.strip!
blocks = str.split(/(?:\r?\n){2}/)
html = blocks.map do |block|
case block
when /^(h[1-6])\.\s*(.+)$/
tag = $1
content = $2
if options[:inline]
"<h6>" + parse_inline(content, options) + "</h6>"
else
"<#{tag}>" + parse_inline(content, options) + "</#{tag}>"
end
when /^\s*\*+ /
parse_list(block, options)
when "[quote]"
if options[:inline]
""
else
'<blockquote>'
end
when "[/quote]"
if options[:inline]
""
else
'</blockquote>'
end
else
'<p>' + parse_inline(block) + "</p>"
end
end
html.join("").html_safe
end
end

View File

@@ -1,26 +1,34 @@
class RelatedTagCalculator
def find_tags(tag, limit)
ActiveRecord::Base.select_values_sql("SELECT tag_string FROM posts WHERE tag_index @@ to_tsquery('danbooru', ?) ORDER BY id DESC LIMIT ?", tag, limit)
Post.find_by_tags(tag, :limit => limit, :select => "posts.tag_string", :order => "posts.md5").map(&:tag_string)
end
def calculate_from_sample(name, limit, category_constraint = nil)
counts = Hash.new {|h, k| h[k] = 0}
case category_constraint
when Tag.categories.artist
limit *= 5
when Tag.categories.copyright
limit *= 4
when Tag.categories.character
limit *= 3
end
find_tags(name, limit).each do |tags|
tag_array = Tag.scan_tags(tags)
if category_constraint
categories = Tag.categories_for(tag_array)
tag_array.each do |tag|
if categories[tag] == category_constraint && tag != name
category = Tag.category_for(tag)
if category == category_constraint
counts[tag] += 1
end
end
else
tag_array.each do |tag|
if tag != name
counts[tag] += 1
end
counts[tag] += 1
end
end
end
@@ -28,7 +36,11 @@ class RelatedTagCalculator
counts
end
def convert_hash_to_array(hash)
hash.to_a.sort_by {|x| -x[1]}.slice(0, 25)
end
def convert_hash_to_string(hash)
hash.to_a.sort_by {|x| -x[1]}.flatten.join(" ")
convert_hash_to_array(hash).flatten.join(" ")
end
end

View File

@@ -1,4 +1,5 @@
class Comment < ActiveRecord::Base
class Comment < ActiveRecord::Base
validate :validate_creator_is_not_limited
validates_format_of :body, :with => /\S/, :message => 'has no content'
belongs_to :post
belongs_to :creator, :class_name => "User"
@@ -6,35 +7,24 @@ class Comment < ActiveRecord::Base
after_save :update_last_commented_at
after_destroy :update_last_commented_at
attr_accessible :body
attr_accessor :do_not_bump_post
scope :recent, :order => "comments.id desc", :limit => 6
scope :search_body, lambda {|query| where("body_index @@ plainto_tsquery(?)", query).order("comments.id DESC")}
scope :hidden, lambda {|user| where("score < ?", user.comment_threshold)}
def creator_name
User.find_name(creator_id)
end
def validate_creator_is_not_limited
creator.is_privileged? || Comment.where("creator_id = ? AND created_at >= ?", creator_id, 1.hour.ago).count < 5
end
def update_last_commented_at
return if do_not_bump_post
comment_count = Comment.where(["post_id = ?", post_id]).count
if comment_count <= Danbooru.config.comment_threshold
if Comment.where(["post_id = ?", post_id]).count <= Danbooru.config.comment_threshold
execute_sql("UPDATE posts SET last_commented_at = ? WHERE id = ?", created_at, post_id)
end
end
def can_be_voted_by?(user)
!votes.exists?(["user_id = ?", user.id])
end
def vote!(user, is_positive)
if can_be_voted_by?(user)
if is_positive
increment!(:score)
else
decrement!(:score)
end
votes.create(:user_id => user.id)
else
raise CommentVote::Error.new("You have already voted for this comment")
end
end
end
Comment.connection.extend(PostgresExtensions)

View File

@@ -1,10 +1,21 @@
class CommentVote < ActiveRecord::Base
class Error < Exception ; end
attr_accessor :is_positive
validates_uniqueness_of :ip_addr, :scope => :comment_id
belongs_to :comment
belongs_to :user
after_save :update_comment_score
def self.prune!
destroy_all(["created_at < ?", 14.days.ago])
end
def update_comment_score
if is_positive
comment.increment!(:score)
else
comment.decrement!(:score)
end
end
end

View File

@@ -6,4 +6,24 @@ class IpBan < ActiveRecord::Base
def self.is_banned?(ip_addr)
exists?(["ip_addr = ?", ip_addr])
end
def self.search(user_ids)
comments = count_by_ip_addr("comments", user_ids, "creator_id")
posts = count_by_ip_addr("post_versions", user_ids, "updater_id")
notes = count_by_ip_addr("note_versions", user_ids, "updater_id")
pools = count_by_ip_addr("pool_updates", user_ids, "updater_id")
wiki_pages = count_by_ip_addr("wiki_page_versions", user_ids, "updater_id")
return {
"comments" => comments,
"posts" => posts,
"notes" => notes,
"pools" => pools,
"wiki_pages" => wiki_pages
}
end
def self.count_by_ip_addr(table, user_ids, user_id_field = "user_id")
select_all_sql("SELECT ip_addr, count(*) FROM #{table} WHERE #{user_id_field} IN (?) GROUP BY ip_addr ORDER BY count(*) DESC", user_ids)
end
end

View File

@@ -1,5 +1,5 @@
class Post < ActiveRecord::Base
attr_accessor :updater_id, :updater_ip_addr, :old_tag_string
attr_accessor :updater_id, :updater_ip_addr, :old_tag_string, :should_create_pool
after_destroy :delete_files
after_destroy :delete_favorites
after_destroy :update_tag_post_counts
@@ -17,6 +17,7 @@ class Post < ActiveRecord::Base
has_many :versions, :class_name => "PostVersion", :dependent => :destroy
has_many :votes, :class_name => "PostVote", :dependent => :destroy
has_many :notes, :dependent => :destroy
has_many :comments
validates_presence_of :updater_id, :updater_ip_addr
validates_uniqueness_of :md5
attr_accessible :source, :rating, :tag_string, :old_tag_string, :updater_id, :updater_ip_addr, :last_noted_at
@@ -136,7 +137,7 @@ class Post < ActiveRecord::Base
def medium_image_height
ratio = Danbooru.config.medium_image_width.to_f / image_width.to_f
if ratio < 1
image_height * ratio
(image_height * ratio).to_i
else
image_height
end
@@ -145,7 +146,7 @@ class Post < ActiveRecord::Base
def large_image_height
ratio = Danbooru.config.large_image_width.to_f / image_width.to_f
if ratio < 1
image_height * ratio
(image_height * ratio).to_i
else
image_height
end
@@ -515,10 +516,10 @@ class Post < ActiveRecord::Base
relation = relation.order("posts.image_width * posts.image_height / 1000000.0, posts.id DESC")
when "portrait"
relation = relation.order("1.0 * image_width / GREATEST(1, image_height), posts.id DESC")
relation = relation.order("1.0 * posts.image_width / GREATEST(1, posts.image_height), posts.id DESC")
when "landscape"
relation = relation.order("1.0 * image_width / GREATEST(1, image_height) DESC, posts.id DESC")
relation = relation.order("1.0 * posts.image_width / GREATEST(1, posts.image_height) DESC, posts.id DESC")
when "filesize", "filesize_desc"
relation = relation.order("posts.file_size DESC")
@@ -538,6 +539,10 @@ class Post < ActiveRecord::Base
relation = relation.offset(options[:offset])
end
if options[:select]
relation = relation.select(options[:select])
end
relation
end
end
@@ -649,3 +654,5 @@ class Post < ActiveRecord::Base
@presenter ||= PostPresenter.new(self)
end
end
Post.connection.extend(PostgresExtensions)

View File

@@ -1,5 +1,16 @@
class PostVote < ActiveRecord::Base
class Error < Exception ; end
attr_accessor :is_positive
validates_uniqueness_of :ip_addr, :scope => :post_id
after_save :update_post_score
belongs_to :post
def update_post_score
if is_positive
post.increment!(:score)
else
post.decrement!(:score)
end
end
end

View File

@@ -2,27 +2,20 @@ class PostPresenter < Presenter
def initialize(post)
@post = post
end
def tag_list_html
end
def image_html(template, current_user)
return "" if @post.is_deleted? && !current_user.is_janitor?
return template.content_tag("p", "This image was deleted.") if @post.is_deleted? && !current_user.is_janitor?
return template.content_tag("p", "You need a privileged account to see this image.") if !Danbooru.config.can_see_post?(@post, current_user)
if @post.is_flash?
template.render(:partial => "posts/flash", :locals => {:post => @post})
template.render(:partial => "posts/partials/show/flash", :locals => {:post => @post})
elsif @post.is_image?
template.image_tag(
@post.file_url_for(current_user),
:alt => @post.tag_string,
:width => @post.image_width_for(current_user),
:height => @post.image_height_for(current_user),
"data-original-width" => @post.image_width,
"data-original-height" => @post.image_height
)
template.render(:partial => "posts/partials/show/image", :locals => {:post => @post})
end
end
def note_html
def tag_list_html(template, current_user)
@tag_set_presenter ||= TagSetPresenter.new(@post.tag_array)
@tag_set_presenter.tag_list_html(template, :show_extra_links => current_user.is_privileged?)
end
end

View File

@@ -2,4 +2,8 @@ class Presenter
def h(s)
CGI.escapeHTML(s)
end
def u(s)
URI.escape(s)
end
end

View File

@@ -5,36 +5,42 @@
=end
class TagSetPresenter < Presenter
def initialize(source)
@category_cache = {}
def initialize(tags)
@tags = tags
fetch_categories
end
def to_list_html(template, options = {})
ul_class_attribute = options[:ul_class] ? %{class="#{options[:ul_class]}"} : ""
ul_id_attribute = options[:ul_id] ? %{id="#{options[:ul_id]}"} : ""
def tag_list_html(template, options = {})
html = ""
html << "<ul #{ul_class_attribute} #{ul_id_attribute}>"
html << "<ul>"
@tags.each do |tag|
html << build_list_item(tag, template, options)
end
html << "</ul>"
html
html.html_safe
end
private
def fetch_categories(tags)
def fetch_categories
@category_cache ||= Tag.categories_for(@tags)
end
def category_for(tag)
@category_cache[tag]
end
def build_list_item(tag, template, options)
html = ""
html << "<li>"
html << %{<li data-tag-type="#{category_for(tag)}">}
if options[:show_extra_links]
html << %{<a href="/wiki_pages/#{u(tag)}">?</a> }
html << %{<a href="#" class="search-inc-tag">+</a> }
html << %{<a href="#" class="search-exl-tag">-</a> }
end
humanized_tag = tag.tr("_", " ")
html << %{a href="/posts?tags=#{u(tag)}">#{h(humanized_tag)}</a>}
html << %{<a href="/posts?tags=#{u(tag)}">#{h(humanized_tag)}</a>}
html << "</li>"
html
end

View File

View File

@@ -0,0 +1,19 @@
<div class="comment-section" data-post-id="<%= post.id %>">
<div class="comment-list">
<%= render :partial => "comments/partials/show/comment", :collection => comments %>
</div>
<div class="comment-response">
<p><%= submit_tag "Post comment", :class => "expand-comment-response" %></p>
<div class="comment-preview"></div>
<% form_tag(comments_path) do %>
<%= hidden_field "comment", "post_id", :value => post.id%>
<%= text_area "comment", "body", :size => "60x7" %><br>
<%= submit_tag "Post" %>
<%= submit_tag "Preview" %>
<% end %>
</div>
</div>

View File

@@ -0,0 +1,22 @@
<article data-comment-id="<%= comment.id %>">
<header>
<h1><%= link_to comment.creator_name, user_path(comment.creator_id) %></h1>
<time datetime="<%= comment.created_at %>"><%= time_ago_in_words(comment.created_at) %> ago</time>
</header>
<div>
<section>
<%= format_text(comment.body) %>
</section>
<footer>
<menu>
<li><span class="link">Quote</span></li>
<% if @current_user.is_janitor? || @current_user.id == comment.creator_id %>
<li><%= link_to "Delete", comment_path(comment.id), :confirm => "Do you really want to delete this comment?", :method => :delete %></li>
<% end %>
<li><%= link_to "Vote up", comment_vote_path(comment.id, :is_positive => true), :method => :post %></li>
<li><%= link_to "Vote down", comment_vote_path(comment.id, :is_positive => false), :method => :post %></li>
</menu>
</footer>
</div>
</article>
<div class="clearfix"></div>

View File

@@ -12,11 +12,13 @@
<%= stylesheet_link_tag "default" %>
<%= javascript_include_tag "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" %>
<%= javascript_include_tag "rails" %>
<%= javascript_include_tag "application" %>
<%= Danbooru.config.custom_html_header_content %>
<%= yield :html_header %>
</head>
<body>
<header>
<h1><%= Danbooru.config.app_name %></h1>
<nav>
<menu>
<% if @current_user.is_anonymous? %>

View File

@@ -0,0 +1 @@
<%= content_tag(:article, note.body, "data-width" => note.width, "data-height" => note.height, "data-x" => note.x, "data-y" => note.y, "data-id" => note.id) %>

View File

@@ -4,7 +4,7 @@
</div>
<% end %>
<aside>
<aside class="sidebar">
<section id="search-box">
<h1>Search</h1>
<% form_tag(posts_path, :method => "get") do %>

View File

@@ -0,0 +1,4 @@
<% form_tag(posts_path, :method => "get") do %>
<%= text_field_tag("tags", params[:tags], :size => 15) %>
<%= submit_tag "Go" %>
<% end %>

View File

@@ -0,0 +1,50 @@
<div id="edit">
<% unless @current_user.is_contributor? %>
<div style="margin-bottom: 1em;">
<p>Before editing, read the <%= link_to "how to tag guide", wiki_page_path(:id => "howto:tag") %>.</p>
</div>
<% end %>
<% form_for(post) do |f| %>
<%= f.hidden_field :old_tags, :value => post.tag_string %>
<p>
<% if post.is_rating_locked? %>
This post is rating locked.
<% else %>
<%= f.label :rating_explicit, "Explicit" %>
<%= f.radio_button :rating, :e %>
<%= f.label :rating_questionable, "Questionable" %>
<%= f.radio_button :rating, :q %>
<%= f.label :rating_safe, "Safe" %>
<%= f.radio_button :rating, :s %>
<% end %>
</p>
<% if @current_user.is_privileged? %>
<p>
<%= f.label :is_note_locked, "Lock notes" %>
<%= f.check_box :is_note_locked %>
</p>
<p>
<%= f.label :is_rating_locked, "Lock rating" %>
<%= f.check_box :is_rating_locked %>
</p>
<% end %>
<p>
<%= f.label :source %>
<%= f.text_field :source %>
</p>
<p>
<%= f.label :tag_string, "Tags" %>
<%= f.text_area :tag_string , :size => "50x3" %>
</p>
<%= submit_tag "Submit" %>
<% end %>
</div>

View File

@@ -0,0 +1,6 @@
<% content_tag(:object, :width => post.image_width, :height => post.image_height) do %>
<%= tag :params, :name => "movie", :value => post.file_url %>
<%= tag :embed, :src => post.file_url, :width => post.image_width, :height => post.image_height, :allowScriptAccess => "never" %>
<% end %>
<p><%= link_to "Save this flash (right click and save)", post.file_url %></p>

View File

@@ -0,0 +1,2 @@
<%= render :partial => "posts/partials/show/notes", :locals => {:post => post, :notes => post.notes.active} %>
<%= image_tag(post.file_url_for(@current_user), :alt => post.tag_string, :width => post.image_width_for(@current_user), :height => post.image_height_for(@current_user), "data-original-width" => post.image_width, "data-original-height" => post.image_height) %>

View File

@@ -0,0 +1,13 @@
<ul>
<li>ID: <%= post.id %></li>
<li>Uploader: <%= link_to_unless(post.uploader.nil?, post.uploader_name, user_path(post.uploader)) %></li>
<li>Uploaded: <%= time_ago_in_words(post.created_at).gsub(/about/, "") %> ago</li>
<% if post.approver %>
<li>Approver: <%= link_to(post.approver.name, user_path(post.approver_id)) %></li>
<% end %>
<li>
Size: <%= image_dimension_menu(post, @current_user) %>
</li>
<li><%= link_to "Tag History", post_versions_path(:post_id => post) %></li>
<li><%= link_to "Note History", note_versions_path(:post_id => post) %></li>
</ul>

View File

@@ -0,0 +1,3 @@
<% notes.each do |note| %>
<%= content_tag(:article, "data-width" => note.width, "data-height" => note.height, "data-x" => note.x, "data-y" => note.y, "data-body" => note.formatted_body) %>
<% end %>

View File

@@ -0,0 +1,15 @@
<ul>
<% if !post.is_deleted? && post.is_image? && post.image_width && post.image_width > 700 %>
<li><%= link_to "Resize", "#" %></li>
<% end %>
<li><%= link_to "Favorite", "#" %></li>
<li><%= link_to "Unfavorite", "#" %></li>
<li><%= link_to "Translate", "#" %></li>
<li><%= link_to "Unapprove", "#" %></li>
<% if @current_user.is_janitor? %>
<li><%= link_to "Approve", "#" %></li>
<li><%= link_to "Undelete", "#" %></li>
<li><%= link_to "Delete", "#" %></li>
<% end %>
<li><%= link_to "Pool", "#" %></li>
</ul>

View File

@@ -1,25 +1,22 @@
<aside>
<aside class="sidebar">
<section>
<h1>Search</h1>
<% form_tag(posts_path, :method => "get") do %>
<%= text_field_tag("tags", params[:tags], :size => 20) %>
<%= submit_tag "Go" %>
<% end %>
<%= render :partial => "posts/partials/common/search" %>
</section>
<section>
<h1>Tags</h1>
<%= @post.presenter.tag_list_html %>
<%= @post.presenter.tag_list_html(self, @current_user) %>
</section>
<section>
<h1>Information</h1>
<%= render :partial => "information", :locals => {:post => @post} %>
<%= render :partial => "posts/partials/show/information", :locals => {:post => @post} %>
</section>
<section>
<h1>Options</h1>
<%= render :partial => "options", :locals => {:post => @post} %>
<%= render :partial => "posts/partials/show/options", :locals => {:post => @post} %>
</section>
</aside>
@@ -33,15 +30,17 @@
<section>
<h2>Notes</h2>
<%= @post.presenter.note_html %>
<%= render :partial => "notes/note", :collection => @post.notes.active %>
</section>
<section>
<h2>Comments</h2>
<%= render :partial => "comments/partials/index/list", :locals => {:comments => @post.comments, :post => @post} %>
</section>
<section>
<h2>Edit</h2>
<%= render :partial => "posts/partials/show/edit", :locals => {:post => @post} %>
</section>
</section>

View File

@@ -0,0 +1,5 @@
<ul>
<% tags.each do |tag| %>
<li><%= tag %></li>
<% end %>
</ul>

View File

@@ -0,0 +1,5 @@
module PostgresExtensions
def columns(*params)
super.reject {|x| x.sql_type == "tsvector"}
end
end

BIN
public/images/arrow2_n.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

BIN
public/images/arrow2_s.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

View File

@@ -0,0 +1,79 @@
// Cookie.setup();
$(document).ready(function() {
// $("#hide-upgrade-account-link").click(function() {
// $("#upgrade-account").hide();
// Cookie.put('hide-upgrade-account', '1', 7);
// });
// Comment listing
$(".comment-section form").hide();
$(".comment-section input.expand-comment-response").click(function() {
var post_id = $(this).closest(".comment-section").attr("data-post-id");
$(".comment-section[data-post-id=" + post_id + "] form").show();
$(this).hide();
})
});
var Danbooru = {};
// ContextMenu
Danbooru.ContextMenu = {};
Danbooru.ContextMenu.add_icon = function() {
$("menu[type=context] > li").append('<img src="/images/arrow2_s.png">');
}
Danbooru.ContextMenu.toggle_icon = function(li) {
if (li == null) {
$("menu[type=context] > li > img").attr("src", "/images/arrow2_s.png");
} else {
$(li).find("img").attr("src", function() {
if (this.src.match(/_n/)) {
return "/images/arrow2_s.png";
} else {
return "/images/arrow2_n.png";
}
});
}
}
Danbooru.ContextMenu.setup = function() {
$("menu[type=context] li").hover(
function() {$(this).css({"background-color": "#F6F6F6"})},
function() {$(this).css({"background-color": "#EEE"})}
);
this.add_icon();
$("menu[type=context] > li").click(function(e) {
$(this).parent().find("ul").toggle();
e.stopPropagation();
Danbooru.ContextMenu.toggle_icon(this);
});
$(document).click(function() {
$("menu[type=context] > ul").hide();
Danbooru.ContextMenu.toggle_icon();
});
$("menu[type=context] > ul > li").click(function(element) {
$(this).closest("ul").toggle();
var text = $(this).text()
var menu = $(this).closest("menu");
menu.children("li").text(text);
if (menu.attr("data-update-field-id")) {
$("#" + menu.attr("data-update-field-id")).val(text);
Danbooru.ContextMenu.add_icon();
}
if (menu.attr("data-submit-on-change") == "true") {
menu.closest("form").submit();
}
});
}
$(document).ready(function() {
Danbooru.ContextMenu.setup();
});

View File

@@ -1,8 +0,0 @@
Cookie.setup();
$(document).ready(function() {
$("#hide-upgrade-account-link").click(function() {
$("#upgrade-account").hide();
Cookie.put('hide-upgrade-account', '1', 7);
});
});

View File

@@ -1,3 +0,0 @@
.blacklisted {
display: none !important;
}

View File

@@ -0,0 +1,259 @@
.blacklisted {
display: none !important;
}
body, div, h1, h2, h3, h4, h5, h6, p, ul, li, dd, dt {
font-family: verdana, sans-serif;
font-size: 100%;
margin: 0;
padding: 0;
border: 0;
vertical-align: baseline;
}
h1, h2, h3, h4 {
font-family: Tahoma;
line-height: 1em;
}
body {
font-size: 80%;
padding: 1em 2em;
margin: 0;
}
article, section {
display: block;
}
a:link {
color: #006FFA;
text-decoration: none;
}
a:visited {
color: #006FFA;
text-decoration: none;
}
a:hover {
color: #9093FF;
text-decoration: none;
}
a:active {
color: #006FFA;
text-decoration: none;
}
addr {
display: block;
margin-left: 2em;
font-weight: bold;
}
blockquote {
margin: 0 0 1em 0;
padding: 1em;
border: 1px solid #666;
background: #EEE;
}
code {
font-family: monospace;
font-size: 1.2em;
}
dd {
margin-bottom: 1em;
}
dt {
font-weight: bold;
}
h1 {
font-size: 2em;
}
h2 {
font-size: 1.8em;
}
h3 {
font-size: 1.6em;
}
h4 {
font-size: 1.4em;
}
h5 {
font-size: 1.2em;
}
h6 {
font-size: 1em;
}
header {
margin: 0 0 1em 0;
padding: 0;
display: block;
}
img {
border: none;
vertical-align: middle;
}
input[type=text], input[type=password], textarea, button {
border: 1px solid #AAA;
font-size: 1em;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
}
input[type=submit] {
padding: 1px 4px;
border: 1px solid #AAA;
background-color: #EEE;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
font-size: 1em;
cursor: pointer;
}
input[type=submit]:hover {
background-color: #F6F6F6;
}
menu {
margin: 0;
padding: 0;
}
menu ul {
margin: 0;
padding: 0;
}
menu li {
margin: 0 1em 0 0;
padding: 0;
list-style-type: none;
display: inline;
}
section {
display: block;
}
span.link {
color: #006FFA;
cursor: pointer;
}
div#content > aside {
width: 25%;
float: left;
}
div#content > aside > section {
margin-bottom: 2em;
}
div#content > section {
width: 75%;
float: left;
}
div.clearfix {
clear: both;
/* border: 1px solid red;*/
}
/*** Context Menu ***/
menu[type=context] {
width: 200px;
cursor: pointer;
}
menu[type=context] li {
list-style: none;
padding: 0px 4px;
}
menu[type=context] > li {
border: 1px solid #333;
-moz-border-radius: 3px;
background-color: #EEE;
}
menu[type=context] > li > img {
float: right;
padding-top: 2px;
}
menu[type=context] > ul {
width: 198px;
margin: 0;
padding: 0;
border: 1px solid #333;
background: #EEE;
-moz-border-radius: 3px;
margin-top: -1px;
display: none;
position: absolute;
}
menu[type=context] > ul li {
cursor: pointer;
}
/*** Header ***/
body > header > h1 {
font-size: 3em;
font-family: Tahoma, Helvetica, sans-serif;
}
/*** Sidebar ***/
aside.sidebar > section > h1 {
font-size: 1.5em;
}
aside.sidebar > section > ul > li {
list-style-type: none;
}
/*** Comments ***/
div.comment-response {
}
div.comment-response > div {
margin-top: 1em;
}
div.comment-list {
}
div.comment-list > article {
margin-bottom: 1em;
display: block;
}
div.comment-list > article > header {
float: left;
width: 15em;
}
div.comment-list > article > div {
float: left;
width: 40em;
}

View File

@@ -1,4 +1,16 @@
require File.dirname(__FILE__) + '/../test_helper'
class IpBanTest < ActiveSupport::TestCase
def test_count_by_ip_addr
comment = Factory.create(:comment)
counts = IpBan.count_by_ip_addr("comments", [comment.creator_id])
assert_equal([{"ip_addr" => "1.2.3.4", "count" => "1"}], counts)
end
def test_search
post = create_post()
comment = create_comment(post, :ip_addr => "1.2.3.4", :body => "aaa")
counts = IpBan.search([comment.user_id])
assert_equal([{"ip_addr" => "1.2.3.4", "count" => "1"}], counts["comments"])
end
end