diff --git a/Gemfile b/Gemfile index a3bd122c4..ce006c104 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,11 @@ group :test do gem "simplecov", :require => false end -gem "rails", "3.1.0.rc1" +group :assets do + +end + +gem "rails", "3.1.0.rc5" gem "pg" gem "memcache-client", :require => "memcache" gem "imagesize", :require => "image_size" @@ -20,3 +24,4 @@ gem "mechanize" gem "nokogiri" gem "meta_search", :git => "git://github.com/ernie/meta_search.git" gem "silent-postgres" +gem "whenever", :require => false \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 6858f8b1a..352b3e780 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -16,37 +16,37 @@ GIT GEM remote: http://gemcutter.org/ specs: - actionmailer (3.1.0.rc1) - actionpack (= 3.1.0.rc1) + aaronh-chronic (0.3.9) + actionmailer (3.1.0.rc5) + actionpack (= 3.1.0.rc5) mail (~> 2.3.0) - actionpack (3.1.0.rc1) - activemodel (= 3.1.0.rc1) - activesupport (= 3.1.0.rc1) + actionpack (3.1.0.rc5) + activemodel (= 3.1.0.rc5) + activesupport (= 3.1.0.rc5) builder (~> 3.0.0) erubis (~> 2.7.0) - i18n (~> 0.6.0beta1) - rack (~> 1.3.0.beta2) - rack-cache (~> 1.0.1) + i18n (~> 0.6) + rack (~> 1.3.1) + rack-cache (~> 1.0.2) rack-mount (~> 0.8.1) rack-test (~> 0.6.0) - sprockets (~> 2.0.0.beta.5) - tzinfo (~> 0.3.27) - activemodel (3.1.0.rc1) - activesupport (= 3.1.0.rc1) + sprockets (~> 2.0.0.beta.12) + activemodel (3.1.0.rc5) + activesupport (= 3.1.0.rc5) bcrypt-ruby (~> 2.1.4) builder (~> 3.0.0) - i18n (~> 0.6.0beta1) - activerecord (3.1.0.rc1) - activemodel (= 3.1.0.rc1) - activesupport (= 3.1.0.rc1) - arel (~> 2.1.1) - tzinfo (~> 0.3.27) - activeresource (3.1.0.rc1) - activemodel (= 3.1.0.rc1) - activesupport (= 3.1.0.rc1) - activesupport (3.1.0.rc1) + i18n (~> 0.6) + activerecord (3.1.0.rc5) + activemodel (= 3.1.0.rc5) + activesupport (= 3.1.0.rc5) + arel (~> 2.1.4) + tzinfo (~> 0.3.29) + activeresource (3.1.0.rc5) + activemodel (= 3.1.0.rc5) + activesupport (= 3.1.0.rc5) + activesupport (3.1.0.rc5) multi_json (~> 1.0) - arel (2.1.3) + arel (2.1.4) bcrypt-ruby (2.1.4) builder (3.0.0) daemons (1.1.4) @@ -54,9 +54,9 @@ GEM activesupport (~> 3.0) daemons erubis (2.7.0) - factory_girl (1.3.3) + factory_girl (2.0.3) haml (3.1.2) - hike (1.1.0) + hike (1.2.0) i18n (0.6.0) imagesize (0.1.1) mail (2.3.0) @@ -64,10 +64,10 @@ GEM mime-types (~> 1.16) treetop (~> 1.4.8) mechanize (2.0.1) - net-http-digest_auth (~> 1.1, >= 1.1.1) + net-http-digest_auth (>= 1.1.1, ~> 1.1) net-http-persistent (~> 1.8) nokogiri (~> 1.4) - webrobots (~> 0.0, >= 0.0.9) + webrobots (>= 0.0.9, ~> 0.0) memcache-client (1.8.5) mime-types (1.16) mocha (0.9.12) @@ -76,31 +76,33 @@ GEM net-http-persistent (1.8) nokogiri (1.5.0) pg (0.11.0) - polyglot (0.3.1) - rack (1.3.1) + polyglot (0.3.2) + rack (1.3.2) rack-cache (1.0.2) rack (>= 0.4) rack-mount (0.8.1) rack (>= 1.0.0) rack-ssl (1.3.2) rack - rack-test (0.6.0) + rack-test (0.6.1) rack (>= 1.0) - rails (3.1.0.rc1) - actionmailer (= 3.1.0.rc1) - actionpack (= 3.1.0.rc1) - activerecord (= 3.1.0.rc1) - activeresource (= 3.1.0.rc1) - activesupport (= 3.1.0.rc1) + rails (3.1.0.rc5) + actionmailer (= 3.1.0.rc5) + actionpack (= 3.1.0.rc5) + activerecord (= 3.1.0.rc5) + activeresource (= 3.1.0.rc5) + activesupport (= 3.1.0.rc5) bundler (~> 1.0) - railties (= 3.1.0.rc1) - railties (3.1.0.rc1) - actionpack (= 3.1.0.rc1) - activesupport (= 3.1.0.rc1) + railties (= 3.1.0.rc5) + railties (3.1.0.rc5) + actionpack (= 3.1.0.rc5) + activesupport (= 3.1.0.rc5) rack-ssl (~> 1.3.2) rake (>= 0.8.7) + rdoc (~> 3.4) thor (~> 0.14.6) rake (0.9.2) + rdoc (3.9.1) shoulda (2.11.3) silent-postgres (0.0.8) simple_form (1.4.2) @@ -109,8 +111,8 @@ GEM simplecov (0.4.2) simplecov-html (~> 0.4.4) simplecov-html (0.4.5) - sprockets (2.0.0.beta.10) - hike (~> 1.0) + sprockets (2.0.0.beta.13) + hike (~> 1.2) rack (~> 1.0) tilt (!= 1.3.0, ~> 1.1) super_exception_notifier (3.0.13) @@ -118,11 +120,15 @@ GEM rake thor (0.14.6) tilt (1.3.2) - treetop (1.4.9) + treetop (1.4.10) + polyglot polyglot (>= 0.3.1) tzinfo (0.3.29) webrobots (0.0.10) nokogiri (>= 1.4.4) + whenever (0.6.8) + aaronh-chronic (>= 0.3.9) + activesupport (>= 2.3.4) PLATFORMS ruby @@ -139,9 +145,10 @@ DEPENDENCIES mocha nokogiri pg - rails (= 3.1.0.rc1) + rails (= 3.1.0.rc5) shoulda silent-postgres simple_form simplecov super_exception_notifier + whenever diff --git a/app/assets/javascripts/blacklists.js b/app/assets/javascripts/blacklists.js new file mode 100644 index 000000000..b587eaaf3 --- /dev/null +++ b/app/assets/javascripts/blacklists.js @@ -0,0 +1,118 @@ +(function() { + Danbooru.Blacklist = {}; + + Danbooru.Blacklist.blacklists = []; + + Danbooru.Blacklist.parse_entries = function() { + var entries = (Danbooru.meta("blacklisted-tags") || "").replace(/(rating:[qes])\w+/, "$1").split(/,/); + $.each(entries, function(i, tags) { + var blacklist = { + "tags": tags, + "require": [], + "exclude": [], + "disabled": false, + "hits": 0 + }; + var matches = tags.match(/\S+/g) || []; + $.each(matches, function(i, tag) { + if (tag.charAt(0) === '-') { + blacklist.exclude.push(tag.slice(1)); + } else { + blacklist.require.push(tag); + } + }) + Danbooru.Blacklist.blacklists.push(blacklist); + }); + } + + Danbooru.Blacklist.toggle = function(e) { + $(".blacklisted").each(function(i, element) { + var $element = $(element); + if ($element.hasClass("blacklisted-active")) { + $element.removeClass("blacklisted-active"); + } else { + $element.addClass("blacklisted-active"); + } + }); + } + + Danbooru.Blacklist.update_sidebar = function() { + $.each(this.blacklists, function(i, blacklist) { + if (blacklist.hits === 0) { + return; + } + + var item = $("
  • "); + var link = $(""); + var count = $(""); + link.html(blacklist.tags); + link.click(Danbooru.Blacklist.toggle); + count.html(blacklist.hits); + item.append(link); + item.append(" "); + item.append(count); + $("#blacklist-list").append(item); + }); + + $("#blacklist-box").show(); + } + + Danbooru.Blacklist.apply = function() { + $.each(this.blacklists, function(i, blacklist) { + blacklist.hits = 0; + }); + + var count = 0 + + $.each(this.posts(), function(i, post) { + $.each(Danbooru.Blacklist.blacklists, function(i, blacklist) { + if (Danbooru.Blacklist.post_match(post, blacklist)) { + Danbooru.Blacklist.post_hide(post); + blacklist.hits += 1; + count += 1; + } + }); + }); + + return count; + } + + Danbooru.Blacklist.posts = function() { + return $("article.post-preview"); + } + + Danbooru.Blacklist.post_match = function(post, blacklist) { + var $post = $(post); + var tags = $post.data("tags").match(/\S+/g) || []; + tags.push("rating:" + $post.data("rating")); + tags.push("uploader:" + $post.data("uploader")); + + if (blacklist.require.length > 0 || blacklist.exclude.length > 0) { + if (blacklist.require.length === 0 || Danbooru.is_subset(tags, blacklist.require)) { + if (blacklist.exclude.length === 0 || (!Danbooru.is_subset(tags, blacklist.exclude))) { + return true; + } + } + } + + return false; + } + + Danbooru.Blacklist.post_hide = function(post) { + var $post = $(post); + $post.addClass("blacklisted").addClass("blacklisted-active"); + } + + Danbooru.Blacklist.initialize = function() { + Danbooru.Blacklist.parse_entries(); + if (Danbooru.Blacklist.apply() > 0) { + Danbooru.Blacklist.update_sidebar(); + } else { + $("#blacklist-box").hide(); + } + } +})(); + +$(document).ready(function() { + Danbooru.Blacklist.initialize(); +}); diff --git a/app/assets/javascripts/cookie.js b/app/assets/javascripts/cookie.js index 9c82ccce5..8613c1f85 100644 --- a/app/assets/javascripts/cookie.js +++ b/app/assets/javascripts/cookie.js @@ -51,15 +51,23 @@ return; } + if (this.get("hide-news-ticker") == "1") { + $("#news-ticker").hide(); + } else { + $("#close-news-ticker-link").click(function(e) { + $("#news-ticker").hide(); + Danbooru.Cookie.put("hide-news-ticker", "1", 1); + return false; + }); + } + if (this.get("hide-upgrade-account") != "1") { - if ($("upgrade-account")) { - $("upgrade-account").show(); - } + $("#upgrade-account").show(); } } })(); -$(document).ready(function() { +$(function() { Danbooru.Cookie.initialize(); }); diff --git a/app/assets/javascripts/utility.js b/app/assets/javascripts/utility.js index 78f6de496..bae2db7cf 100644 --- a/app/assets/javascripts/utility.js +++ b/app/assets/javascripts/utility.js @@ -18,4 +18,16 @@ Danbooru.ajax_stop = function(target) { $(target).next("img.wait").remove(); } + + Danbooru.is_subset = function(array, subarray) { + var all = true; + + $.each(subarray, function(i, val) { + if ($.inArray(val, array) === -1) { + all = false; + } + }); + + return all; + } })(); diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index 6ff47eb82..098fea73d 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -334,6 +334,7 @@ div#page { width: 75%; float: left; margin-left: 2em; + overflow: hidden; } } @@ -626,6 +627,10 @@ article.post-preview { float: left; } +article.post-preview.blacklisted-active { + display: none; +} + div#c-posts { div.notice { font-size: 0.8em; @@ -650,6 +655,23 @@ div#c-posts { aside#sidebar > section > ul ul li { margin-left: 1em; } + + aside#sidebar > section#blacklist-box ul { + margin-left: 1em; + + li { + list-style-type: disc; + } + + a { + color: $link_color; + cursor: pointer; + } + + span { + color: #AAA; + } + } h1 { font-size: $h3_size; @@ -693,6 +715,9 @@ div#c-posts { } } +div#c-explore-posts { +} + /*** Post Histories ***/ div.post_histories { @@ -1046,3 +1071,40 @@ div#moderator-dashboard { font-size: 1.5em; } } + + +/*** page footer ***/ +footer#page-footer { + clear: both; + margin: 1em; + text-align: center; + padding-top: 1em; + border-top: 2px solid #CCC; +} + +/*** news ticker ***/ + +div#news-ticker { + padding: 5px; + background: #EEE; + border-bottom: 2px solid #666; + overflow: hidden; + font-size: 0.8em; + + ul { + float: left; + margin: 0; + padding: 0; + } + + li { + list-style-type: none; + float: left; + margin: 0 2em 0 0; + padding: 0; + } + + a#close-news-ticker-link { + float: right; + } +} diff --git a/app/controllers/admin/posts_controller.rb b/app/controllers/admin/posts_controller.rb deleted file mode 100644 index 70c6c2ff5..000000000 --- a/app/controllers/admin/posts_controller.rb +++ /dev/null @@ -1,6 +0,0 @@ -module Admin - class PostsController - def edit - end - end -end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb deleted file mode 100644 index 58404a3e2..000000000 --- a/app/controllers/admin/users_controller.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Admin::UsersController < ApplicationController - def edit - end - - def update - end -end diff --git a/app/controllers/explore/posts_controller.rb b/app/controllers/explore/posts_controller.rb new file mode 100644 index 000000000..324e013cd --- /dev/null +++ b/app/controllers/explore/posts_controller.rb @@ -0,0 +1,11 @@ +module Explore + class PostsController < ApplicationController + respond_to :html, :xml, :json + + def popular + @post_set = PostSets::Popular.new(params[:date], params[:scale]) + @posts = @post_set.posts + respond_with(@posts) + end + end +end diff --git a/app/controllers/moderator/tags_controller.rb b/app/controllers/moderator/tags_controller.rb new file mode 100644 index 000000000..43adc8a4e --- /dev/null +++ b/app/controllers/moderator/tags_controller.rb @@ -0,0 +1,19 @@ +module Moderator + class TagsController < ApplicationController + before_filter :moderator_only + rescue_from TagBatchChange::Error, :with => :error + + def edit + end + + def update + tag_batch_change = TagBatchChange.new(params[:tag][:predicate], params[:tag][:consequent]) + tag_batch_change.execute + redirect_to edit_moderator_tag_path, :notice => "Posts updated" + end + + def error + redirect_to edit_moderator_tag_path, :notice => "Error" + end + end +end diff --git a/app/controllers/tag_aliases_controller.rb b/app/controllers/tag_aliases_controller.rb index 61f4a03d5..7e9f6df95 100644 --- a/app/controllers/tag_aliases_controller.rb +++ b/app/controllers/tag_aliases_controller.rb @@ -15,6 +15,7 @@ class TagAliasesController < ApplicationController def create @tag_alias = TagAlias.create(params[:tag_alias]) + @tag_alias.delay.process! respond_with(@tag_alias, :location => tag_aliases_path(:search => {:id_eq => @tag_alias.id})) end diff --git a/app/controllers/tag_implications_controller.rb b/app/controllers/tag_implications_controller.rb index 91ace2f76..36f829baf 100644 --- a/app/controllers/tag_implications_controller.rb +++ b/app/controllers/tag_implications_controller.rb @@ -15,6 +15,7 @@ class TagImplicationsController < ApplicationController def create @tag_implication = TagImplication.create(params[:tag_implication]) + @tag_implication.delay.process! respond_with(@tag_implication, :location => tag_implications_path(:search => {:id_eq => @tag_implication.id})) end diff --git a/app/logical/moderator/tag_batch_change.rb b/app/logical/moderator/tag_batch_change.rb new file mode 100644 index 000000000..835612239 --- /dev/null +++ b/app/logical/moderator/tag_batch_change.rb @@ -0,0 +1,24 @@ +module Moderator + class TagBatchChange + class Error < Exception ; end + + attr_reader :predicate, :consequent + + def initialize(predicate, consequent) + @predicate = predicate + @consequent = consequent + end + + def execute + raise Error.new("Predicate is missing") if predicate.blank? + + normalized_predicate = TagAlias.to_aliased(::Tag.scan_tags(predicate)) + normalized_consequent = TagAlias.to_aliased(::Tag.scan_tags(consequent)) + + ::Post.tag_match(predicate).each do |post| + tags = (post.tag_array - normalized_predicate + normalized_consequent).join(" ") + post.update_attributes(:tag_string => tags) + end + end + end +end diff --git a/app/logical/pixiv_proxy.rb b/app/logical/pixiv_proxy.rb index febb33296..5db0dcdc9 100644 --- a/app/logical/pixiv_proxy.rb +++ b/app/logical/pixiv_proxy.rb @@ -4,7 +4,7 @@ class PixivProxy end def self.get(url) - if url =~ /\/(\d+)(_m)?\.(jpg|jpeg|png|gif)/i + if url =~ /\/(\d+)(_m|_p\d+)?\.(jpg|jpeg|png|gif)/i url = "http://www.pixiv.net/member_illust.php?mode=medium&illust_id=#{$1}" get_single(url) elsif url =~ /member_illust\.php/ && url =~ /illust_id=/ diff --git a/app/logical/post_sets/artist.rb b/app/logical/post_sets/artist.rb index cdd822f8f..d7dbb4903 100644 --- a/app/logical/post_sets/artist.rb +++ b/app/logical/post_sets/artist.rb @@ -1,9 +1,9 @@ module PostSets - class Artist < Post + class Artist < PostSets::Post attr_reader :artist def initialize(artist) - super(:tags => artist.name) + super(artist.name) @artist = artist end diff --git a/app/logical/post_sets/base.rb b/app/logical/post_sets/base.rb index cef6b5b8e..dcd62826e 100644 --- a/app/logical/post_sets/base.rb +++ b/app/logical/post_sets/base.rb @@ -13,6 +13,7 @@ module PostSets end def artist + nil end def is_single_tag? diff --git a/app/logical/post_sets/popular.rb b/app/logical/post_sets/popular.rb new file mode 100644 index 000000000..30550cd6c --- /dev/null +++ b/app/logical/post_sets/popular.rb @@ -0,0 +1,48 @@ +module PostSets + class Popular < Base + attr_reader :date, :scale + + def initialize(date, scale) + @date = date.blank? ? Date.today : date.to_date + @scale = scale + end + + def posts + ::Post.where("created_at between ? and ?", min_date, max_date + 1).order("score desc").limit(limit) + end + + def limit + 25 + end + + def min_date + case scale + when "week" + date.beginning_of_week + + when "month" + date.beginning_of_month + + else + date + end + end + + def max_date + case scale + when "week" + date.end_of_week + + when "month" + date.end_of_month + + else + date + end + end + + def presenter + ::PostSetPresenters::Popular.new(self) + end + end +end diff --git a/app/models/comment.rb b/app/models/comment.rb index ef2bafdd5..2d2658faa 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -13,6 +13,7 @@ class Comment < ActiveRecord::Base scope :body_matches, lambda {|query| where("body_index @@ plainto_tsquery(?)", query).order("comments.id DESC")} scope :hidden, lambda {|user| where("score < ?", user.comment_threshold)} scope :post_tag_match, lambda {|query| joins(:post).where("posts.tag_index @@ to_tsquery('danbooru', ?)", query)} + scope :for_user, lambda {|user_id| where("creator_id = ?", user_id)} search_methods :body_matches, :post_tag_match diff --git a/app/models/favorite.rb b/app/models/favorite.rb index c267ce368..4798cb231 100644 --- a/app/models/favorite.rb +++ b/app/models/favorite.rb @@ -1,4 +1,4 @@ class Favorite < ActiveRecord::Base belongs_to :post - scope :for_user, lambda {|user_id| where("user_id = ?", user_id)} + scope :for_user, lambda {|user_id| where("user_id = #{user_id}")} end diff --git a/app/models/forum_post.rb b/app/models/forum_post.rb index 9002ec4aa..f82238bba 100644 --- a/app/models/forum_post.rb +++ b/app/models/forum_post.rb @@ -8,6 +8,7 @@ class ForumPost < ActiveRecord::Base validates_presence_of :body, :creator_id validate :validate_topic_is_unlocked scope :body_matches, lambda {|body| where(["text_index @@ plainto_tsquery(?)", body])} + scope :for_user, lambda {|user_id| where("creator_id = ?", user_id)} search_methods :body_matches def self.new_reply(params) diff --git a/app/models/note_version.rb b/app/models/note_version.rb index c08d8dfb3..96076ac5e 100644 --- a/app/models/note_version.rb +++ b/app/models/note_version.rb @@ -1,6 +1,7 @@ class NoteVersion < ActiveRecord::Base before_validation :initialize_updater belongs_to :updater, :class_name => "User" + scope :for_user, lambda {|user_id| where("updater_id = ?", user_id)} def initialize_updater self.updater_id = CurrentUser.id diff --git a/app/models/pool_version.rb b/app/models/pool_version.rb index ef8712566..c85cf236e 100644 --- a/app/models/pool_version.rb +++ b/app/models/pool_version.rb @@ -5,6 +5,7 @@ class PoolVersion < ActiveRecord::Base belongs_to :pool belongs_to :updater, :class_name => "User" before_validation :initialize_updater + scope :for_user, lambda {|user_id| where("updater_id = ?", user_id)} def initialize_updater self.updater_id = CurrentUser.id diff --git a/app/models/post.rb b/app/models/post.rb index bac987551..d9e82f357 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -41,6 +41,7 @@ class Post < ActiveRecord::Base scope :available_for_moderation, lambda {where(["id NOT IN (SELECT pd.post_id FROM post_disapprovals pd WHERE pd.user_id = ?)", CurrentUser.id])} scope :hidden_from_moderation, lambda {where(["id IN (SELECT pd.post_id FROM post_disapprovals pd WHERE pd.user_id = ?)", CurrentUser.id])} scope :tag_match, lambda {|query| Post.tag_match_helper(query)} + scope :exact_tag_match, lambda {|query| Post.exact_tag_match_helper(query)} scope :positive, where("score > 1") scope :negative, where("score < -1") search_methods :tag_match @@ -487,20 +488,29 @@ class Post < ActiveRecord::Base def add_tag_subscription_relation(subscriptions, relation) subscriptions.each do |subscription| - subscription =~ /^(.+?):(.+)$/ - user_name = $1 || subscription - subscription_name = $2 - - user = User.find_by_name(user_name) - - if user + if subscription =~ /^(.+?):(.+)$/ + user_name = $1 + subscription_name = $2 + user = User.find_by_name(user_name) + return relation if user.nil? post_ids = TagSubscription.find_post_ids(user.id, subscription_name) - relation = relation.where(["posts.id IN (?)", post_ids]) + else + user = User.find_by_name(subscription) + return relation if user.nil? + post_ids = TagSubscription.find_post_ids(user.id) end + + post_ids = [0] if post_ids.empty? + relation = relation.where(["posts.id IN (?)", post_ids]) end relation end + + def exact_tag_match_helper(q) + arel = Post.scoped + add_tag_string_search_relation({:related => [q].flatten, :include => [], :exclude => []}, arel) + end def tag_match_helper(q) unless q.is_a?(Hash) @@ -576,6 +586,10 @@ class Post < ActiveRecord::Base relation = relation.where("posts.rating <> 'e'") end + if q[:order] == "rank" + relation = relation.where("p.score > 0 and p.created_at >= ?", 0, 3.days.ago) + end + case q[:order] when "id", "id_asc" relation = relation.order("posts.id") @@ -609,6 +623,9 @@ class Post < ActiveRecord::Base when "filesize_asc" relation = relation.order("posts.file_size") + when "rank" + sql << " ORDER BY log(3, p.score) + (extract(epoch from p.created_at) - extract(epoch from timestamp '2005-05-24')) / 45000 DESC" + else relation = relation.order("posts.id DESC") end diff --git a/app/models/post_version.rb b/app/models/post_version.rb index 4c2ab2fac..0a99660fb 100644 --- a/app/models/post_version.rb +++ b/app/models/post_version.rb @@ -2,6 +2,7 @@ class PostVersion < ActiveRecord::Base belongs_to :post belongs_to :updater, :class_name => "User" before_validation :initialize_updater + scope :for_user, lambda {|user_id| where("updater_id = ?", user_id)} def self.create_from_post(post) if post.created_at == post.updated_at diff --git a/app/models/tag.rb b/app/models/tag.rb index ee6d65cf6..9282780ba 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -106,21 +106,7 @@ class Tag < ActiveRecord::Base m.extend(ClassMethods) end end - - module UpdateMethods - def mass_edit(start_tags, result_tags, updater_id, updater_ip_addr) - updater = User.find(updater_id) - Post.tag_match(start_tags).each do |p| - start = TagAlias.to_aliased(scan_tags(start_tags)) - result = TagAlias.to_aliased(scan_tags(result_tags)) - tags = (p.tag_array - start + result).join(" ") - CurrentUser.scoped(updater, updater_ip_addr) do - p.update_attributes(:tag_string => tags) - end - end - end - end - + module ParseMethods def normalize(query) query.to_s.downcase.strip @@ -323,12 +309,13 @@ class Tag < ActiveRecord::Base module RelationMethods def update_related + return unless should_update_related? counts = RelatedTagCalculator.calculate_from_sample(Danbooru.config.post_sample_size, name) - self.related_tags = RelatedTagCalculator.convert_hash_to_string(counts) + update_attributes(:related_tags => RelatedTagCalculator.convert_hash_to_string(counts), :related_tags_updated_at => Time.now) end def update_related_if_outdated - updated_related if should_update_related? + delay.update_related if should_update_related? end def related_cache_expiry @@ -345,7 +332,8 @@ class Tag < ActiveRecord::Base end def related_tag_array - related_tags.split(/ /).in_groups_of(2) + update_related_if_outdated + related_tags.to_s.split(/ /).in_groups_of(2) end end @@ -367,7 +355,7 @@ class Tag < ActiveRecord::Base include CategoryMethods extend StatisticsMethods include NameMethods - extend UpdateMethods extend ParseMethods + include RelationMethods extend SuggestionMethods end diff --git a/app/models/tag_alias.rb b/app/models/tag_alias.rb index 4eefe90fb..786c87951 100644 --- a/app/models/tag_alias.rb +++ b/app/models/tag_alias.rb @@ -1,11 +1,7 @@ class TagAlias < ActiveRecord::Base - attr_accessor :creator_ip_addr - after_save :update_posts - after_save :clear_cache - after_save :clear_remote_cache + before_save :clear_all_cache after_save :update_cache - after_destroy :clear_cache - after_destroy :clear_remote_cache + after_destroy :clear_all_cache before_validation :initialize_creator, :on => :create validates_presence_of :creator_id validates_uniqueness_of :antecedent_name @@ -25,8 +21,17 @@ class TagAlias < ActiveRecord::Base alias_hash.values.flatten.uniq end + def process! + update_column(:status, "processing") + update_posts + update_column(:status, "active") + rescue Exception => e + update_column(:status, "error: #{e}") + end + def initialize_creator self.creator_id = CurrentUser.user.id + self.creator_ip_addr = CurrentUser.ip_addr end def antecedent_tag @@ -44,6 +49,11 @@ class TagAlias < ActiveRecord::Base false end end + + def clear_all_cache + clear_cache + clear_remote_cache + end def clear_cache Cache.delete("ta:#{Cache.sanitize(antecedent_name)}") @@ -60,13 +70,15 @@ class TagAlias < ActiveRecord::Base end def update_posts - Post.tag_match(antecedent_name).find_each do |post| + Post.exact_tag_match(antecedent_name).find_each do |post| escaped_antecedent_name = Regexp.escape(antecedent_name) fixed_tags = post.tag_string.sub(/(?:\A| )#{escaped_antecedent_name}(?:\Z| )/, " #{consequent_name} ").strip - post.update_attributes( - :tag_string => fixed_tags - ) + CurrentUser.scoped(creator, creator_ip_addr) do + post.update_attributes( + :tag_string => fixed_tags + ) + end end end end diff --git a/app/models/tag_implication.rb b/app/models/tag_implication.rb index 17966d9e8..e0a497f87 100644 --- a/app/models/tag_implication.rb +++ b/app/models/tag_implication.rb @@ -1,43 +1,19 @@ class TagImplication < ActiveRecord::Base - before_save :clear_cache before_save :update_descendant_names after_save :update_descendant_names_for_parent - after_save :update_cache - after_save :update_posts - after_destroy :clear_cache - after_destroy :clear_remote_cache belongs_to :creator, :class_name => "User" before_validation :initialize_creator, :on => :create validates_presence_of :creator_id validates_uniqueness_of :antecedent_name, :scope => :consequent_name validate :absence_of_circular_relation - - module CacheMethods - def clear_cache - Cache.delete("ti:#{Cache.sanitize(antecedent_name)}") - @descendants = nil - end - - def clear_remote_cache - Danbooru.config.other_server_hosts.each do |server| - Net::HTTP.delete(URI.parse("http://#{server}/tag_implications/#{id}/cache")) - end - end - - def update_cache - descendant_names_array - true - end - end module DescendantMethods extend ActiveSupport::Concern module ClassMethods + # assumes names are normalized def with_descendants(names) - names + Cache.get_multi(names.flatten, "ti") do |name| - ([name] + where(["antecedent_name = ?", name]).all.map {|x| x.descendant_names_array}).flatten - end.values.flatten.uniq + (names + where("antecedent_name in (?)", names).map(&:descendant_names_array)).flatten.uniq end end @@ -55,9 +31,7 @@ class TagImplication < ActiveRecord::Base end def descendant_names_array - Cache.get("ti:#{Cache.sanitize(antecedent_name)}") do - descendant_names.split(/ /) - end + descendant_names.split(/ /) end def update_descendant_names @@ -93,12 +67,20 @@ class TagImplication < ActiveRecord::Base end end - include CacheMethods include DescendantMethods include ParentMethods def initialize_creator self.creator_id = CurrentUser.user.id + self.creator_ip_addr = CurrentUser.ip_addr + end + + def process! + update_column(:status, "processing") + update_posts + update_column(:status, "active") + rescue Exception => e + update_column(:status, "error: #{e}") end def absence_of_circular_relation @@ -110,12 +92,14 @@ class TagImplication < ActiveRecord::Base end def update_posts - Post.tag_match(antecedent_name).find_each do |post| + Post.exact_tag_match(antecedent_name).find_each do |post| escaped_antecedent_name = Regexp.escape(antecedent_name) fixed_tags = post.tag_string.sub(/(?:\A| )#{escaped_antecedent_name}(?:\Z| )/, " #{antecedent_name} #{descendant_names} ").strip - post.update_attributes( - :tag_string => fixed_tags - ) + CurrentUser.scoped(creator, creator_ip_addr) do + post.update_attributes( + :tag_string => fixed_tags + ) + end end end diff --git a/app/models/user_feedback.rb b/app/models/user_feedback.rb index e10cba48a..ada74f13b 100644 --- a/app/models/user_feedback.rb +++ b/app/models/user_feedback.rb @@ -9,6 +9,7 @@ class UserFeedback < ActiveRecord::Base scope :positive, where("category = ?", "positive") scope :neutral, where("category = ?", "neutral") scope :negative, where("category = ?", "negative") + scope :for_user, lambda {|user_id| where("user_id = ?", user_id)} def initialize_creator self.creator_id = CurrentUser.id diff --git a/app/models/wiki_page_version.rb b/app/models/wiki_page_version.rb index 00a5cb728..75e5f01fa 100644 --- a/app/models/wiki_page_version.rb +++ b/app/models/wiki_page_version.rb @@ -1,6 +1,7 @@ class WikiPageVersion < ActiveRecord::Base belongs_to :wiki_page belongs_to :updater, :class_name => "User" + scope :for_user, lambda {|user_id| where("updater_id = ?", user_id)} def updater_name User.id_to_name(updater_id) diff --git a/app/presenters/post_set_presenters/base.rb b/app/presenters/post_set_presenters/base.rb new file mode 100644 index 000000000..b2b93dcf6 --- /dev/null +++ b/app/presenters/post_set_presenters/base.rb @@ -0,0 +1,21 @@ +module PostSetPresenters + class Base + def posts + raise NotImplementedError + end + + def post_previews_html(template) + html = "" + + if posts.empty? + return template.render(:partial => "post_sets/blank") + end + + posts.each do |post| + html << PostPresenter.preview(post) + end + + html.html_safe + end + end +end diff --git a/app/presenters/post_set_presenters/favorite.rb b/app/presenters/post_set_presenters/favorite.rb index 1b7fb7b5e..b9a97fd4f 100644 --- a/app/presenters/post_set_presenters/favorite.rb +++ b/app/presenters/post_set_presenters/favorite.rb @@ -1,7 +1,7 @@ module PostSetPresenters - class Favorite + class Favorite < Base attr_accessor :favorite_set, :tag_set_presenter - delegate :favorites, :posts, :to => :favorite_set + delegate :favorites, :to => :favorite_set def initialize(favorite_set) @favorite_set = favorite_set @@ -15,19 +15,9 @@ module PostSetPresenters def tag_list_html(template) tag_set_presenter.tag_list_html(template) end - - def post_previews_html(template) - html = "" - - if favorites.empty? - return template.render(:partial => "post_sets/blank") - end - - favorites.each do |favorite| - html << PostPresenter.preview(favorite.post) - end - - html.html_safe + + def posts + favorites.map(&:post) end end end diff --git a/app/presenters/post_set_presenters/pool.rb b/app/presenters/post_set_presenters/pool.rb index fbb805748..c16270f80 100644 --- a/app/presenters/post_set_presenters/pool.rb +++ b/app/presenters/post_set_presenters/pool.rb @@ -1,12 +1,13 @@ module PostSetPresenters - class Pool - attr_reader :tag_set_presenter, :pool_set + class Pool < Base + attr_reader :tag_set_presenter, :post_set + delegate :posts, :to => :post_set - def initialize(pool_set) - @pool_set = pool_set + def initialize(post_set) + @post_set = post_set @tag_set_presenter = TagSetPresenter.new( RelatedTagCalculator.calculate_from_sample_to_array( - pool_set.tag_string + post_set.tag_string ).map {|x| x[0]} ) end @@ -14,19 +15,5 @@ module PostSetPresenters def tag_list_html(template) tag_set_presenter.tag_list_html(template) end - - def post_previews_html(template) - html = "" - - if pool_set.posts.empty? - return template.render(:partial => "post_sets/blank") - end - - pool_set.posts.each do |post| - html << PostPresenter.preview(post) - end - - html.html_safe - end end end diff --git a/app/presenters/post_set_presenters/popular.rb b/app/presenters/post_set_presenters/popular.rb new file mode 100644 index 000000000..0d58f5e19 --- /dev/null +++ b/app/presenters/post_set_presenters/popular.rb @@ -0,0 +1,34 @@ +module PostSetPresenters + class Popular < Base + attr_accessor :post_set, :tag_set_presenter + delegate :posts, :date, :to => :post_set + + def initialize(post_set) + @post_set = post_set + end + + def prev_day + date - 1 + end + + def next_day + date + 1 + end + + def prev_week + date - 7 + end + + def next_week + date + 7 + end + + def prev_month + 1.month.ago(date) + end + + def next_month + 1.month.since(date) + end + end +end diff --git a/app/presenters/post_set_presenters/post.rb b/app/presenters/post_set_presenters/post.rb index 0ce1a4b46..1a5b0351c 100644 --- a/app/presenters/post_set_presenters/post.rb +++ b/app/presenters/post_set_presenters/post.rb @@ -1,36 +1,26 @@ module PostSetPresenters - class Post + class Post < Base attr_accessor :post_set, :tag_set_presenter + delegate :posts, :to => :post_set def initialize(post_set) @post_set = post_set - @tag_set_presenter = TagSetPresenter.new( - RelatedTagCalculator.calculate_from_sample_to_array( - post_set.tag_string - ).map {|x| x[0]} - ) + @tag_set_presenter = TagSetPresenter.new(related_tags) end - - def posts - post_set.posts + + def related_tags + if post_set.is_single_tag? + tag = Tag.find_by_name(post_set.tag_string) + if tag + return tag.related_tag_array.map(&:first) + end + end + + RelatedTagCalculator.calculate_from_sample_to_array(post_set.tag_string).map(&:first) end def tag_list_html(template) tag_set_presenter.tag_list_html(template) end - - def post_previews_html(template) - html = "" - - if posts.empty? - return template.render(:partial => "post_sets/blank") - end - - posts.each do |post| - html << PostPresenter.preview(post) - end - - html.html_safe - end end end diff --git a/app/presenters/user_presenter.rb b/app/presenters/user_presenter.rb new file mode 100644 index 000000000..9b3fb0e64 --- /dev/null +++ b/app/presenters/user_presenter.rb @@ -0,0 +1,107 @@ +class UserPresenter + attr_reader :user + + def initialize(user) + @user = user + end + + def level + user.level_string + end + + def ban_reason + if user.is_banned? + "#{user.ban.reason}; expires #{user.ban.expires_at}" + else + nil + end + end + + def tag_subscriptions(template) + user.subscriptions.map do |subscription| + template.link_to(subscription.name, template.tag_subscription_path(subscription)) + end.join("; ") + end + + def upload_limit + deleted_count = Post.for_user(user.id).deleted.count + pending_count = Post.for_user(user.id).pending.count + approved_count = Post.where("is_flagged = false and is_pending = false and user_id = ?", user.id).count + + if user.base_upload_limit + limit = user.base_upload_limit - pending_count + string = "base:#{user.base_upload_limit} - pending:#{pending_count}" + else + limit = 10 + (approved_count / 10) - (deleted_count / 4) - pending_count + string = "base:10 + approved:(#{approved_count} / 10) - deleted:(#{deleted_count}) / 4 - pending:#{pending_count}" + end + + if limit > 20 + limit = 20 + string += " = capped:20" + elsif limit < 0 + limit = 0 + string += " = capped:0" + else + string += " = #{limit}" + end + + return string + end + + def uploads(template) + template.link_to(Post.for_user(user.id).count, template.posts_path(:tags => "uploader:#{user.name}")) + end + + def deleted_uploads(template) + template.link_to(Post.for_user(user.id).deleted.count, template.posts_path(:tags => "status:deleted uploader:#{user.name}")) + end + + def favorites(template) + template.link_to(Favorite.for_user(user.id).count, template.favorites_path(:user_id => user.id)) + end + + def comments(template) + template.link_to(Comment.for_user(user.id).count, template.comments_path(:search => {:creator_id_eq => user.id})) + end + + def post_versions(template) + template.link_to(PostVersion.for_user(user.id).count, template.post_versions_path(:search => {:updater_id_eq => user.id})) + end + + def note_versions(template) + template.link_to(NoteVersion.for_user(user.id).count, template.note_versions_path(:search => {:updater_id_eq => user.id})) + end + + def wiki_page_versions(template) + template.link_to(WikiPageVersion.for_user(user.id).count, template.wiki_page_versions_path(:search => {:updater_id_eq => user.id})) + end + + def forum_posts(template) + template.link_to(ForumPost.for_user(user.id).count, template.forum_posts_path(:search => {:creator_id_eq => user.id})) + end + + def pool_versions(template) + template.link_to(PoolVersion.for_user(user.id).count, template.pool_versions_path(:search => {:updater_id_eq => user.id})) + end + + def inviter(template) + if user.inviter_id + template.link_to(user.inviter.name, template.user_path(user.inviter_id)) + else + nil + end + end + + def approvals(template) + template.link_to(Post.where("approver_id = ?", user.id).count, template.posts_path(:tags => "approver:#{user.name}")) + end + + def feedbacks(template) + positive = UserFeedback.for_user(user.id).positive.count + neutral = UserFeedback.for_user(user.id).neutral.count + negative = UserFeedback.for_user(user.id).negative.count + + template.link_to("positive:#{positive} neutral:#{neutral} negative:#{negative}", user_feedbacks_path(:search => {:user_id_rq => user.id})) + end +end diff --git a/app/views/admin/tags/edit.html.erb b/app/views/admin/tags/edit.html.erb deleted file mode 100644 index 17f42b5b8..000000000 --- a/app/views/admin/tags/edit.html.erb +++ /dev/null @@ -1,10 +0,0 @@ -<% form_tag({:action => "mass_edit"}, :onsubmit => "return confirm('Are you sure you wish to perform this tag edit?')") do %> - <%= text_field_tag "start", params[:source], :size => 60 %> - <%= text_field_tag "result", params[:name], :size => 60 %> - <%= button_to_function "Preview", "$('preview').innerHTML = 'Loading...'; new Ajax.Updater('preview', '#{url_for(:controller=>"tag",:action=>"edit_preview")}', {method: 'get', parameters: 'tags=' + $F('start')})" %><%= submit_tag "Save" %> -<% end %> - -<%= render :partial => "footer" %> - -
    -
    diff --git a/app/views/explore/posts/_date_explore.html.erb b/app/views/explore/posts/_date_explore.html.erb new file mode 100644 index 000000000..b391930dc --- /dev/null +++ b/app/views/explore/posts/_date_explore.html.erb @@ -0,0 +1,19 @@ + diff --git a/app/views/explore/posts/popular.html.erb b/app/views/explore/posts/popular.html.erb new file mode 100644 index 000000000..28440a27f --- /dev/null +++ b/app/views/explore/posts/popular.html.erb @@ -0,0 +1,14 @@ +
    +
    +

    Explore: <%= @post_set.min_date %> – <%= @post_set.max_date %>

    + + <%= render "date_explore", :post_set => @post_set %> + +
    + <%= @post_set.presenter.post_previews_html(self) %> +
    +
    +
    + +<%= render "posts/partials/common/secondary_links" %> + diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb index 1ed3e6d0e..edc54c776 100644 --- a/app/views/layouts/default.html.erb +++ b/app/views/layouts/default.html.erb @@ -22,6 +22,8 @@
    + <%= render "news/listing" %> +

    <%= link_to Danbooru.config.app_name, "/" %>