* 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:
2
Gemfile
2
Gemfile
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
module PostHelper
|
||||
end
|
||||
27
app/helpers/posts_helper.rb
Normal file
27
app/helpers/posts_helper.rb
Normal 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
|
||||
@@ -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
138
app/logical/d_text.rb
Normal file
@@ -0,0 +1,138 @@
|
||||
require 'cgi'
|
||||
|
||||
class DText
|
||||
def self.parse_inline(str, options = {})
|
||||
str = str.gsub(/&/, "&")
|
||||
str.gsub!(/</, "<")
|
||||
str.gsub!(/>/, ">")
|
||||
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!(/"/, '"')
|
||||
'<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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,4 +2,8 @@ class Presenter
|
||||
def h(s)
|
||||
CGI.escapeHTML(s)
|
||||
end
|
||||
|
||||
def u(s)
|
||||
URI.escape(s)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
0
app/views/comments/create.js.rjs
Normal file
0
app/views/comments/create.js.rjs
Normal file
19
app/views/comments/partials/index/_list.html.erb
Normal file
19
app/views/comments/partials/index/_list.html.erb
Normal 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>
|
||||
|
||||
22
app/views/comments/partials/show/_comment.html.erb
Normal file
22
app/views/comments/partials/show/_comment.html.erb
Normal 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>
|
||||
@@ -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? %>
|
||||
|
||||
1
app/views/notes/_note.html.erb
Normal file
1
app/views/notes/_note.html.erb
Normal 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) %>
|
||||
@@ -4,7 +4,7 @@
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<aside>
|
||||
<aside class="sidebar">
|
||||
<section id="search-box">
|
||||
<h1>Search</h1>
|
||||
<% form_tag(posts_path, :method => "get") do %>
|
||||
|
||||
4
app/views/posts/partials/common/_search.html.erb
Normal file
4
app/views/posts/partials/common/_search.html.erb
Normal file
@@ -0,0 +1,4 @@
|
||||
<% form_tag(posts_path, :method => "get") do %>
|
||||
<%= text_field_tag("tags", params[:tags], :size => 15) %>
|
||||
<%= submit_tag "Go" %>
|
||||
<% end %>
|
||||
50
app/views/posts/partials/show/_edit.html.erb
Normal file
50
app/views/posts/partials/show/_edit.html.erb
Normal 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>
|
||||
6
app/views/posts/partials/show/_flash.html.erb
Normal file
6
app/views/posts/partials/show/_flash.html.erb
Normal 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>
|
||||
2
app/views/posts/partials/show/_image.html.erb
Normal file
2
app/views/posts/partials/show/_image.html.erb
Normal 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) %>
|
||||
13
app/views/posts/partials/show/_information.html.erb
Normal file
13
app/views/posts/partials/show/_information.html.erb
Normal 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>
|
||||
3
app/views/posts/partials/show/_notes.html.erb
Normal file
3
app/views/posts/partials/show/_notes.html.erb
Normal 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 %>
|
||||
15
app/views/posts/partials/show/_options.html.erb
Normal file
15
app/views/posts/partials/show/_options.html.erb
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
5
app/views/tags/partials/_list.html.erb
Normal file
5
app/views/tags/partials/_list.html.erb
Normal file
@@ -0,0 +1,5 @@
|
||||
<ul>
|
||||
<% tags.each do |tag| %>
|
||||
<li><%= tag %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
5
config/initializers/postgres_extensions.rb
Normal file
5
config/initializers/postgres_extensions.rb
Normal 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
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
BIN
public/images/arrow2_s.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 158 B |
79
public/javascripts/application.js
Normal file
79
public/javascripts/application.js
Normal 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();
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
.blacklisted {
|
||||
display: none !important;
|
||||
}
|
||||
259
public/stylesheets/default.css
Normal file
259
public/stylesheets/default.css
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user