Merge branch 'master' into attribute-searching
This commit is contained in:
46
Gemfile.lock
46
Gemfile.lock
@@ -77,8 +77,8 @@ GEM
|
|||||||
ansi (1.5.0)
|
ansi (1.5.0)
|
||||||
ast (2.4.1)
|
ast (2.4.1)
|
||||||
aws-eventstream (1.1.0)
|
aws-eventstream (1.1.0)
|
||||||
aws-partitions (1.341.0)
|
aws-partitions (1.356.0)
|
||||||
aws-sdk-core (3.103.0)
|
aws-sdk-core (3.104.3)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.239.0)
|
aws-partitions (~> 1, >= 1.239.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
@@ -86,10 +86,10 @@ GEM
|
|||||||
aws-sdk-sqs (1.30.0)
|
aws-sdk-sqs (1.30.0)
|
||||||
aws-sdk-core (~> 3, >= 3.99.0)
|
aws-sdk-core (~> 3, >= 3.99.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sigv4 (1.2.1)
|
aws-sigv4 (1.2.2)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
bcrypt (3.1.13)
|
bcrypt (3.1.15)
|
||||||
bootsnap (1.4.6)
|
bootsnap (1.4.8)
|
||||||
msgpack (~> 1.0)
|
msgpack (~> 1.0)
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
byebug (11.1.3)
|
byebug (11.1.3)
|
||||||
@@ -98,7 +98,7 @@ GEM
|
|||||||
i18n
|
i18n
|
||||||
rake (>= 10.0.0)
|
rake (>= 10.0.0)
|
||||||
sshkit (>= 1.9.0)
|
sshkit (>= 1.9.0)
|
||||||
capistrano-bundler (2.0.0)
|
capistrano-bundler (2.0.1)
|
||||||
capistrano (~> 3.1)
|
capistrano (~> 3.1)
|
||||||
capistrano-deploytags (1.0.7)
|
capistrano-deploytags (1.0.7)
|
||||||
capistrano (>= 3.7.0)
|
capistrano (>= 3.7.0)
|
||||||
@@ -120,13 +120,13 @@ GEM
|
|||||||
xpath (~> 3.2)
|
xpath (~> 3.2)
|
||||||
childprocess (3.0.0)
|
childprocess (3.0.0)
|
||||||
chronic (0.10.2)
|
chronic (0.10.2)
|
||||||
codecov (0.2.0)
|
codecov (0.2.5)
|
||||||
colorize
|
colorize
|
||||||
json
|
json
|
||||||
simplecov
|
simplecov
|
||||||
coderay (1.1.3)
|
coderay (1.1.3)
|
||||||
colorize (0.8.1)
|
colorize (0.8.1)
|
||||||
concurrent-ruby (1.1.6)
|
concurrent-ruby (1.1.7)
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
daemons (1.3.1)
|
daemons (1.3.1)
|
||||||
delayed_job (4.1.8)
|
delayed_job (4.1.8)
|
||||||
@@ -147,7 +147,7 @@ GEM
|
|||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
faraday (1.0.1)
|
faraday (1.0.1)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
ffaker (2.15.0)
|
ffaker (2.16.0)
|
||||||
ffi (1.13.1)
|
ffi (1.13.1)
|
||||||
ffi-compiler (1.0.1)
|
ffi-compiler (1.0.1)
|
||||||
ffi (>= 1.0.0)
|
ffi (>= 1.0.0)
|
||||||
@@ -167,7 +167,7 @@ GEM
|
|||||||
http-form_data (2.3.0)
|
http-form_data (2.3.0)
|
||||||
http-parser (1.2.1)
|
http-parser (1.2.1)
|
||||||
ffi-compiler (>= 1.0, < 2.0)
|
ffi-compiler (>= 1.0, < 2.0)
|
||||||
i18n (1.8.3)
|
i18n (1.8.5)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
ipaddress_2 (0.13.0)
|
ipaddress_2 (0.13.0)
|
||||||
jmespath (1.4.0)
|
jmespath (1.4.0)
|
||||||
@@ -212,7 +212,7 @@ GEM
|
|||||||
net-sftp (3.0.0)
|
net-sftp (3.0.0)
|
||||||
net-ssh (>= 5.0.0, < 7.0.0)
|
net-ssh (>= 5.0.0, < 7.0.0)
|
||||||
net-ssh (6.1.0)
|
net-ssh (6.1.0)
|
||||||
newrelic_rpm (6.11.0.365)
|
newrelic_rpm (6.12.0.367)
|
||||||
nio4r (2.5.2)
|
nio4r (2.5.2)
|
||||||
nokogiri (1.10.10)
|
nokogiri (1.10.10)
|
||||||
mini_portile2 (~> 2.4.0)
|
mini_portile2 (~> 2.4.0)
|
||||||
@@ -244,7 +244,7 @@ GEM
|
|||||||
rack (2.2.3)
|
rack (2.2.3)
|
||||||
rack-contrib (2.2.0)
|
rack-contrib (2.2.0)
|
||||||
rack (~> 2.0)
|
rack (~> 2.0)
|
||||||
rack-mini-profiler (2.0.2)
|
rack-mini-profiler (2.0.4)
|
||||||
rack (>= 1.2.0)
|
rack (>= 1.2.0)
|
||||||
rack-proxy (0.6.5)
|
rack-proxy (0.6.5)
|
||||||
rack
|
rack
|
||||||
@@ -293,21 +293,21 @@ GEM
|
|||||||
actionpack (>= 5.0)
|
actionpack (>= 5.0)
|
||||||
railties (>= 5.0)
|
railties (>= 5.0)
|
||||||
rexml (3.2.4)
|
rexml (3.2.4)
|
||||||
rubocop (0.88.0)
|
rubocop (0.89.1)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 2.7.1.1)
|
parser (>= 2.7.1.1)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 1.7)
|
regexp_parser (>= 1.7)
|
||||||
rexml
|
rexml
|
||||||
rubocop-ast (>= 0.1.0, < 1.0)
|
rubocop-ast (>= 0.3.0, < 1.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 1.4.0, < 2.0)
|
unicode-display_width (>= 1.4.0, < 2.0)
|
||||||
rubocop-ast (0.1.0)
|
rubocop-ast (0.3.0)
|
||||||
parser (>= 2.7.0.1)
|
parser (>= 2.7.1.4)
|
||||||
rubocop-rails (2.6.0)
|
rubocop-rails (2.7.1)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
rubocop (>= 0.82.0)
|
rubocop (>= 0.87.0)
|
||||||
ruby-progressbar (1.10.1)
|
ruby-progressbar (1.10.1)
|
||||||
ruby-vips (2.0.17)
|
ruby-vips (2.0.17)
|
||||||
ffi (~> 1.9)
|
ffi (~> 1.9)
|
||||||
@@ -329,7 +329,7 @@ GEM
|
|||||||
simple_form (5.0.2)
|
simple_form (5.0.2)
|
||||||
actionpack (>= 5.0)
|
actionpack (>= 5.0)
|
||||||
activemodel (>= 5.0)
|
activemodel (>= 5.0)
|
||||||
simplecov (0.18.5)
|
simplecov (0.19.0)
|
||||||
docile (~> 1.1)
|
docile (~> 1.1)
|
||||||
simplecov-html (~> 0.11)
|
simplecov-html (~> 0.11)
|
||||||
simplecov-html (0.12.2)
|
simplecov-html (0.12.2)
|
||||||
@@ -346,7 +346,7 @@ GEM
|
|||||||
stackprof (0.2.15)
|
stackprof (0.2.15)
|
||||||
streamio-ffmpeg (3.0.2)
|
streamio-ffmpeg (3.0.2)
|
||||||
multi_json (~> 1.8)
|
multi_json (~> 1.8)
|
||||||
stripe (5.22.0)
|
stripe (5.23.1)
|
||||||
thor (1.0.1)
|
thor (1.0.1)
|
||||||
thread_safe (0.3.6)
|
thread_safe (0.3.6)
|
||||||
tzinfo (1.2.7)
|
tzinfo (1.2.7)
|
||||||
@@ -355,13 +355,13 @@ GEM
|
|||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.7.7)
|
unf_ext (0.0.7.7)
|
||||||
unicode-display_width (1.7.0)
|
unicode-display_width (1.7.0)
|
||||||
unicorn (5.5.5)
|
unicorn (5.6.0)
|
||||||
kgio (~> 2.6)
|
kgio (~> 2.6)
|
||||||
raindrops (~> 0.7)
|
raindrops (~> 0.7)
|
||||||
unicorn-worker-killer (0.4.4)
|
unicorn-worker-killer (0.4.4)
|
||||||
get_process_mem (~> 0)
|
get_process_mem (~> 0)
|
||||||
unicorn (>= 4, < 6)
|
unicorn (>= 4, < 6)
|
||||||
webpacker (5.1.1)
|
webpacker (5.2.0)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
rack-proxy (>= 0.6.1)
|
rack-proxy (>= 0.6.1)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
@@ -373,7 +373,7 @@ GEM
|
|||||||
chronic (>= 0.6.3)
|
chronic (>= 0.6.3)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
zeitwerk (2.3.1)
|
zeitwerk (2.4.0)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class EmailsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
if @user.errors.none?
|
if @user.errors.none?
|
||||||
flash[:notice] = "Email updated"
|
flash[:notice] = "Email updated. Check your email to confirm your new address"
|
||||||
UserMailer.email_change_confirmation(@user).deliver_later
|
UserMailer.email_change_confirmation(@user).deliver_later
|
||||||
respond_with(@user, location: settings_url)
|
respond_with(@user, location: settings_url)
|
||||||
else
|
else
|
||||||
@@ -31,10 +31,27 @@ class EmailsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def verify
|
def verify
|
||||||
@email_address = authorize EmailAddress.find_by_user_id!(params[:user_id])
|
@user = User.find(params[:user_id])
|
||||||
@email_address.update!(is_verified: true)
|
@email_address = @user.email_address
|
||||||
|
|
||||||
flash[:notice] = "Email address verified"
|
if @email_address.blank?
|
||||||
redirect_to @email_address.user
|
redirect_to edit_user_email_path(@user)
|
||||||
|
elsif params[:email_verification_key].present?
|
||||||
|
authorize @email_address
|
||||||
|
@email_address.update!(is_verified: true)
|
||||||
|
flash[:notice] = "Email address verified"
|
||||||
|
redirect_to @email_address.user
|
||||||
|
else
|
||||||
|
authorize @email_address
|
||||||
|
respond_with(@user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_confirmation
|
||||||
|
@user = authorize User.find(params[:user_id]), policy_class: EmailAddressPolicy
|
||||||
|
UserMailer.welcome_user(@user).deliver_later
|
||||||
|
|
||||||
|
flash[:notice] = "Confirmation email sent to #{@user.email_address.address}. Check your email to confirm your address"
|
||||||
|
redirect_to @user
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,18 +4,6 @@ module Moderator
|
|||||||
skip_before_action :api_check
|
skip_before_action :api_check
|
||||||
respond_to :html, :json, :xml, :js
|
respond_to :html, :json, :xml, :js
|
||||||
|
|
||||||
def confirm_delete
|
|
||||||
@post = ::Post.find(params[:id])
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete
|
|
||||||
@post = authorize ::Post.find(params[:id])
|
|
||||||
if params[:commit] == "Delete"
|
|
||||||
@post.delete!(params[:reason], :move_favorites => params[:move_favorites].present?)
|
|
||||||
end
|
|
||||||
redirect_to(post_path(@post))
|
|
||||||
end
|
|
||||||
|
|
||||||
def confirm_move_favorites
|
def confirm_move_favorites
|
||||||
@post = ::Post.find(params[:id])
|
@post = ::Post.find(params[:id])
|
||||||
end
|
end
|
||||||
@@ -44,7 +32,7 @@ module Moderator
|
|||||||
def unban
|
def unban
|
||||||
@post = authorize ::Post.find(params[:id])
|
@post = authorize ::Post.find(params[:id])
|
||||||
@post.unban!
|
@post.unban!
|
||||||
flash[:notice] = "Post was banned"
|
flash[:notice] = "Post was unbanned"
|
||||||
|
|
||||||
respond_with(@post)
|
respond_with(@post)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,14 +4,15 @@ class ModqueueController < ApplicationController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
authorize :modqueue
|
authorize :modqueue
|
||||||
@posts = Post.includes(:appeals, :disapprovals, :uploader, flags: [:creator]).pending_or_flagged.available_for_moderation(CurrentUser.user, hidden: search_params[:hidden])
|
@posts = Post.includes(:appeals, :disapprovals, :uploader, flags: [:creator]).in_modqueue.available_for_moderation(CurrentUser.user, hidden: search_params[:hidden])
|
||||||
@posts = @posts.paginated_search(params, order: "modqueue", count_pages: true)
|
@modqueue_posts = @posts.reselect(nil).reorder(nil).offset(nil).limit(nil)
|
||||||
|
@posts = @posts.paginated_search(params, order: "modqueue", count_pages: true, count: @modqueue_posts.to_a.size)
|
||||||
|
|
||||||
@modqueue_posts = @posts.except(:offset, :limit, :order)
|
@pending_post_count = @modqueue_posts.select(&:is_pending?).count
|
||||||
@pending_post_count = @modqueue_posts.pending.count
|
@flagged_post_count = @modqueue_posts.select(&:is_flagged?).count
|
||||||
@flagged_post_count = @modqueue_posts.flagged.count
|
@appealed_post_count = @modqueue_posts.select(&:is_appealed?).count
|
||||||
@disapproval_reasons = PostDisapproval.where(post: @modqueue_posts).where.not(reason: "disinterest").group(:reason).order(count: :desc).distinct.count(:post_id)
|
@disapproval_reasons = PostDisapproval.where(post_id: @modqueue_posts.map(&:id)).where.not(reason: "disinterest").group(:reason).order(count: :desc).distinct.count(:post_id)
|
||||||
@uploaders = @modqueue_posts.group(:uploader).order(count: :desc).limit(20).count
|
@uploaders = @modqueue_posts.map(&:uploader).tally.sort_by(&:last).reverse.take(20).to_h
|
||||||
|
|
||||||
@tags = RelatedTagCalculator.frequent_tags_for_post_relation(@modqueue_posts)
|
@tags = RelatedTagCalculator.frequent_tags_for_post_relation(@modqueue_posts)
|
||||||
@artist_tags = @tags.select(&:artist?).sort_by(&:overlap_count).reverse.take(10)
|
@artist_tags = @tags.select(&:artist?).sort_by(&:overlap_count).reverse.take(10)
|
||||||
|
|||||||
@@ -56,6 +56,18 @@ class PostsController < ApplicationController
|
|||||||
respond_with_post_after_update(@post)
|
respond_with_post_after_update(@post)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@post = authorize Post.find(params[:id])
|
||||||
|
|
||||||
|
if params[:commit] == "Delete"
|
||||||
|
move_favorites = params.dig(:post, :move_favorites).to_s.truthy?
|
||||||
|
@post.delete!(params.dig(:post, :reason), move_favorites: move_favorites, user: CurrentUser.user)
|
||||||
|
flash[:notice] = "Post deleted"
|
||||||
|
end
|
||||||
|
|
||||||
|
respond_with_post_after_update(@post)
|
||||||
|
end
|
||||||
|
|
||||||
def revert
|
def revert
|
||||||
@post = authorize Post.find(params[:id])
|
@post = authorize Post.find(params[:id])
|
||||||
@version = @post.versions.find(params[:version_id])
|
@version = @post.versions.find(params[:version_id])
|
||||||
|
|||||||
@@ -16,14 +16,10 @@ require("jquery-ui/ui/effects/effect-shake");
|
|||||||
require("jquery-ui/ui/widgets/autocomplete");
|
require("jquery-ui/ui/widgets/autocomplete");
|
||||||
require("jquery-ui/ui/widgets/button");
|
require("jquery-ui/ui/widgets/button");
|
||||||
require("jquery-ui/ui/widgets/dialog");
|
require("jquery-ui/ui/widgets/dialog");
|
||||||
require("jquery-ui/ui/widgets/draggable");
|
|
||||||
require("jquery-ui/ui/widgets/resizable");
|
|
||||||
require("jquery-ui/themes/base/core.css");
|
require("jquery-ui/themes/base/core.css");
|
||||||
require("jquery-ui/themes/base/autocomplete.css");
|
require("jquery-ui/themes/base/autocomplete.css");
|
||||||
require("jquery-ui/themes/base/button.css");
|
require("jquery-ui/themes/base/button.css");
|
||||||
require("jquery-ui/themes/base/dialog.css");
|
require("jquery-ui/themes/base/dialog.css");
|
||||||
require("jquery-ui/themes/base/draggable.css");
|
|
||||||
require("jquery-ui/themes/base/resizable.css");
|
|
||||||
require("jquery-ui/themes/base/theme.css");
|
require("jquery-ui/themes/base/theme.css");
|
||||||
|
|
||||||
require("@fortawesome/fontawesome-free/css/fontawesome.css");
|
require("@fortawesome/fontawesome-free/css/fontawesome.css");
|
||||||
@@ -47,6 +43,7 @@ export { default as PostTooltip } from '../src/javascripts/post_tooltips.js';
|
|||||||
export { default as PostVersion } from '../src/javascripts/post_version.js';
|
export { default as PostVersion } from '../src/javascripts/post_version.js';
|
||||||
export { default as RelatedTag } from '../src/javascripts/related_tag.js';
|
export { default as RelatedTag } from '../src/javascripts/related_tag.js';
|
||||||
export { default as Shortcuts } from '../src/javascripts/shortcuts.js';
|
export { default as Shortcuts } from '../src/javascripts/shortcuts.js';
|
||||||
|
export { default as TagCounter } from '../src/javascripts/tag_counter.js';
|
||||||
export { default as Upload } from '../src/javascripts/uploads.js.erb';
|
export { default as Upload } from '../src/javascripts/uploads.js.erb';
|
||||||
export { default as UserTooltip } from '../src/javascripts/user_tooltips.js';
|
export { default as UserTooltip } from '../src/javascripts/user_tooltips.js';
|
||||||
export { default as Utility } from '../src/javascripts/utility.js';
|
export { default as Utility } from '../src/javascripts/utility.js';
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Autocomplete.ORDER_METATAGS = <%= PostQueryBuilder::ORDER_METATAGS.to_json.html_
|
|||||||
Autocomplete.DISAPPROVAL_REASONS = <%= PostDisapproval::REASONS.to_json.html_safe %>;
|
Autocomplete.DISAPPROVAL_REASONS = <%= PostDisapproval::REASONS.to_json.html_safe %>;
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
|
||||||
Autocomplete.MISC_STATUSES = ["deleted", "active", "pending", "flagged", "banned", "modqueue", "unmoderated"];
|
Autocomplete.MISC_STATUSES = ["deleted", "active", "pending", "flagged", "banned", "modqueue", "unmoderated", "appealed"];
|
||||||
Autocomplete.TAG_PREFIXES = "-|~|" + Object.keys(Autocomplete.TAG_CATEGORIES).map(category => category + ":").join("|");
|
Autocomplete.TAG_PREFIXES = "-|~|" + Object.keys(Autocomplete.TAG_CATEGORIES).map(category => category + ":").join("|");
|
||||||
Autocomplete.METATAGS_REGEX = Autocomplete.METATAGS.concat(Object.keys(Autocomplete.TAG_CATEGORIES)).join("|");
|
Autocomplete.METATAGS_REGEX = Autocomplete.METATAGS.concat(Object.keys(Autocomplete.TAG_CATEGORIES)).join("|");
|
||||||
Autocomplete.TERM_REGEX = new RegExp(`([-~]*)(?:(${Autocomplete.METATAGS_REGEX}):)?(\\S*)$`, "i");
|
Autocomplete.TERM_REGEX = new RegExp(`([-~]*)(?:(${Autocomplete.METATAGS_REGEX}):)?(\\S*)$`, "i");
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ $(function() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#hide-verify-account-notice").on("click.danbooru", function(e) {
|
||||||
|
$("#verify-account-notice").hide();
|
||||||
|
Cookie.put('hide_verify_account_notice', '1', 3);
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
$("#close-notice-link").on("click.danbooru", function(e) {
|
$("#close-notice-link").on("click.danbooru", function(e) {
|
||||||
$('#notice').fadeOut("fast");
|
$('#notice').fadeOut("fast");
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,7 @@ PostTooltip.initialize = function () {
|
|||||||
|
|
||||||
delegate("body", {
|
delegate("body", {
|
||||||
allowHTML: true,
|
allowHTML: true,
|
||||||
appendTo: document.body,
|
appendTo: document.querySelector("#post-tooltips"),
|
||||||
delay: [PostTooltip.SHOW_DELAY, PostTooltip.HIDE_DELAY],
|
delay: [PostTooltip.SHOW_DELAY, PostTooltip.HIDE_DELAY],
|
||||||
duration: PostTooltip.DURATION,
|
duration: PostTooltip.DURATION,
|
||||||
interactive: true,
|
interactive: true,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Post.SWIPE_VELOCITY = 0.6;
|
|||||||
Post.MAX_RECOMMENDATIONS = 45; // 3 rows of 9 posts at 1920x1080.
|
Post.MAX_RECOMMENDATIONS = 45; // 3 rows of 9 posts at 1920x1080.
|
||||||
Post.LOW_TAG_COUNT = 10;
|
Post.LOW_TAG_COUNT = 10;
|
||||||
Post.HIGH_TAG_COUNT = 20;
|
Post.HIGH_TAG_COUNT = 20;
|
||||||
|
Post.EDIT_DIALOG_WIDTH = 720;
|
||||||
|
|
||||||
Post.initialize_all = function() {
|
Post.initialize_all = function() {
|
||||||
|
|
||||||
@@ -42,8 +43,6 @@ Post.initialize_all = function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var $fields_multiple = $('[data-autocomplete="tag-edit"]');
|
var $fields_multiple = $('[data-autocomplete="tag-edit"]');
|
||||||
$fields_multiple.on("keypress.danbooru", Post.update_tag_count);
|
|
||||||
$fields_multiple.on("click", Post.update_tag_count);
|
|
||||||
|
|
||||||
$(window).on('danbooru:initialize_saved_seraches', () => {
|
$(window).on('danbooru:initialize_saved_seraches', () => {
|
||||||
Post.initialize_saved_searches();
|
Post.initialize_saved_searches();
|
||||||
@@ -105,14 +104,13 @@ Post.open_edit_dialog = function() {
|
|||||||
$("#post-edit-link").parent("li").addClass("active");
|
$("#post-edit-link").parent("li").addClass("active");
|
||||||
|
|
||||||
var $tag_string = $("#post_tag_string,#upload_tag_string");
|
var $tag_string = $("#post_tag_string,#upload_tag_string");
|
||||||
$("div.input").has($tag_string).prevAll().hide();
|
|
||||||
$("#open-edit-dialog").hide();
|
$("#open-edit-dialog").hide();
|
||||||
|
|
||||||
var dialog = $("<div/>").attr("id", "edit-dialog");
|
var dialog = $("<div/>").attr("id", "edit-dialog");
|
||||||
$("#form").appendTo(dialog);
|
$("#form").appendTo(dialog);
|
||||||
dialog.dialog({
|
dialog.dialog({
|
||||||
title: "Edit tags",
|
title: "Edit tags",
|
||||||
width: $(window).width() * 0.6,
|
width: Post.EDIT_DIALOG_WIDTH,
|
||||||
position: {
|
position: {
|
||||||
my: "right",
|
my: "right",
|
||||||
at: "right-20",
|
at: "right-20",
|
||||||
@@ -307,10 +305,11 @@ Post.view_original = function(e = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var $image = $("#image");
|
var $image = $("#image");
|
||||||
|
var $post = $(".image-container");
|
||||||
$image.attr("src", $(".image-view-original-link").attr("href"));
|
$image.attr("src", $(".image-view-original-link").attr("href"));
|
||||||
$image.css("filter", "blur(8px)");
|
$image.css("filter", "blur(8px)");
|
||||||
$image.width($image.data("original-width"));
|
$image.width($post.data("width"));
|
||||||
$image.height($image.data("original-height"));
|
$image.height($post.data("height"));
|
||||||
$image.on("load.danbooru", function() {
|
$image.on("load.danbooru", function() {
|
||||||
$image.css("animation", "sharpen 0.5s forwards");
|
$image.css("animation", "sharpen 0.5s forwards");
|
||||||
});
|
});
|
||||||
@@ -326,10 +325,11 @@ Post.view_large = function(e = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var $image = $("#image");
|
var $image = $("#image");
|
||||||
|
var $post = $(".image-container");
|
||||||
$image.attr("src", $(".image-view-large-link").attr("href"));
|
$image.attr("src", $(".image-view-large-link").attr("href"));
|
||||||
$image.css("filter", "blur(8px)");
|
$image.css("filter", "blur(8px)");
|
||||||
$image.width($image.data("large-width"));
|
$image.width($post.data("large-width"));
|
||||||
$image.height($image.data("large-height"));
|
$image.height($post.data("large-height"));
|
||||||
$image.on("load.danbooru", function() {
|
$image.on("load.danbooru", function() {
|
||||||
$image.css("animation", "sharpen 0.5s forwards");
|
$image.css("animation", "sharpen 0.5s forwards");
|
||||||
});
|
});
|
||||||
@@ -400,7 +400,6 @@ Post.initialize_post_sections = function() {
|
|||||||
$("#post_tag_string").focus().selectEnd().height($("#post_tag_string")[0].scrollHeight);
|
$("#post_tag_string").focus().selectEnd().height($("#post_tag_string")[0].scrollHeight);
|
||||||
$("#recommended").hide();
|
$("#recommended").hide();
|
||||||
$(document).trigger("danbooru:open-post-edit-tab");
|
$(document).trigger("danbooru:open-post-edit-tab");
|
||||||
Post.update_tag_count({target: $("#post_tag_string")});
|
|
||||||
} else if (e.target.hash === "#recommended") {
|
} else if (e.target.hash === "#recommended") {
|
||||||
$("#comments").hide();
|
$("#comments").hide();
|
||||||
$("#edit").hide();
|
$("#edit").hide();
|
||||||
@@ -511,32 +510,6 @@ Post.initialize_recommended = function() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Post.update_tag_count = function(event) {
|
|
||||||
let string = "0 tags";
|
|
||||||
let count = 0;
|
|
||||||
|
|
||||||
if (event) {
|
|
||||||
let tags = Utility.regexp_split($(event.target).val());
|
|
||||||
if (tags.length) {
|
|
||||||
count = tags.length;
|
|
||||||
string = (count === 1) ? (count + " tag") : (count + " tags")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#tags-container .count").html(string);
|
|
||||||
let klass = "";
|
|
||||||
|
|
||||||
if (count < Post.LOW_TAG_COUNT) {
|
|
||||||
klass = "frown";
|
|
||||||
} else if (count >= Post.LOW_TAG_COUNT && count < Post.HIGH_TAG_COUNT) {
|
|
||||||
klass = "meh";
|
|
||||||
} else {
|
|
||||||
klass = "smile";
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#tags-container .options #face").removeClass().addClass(`far fa-${klass}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
Post.initialize_all();
|
Post.initialize_all();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import Uploads from './uploads.js.erb';
|
import Uploads from './uploads.js.erb';
|
||||||
import Utility from './utility';
|
import Utility from './utility';
|
||||||
import Post from './posts.js.erb';
|
|
||||||
|
|
||||||
let RelatedTag = {};
|
let RelatedTag = {};
|
||||||
|
|
||||||
@@ -121,7 +120,8 @@ RelatedTag.toggle_tag = function(e) {
|
|||||||
setTimeout(function () { $field.prop('selectionStart', $field.val().length);}, 100);
|
setTimeout(function () { $field.prop('selectionStart', $field.val().length);}, 100);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
Post.update_tag_count({ target: $field });
|
// Artificially trigger input event so the tag counter updates.
|
||||||
|
$field.trigger("input");
|
||||||
}
|
}
|
||||||
|
|
||||||
RelatedTag.show = function(e) {
|
RelatedTag.show = function(e) {
|
||||||
|
|||||||
49
app/javascript/src/javascripts/tag_counter.js
Normal file
49
app/javascript/src/javascripts/tag_counter.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { h, Component, render } from "preact";
|
||||||
|
import { observable, computed, action } from "mobx";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
|
||||||
|
import Utility from "./utility";
|
||||||
|
|
||||||
|
export default @observer class TagCounter extends Component {
|
||||||
|
static lowCount = 10;
|
||||||
|
static highCount = 20;
|
||||||
|
|
||||||
|
@observable tagCount = 0;
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
$(this.props.tags).on("input", this.updateCount);
|
||||||
|
this.updateCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<span class="tag-counter">
|
||||||
|
<span class="tag-count">{this.tagCount}</span> / {TagCounter.highCount} tags
|
||||||
|
<img src={`/images/${this.iconName}.png`}/>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action.bound updateCount() {
|
||||||
|
this.tagCount = Utility.regexp_split($(this.props.tags).val()).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get iconName() {
|
||||||
|
if (this.tagCount < TagCounter.lowCount) {
|
||||||
|
return "blobglare";
|
||||||
|
} else if (this.tagCount >= TagCounter.lowCount && this.tagCount < TagCounter.highCount) {
|
||||||
|
return "blobthinkingglare";
|
||||||
|
} else {
|
||||||
|
return "blobaww";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static initialize() {
|
||||||
|
$("[data-tag-counter]").toArray().forEach(element => {
|
||||||
|
let target = $($(element).attr("data-for")).get(0);
|
||||||
|
render(h(TagCounter, { tags: target }), element);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(TagCounter.initialize);
|
||||||
@@ -143,10 +143,13 @@ Upload.toggle_size = function(e) {
|
|||||||
|
|
||||||
Upload.update_scale = function() {
|
Upload.update_scale = function() {
|
||||||
let $image = $("#image");
|
let $image = $("#image");
|
||||||
let natural_width = $image.get(0).naturalWidth;
|
|
||||||
let natural_height = $image.get(0).naturalHeight;
|
if ($image.length) {
|
||||||
let scale_percentage = Math.round(100 * $image.width() / natural_width);
|
let natural_width = $image.get(0).naturalWidth;
|
||||||
$("#upload-image-metadata-resolution").html(`(${natural_width}x${natural_height}, resized to ${scale_percentage}%)`);
|
let natural_height = $image.get(0).naturalHeight;
|
||||||
|
let scale_percentage = Math.round(100 * $image.width() / natural_width);
|
||||||
|
$("#upload-image-metadata-resolution").html(`(${natural_width}x${natural_height}, resized to ${scale_percentage}%)`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Upload.fetch_data_manual = function(e) {
|
Upload.fetch_data_manual = function(e) {
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ UserTooltip.DURATION = 250;
|
|||||||
UserTooltip.MAX_WIDTH = 600;
|
UserTooltip.MAX_WIDTH = 600;
|
||||||
|
|
||||||
UserTooltip.initialize = function () {
|
UserTooltip.initialize = function () {
|
||||||
delegate("body", {
|
delegate("#page", {
|
||||||
allowHTML: true,
|
allowHTML: true,
|
||||||
appendTo: document.body,
|
appendTo: document.querySelector("#user-tooltips"),
|
||||||
delay: [UserTooltip.SHOW_DELAY, UserTooltip.HIDE_DELAY],
|
delay: [UserTooltip.SHOW_DELAY, UserTooltip.HIDE_DELAY],
|
||||||
duration: UserTooltip.DURATION,
|
duration: UserTooltip.DURATION,
|
||||||
interactive: true,
|
interactive: true,
|
||||||
@@ -26,7 +26,7 @@ UserTooltip.initialize = function () {
|
|||||||
onHide: UserTooltip.on_hide,
|
onHide: UserTooltip.on_hide,
|
||||||
});
|
});
|
||||||
|
|
||||||
delegate("body", {
|
delegate("#user-tooltips", {
|
||||||
allowHTML: true,
|
allowHTML: true,
|
||||||
interactive: true,
|
interactive: true,
|
||||||
theme: "common-tooltip",
|
theme: "common-tooltip",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ import Rails from '@rails/ujs';
|
|||||||
|
|
||||||
let Utility = {};
|
let Utility = {};
|
||||||
|
|
||||||
|
export function clamp(value, low, high) {
|
||||||
|
return Math.max(low, Math.min(value, high));
|
||||||
|
}
|
||||||
|
|
||||||
Utility.delay = function(milliseconds) {
|
Utility.delay = function(milliseconds) {
|
||||||
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
$h1_size: 2em;
|
:root {
|
||||||
$h2_size: 1.5em;
|
--text-xs: 0.8em;
|
||||||
$h3_size: 1.16667em;
|
--text-sm: 0.9em;
|
||||||
$h4_size: 1em;
|
--text-md: 1em;
|
||||||
$dtext_h1_size: 2em;
|
--text-lg: 1.16667em;
|
||||||
$dtext_h2_size: 1.8em;
|
--text-xl: 1.5em;
|
||||||
$dtext_h3_size: 1.6em;
|
--text-xxl: 2em;
|
||||||
$dtext_h4_size: 1.4em;
|
--header-font: Tahoma, Verdana, Helvetica, sans-serif;
|
||||||
$dtext_h5_size: 1.2em;
|
--body-font: Verdana, Helvetica, sans-serif;
|
||||||
$dtext_h6_size: 1em;
|
--monospace-font: 1.2em monospace;
|
||||||
|
}
|
||||||
|
|
||||||
$h1_padding: 0.8em 0 0.25em 0;
|
$h1_padding: 0.8em 0 0.25em 0;
|
||||||
$h2_padding: 0.8em 0 0.25em 0;
|
$h2_padding: 0.8em 0 0.25em 0;
|
||||||
$h3_padding: 0.8em 0 0.25em 0;
|
$h3_padding: 0.8em 0 0.25em 0;
|
||||||
$h4_padding: 0.8em 0 0.25em 0;
|
$h4_padding: 0.8em 0 0.25em 0;
|
||||||
|
|
||||||
/* stylelint-disable-next-line value-keyword-case */
|
|
||||||
$base_font_family: Verdana, Helvetica, sans-serif;
|
|
||||||
|
|
||||||
@mixin animated-icon {
|
@mixin animated-icon {
|
||||||
content: "►";
|
content: "►";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
@import "../base/000_vars.scss";
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
background-color: var(--body-background-color);
|
background-color: var(--body-background-color);
|
||||||
font-family: $base_font_family;
|
font-family: var(--body-font);
|
||||||
font-size: 87.5%;
|
font-size: 87.5%;
|
||||||
line-height: 1.25em;
|
line-height: 1.25em;
|
||||||
}
|
}
|
||||||
@@ -12,11 +10,6 @@ abbr[title=required] {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd {
|
dd {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
@@ -25,22 +18,23 @@ dt {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1, h2, h3, h4, h5, h6, .heading {
|
||||||
font-family: Tahoma, Verdana, Helvetica, sans-serif;
|
font-family: var(--header-font);
|
||||||
|
font-weight: bold;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
color: var(--header-color);
|
color: var(--header-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: $h1_size;
|
font-size: var(--text-xxl);
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: $h2_size;
|
font-size: var(--text-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
h3, h4, h5, h6 {
|
h3, h4, h5, h6 {
|
||||||
font-size: $h3_size;
|
font-size: var(--text-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
@@ -62,6 +56,11 @@ input, select, textarea {
|
|||||||
border: var(--form-input-border);
|
border: var(--form-input-border);
|
||||||
color: var(--form-input-text-color);
|
color: var(--form-input-text-color);
|
||||||
padding-left: 0.25em;
|
padding-left: 0.25em;
|
||||||
|
font: var(--body-font);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
font-size: var(--text-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="button"], input[type="submit"], button {
|
input[type="button"], input[type="submit"], button {
|
||||||
|
|||||||
@@ -104,10 +104,8 @@
|
|||||||
--post-parent-notice-background: var(--success-background-color);
|
--post-parent-notice-background: var(--success-background-color);
|
||||||
--post-child-notice-background: var(--warning-background-color);
|
--post-child-notice-background: var(--warning-background-color);
|
||||||
--post-pending-notice-background: #D8D8FC;
|
--post-pending-notice-background: #D8D8FC;
|
||||||
--post-flagged-notice-background: var(--error-background-color);
|
|
||||||
--post-banned-notice-background: var(--error-background-color);
|
--post-banned-notice-background: var(--error-background-color);
|
||||||
--post-deleted-notice-background: var(--error-background-color);
|
--post-deleted-notice-background: var(--error-background-color);
|
||||||
--post-appealed-notice-background: #D8F2FC;
|
|
||||||
--post-resized-notice-background: #EED8FC;
|
--post-resized-notice-background: #EED8FC;
|
||||||
--post-search-notice-background: #EEE;
|
--post-search-notice-background: #EEE;
|
||||||
|
|
||||||
@@ -145,10 +143,6 @@
|
|||||||
--tag-count-color: var(--muted-text-color);
|
--tag-count-color: var(--muted-text-color);
|
||||||
--low-post-count-color: red;
|
--low-post-count-color: red;
|
||||||
|
|
||||||
--tag-count-indicator-frown-color: red;
|
|
||||||
--tag-count-indicator-meh-color: darkkhaki;
|
|
||||||
--tag-count-indicator-smile-color: green;
|
|
||||||
|
|
||||||
--remove-favorite-button: deeppink;
|
--remove-favorite-button: deeppink;
|
||||||
|
|
||||||
--ugoira-seek-slider-background: #EEE;
|
--ugoira-seek-slider-background: #EEE;
|
||||||
@@ -387,10 +381,8 @@ body[data-current-user-theme="dark"] {
|
|||||||
--post-parent-notice-background: var(--green-0);
|
--post-parent-notice-background: var(--green-0);
|
||||||
--post-resized-notice-background: var(--purple-0);
|
--post-resized-notice-background: var(--purple-0);
|
||||||
--post-pending-notice-background: var(--indigo-0);
|
--post-pending-notice-background: var(--indigo-0);
|
||||||
--post-flagged-notice-background: var(--red-0);
|
|
||||||
--post-deleted-notice-background: var(--red-0);
|
--post-deleted-notice-background: var(--red-0);
|
||||||
--post-banned-notice-background: var(--red-0);
|
--post-banned-notice-background: var(--red-0);
|
||||||
--post-appealed-notice-background: var(--blue-0);
|
|
||||||
|
|
||||||
--post-tooltip-background-color: var(--grey-3);
|
--post-tooltip-background-color: var(--grey-3);
|
||||||
--post-tooltip-border-color: var(--grey-4);
|
--post-tooltip-border-color: var(--grey-4);
|
||||||
@@ -417,10 +409,6 @@ body[data-current-user-theme="dark"] {
|
|||||||
|
|
||||||
--target-background: var(--blue-0);
|
--target-background: var(--blue-0);
|
||||||
|
|
||||||
--tag-count-indicator-frown-color: var(--red-1);
|
|
||||||
--tag-count-indicator-meh-color: var(--yellow-1);
|
|
||||||
--tag-count-indicator-smile-color: var(--green-1);
|
|
||||||
|
|
||||||
--uploads-dropzone-background: var(--grey-3);
|
--uploads-dropzone-background: var(--grey-3);
|
||||||
--uploads-dropzone-progress-bar-foreground-color: var(--link-color);
|
--uploads-dropzone-progress-bar-foreground-color: var(--link-color);
|
||||||
--uploads-dropzone-progress-bar-background-color: var(--link-hover-color);
|
--uploads-dropzone-progress-bar-background-color: var(--link-hover-color);
|
||||||
|
|||||||
@@ -9,32 +9,32 @@ div.prose {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: $dtext_h1_size;
|
font-size: var(--text-xl);
|
||||||
padding: $h1_padding;
|
padding: $h1_padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: $dtext_h2_size;
|
font-size: var(--text-xl);
|
||||||
padding: $h2_padding;
|
padding: $h2_padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
font-size: $dtext_h3_size;
|
font-size: var(--text-xl);
|
||||||
padding: $h3_padding;
|
padding: $h3_padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
font-size: $dtext_h4_size;
|
font-size: var(--text-xl);
|
||||||
padding: $h4_padding;
|
padding: $h4_padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
h5 {
|
h5 {
|
||||||
font-size: $dtext_h5_size;
|
font-size: var(--text-lg);
|
||||||
padding: $h4_padding;
|
padding: $h4_padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
h6 {
|
h6 {
|
||||||
font-size: $dtext_h6_size;
|
font-size: var(--text-md);
|
||||||
padding: $h4_padding;
|
padding: $h4_padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,12 +57,14 @@ div.prose {
|
|||||||
list-style-type: disc;
|
list-style-type: disc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code, pre {
|
||||||
|
font: var(--monospace-font);
|
||||||
|
background: var(--dtext-code-background);
|
||||||
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
font-family: monospace;
|
|
||||||
font-size: 1.2em;
|
|
||||||
margin: 0.5em 0;
|
margin: 0.5em 0;
|
||||||
padding: 0.5em 1em;
|
padding: 0.5em 1em;
|
||||||
background: var(--dtext-code-background);
|
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,11 +75,6 @@ div.prose {
|
|||||||
background: var(--dtext-blockquote-background);
|
background: var(--dtext-blockquote-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: monospace;
|
|
||||||
background: var(--dtext-code-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tn {
|
.tn {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
color: var(--muted-text-color);
|
color: var(--muted-text-color);
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
@import "../base/000_vars.scss";
|
|
||||||
|
|
||||||
.ui-widget {
|
.ui-widget {
|
||||||
font-family: $base_font_family;
|
font-family: var(--body-font);
|
||||||
|
|
||||||
input, select, textarea, button {
|
input, select, textarea, button {
|
||||||
font-family: $base_font_family;
|
font-family: var(--body-font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,6 @@ div#page {
|
|||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
|
||||||
aside#sidebar {
|
aside#sidebar {
|
||||||
h1 {
|
|
||||||
font-size: $h3_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
#options-box i.fa-bookmark {
|
#options-box i.fa-bookmark {
|
||||||
margin-right: 0.25em;
|
margin-right: 0.25em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ div.list-of-messages {
|
|||||||
width: 12em;
|
width: 12em;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
|
|
||||||
|
div.author-name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
a.message-timestamp {
|
a.message-timestamp {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-size: 0.90em;
|
font-size: 0.90em;
|
||||||
@@ -42,7 +46,7 @@ div.list-of-messages {
|
|||||||
margin: 0 0 1em;
|
margin: 0 0 1em;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
|
||||||
h4 {
|
div.author-name {
|
||||||
display: inline;
|
display: inline;
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
header#top {
|
header#top {
|
||||||
h1#app-name-header {
|
#app-name-header {
|
||||||
font-size: 2em;
|
font-size: var(--text-xxl);
|
||||||
margin: 0 30px;
|
margin: 0 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ form.simple_form {
|
|||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
width: 70%;
|
width: 70%;
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
@@ -107,10 +106,31 @@ form.one-line-form {
|
|||||||
}
|
}
|
||||||
|
|
||||||
div.ui-dialog {
|
div.ui-dialog {
|
||||||
div.input {
|
textarea, input[type="text"] {
|
||||||
input[type="text"] {
|
width: 100%;
|
||||||
width: 100%;
|
max-width: 100%;
|
||||||
max-width: 100%;
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.simple_form {
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
div.input {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.input.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the submit and close buttons */
|
||||||
|
.ui-dialog-buttonpane {
|
||||||
|
margin-top: 0;
|
||||||
|
padding: 1em 1em 1em 0;
|
||||||
|
|
||||||
|
.ui-button {
|
||||||
|
margin: 0 0.25em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
ul.backtrace {
|
ul.backtrace {
|
||||||
font-family: monospace;
|
font: var(--monospace-font);
|
||||||
font-size: 1.2em;
|
|
||||||
background: var(--dtext-code-background);
|
background: var(--dtext-code-background);
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
div.note-body {
|
div.note-body {
|
||||||
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
font-size: 14px;
|
||||||
border: var(--note-body-border);
|
border: var(--note-body-border);
|
||||||
background: var(--note-body-background);
|
background: var(--note-body-background);
|
||||||
color: var(--note-body-text-color);
|
color: var(--note-body-text-color);
|
||||||
@@ -81,7 +83,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: absolute;
|
position: absolute !important;
|
||||||
border: var(--note-box-border);
|
border: var(--note-box-border);
|
||||||
min-width: 5px;
|
min-width: 5px;
|
||||||
min-height: 5px;
|
min-height: 5px;
|
||||||
@@ -93,6 +95,11 @@
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|
||||||
|
/* Raise notes on hover so overlapping embedded notes are readable. */
|
||||||
|
&:hover {
|
||||||
|
z-index: 200;
|
||||||
|
}
|
||||||
|
|
||||||
&.unsaved {
|
&.unsaved {
|
||||||
border: var(--unsaved-note-box-border);
|
border: var(--unsaved-note-box-border);
|
||||||
}
|
}
|
||||||
@@ -106,7 +113,7 @@
|
|||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
||||||
&.hovering {
|
&:hover {
|
||||||
border: var(--note-box-border);
|
border: var(--note-box-border);
|
||||||
box-shadow: var(--note-box-shadow);
|
box-shadow: var(--note-box-shadow);
|
||||||
|
|
||||||
@@ -114,10 +121,6 @@
|
|||||||
&.movable {
|
&.movable {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.ui-resizable-handle {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.editing,
|
&.editing,
|
||||||
@@ -134,24 +137,30 @@
|
|||||||
border: var(--movable-note-box-border);
|
border: var(--movable-note-box-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
div.ui-resizable-handle {
|
&:not(:hover) div.ui-resizable-handle {
|
||||||
display: none;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.note-box-highlighted {
|
&.note-box-highlighted {
|
||||||
outline: 2px solid var(--note-highlight-color);
|
outline: 2px solid var(--note-highlight-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.ui-resizable-handle {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* the box that appears when dragging to create a new note. */
|
||||||
div#note-preview {
|
div#note-preview {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
cursor: crosshair;
|
||||||
border: var(--note-preview-border);
|
border: var(--note-preview-border);
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
display: none;
|
display: none;
|
||||||
background: var(--note-preview-background);
|
background: var(--note-preview-background);
|
||||||
z-index: 100;
|
z-index: 250;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.note-edit-dialog {
|
div.note-edit-dialog {
|
||||||
|
|||||||
@@ -27,17 +27,9 @@ div#add-to-pool-dialog {
|
|||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: $h3_size;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div#c-pools {
|
div#c-pools {
|
||||||
h1 {
|
|
||||||
font-size: $h2_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
height: 10em;
|
height: 10em;
|
||||||
}
|
}
|
||||||
@@ -50,10 +42,6 @@ div#c-pools {
|
|||||||
}
|
}
|
||||||
|
|
||||||
div#c-pool-orders, div#c-favorite-group-orders {
|
div#c-pool-orders, div#c-favorite-group-orders {
|
||||||
h1 {
|
|
||||||
font-size: $h2_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#a-edit {
|
div#a-edit {
|
||||||
ul.ui-sortable {
|
ul.ui-sortable {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
|
|||||||
@@ -80,8 +80,13 @@ table article.post-preview {
|
|||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#edit-dialog textarea {
|
#edit-dialog {
|
||||||
margin-bottom: 0.25em;
|
/* Hide everything but the rating and tags fields. */
|
||||||
|
.post_has_embedded_notes_fieldset, .post_lock_fieldset, .post_parent_id,
|
||||||
|
.post_source, #filedropzone, .upload_as_pending, .upload_source_container,
|
||||||
|
.upload_parent_id, .upload_artist_commentary_container, .upload_commentary_translation_container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-preview {
|
.post-preview {
|
||||||
@@ -258,26 +263,15 @@ div#c-posts {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.resolved {
|
|
||||||
margin-left: 0.5em;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.post-notice-parent { background: var(--post-parent-notice-background); }
|
&.post-notice-parent { background: var(--post-parent-notice-background); }
|
||||||
&.post-notice-child { background: var(--post-child-notice-background); }
|
&.post-notice-child { background: var(--post-child-notice-background); }
|
||||||
&.post-notice-pending { background: var(--post-pending-notice-background); }
|
&.post-notice-pending { background: var(--post-pending-notice-background); }
|
||||||
&.post-notice-flagged { background: var(--post-flagged-notice-background); }
|
|
||||||
&.post-notice-banned { background: var(--post-banned-notice-background); }
|
&.post-notice-banned { background: var(--post-banned-notice-background); }
|
||||||
&.post-notice-deleted { background: var(--post-deleted-notice-background); }
|
&.post-notice-deleted { background: var(--post-deleted-notice-background); }
|
||||||
&.post-notice-appealed { background: var(--post-appealed-notice-background); }
|
|
||||||
&.post-notice-resized { background: var(--post-resized-notice-background); }
|
&.post-notice-resized { background: var(--post-resized-notice-background); }
|
||||||
&.post-notice-search { background: var(--post-search-notice-background); }
|
&.post-notice-search { background: var(--post-search-notice-background); }
|
||||||
}
|
}
|
||||||
|
|
||||||
aside#sidebar #tag-list h2 {
|
|
||||||
font-size: $h4_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
aside#sidebar > section > ul {
|
aside#sidebar > section > ul {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
|
||||||
@@ -308,7 +302,7 @@ div#c-posts {
|
|||||||
div#a-index {
|
div#a-index {
|
||||||
menu#post-sections {
|
menu#post-sections {
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
font-size: $h3_size;
|
font-size: var(--text-lg);
|
||||||
|
|
||||||
li {
|
li {
|
||||||
padding: 0 1em 0.5em 0;
|
padding: 0 1em 0.5em 0;
|
||||||
@@ -331,7 +325,7 @@ div#c-posts {
|
|||||||
|
|
||||||
menu#post-sections {
|
menu#post-sections {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: $h3_size;
|
font-size: var(--text-lg);
|
||||||
|
|
||||||
li {
|
li {
|
||||||
padding: 0 1em 0 0;
|
padding: 0 1em 0 0;
|
||||||
@@ -434,12 +428,27 @@ div#c-posts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body[data-post-current-image-size="large"] .image-view-large-link,
|
body[data-post-current-image-size="large"] #post-options .image-view-large-link,
|
||||||
body[data-post-current-image-size="original"] .image-view-original-link,
|
body[data-post-current-image-size="original"] #post-options .image-view-original-link,
|
||||||
body[data-post-current-image-size="original"] #image-resize-notice {
|
body[data-post-current-image-size="original"] #image-resize-notice {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Always show the "Resized to X% of original" notice on mobile when it exists. */
|
||||||
|
#image-resize-notice {
|
||||||
|
@media screen and (max-width: 660px) {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mode-translation .note-container {
|
||||||
|
cursor: crosshair;
|
||||||
|
}
|
||||||
|
|
||||||
|
body:not(.mode-translation) div#c-posts div#a-show #mark-as-translated-section {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
div#c-post-versions, div#c-artist-versions {
|
div#c-post-versions, div#c-artist-versions {
|
||||||
div#a-index {
|
div#a-index {
|
||||||
a {
|
a {
|
||||||
@@ -474,6 +483,33 @@ div#c-posts, div#c-uploads {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Container for the tag edit <textarea>, header, and related tags buttons. */
|
||||||
|
#tags-container {
|
||||||
|
div.header {
|
||||||
|
line-height: 1.5em;
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
i.fa-external-link-alt {
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
span[data-tag-counter] {
|
||||||
|
float: right;
|
||||||
|
color: var(--muted-text-color);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
|
||||||
|
img {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
div#c-explore-posts {
|
div#c-explore-posts {
|
||||||
a.desc {
|
a.desc {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -499,10 +535,6 @@ div#unapprove-dialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea[data-autocomplete="tag-edit"] {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
#add-commentary-dialog {
|
#add-commentary-dialog {
|
||||||
input {
|
input {
|
||||||
width: 70%;
|
width: 70%;
|
||||||
|
|||||||
@@ -7,10 +7,6 @@ div#c-static {
|
|||||||
section {
|
section {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: $h3_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
margin-bottom: 1.5em;
|
margin-bottom: 1.5em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,45 +5,3 @@ div#c-tags {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#tags-container {
|
|
||||||
div.header {
|
|
||||||
margin: 0;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 50% 50%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
label {
|
|
||||||
grid-column: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.options {
|
|
||||||
grid-column: 2;
|
|
||||||
justify-self: end;
|
|
||||||
|
|
||||||
.count {
|
|
||||||
color: var(--tag-count-color);
|
|
||||||
text-decoration: italic;
|
|
||||||
margin-left: 0.25em;
|
|
||||||
padding-bottom: 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
margin-left: 0.25em;
|
|
||||||
font-size: 11pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa-frown {
|
|
||||||
color: var(--tag-count-indicator-frown-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa-meh {
|
|
||||||
color: var(--tag-count-indicator-meh-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa-smile {
|
|
||||||
color: var(--tag-count-indicator-smile-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,20 +4,12 @@
|
|||||||
div#page {
|
div#page {
|
||||||
margin: 0 0.5rem;
|
margin: 0 0.5rem;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
aside#sidebar {
|
|
||||||
font-size: $h3_size;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
header#top {
|
header#top {
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
h1#app-name-header {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
#maintoggle {
|
#maintoggle {
|
||||||
display: block;
|
display: block;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -28,7 +20,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
nav#nav {
|
nav#nav {
|
||||||
font-size: $h3_size;
|
|
||||||
line-height: 2em;
|
line-height: 2em;
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
@@ -53,7 +44,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
div.paginator {
|
div.paginator {
|
||||||
font-size: $h2_size;
|
font-size: var(--text-lg);
|
||||||
padding: 1em 0 0;
|
padding: 1em 0 0;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
|
|||||||
@@ -163,7 +163,10 @@ module Searchable
|
|||||||
type = column.type || reflect_on_association(name)&.class_name
|
type = column.type || reflect_on_association(name)&.class_name
|
||||||
|
|
||||||
if column.try(:array?)
|
if column.try(:array?)
|
||||||
return search_array_attribute(name, type, params)
|
subtype = type
|
||||||
|
type = :array
|
||||||
|
elsif defined_enums.has_key?(name.to_s)
|
||||||
|
type = :enum
|
||||||
end
|
end
|
||||||
|
|
||||||
case type
|
case type
|
||||||
@@ -181,6 +184,10 @@ module Searchable
|
|||||||
numeric_attribute_matches(name, params[name])
|
numeric_attribute_matches(name, params[name])
|
||||||
when :inet
|
when :inet
|
||||||
search_inet_attribute(name, params)
|
search_inet_attribute(name, params)
|
||||||
|
when :enum
|
||||||
|
search_enum_attribute(name, params)
|
||||||
|
when :array
|
||||||
|
search_array_attribute(name, subtype, params)
|
||||||
else
|
else
|
||||||
raise NotImplementedError, "unhandled attribute type: #{name}" if type.blank?
|
raise NotImplementedError, "unhandled attribute type: #{name}" if type.blank?
|
||||||
search_includes(name, params, type)
|
search_includes(name, params, type)
|
||||||
@@ -279,6 +286,19 @@ module Searchable
|
|||||||
relation
|
relation
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def search_enum_attribute(name, params)
|
||||||
|
relation = all
|
||||||
|
|
||||||
|
if params[name].present?
|
||||||
|
value = params[name].split(/[, ]+/).map(&:downcase)
|
||||||
|
relation = relation.where(name => value)
|
||||||
|
elsif params["#{name}_id"].present?
|
||||||
|
relation = relation.numeric_attribute_matches(name, params["#{name}_id"])
|
||||||
|
end
|
||||||
|
|
||||||
|
relation
|
||||||
|
end
|
||||||
|
|
||||||
def search_array_attribute(name, type, params)
|
def search_array_attribute(name, type, params)
|
||||||
relation = all
|
relation = all
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
require "danbooru/http/html_adapter"
|
require "danbooru/http/html_adapter"
|
||||||
require "danbooru/http/xml_adapter"
|
require "danbooru/http/xml_adapter"
|
||||||
require "danbooru/http/cache"
|
require "danbooru/http/cache"
|
||||||
|
require "danbooru/http/logger"
|
||||||
require "danbooru/http/redirector"
|
require "danbooru/http/redirector"
|
||||||
require "danbooru/http/retriable"
|
require "danbooru/http/retriable"
|
||||||
require "danbooru/http/session"
|
require "danbooru/http/session"
|
||||||
|
|||||||
35
app/logical/danbooru/http/logger.rb
Normal file
35
app/logical/danbooru/http/logger.rb
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
module Danbooru
|
||||||
|
class Http
|
||||||
|
class Logger < HTTP::Feature
|
||||||
|
HTTP::Options.register_feature :logger, self
|
||||||
|
|
||||||
|
attr_reader :logger
|
||||||
|
|
||||||
|
def initialize(logger: ::Logger.new(STDOUT))
|
||||||
|
@logger = logger
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform(request, &block)
|
||||||
|
log_request(request)
|
||||||
|
response = yield request
|
||||||
|
log_response(request, response)
|
||||||
|
response
|
||||||
|
end
|
||||||
|
|
||||||
|
def log_request(request)
|
||||||
|
logger.info do
|
||||||
|
verb = request.verb.to_s.upcase
|
||||||
|
headers = request.headers.map { |name, value| "#{name}: #{value}" }.join("\n")
|
||||||
|
"> #{verb} #{request.uri}\n#{headers}\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def log_response(request, response)
|
||||||
|
logger.info do
|
||||||
|
headers = response.headers.map { |name, value| "#{name}: #{value}" }.join("\n")
|
||||||
|
"< #{response.status.to_i} | #{request.uri}\n#{headers}\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -3,13 +3,14 @@ module DanbooruMaintenance
|
|||||||
|
|
||||||
def hourly
|
def hourly
|
||||||
safely { Upload.prune! }
|
safely { Upload.prune! }
|
||||||
|
safely { PostPruner.prune! }
|
||||||
|
safely { PostAppealForumUpdater.update_forum! }
|
||||||
|
safely { regenerate_post_counts! }
|
||||||
end
|
end
|
||||||
|
|
||||||
def daily
|
def daily
|
||||||
safely { PostPruner.new.prune! }
|
|
||||||
safely { Delayed::Job.where('created_at < ?', 45.days.ago).delete_all }
|
safely { Delayed::Job.where('created_at < ?', 45.days.ago).delete_all }
|
||||||
safely { PostDisapproval.prune! }
|
safely { PostDisapproval.prune! }
|
||||||
safely { regenerate_post_counts! }
|
|
||||||
safely { TokenBucket.prune! }
|
safely { TokenBucket.prune! }
|
||||||
safely { BulkUpdateRequestPruner.warn_old }
|
safely { BulkUpdateRequestPruner.warn_old }
|
||||||
safely { BulkUpdateRequestPruner.reject_expired }
|
safely { BulkUpdateRequestPruner.reject_expired }
|
||||||
@@ -35,8 +36,12 @@ module DanbooruMaintenance
|
|||||||
|
|
||||||
def safely(&block)
|
def safely(&block)
|
||||||
ActiveRecord::Base.connection.execute("set statement_timeout = 0")
|
ActiveRecord::Base.connection.execute("set statement_timeout = 0")
|
||||||
yield
|
|
||||||
|
CurrentUser.scoped(User.system, "127.0.0.1") do
|
||||||
|
yield
|
||||||
|
end
|
||||||
rescue StandardError => exception
|
rescue StandardError => exception
|
||||||
DanbooruLogger.log(exception)
|
DanbooruLogger.log(exception)
|
||||||
|
raise exception if Rails.env.test?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
26
app/logical/post_appeal_forum_updater.rb
Normal file
26
app/logical/post_appeal_forum_updater.rb
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
module PostAppealForumUpdater
|
||||||
|
APPEAL_TOPIC_TITLE = "Deletion appeal thread"
|
||||||
|
|
||||||
|
def self.update_forum!
|
||||||
|
return if pending_appeals.empty?
|
||||||
|
|
||||||
|
CurrentUser.scoped(User.system) do
|
||||||
|
topic = ForumTopic.order(:id).create_with(creator: User.system).find_or_create_by!(title: APPEAL_TOPIC_TITLE)
|
||||||
|
ForumPost.create!(creator: User.system, topic: topic, body: forum_post_body)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.pending_appeals
|
||||||
|
PostAppeal.pending.where(created_at: (1.hour.ago..Time.zone.now)).order(post_id: :asc)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.forum_post_body
|
||||||
|
pending_appeals.map do |appeal|
|
||||||
|
if appeal.reason.present?
|
||||||
|
"post ##{appeal.post_id}: #{appeal.reason}"
|
||||||
|
else
|
||||||
|
"post ##{appeal.post_id}"
|
||||||
|
end
|
||||||
|
end.join("\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,37 +1,27 @@
|
|||||||
class PostPruner
|
module PostPruner
|
||||||
|
module_function
|
||||||
|
|
||||||
def prune!
|
def prune!
|
||||||
prune_pending!
|
prune_pending!
|
||||||
prune_flagged!
|
prune_flagged!
|
||||||
prune_mod_actions!
|
prune_appealed!
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
def prune_pending!
|
def prune_pending!
|
||||||
CurrentUser.scoped(User.system, "127.0.0.1") do
|
Post.pending.expired.each do |post|
|
||||||
Post.where("is_deleted = ? and is_pending = ? and created_at < ?", false, true, 3.days.ago).each do |post|
|
post.delete!("Unapproved in three days", user: User.system)
|
||||||
post.delete!("Unapproved in three days")
|
|
||||||
rescue PostFlag::Error
|
|
||||||
# swallow
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def prune_flagged!
|
def prune_flagged!
|
||||||
CurrentUser.scoped(User.system, "127.0.0.1") do
|
PostFlag.expired.each do |flag|
|
||||||
Post.where("is_deleted = ? and is_flagged = ?", false, true).each do |post|
|
flag.post.delete!("Unapproved in three days after returning to moderation queue", user: User.system)
|
||||||
if post.flags.unresolved.old.any?
|
|
||||||
begin
|
|
||||||
post.delete!("Unapproved in three days after returning to moderation queue")
|
|
||||||
rescue PostFlag::Error
|
|
||||||
# swallow
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def prune_mod_actions!
|
def prune_appealed!
|
||||||
ModAction.where(["creator_id = ? and description like ?", User.system.id, "deleted post %"]).destroy_all
|
PostAppeal.expired.each do |appeal|
|
||||||
|
appeal.post.delete!("Unapproved in three days after returning to moderation queue", user: User.system)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ class PostQueryBuilder
|
|||||||
COUNT_METATAGS = %w[
|
COUNT_METATAGS = %w[
|
||||||
comment_count deleted_comment_count active_comment_count
|
comment_count deleted_comment_count active_comment_count
|
||||||
note_count deleted_note_count active_note_count
|
note_count deleted_note_count active_note_count
|
||||||
flag_count resolved_flag_count unresolved_flag_count
|
flag_count
|
||||||
child_count deleted_child_count active_child_count
|
child_count deleted_child_count active_child_count
|
||||||
pool_count deleted_pool_count active_pool_count series_pool_count collection_pool_count
|
pool_count deleted_pool_count active_pool_count series_pool_count collection_pool_count
|
||||||
appeal_count approval_count replacement_count
|
appeal_count approval_count replacement_count
|
||||||
@@ -274,8 +274,10 @@ class PostQueryBuilder
|
|||||||
Post.pending
|
Post.pending
|
||||||
when "flagged"
|
when "flagged"
|
||||||
Post.flagged
|
Post.flagged
|
||||||
|
when "appealed"
|
||||||
|
Post.appealed
|
||||||
when "modqueue"
|
when "modqueue"
|
||||||
Post.pending_or_flagged
|
Post.in_modqueue
|
||||||
when "deleted"
|
when "deleted"
|
||||||
Post.deleted
|
Post.deleted
|
||||||
when "banned"
|
when "banned"
|
||||||
@@ -283,7 +285,7 @@ class PostQueryBuilder
|
|||||||
when "active"
|
when "active"
|
||||||
Post.active
|
Post.active
|
||||||
when "unmoderated"
|
when "unmoderated"
|
||||||
Post.pending_or_flagged.available_for_moderation(current_user, hidden: false)
|
Post.in_modqueue.available_for_moderation(current_user, hidden: false)
|
||||||
when "all", "any"
|
when "all", "any"
|
||||||
Post.all
|
Post.all
|
||||||
else
|
else
|
||||||
@@ -307,7 +309,7 @@ class PostQueryBuilder
|
|||||||
Post.where(parent: nil)
|
Post.where(parent: nil)
|
||||||
when "any"
|
when "any"
|
||||||
Post.where.not(parent: nil)
|
Post.where.not(parent: nil)
|
||||||
when /pending|flagged|modqueue|deleted|banned|active|unmoderated/
|
when "pending", "flagged", "appealed", "modqueue", "deleted", "banned", "active", "unmoderated"
|
||||||
Post.where.not(parent: nil).where(parent: status_matches(parent))
|
Post.where.not(parent: nil).where(parent: status_matches(parent))
|
||||||
when /\A\d+\z/
|
when /\A\d+\z/
|
||||||
Post.where(id: parent).or(Post.where(parent: parent))
|
Post.where(id: parent).or(Post.where(parent: parent))
|
||||||
@@ -322,7 +324,7 @@ class PostQueryBuilder
|
|||||||
Post.where(has_children: false)
|
Post.where(has_children: false)
|
||||||
when "any"
|
when "any"
|
||||||
Post.where(has_children: true)
|
Post.where(has_children: true)
|
||||||
when /pending|flagged|modqueue|deleted|banned|active|unmoderated/
|
when "pending", "flagged", "appealed", "modqueue", "deleted", "banned", "active", "unmoderated"
|
||||||
Post.where(has_children: true).where(children: status_matches(child))
|
Post.where(has_children: true).where(children: status_matches(child))
|
||||||
else
|
else
|
||||||
Post.none
|
Post.none
|
||||||
@@ -330,8 +332,9 @@ class PostQueryBuilder
|
|||||||
end
|
end
|
||||||
|
|
||||||
def source_matches(source, quoted = false)
|
def source_matches(source, quoted = false)
|
||||||
case source.downcase
|
if source.empty?
|
||||||
in "none" unless quoted
|
Post.where_like(:source, "")
|
||||||
|
elsif source.downcase == "none" && !quoted
|
||||||
Post.where_like(:source, "")
|
Post.where_like(:source, "")
|
||||||
else
|
else
|
||||||
Post.where_ilike(:source, source + "*")
|
Post.where_ilike(:source, source + "*")
|
||||||
@@ -606,10 +609,10 @@ class PostQueryBuilder
|
|||||||
.order("contributor_fav_count DESC, posts.fav_count DESC, posts.id DESC")
|
.order("contributor_fav_count DESC, posts.fav_count DESC, posts.id DESC")
|
||||||
|
|
||||||
when "modqueue", "modqueue_desc"
|
when "modqueue", "modqueue_desc"
|
||||||
relation = relation.left_outer_joins(:flags).order(Arel.sql("GREATEST(posts.created_at, post_flags.created_at) DESC, posts.id DESC"))
|
relation = relation.with_queued_at.order("queued_at DESC, posts.id DESC")
|
||||||
|
|
||||||
when "modqueue_asc"
|
when "modqueue_asc"
|
||||||
relation = relation.left_outer_joins(:flags).order(Arel.sql("GREATEST(posts.created_at, post_flags.created_at) ASC, posts.id ASC"))
|
relation = relation.with_queued_at.order("queued_at ASC, posts.id ASC")
|
||||||
|
|
||||||
when "none"
|
when "none"
|
||||||
relation = relation.reorder(nil)
|
relation = relation.reorder(nil)
|
||||||
@@ -642,14 +645,7 @@ class PostQueryBuilder
|
|||||||
if scanner.scan(/(-)?(#{METATAGS.join("|")}):/io)
|
if scanner.scan(/(-)?(#{METATAGS.join("|")}):/io)
|
||||||
operator = scanner.captures.first
|
operator = scanner.captures.first
|
||||||
metatag = scanner.captures.second.downcase
|
metatag = scanner.captures.second.downcase
|
||||||
|
value, quoted = scan_string(scanner)
|
||||||
if scanner.scan(/"(.+)"/) || scanner.scan(/'(.+)'/)
|
|
||||||
value = scanner.captures.first
|
|
||||||
quoted = true
|
|
||||||
else
|
|
||||||
value = scanner.scan(/[^ ]*/)
|
|
||||||
quoted = false
|
|
||||||
end
|
|
||||||
|
|
||||||
if metatag.in?(COUNT_METATAG_SYNONYMS)
|
if metatag.in?(COUNT_METATAG_SYNONYMS)
|
||||||
metatag = metatag.singularize + "_count"
|
metatag = metatag.singularize + "_count"
|
||||||
@@ -673,23 +669,41 @@ class PostQueryBuilder
|
|||||||
terms
|
terms
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def scan_string(scanner)
|
||||||
|
if scanner.scan(/"((?:\\"|[^"])*)"/)
|
||||||
|
value = scanner.captures.first.gsub(/\\(.)/) { $1 }
|
||||||
|
quoted = true
|
||||||
|
elsif scanner.scan(/'((?:\\'|[^'])*)'/)
|
||||||
|
value = scanner.captures.first.gsub(/\\(.)/) { $1 }
|
||||||
|
quoted = true
|
||||||
|
else
|
||||||
|
value = scanner.scan(/(\\ |[^ ])*/)
|
||||||
|
value = value.gsub(/\\ /) { " " }
|
||||||
|
quoted = false
|
||||||
|
end
|
||||||
|
|
||||||
|
[value, quoted]
|
||||||
|
end
|
||||||
|
|
||||||
def split_query
|
def split_query
|
||||||
terms.map do |term|
|
terms.map do |term|
|
||||||
if term.type == :metatag && !term.negated && !term.quoted
|
type, name, value = term.type, term.name, term.value
|
||||||
"#{term.name}:#{term.value}"
|
|
||||||
elsif term.type == :metatag && !term.negated && term.quoted
|
str = ""
|
||||||
"#{term.name}:\"#{term.value}\""
|
str += "-" if term.negated
|
||||||
elsif term.type == :metatag && term.negated && !term.quoted
|
str += "~" if term.optional
|
||||||
"-#{term.name}:#{term.value}"
|
|
||||||
elsif term.type == :metatag && term.negated && term.quoted
|
if type == :tag
|
||||||
"-#{term.name}:\"#{term.value}\""
|
str += name
|
||||||
elsif term.type == :tag && term.negated
|
elsif type == :metatag && (term.quoted || value.include?(" "))
|
||||||
"-#{term.name}"
|
value = value.gsub(/\\/) { '\\\\' }
|
||||||
elsif term.type == :tag && term.optional
|
value = value.gsub(/"/) { '\\"' }
|
||||||
"~#{term.name}"
|
str += "#{name}:\"#{value}\""
|
||||||
elsif term.type == :tag
|
elsif type == :metatag
|
||||||
term.name
|
str += "#{name}:#{value}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
str
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -898,8 +912,9 @@ class PostQueryBuilder
|
|||||||
metatags
|
metatags
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# XXX unify with PostSets::Post#show_deleted?
|
||||||
def hide_deleted?
|
def hide_deleted?
|
||||||
has_status_metatag = select_metatags(:status).any? { |metatag| metatag.value.downcase.in?(%w[deleted active any all]) }
|
has_status_metatag = select_metatags(:status).any? { |metatag| metatag.value.downcase.in?(%w[deleted active any all unmoderated modqueue appealed]) }
|
||||||
hide_deleted_posts? && !has_status_metatag
|
hide_deleted_posts? && !has_status_metatag
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -59,6 +59,12 @@ module PostSets
|
|||||||
posts.any? {|x| x.rating == "e"}
|
posts.any? {|x| x.rating == "e"}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def shown_posts
|
||||||
|
shown_posts = posts.select(&:visible?)
|
||||||
|
shown_posts = shown_posts.reject(&:is_deleted?) unless show_deleted?
|
||||||
|
shown_posts
|
||||||
|
end
|
||||||
|
|
||||||
def hidden_posts
|
def hidden_posts
|
||||||
posts.reject(&:visible?)
|
posts.reject(&:visible?)
|
||||||
end
|
end
|
||||||
@@ -136,24 +142,22 @@ module PostSets
|
|||||||
|
|
||||||
def post_previews_html(template)
|
def post_previews_html(template)
|
||||||
html = ""
|
html = ""
|
||||||
if none_shown
|
if shown_posts.empty?
|
||||||
return template.render("post_sets/blank")
|
return template.render("post_sets/blank")
|
||||||
end
|
end
|
||||||
|
|
||||||
posts.each do |post|
|
shown_posts.each do |post|
|
||||||
html << PostPresenter.preview(post, show_cropped: true, tags: tag_string)
|
html << PostPresenter.preview(post, show_deleted: show_deleted?, show_cropped: true, tags: tag_string)
|
||||||
html << "\n"
|
html << "\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
html.html_safe
|
html.html_safe
|
||||||
end
|
end
|
||||||
|
|
||||||
def not_shown(post)
|
def show_deleted?
|
||||||
post.is_deleted? && tag_string !~ /status:(?:all|any|deleted|banned)/
|
query.select_metatags("status").any? do |metatag|
|
||||||
end
|
metatag.value.in?(%w[all any active unmoderated modqueue deleted appealed])
|
||||||
|
end
|
||||||
def none_shown
|
|
||||||
posts.reject {|post| not_shown(post) }.empty?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
concerning :TagListMethods do
|
concerning :TagListMethods do
|
||||||
|
|||||||
@@ -77,6 +77,10 @@ module Sources
|
|||||||
|
|
||||||
FAVME = %r{\Ahttps?://(?:www\.)?fav\.me/d(?<base36_deviation_id>[a-z0-9]+)\z}i
|
FAVME = %r{\Ahttps?://(?:www\.)?fav\.me/d(?<base36_deviation_id>[a-z0-9]+)\z}i
|
||||||
|
|
||||||
|
def self.enabled?
|
||||||
|
Danbooru.config.deviantart_client_id.present? && Danbooru.config.deviantart_client_secret.present?
|
||||||
|
end
|
||||||
|
|
||||||
def domains
|
def domains
|
||||||
["deviantart.net", "deviantart.com", "fav.me"]
|
["deviantart.net", "deviantart.com", "fav.me"]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ module Sources
|
|||||||
|
|
||||||
PROFILE_PAGE = %r{\Ahttps?://seiga\.nicovideo\.jp/user/illust/(?<artist_id>\d+)}i
|
PROFILE_PAGE = %r{\Ahttps?://seiga\.nicovideo\.jp/user/illust/(?<artist_id>\d+)}i
|
||||||
|
|
||||||
|
def self.enabled?
|
||||||
|
Danbooru.config.nico_seiga_login.present? && Danbooru.config.nico_seiga_password.present?
|
||||||
|
end
|
||||||
|
|
||||||
def domains
|
def domains
|
||||||
["nicoseiga.jp", "nicovideo.jp"]
|
["nicoseiga.jp", "nicovideo.jp"]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ module Sources
|
|||||||
DIR = %r{(?:\d+/)?(?:__rs_\w+/)?nijie_picture(?:/diff/main)?}
|
DIR = %r{(?:\d+/)?(?:__rs_\w+/)?nijie_picture(?:/diff/main)?}
|
||||||
IMAGE_URL = %r{#{IMAGE_BASE_URL}/#{DIR}/#{Regexp.union(FILENAME1, FILENAME2, FILENAME3)}\.\w+\z}i
|
IMAGE_URL = %r{#{IMAGE_BASE_URL}/#{DIR}/#{Regexp.union(FILENAME1, FILENAME2, FILENAME3)}\.\w+\z}i
|
||||||
|
|
||||||
|
def self.enabled?
|
||||||
|
Danbooru.config.nijie_login.present? && Danbooru.config.nijie_password.present?
|
||||||
|
end
|
||||||
|
|
||||||
def domains
|
def domains
|
||||||
["nijie.info", "nijie.net"]
|
["nijie.info", "nijie.net"]
|
||||||
end
|
end
|
||||||
@@ -176,23 +180,37 @@ module Sources
|
|||||||
end
|
end
|
||||||
|
|
||||||
def page
|
def page
|
||||||
return nil if page_url.blank?
|
return nil if page_url.blank? || client.blank?
|
||||||
|
|
||||||
http = Danbooru::Http.new
|
response = client.cache(1.minute).get(page_url)
|
||||||
form = { email: Danbooru.config.nijie_login, password: Danbooru.config.nijie_password }
|
|
||||||
|
|
||||||
# XXX `retriable` must come after `cache` so that retries don't return cached error responses.
|
|
||||||
response = http.cache(1.hour).use(retriable: { max_retries: 20 }).post("https://nijie.info/login_int.php", form: form)
|
|
||||||
DanbooruLogger.info "Nijie login failed (#{url}, #{response.status})" if response.status != 200
|
|
||||||
return nil unless response.status == 200
|
|
||||||
|
|
||||||
response = http.cookies(R18: 1).cache(1.minute).get(page_url)
|
|
||||||
return nil unless response.status == 200
|
return nil unless response.status == 200
|
||||||
|
|
||||||
response&.parse
|
response&.parse
|
||||||
end
|
end
|
||||||
|
|
||||||
memoize :page
|
memoize :page
|
||||||
|
|
||||||
|
def client
|
||||||
|
http = Danbooru::Http.new.timeout(60)
|
||||||
|
|
||||||
|
cookie = Cache.get("nijie-session-cookie", 1.week) do
|
||||||
|
login_page = http.use(retriable: { max_retries: 20 }).get("https://nijie.info/login.php").parse
|
||||||
|
form = {
|
||||||
|
email: Danbooru.config.nijie_login,
|
||||||
|
password: Danbooru.config.nijie_password,
|
||||||
|
url: login_page.at("input[name='url']")["value"],
|
||||||
|
save: "on",
|
||||||
|
ticket: ""
|
||||||
|
}
|
||||||
|
response = http.use(retriable: { max_retries: 20 }).post("https://nijie.info/login_int.php", form: form)
|
||||||
|
DanbooruLogger.info "Nijie login failed (#{url}, #{response.status})" if response.status != 200
|
||||||
|
return nil unless response.status == 200
|
||||||
|
|
||||||
|
response.cookies.select { |c| c.name == "NIJIEIJIEID" }.compact.first
|
||||||
|
end
|
||||||
|
|
||||||
|
http.cookies(NIJIEIJIEID: cookie, R18: 1)
|
||||||
|
end
|
||||||
|
memoize :client
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ module Sources::Strategies
|
|||||||
STATUS1 = %r{\A#{HOST}/web/statuses/(?<status_id>\d+)}
|
STATUS1 = %r{\A#{HOST}/web/statuses/(?<status_id>\d+)}
|
||||||
STATUS2 = %r{\A#{NAMED_PROFILE}/(?<status_id>\d+)}
|
STATUS2 = %r{\A#{NAMED_PROFILE}/(?<status_id>\d+)}
|
||||||
|
|
||||||
|
def self.enabled?
|
||||||
|
Danbooru.config.pawoo_client_id.present? && Danbooru.config.pawoo_client_secret.present?
|
||||||
|
end
|
||||||
|
|
||||||
def domains
|
def domains
|
||||||
["pawoo.net"]
|
["pawoo.net"]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -65,6 +65,10 @@ module Sources
|
|||||||
STACC_PAGE = %r{\A#{WEB}/stacc/#{MONIKER}/?\z}i
|
STACC_PAGE = %r{\A#{WEB}/stacc/#{MONIKER}/?\z}i
|
||||||
NOVEL_PAGE = %r{(?:\Ahttps?://www\.pixiv\.net/novel/show\.php\?id=(\d+))}
|
NOVEL_PAGE = %r{(?:\Ahttps?://www\.pixiv\.net/novel/show\.php\?id=(\d+))}
|
||||||
|
|
||||||
|
def self.enabled?
|
||||||
|
Danbooru.config.pixiv_login.present? && Danbooru.config.pixiv_password.present?
|
||||||
|
end
|
||||||
|
|
||||||
def self.to_dtext(text)
|
def self.to_dtext(text)
|
||||||
if text.nil?
|
if text.nil?
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
# https://66.media.tumblr.com/5a2c3fe25c977e2281392752ab971c90/3dbfaec9b9e0c2e3-92/s500x750/4f92bbaaf95c0b4e7970e62b1d2e1415859dd659.png
|
# https://66.media.tumblr.com/5a2c3fe25c977e2281392752ab971c90/3dbfaec9b9e0c2e3-92/s500x750/4f92bbaaf95c0b4e7970e62b1d2e1415859dd659.png
|
||||||
#
|
#
|
||||||
# https://superboin.tumblr.com/post/141169066579/photoset_iframe/superboin/tumblr_o45miiAOts1u6rxu8/500/false
|
# https://superboin.tumblr.com/post/141169066579/photoset_iframe/superboin/tumblr_o45miiAOts1u6rxu8/500/false
|
||||||
|
#
|
||||||
|
# https://make-do5.tumblr.com/post/619663949657423872 (extremely high res, extractable)
|
||||||
|
|
||||||
module Sources::Strategies
|
module Sources::Strategies
|
||||||
class Tumblr < Base
|
class Tumblr < Base
|
||||||
@@ -26,6 +28,11 @@ module Sources::Strategies
|
|||||||
VIDEO = %r{\Ahttps?://(?:vtt|ve|va\.media)\.tumblr\.com/}i
|
VIDEO = %r{\Ahttps?://(?:vtt|ve|va\.media)\.tumblr\.com/}i
|
||||||
POST = %r{\Ahttps?://(?<blog_name>[^.]+)\.tumblr\.com/(?:post|image)/(?<post_id>\d+)}i
|
POST = %r{\Ahttps?://(?<blog_name>[^.]+)\.tumblr\.com/(?:post|image)/(?<post_id>\d+)}i
|
||||||
|
|
||||||
|
NEW_HEADERS = {
|
||||||
|
"user-agent": Danbooru.config.canonical_app_name,
|
||||||
|
"accept": "text/html"
|
||||||
|
}
|
||||||
|
|
||||||
def self.enabled?
|
def self.enabled?
|
||||||
Danbooru.config.tumblr_consumer_key.present?
|
Danbooru.config.tumblr_consumer_key.present?
|
||||||
end
|
end
|
||||||
@@ -161,14 +168,22 @@ module Sources::Strategies
|
|||||||
# http://media.tumblr.com/tumblr_m24kbxqKAX1rszquso1_1280.jpg
|
# http://media.tumblr.com/tumblr_m24kbxqKAX1rszquso1_1280.jpg
|
||||||
# => https://media.tumblr.com/tumblr_m24kbxqKAX1rszquso1_1280.jpg
|
# => https://media.tumblr.com/tumblr_m24kbxqKAX1rszquso1_1280.jpg
|
||||||
def find_largest(url, sizes: SIZES)
|
def find_largest(url, sizes: SIZES)
|
||||||
return url unless url =~ OLD_IMAGE
|
if url =~ OLD_IMAGE
|
||||||
|
candidates = sizes.map do |size|
|
||||||
|
"https://media.tumblr.com/#{$~[:dir]}#{$~[:filename]}_#{size}.#{$~[:ext]}"
|
||||||
|
end
|
||||||
|
|
||||||
candidates = sizes.map do |size|
|
candidates.find do |candidate|
|
||||||
"https://media.tumblr.com/#{$~[:dir]}#{$~[:filename]}_#{size}.#{$~[:ext]}"
|
http_exists?(candidate)
|
||||||
end
|
end
|
||||||
|
elsif url =~ %r{/s\d+x\d+/(\w+\.\w+)$}i
|
||||||
|
max_size = Integer.sqrt(Danbooru.config.max_image_resolution)
|
||||||
|
url = url.gsub(%r{/s\d+x\d+/\w+\.\w+$}i, "/s#{max_size}x#{max_size}/#{$1}")
|
||||||
|
|
||||||
candidates.find do |candidate|
|
resp = Danbooru::Http.cache(1.minute).get(url, headers: NEW_HEADERS).parse
|
||||||
http_exists?(candidate)
|
resp.at("img[src*='/s#{max_size}x#{max_size}/']")["src"]
|
||||||
|
else
|
||||||
|
url
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -25,11 +25,6 @@ class TagCategory
|
|||||||
@@short_name_mapping ||= Hash[Danbooru.config.full_tag_config_info.map { |k, v| [v["short"], k] }]
|
@@short_name_mapping ||= Hash[Danbooru.config.full_tag_config_info.map { |k, v| [v["short"], k] }]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a hash mapping for split_tag_list_html (presenters/tag_set_presenter.rb)
|
|
||||||
def header_mapping
|
|
||||||
@@header_mapping ||= Hash[Danbooru.config.full_tag_config_info.map { |k, v| [k, v["header"]] }]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a hash mapping for related tag buttons (javascripts/related_tag.js.erb)
|
# Returns a hash mapping for related tag buttons (javascripts/related_tag.js.erb)
|
||||||
def related_button_mapping
|
def related_button_mapping
|
||||||
@@related_button_mapping ||= Hash[Danbooru.config.full_tag_config_info.map { |k, v| [k, v["relatedbutton"]] }]
|
@@related_button_mapping ||= Hash[Danbooru.config.full_tag_config_info.map { |k, v| [k, v["relatedbutton"]] }]
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ class UploadLimit
|
|||||||
|
|
||||||
INITIAL_POINTS = 1000
|
INITIAL_POINTS = 1000
|
||||||
MAXIMUM_POINTS = 10_000
|
MAXIMUM_POINTS = 10_000
|
||||||
|
APPEAL_COST = 3
|
||||||
|
DELETION_COST = 5
|
||||||
|
|
||||||
attr_reader :user
|
attr_reader :user
|
||||||
|
|
||||||
@@ -30,11 +32,20 @@ class UploadLimit
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def used_upload_slots
|
def maxed?
|
||||||
pending = user.posts.pending
|
user.upload_points >= MAXIMUM_POINTS
|
||||||
early_deleted = user.posts.deleted.where("created_at >= ?", 3.days.ago)
|
end
|
||||||
|
|
||||||
pending.or(early_deleted).count
|
def used_upload_slots
|
||||||
|
pending_count = user.posts.pending.count
|
||||||
|
appealed_count = user.post_appeals.pending.count
|
||||||
|
early_deleted_count = user.posts.deleted.where("created_at >= ?", Danbooru.config.moderation_period.ago).count
|
||||||
|
|
||||||
|
pending_count + (early_deleted_count * DELETION_COST) + (appealed_count * APPEAL_COST)
|
||||||
|
end
|
||||||
|
|
||||||
|
def free_upload_slots
|
||||||
|
upload_slots - used_upload_slots
|
||||||
end
|
end
|
||||||
|
|
||||||
def upload_slots
|
def upload_slots
|
||||||
@@ -111,6 +122,4 @@ class UploadLimit
|
|||||||
points_for_next_level(n - 1)
|
points_for_next_level(n - 1)
|
||||||
end.sum
|
end.sum
|
||||||
end
|
end
|
||||||
|
|
||||||
memoize :used_upload_slots
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ class UploadService
|
|||||||
end
|
end
|
||||||
|
|
||||||
def start!
|
def start!
|
||||||
|
raise NotImplementedError, "No login credentials configured for #{strategy.site_name}." unless strategy.class.enabled?
|
||||||
|
|
||||||
if Utils.is_downloadable?(source)
|
if Utils.is_downloadable?(source)
|
||||||
if Post.system_tag_match("source:#{canonical_source}").where.not(id: original_post_id).exists?
|
if Post.system_tag_match("source:#{canonical_source}").where.not(id: original_post_id).exists?
|
||||||
raise ActiveRecord::RecordNotUnique, "A post with source #{canonical_source} already exists"
|
raise ActiveRecord::RecordNotUnique, "A post with source #{canonical_source} already exists"
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ class UploadService
|
|||||||
raise "No file or source URL provided" if upload.source_url.blank?
|
raise "No file or source URL provided" if upload.source_url.blank?
|
||||||
|
|
||||||
strategy = Sources::Strategies.find(upload.source_url, upload.referer_url)
|
strategy = Sources::Strategies.find(upload.source_url, upload.referer_url)
|
||||||
|
raise NotImplementedError, "No login credentials configured for #{strategy.site_name}." unless strategy.class.enabled?
|
||||||
|
|
||||||
file = strategy.download_file!
|
file = strategy.download_file!
|
||||||
|
|
||||||
if strategy.data[:ugoira_frame_data].present?
|
if strategy.data[:ugoira_frame_data].present?
|
||||||
|
|||||||
@@ -127,6 +127,10 @@ class ApplicationRecord < ActiveRecord::Base
|
|||||||
ensure
|
ensure
|
||||||
connection.execute("SET STATEMENT_TIMEOUT = #{CurrentUser.user.try(:statement_timeout) || 3_000}") unless Rails.env == "test"
|
connection.execute("SET STATEMENT_TIMEOUT = #{CurrentUser.user.try(:statement_timeout) || 3_000}") unless Rails.env == "test"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update!(*args)
|
||||||
|
all.each { |record| record.update!(*args) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,15 @@ class Ban < ApplicationRecord
|
|||||||
after_destroy :create_unban_mod_action
|
after_destroy :create_unban_mod_action
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :banner, :class_name => "User"
|
belongs_to :banner, :class_name => "User"
|
||||||
|
|
||||||
validates_presence_of :reason, :duration
|
validates_presence_of :reason, :duration
|
||||||
|
validate :user, :validate_user_is_bannable, on: :create
|
||||||
|
|
||||||
scope :unexpired, -> { where("bans.expires_at > ?", Time.now) }
|
scope :unexpired, -> { where("bans.expires_at > ?", Time.now) }
|
||||||
scope :expired, -> { where("bans.expires_at <= ?", Time.now) }
|
scope :expired, -> { where("bans.expires_at <= ?", Time.now) }
|
||||||
|
|
||||||
|
attr_reader :duration
|
||||||
|
|
||||||
def self.is_banned?(user)
|
def self.is_banned?(user)
|
||||||
exists?(["user_id = ? AND expires_at > ?", user.id, Time.now])
|
exists?(["user_id = ? AND expires_at > ?", user.id, Time.now])
|
||||||
end
|
end
|
||||||
@@ -48,6 +52,10 @@ class Ban < ApplicationRecord
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_user_is_bannable
|
||||||
|
self.errors[:user] << "is already banned" if user.is_banned?
|
||||||
|
end
|
||||||
|
|
||||||
def update_user_on_create
|
def update_user_on_create
|
||||||
user.update!(is_banned: true)
|
user.update!(is_banned: true)
|
||||||
end
|
end
|
||||||
@@ -69,8 +77,6 @@ class Ban < ApplicationRecord
|
|||||||
@duration = dur
|
@duration = dur
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :duration
|
|
||||||
|
|
||||||
def humanized_duration
|
def humanized_duration
|
||||||
ApplicationController.helpers.distance_of_time_in_words(created_at, expires_at)
|
ApplicationController.helpers.distance_of_time_in_words(created_at, expires_at)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ class Post < ApplicationRecord
|
|||||||
class TimeoutError < StandardError; end
|
class TimeoutError < StandardError; end
|
||||||
|
|
||||||
# Tags to copy when copying notes.
|
# Tags to copy when copying notes.
|
||||||
NOTE_COPY_TAGS = %w[translated partially_translated check_translation translation_request reverse_translation]
|
NOTE_COPY_TAGS = %w[translated partially_translated check_translation translation_request reverse_translation
|
||||||
|
annotated partially_annotated check_annotation annotation_request]
|
||||||
|
|
||||||
deletable
|
deletable
|
||||||
|
|
||||||
@@ -61,8 +62,10 @@ class Post < ApplicationRecord
|
|||||||
scope :pending, -> { where(is_pending: true) }
|
scope :pending, -> { where(is_pending: true) }
|
||||||
scope :flagged, -> { where(is_flagged: true) }
|
scope :flagged, -> { where(is_flagged: true) }
|
||||||
scope :banned, -> { where(is_banned: true) }
|
scope :banned, -> { where(is_banned: true) }
|
||||||
scope :active, -> { where(is_pending: false, is_deleted: false, is_flagged: false) }
|
scope :active, -> { where(is_pending: false, is_deleted: false, is_flagged: false).where.not(id: PostAppeal.pending) }
|
||||||
scope :pending_or_flagged, -> { pending.or(flagged) }
|
scope :appealed, -> { deleted.where(id: PostAppeal.pending.select(:post_id)) }
|
||||||
|
scope :in_modqueue, -> { pending.or(flagged).or(appealed) }
|
||||||
|
scope :expired, -> { pending.where("posts.created_at < ?", Danbooru.config.moderation_period.ago) }
|
||||||
|
|
||||||
scope :unflagged, -> { where(is_flagged: false) }
|
scope :unflagged, -> { where(is_flagged: false) }
|
||||||
scope :has_notes, -> { where.not(last_noted_at: nil) }
|
scope :has_notes, -> { where.not(last_noted_at: nil) }
|
||||||
@@ -237,9 +240,9 @@ class Post < ApplicationRecord
|
|||||||
|
|
||||||
def large_image_width
|
def large_image_width
|
||||||
if has_large?
|
if has_large?
|
||||||
[Danbooru.config.large_image_width, image_width].min
|
[Danbooru.config.large_image_width, image_width.to_i].min
|
||||||
else
|
else
|
||||||
image_width
|
image_width.to_i
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -269,6 +272,7 @@ class Post < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def resize_percentage
|
def resize_percentage
|
||||||
|
return 100 if image_width.to_i == 0
|
||||||
100 * large_image_width.to_f / image_width.to_f
|
100 * large_image_width.to_f / image_width.to_f
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -279,12 +283,28 @@ class Post < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
module ApprovalMethods
|
module ApprovalMethods
|
||||||
|
def in_modqueue?
|
||||||
|
is_pending? || is_flagged? || is_appealed?
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_active?
|
||||||
|
!is_deleted? && !in_modqueue?
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_appealed?
|
||||||
|
is_deleted? && appeals.any?(&:pending?)
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_appealable?
|
||||||
|
is_deleted? && !is_appealed?
|
||||||
|
end
|
||||||
|
|
||||||
def is_approvable?(user = CurrentUser.user)
|
def is_approvable?(user = CurrentUser.user)
|
||||||
!is_status_locked? && (is_pending? || is_flagged? || is_deleted?) && uploader != user
|
!is_status_locked? && !is_active? && uploader != user
|
||||||
end
|
end
|
||||||
|
|
||||||
def flag!(reason, is_deletion: false)
|
def flag!(reason, is_deletion: false)
|
||||||
flag = flags.create(reason: reason, is_resolved: false, is_deletion: is_deletion, creator: CurrentUser.user)
|
flag = flags.create(reason: reason, is_deletion: is_deletion, creator: CurrentUser.user)
|
||||||
|
|
||||||
if flag.errors.any?
|
if flag.errors.any?
|
||||||
raise PostFlag::Error.new(flag.errors.full_messages.join("; "))
|
raise PostFlag::Error.new(flag.errors.full_messages.join("; "))
|
||||||
@@ -375,12 +395,6 @@ class Post < ApplicationRecord
|
|||||||
def update_tag_post_counts
|
def update_tag_post_counts
|
||||||
decrement_tags = tag_array_was - tag_array
|
decrement_tags = tag_array_was - tag_array
|
||||||
|
|
||||||
decrement_tags_except_requests = decrement_tags.reject {|tag| tag == "tagme" || tag.end_with?("_request")}
|
|
||||||
if !decrement_tags_except_requests.empty? && !CurrentUser.is_builder? && CurrentUser.created_at > 1.week.ago
|
|
||||||
self.errors.add(:updater_id, "must have an account at least 1 week old to remove tags")
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
increment_tags = tag_array - tag_array_was
|
increment_tags = tag_array - tag_array_was
|
||||||
if increment_tags.any?
|
if increment_tags.any?
|
||||||
Tag.increment_post_counts(increment_tags)
|
Tag.increment_post_counts(increment_tags)
|
||||||
@@ -398,24 +412,16 @@ class Post < ApplicationRecord
|
|||||||
set_tag_count(category, self.send("tag_count_#{category}") + 1)
|
set_tag_count(category, self.send("tag_count_#{category}") + 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_tag_counts(disable_cache = true)
|
def set_tag_counts
|
||||||
self.tag_count = 0
|
self.tag_count = 0
|
||||||
TagCategory.categories.each {|x| set_tag_count(x, 0)}
|
TagCategory.categories.each {|x| set_tag_count(x, 0)}
|
||||||
categories = Tag.categories_for(tag_array, :disable_caching => disable_cache)
|
categories = Tag.categories_for(tag_array, disable_caching: true)
|
||||||
categories.each_value do |category|
|
categories.each_value do |category|
|
||||||
self.tag_count += 1
|
self.tag_count += 1
|
||||||
inc_tag_count(TagCategory.reverse_mapping[category])
|
inc_tag_count(TagCategory.reverse_mapping[category])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_post_counts(post)
|
|
||||||
post.set_tag_counts(false)
|
|
||||||
if post.changes_saved?
|
|
||||||
args = Hash[TagCategory.categories.map {|x| ["tag_count_#{x}", post.send("tag_count_#{x}")]}].update(:tag_count => post.tag_count)
|
|
||||||
post.update_columns(args)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def merge_old_changes
|
def merge_old_changes
|
||||||
reset_tag_array_cache
|
reset_tag_array_cache
|
||||||
@removed_tags = []
|
@removed_tags = []
|
||||||
@@ -932,14 +938,7 @@ class Post < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update_children_on_destroy
|
def update_children_on_destroy
|
||||||
return unless children.present?
|
children.update(parent: nil)
|
||||||
|
|
||||||
eldest = children[0]
|
|
||||||
siblings = children[1..-1]
|
|
||||||
|
|
||||||
eldest.update(parent_id: nil)
|
|
||||||
Post.where(id: siblings).find_each { |p| p.update(parent_id: eldest.id) }
|
|
||||||
# Post.where(id: siblings).update(parent_id: eldest.id) # XXX rails 5
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_parent_on_save
|
def update_parent_on_save
|
||||||
@@ -949,7 +948,7 @@ class Post < ApplicationRecord
|
|||||||
Post.find(parent_id_before_last_save).update_has_children_flag if parent_id_before_last_save.present?
|
Post.find(parent_id_before_last_save).update_has_children_flag if parent_id_before_last_save.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def give_favorites_to_parent(options = {})
|
def give_favorites_to_parent
|
||||||
return if parent.nil?
|
return if parent.nil?
|
||||||
|
|
||||||
transaction do
|
transaction do
|
||||||
@@ -959,9 +958,7 @@ class Post < ApplicationRecord
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
unless options[:without_mod_action]
|
ModAction.log("moved favorites from post ##{id} to post ##{parent.id}", :post_move_favorites)
|
||||||
ModAction.log("moved favorites from post ##{id} to post ##{parent.id}", :post_move_favorites)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_visible_children?
|
def has_visible_children?
|
||||||
@@ -985,9 +982,8 @@ class Post < ApplicationRecord
|
|||||||
|
|
||||||
transaction do
|
transaction do
|
||||||
Post.without_timeout do
|
Post.without_timeout do
|
||||||
ModAction.log("permanently deleted post ##{id}", :post_permanent_delete)
|
ModAction.log("permanently deleted post ##{id} (md5=#{md5})", :post_permanent_delete)
|
||||||
|
|
||||||
give_favorites_to_parent
|
|
||||||
update_children_on_destroy
|
update_children_on_destroy
|
||||||
decrement_tag_post_counts
|
decrement_tag_post_counts
|
||||||
remove_from_all_pools
|
remove_from_all_pools
|
||||||
@@ -1009,29 +1005,22 @@ class Post < ApplicationRecord
|
|||||||
ModAction.log("unbanned post ##{id}", :post_unban)
|
ModAction.log("unbanned post ##{id}", :post_unban)
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete!(reason, options = {})
|
def delete!(reason, move_favorites: false, user: CurrentUser.user)
|
||||||
if is_status_locked?
|
transaction do
|
||||||
self.errors.add(:is_status_locked, "; cannot delete post")
|
automated = (user == User.system)
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
Post.transaction do
|
flags.pending.update!(status: :succeeded)
|
||||||
flag!(reason, is_deletion: true)
|
appeals.pending.update!(status: :rejected)
|
||||||
|
|
||||||
update(
|
flags.create!(reason: reason, is_deletion: true, creator: user, status: :succeeded)
|
||||||
is_deleted: true,
|
update!(is_deleted: true, is_pending: false, is_flagged: false)
|
||||||
is_pending: false,
|
|
||||||
is_flagged: false,
|
|
||||||
is_banned: is_banned || options[:ban] || has_tag?("banned_artist")
|
|
||||||
)
|
|
||||||
|
|
||||||
# XXX This must happen *after* the `is_deleted` flag is set to true (issue #3419).
|
# XXX This must happen *after* the `is_deleted` flag is set to true (issue #3419).
|
||||||
give_favorites_to_parent(options) if options[:move_favorites]
|
give_favorites_to_parent if move_favorites
|
||||||
|
|
||||||
is_automatic = (reason == "Unapproved in three days")
|
uploader.upload_limit.update_limit!(self, incremental: automated)
|
||||||
uploader.upload_limit.update_limit!(self, incremental: is_automatic)
|
|
||||||
|
|
||||||
unless options[:without_mod_action]
|
unless automated
|
||||||
ModAction.log("deleted post ##{id}, reason: #{reason}", :post_delete)
|
ModAction.log("deleted post ##{id}, reason: #{reason}", :post_delete)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1213,8 +1202,6 @@ class Post < ApplicationRecord
|
|||||||
def with_flag_stats
|
def with_flag_stats
|
||||||
relation = left_outer_joins(:flags).group(:id).select("posts.*")
|
relation = left_outer_joins(:flags).group(:id).select("posts.*")
|
||||||
relation = relation.select("COUNT(post_flags.id) AS flag_count")
|
relation = relation.select("COUNT(post_flags.id) AS flag_count")
|
||||||
relation = relation.select("COUNT(post_flags.id) FILTER (WHERE post_flags.is_resolved = TRUE) AS resolved_flag_count")
|
|
||||||
relation = relation.select("COUNT(post_flags.id) FILTER (WHERE post_flags.is_resolved = FALSE) AS unresolved_flag_count")
|
|
||||||
relation
|
relation
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1256,6 +1243,14 @@ class Post < ApplicationRecord
|
|||||||
relation
|
relation
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def with_queued_at
|
||||||
|
relation = group(:id)
|
||||||
|
relation = relation.left_outer_joins(:flags, :appeals)
|
||||||
|
relation = relation.select("posts.*")
|
||||||
|
relation = relation.select(Arel.sql("MAX(GREATEST(posts.created_at, post_flags.created_at, post_appeals.created_at)) AS queued_at"))
|
||||||
|
relation
|
||||||
|
end
|
||||||
|
|
||||||
def with_stats(tables)
|
def with_stats(tables)
|
||||||
return all if tables.empty?
|
return all if tables.empty?
|
||||||
|
|
||||||
|
|||||||
@@ -1,57 +1,38 @@
|
|||||||
class PostAppeal < ApplicationRecord
|
class PostAppeal < ApplicationRecord
|
||||||
class Error < StandardError; end
|
|
||||||
|
|
||||||
MAX_APPEALS_PER_DAY = 1
|
|
||||||
|
|
||||||
belongs_to :creator, :class_name => "User"
|
belongs_to :creator, :class_name => "User"
|
||||||
belongs_to :post
|
belongs_to :post
|
||||||
validates_presence_of :reason
|
|
||||||
validates :reason, presence: true, length: { in: 1..140 }
|
|
||||||
validate :validate_post_is_inactive
|
|
||||||
validate :validate_creator_is_not_limited
|
|
||||||
validates_uniqueness_of :creator_id, :scope => :post_id, :message => "have already appealed this post"
|
|
||||||
|
|
||||||
scope :resolved, -> { where(post: Post.undeleted.unflagged) }
|
validates :reason, length: { maximum: 140 }
|
||||||
scope :unresolved, -> { where(post: Post.deleted.or(Post.flagged)) }
|
validate :validate_post_is_appealable, on: :create
|
||||||
scope :recent, -> { where("post_appeals.created_at >= ?", 1.day.ago) }
|
validate :validate_creator_is_not_limited, on: :create
|
||||||
|
validates :creator, uniqueness: { scope: :post, message: "have already appealed this post" }, on: :create
|
||||||
|
|
||||||
|
enum status: {
|
||||||
|
pending: 0,
|
||||||
|
succeeded: 1,
|
||||||
|
rejected: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
scope :expired, -> { pending.where("post_appeals.created_at < ?", Danbooru.config.moderation_period.ago) }
|
||||||
|
|
||||||
module SearchMethods
|
module SearchMethods
|
||||||
def search(params)
|
def search(params)
|
||||||
q = super
|
q = super
|
||||||
q = q.search_attributes(params, :reason)
|
q = q.search_attributes(params, :reason, :status)
|
||||||
q = q.text_attribute_matches(:reason, params[:reason_matches])
|
q = q.text_attribute_matches(:reason, params[:reason_matches])
|
||||||
|
|
||||||
q = q.resolved if params[:is_resolved].to_s.truthy?
|
|
||||||
q = q.unresolved if params[:is_resolved].to_s.falsy?
|
|
||||||
|
|
||||||
q.apply_default_order(params)
|
q.apply_default_order(params)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
extend SearchMethods
|
extend SearchMethods
|
||||||
|
|
||||||
def resolved?
|
|
||||||
post.present? && !post.is_deleted? && !post.is_flagged?
|
|
||||||
end
|
|
||||||
|
|
||||||
def is_resolved
|
|
||||||
resolved?
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_creator_is_not_limited
|
def validate_creator_is_not_limited
|
||||||
if appeal_count_for_creator >= MAX_APPEALS_PER_DAY
|
errors[:creator] << "have reached your appeal limit" if creator.is_appeal_limited?
|
||||||
errors[:creator] << "can appeal at most #{MAX_APPEALS_PER_DAY} post a day"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_post_is_inactive
|
def validate_post_is_appealable
|
||||||
if resolved?
|
errors[:post] << "cannot be appealed" if post.is_status_locked? || !post.is_appealable?
|
||||||
errors[:post] << "is active"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def appeal_count_for_creator
|
|
||||||
creator.post_appeals.recent.count
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.searchable_includes
|
def self.searchable_includes
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class PostApproval < ApplicationRecord
|
|||||||
errors.add(:post, "is locked and cannot be approved")
|
errors.add(:post, "is locked and cannot be approved")
|
||||||
end
|
end
|
||||||
|
|
||||||
if post.status == "active"
|
if post.is_active?
|
||||||
errors.add(:post, "is already active and cannot be approved")
|
errors.add(:post, "is already active and cannot be approved")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -28,7 +28,9 @@ class PostApproval < ApplicationRecord
|
|||||||
def approve_post
|
def approve_post
|
||||||
is_undeletion = post.is_deleted
|
is_undeletion = post.is_deleted
|
||||||
|
|
||||||
post.flags.each(&:resolve!)
|
post.flags.pending.update!(status: :rejected)
|
||||||
|
post.appeals.pending.update!(status: :succeeded)
|
||||||
|
|
||||||
post.update(approver: user, is_flagged: false, is_pending: false, is_deleted: false)
|
post.update(approver: user, is_flagged: false, is_pending: false, is_deleted: false)
|
||||||
ModAction.log("undeleted post ##{post_id}", :post_undelete) if is_undeletion
|
ModAction.log("undeleted post ##{post_id}", :post_undelete) if is_undeletion
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class PostDisapproval < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def validate_disapproval
|
def validate_disapproval
|
||||||
if post.status == "active"
|
if post.is_active?
|
||||||
errors[:post] << "is already active and cannot be disapproved"
|
errors[:post] << "is already active and cannot be disapproved"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -30,10 +30,6 @@ class PostEvent
|
|||||||
event.try(:reason) || ""
|
event.try(:reason) || ""
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_resolved
|
|
||||||
event.try(:is_resolved) || false
|
|
||||||
end
|
|
||||||
|
|
||||||
def creator_id
|
def creator_id
|
||||||
event.try(:creator_id) || event.try(:user_id)
|
event.try(:creator_id) || event.try(:user_id)
|
||||||
end
|
end
|
||||||
@@ -42,6 +38,18 @@ class PostEvent
|
|||||||
event.try(:creator) || event.try(:user)
|
event.try(:creator) || event.try(:user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def status
|
||||||
|
if event.is_a?(PostApproval)
|
||||||
|
"approved"
|
||||||
|
elsif (event.is_a?(PostAppeal) && event.succeeded?) || (event.is_a?(PostFlag) && event.rejected?)
|
||||||
|
"approved"
|
||||||
|
elsif (event.is_a?(PostAppeal) && event.rejected?) || (event.is_a?(PostFlag) && event.succeeded?)
|
||||||
|
"deleted"
|
||||||
|
else
|
||||||
|
"pending"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def is_creator_visible?(user = CurrentUser.user)
|
def is_creator_visible?(user = CurrentUser.user)
|
||||||
case event
|
case event
|
||||||
when PostAppeal, PostApproval
|
when PostAppeal, PostApproval
|
||||||
@@ -57,7 +65,7 @@ class PostEvent
|
|||||||
"creator_id": nil,
|
"creator_id": nil,
|
||||||
"created_at": nil,
|
"created_at": nil,
|
||||||
"reason": nil,
|
"reason": nil,
|
||||||
"is_resolved": nil,
|
"status": nil,
|
||||||
"type": nil
|
"type": nil
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,24 +6,26 @@ class PostFlag < ApplicationRecord
|
|||||||
REJECTED = "Unapproved in three days after returning to moderation queue%"
|
REJECTED = "Unapproved in three days after returning to moderation queue%"
|
||||||
end
|
end
|
||||||
|
|
||||||
COOLDOWN_PERIOD = 3.days
|
|
||||||
|
|
||||||
belongs_to :creator, class_name: "User"
|
belongs_to :creator, class_name: "User"
|
||||||
belongs_to :post
|
belongs_to :post
|
||||||
validates :reason, presence: true, length: { in: 1..140 }
|
validates :reason, presence: true, length: { in: 1..140 }
|
||||||
validate :validate_creator_is_not_limited, on: :create
|
validate :validate_creator_is_not_limited, on: :create
|
||||||
validate :validate_post
|
validate :validate_post, on: :create
|
||||||
validates_uniqueness_of :creator_id, :scope => :post_id, :on => :create, :unless => :is_deletion, :message => "have already flagged this post"
|
validates_uniqueness_of :creator_id, scope: :post_id, on: :create, unless: :is_deletion, message: "have already flagged this post"
|
||||||
before_save :update_post
|
before_save :update_post
|
||||||
attr_accessor :is_deletion
|
attr_accessor :is_deletion
|
||||||
|
|
||||||
|
enum status: {
|
||||||
|
pending: 0,
|
||||||
|
succeeded: 1,
|
||||||
|
rejected: 2
|
||||||
|
}
|
||||||
|
|
||||||
scope :by_users, -> { where.not(creator: User.system) }
|
scope :by_users, -> { where.not(creator: User.system) }
|
||||||
scope :by_system, -> { where(creator: User.system) }
|
scope :by_system, -> { where(creator: User.system) }
|
||||||
scope :in_cooldown, -> { by_users.where("created_at >= ?", COOLDOWN_PERIOD.ago) }
|
scope :in_cooldown, -> { by_users.where("created_at >= ?", Danbooru.config.moderation_period.ago) }
|
||||||
scope :resolved, -> { where(is_resolved: true) }
|
scope :expired, -> { pending.where("post_flags.created_at < ?", Danbooru.config.moderation_period.ago) }
|
||||||
scope :unresolved, -> { where(is_resolved: false) }
|
scope :active, -> { pending.or(rejected.in_cooldown) }
|
||||||
scope :recent, -> { where("post_flags.created_at >= ?", 1.day.ago) }
|
|
||||||
scope :old, -> { where("post_flags.created_at <= ?", 3.days.ago) }
|
|
||||||
|
|
||||||
module SearchMethods
|
module SearchMethods
|
||||||
def creator_matches(creator, searcher)
|
def creator_matches(creator, searcher)
|
||||||
@@ -56,7 +58,7 @@ class PostFlag < ApplicationRecord
|
|||||||
def search(params)
|
def search(params)
|
||||||
q = super
|
q = super
|
||||||
|
|
||||||
q = q.search_attributes(params, :is_resolved, :reason)
|
q = q.search_attributes(params, :reason, :status)
|
||||||
q = q.text_attribute_matches(:reason, params[:reason_matches])
|
q = q.text_attribute_matches(:reason, params[:reason_matches])
|
||||||
|
|
||||||
if params[:creator_id].present?
|
if params[:creator_id].present?
|
||||||
@@ -93,36 +95,18 @@ class PostFlag < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def validate_creator_is_not_limited
|
def validate_creator_is_not_limited
|
||||||
return if is_deletion
|
errors[:creator] << "have reached your flag limit" if creator.is_flag_limited? && !is_deletion
|
||||||
|
|
||||||
if creator.can_approve_posts?
|
|
||||||
# do nothing
|
|
||||||
elsif creator.created_at > 1.week.ago
|
|
||||||
errors[:creator] << "cannot flag within the first week of sign up"
|
|
||||||
elsif creator.is_gold? && flag_count_for_creator >= 10
|
|
||||||
errors[:creator] << "can flag 10 posts a day"
|
|
||||||
elsif !creator.is_gold? && flag_count_for_creator >= 1
|
|
||||||
errors[:creator] << "can flag 1 post a day"
|
|
||||||
end
|
|
||||||
|
|
||||||
flag = post.flags.in_cooldown.last
|
|
||||||
if flag.present?
|
|
||||||
errors[:post] << "cannot be flagged more than once every #{COOLDOWN_PERIOD.inspect} (last flagged: #{flag.created_at.to_s(:long)})"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_post
|
def validate_post
|
||||||
errors[:post] << "is pending and cannot be flagged" if post.is_pending? && !is_deletion
|
errors[:post] << "is pending and cannot be flagged" if post.is_pending? && !is_deletion
|
||||||
|
errors[:post] << "is deleted and cannot be flagged" if post.is_deleted? && !is_deletion
|
||||||
errors[:post] << "is locked and cannot be flagged" if post.is_status_locked?
|
errors[:post] << "is locked and cannot be flagged" if post.is_status_locked?
|
||||||
errors[:post] << "is deleted" if post.is_deleted?
|
|
||||||
end
|
|
||||||
|
|
||||||
def resolve!
|
flag = post.flags.in_cooldown.last
|
||||||
update_column(:is_resolved, true)
|
if !is_deletion && flag.present?
|
||||||
end
|
errors[:post] << "cannot be flagged more than once every #{Danbooru.config.moderation_period.inspect} (last flagged: #{flag.created_at.to_s(:long)})"
|
||||||
|
end
|
||||||
def flag_count_for_creator
|
|
||||||
creator.post_flags.recent.count
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def uploader_id
|
def uploader_id
|
||||||
|
|||||||
@@ -15,12 +15,6 @@ class PostVersion < ApplicationRecord
|
|||||||
|
|
||||||
establish_connection database_url if enabled?
|
establish_connection database_url if enabled?
|
||||||
|
|
||||||
def self.check_for_retry(msg)
|
|
||||||
if msg =~ /can't get socket descriptor/ && msg =~ /post_versions/
|
|
||||||
connection.reconnect!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module SearchMethods
|
module SearchMethods
|
||||||
def changed_tags_include(tag)
|
def changed_tags_include(tag)
|
||||||
where_array_includes_all(:added_tags, [tag]).or(where_array_includes_all(:removed_tags, [tag]))
|
where_array_includes_all(:added_tags, [tag]).or(where_array_includes_all(:removed_tags, [tag]))
|
||||||
@@ -32,6 +26,10 @@ class PostVersion < ApplicationRecord
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def changed_tags_include_any(tags)
|
||||||
|
where_array_includes_any(:added_tags, tags).or(where_array_includes_any(:removed_tags, tags))
|
||||||
|
end
|
||||||
|
|
||||||
def tag_matches(string)
|
def tag_matches(string)
|
||||||
tag = string.match(/\S+/)[0]
|
tag = string.match(/\S+/)[0]
|
||||||
return all if tag.nil?
|
return all if tag.nil?
|
||||||
@@ -47,6 +45,14 @@ class PostVersion < ApplicationRecord
|
|||||||
q = q.changed_tags_include_all(params[:changed_tags].scan(/[^[:space:]]+/))
|
q = q.changed_tags_include_all(params[:changed_tags].scan(/[^[:space:]]+/))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if params[:all_changed_tags]
|
||||||
|
q = q.changed_tags_include_all(params[:all_changed_tags].scan(/[^[:space:]]+/))
|
||||||
|
end
|
||||||
|
|
||||||
|
if params[:any_changed_tags]
|
||||||
|
q = q.changed_tags_include_any(params[:any_changed_tags].scan(/[^[:space:]]+/))
|
||||||
|
end
|
||||||
|
|
||||||
if params[:tag_matches]
|
if params[:tag_matches]
|
||||||
q = q.tag_matches(params[:tag_matches])
|
q = q.tag_matches(params[:tag_matches])
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ class Tag < ApplicationRecord
|
|||||||
validates :name, tag_name: true, on: :name
|
validates :name, tag_name: true, on: :name
|
||||||
validates_inclusion_of :category, in: TagCategory.category_ids
|
validates_inclusion_of :category, in: TagCategory.category_ids
|
||||||
|
|
||||||
before_save :update_category_cache, if: :category_changed?
|
after_save :update_category_cache, if: :saved_change_to_category?
|
||||||
before_save :update_category_post_counts, if: :category_changed?
|
after_save :update_category_post_counts, if: :saved_change_to_category?
|
||||||
|
|
||||||
scope :empty, -> { where("tags.post_count <= 0") }
|
scope :empty, -> { where("tags.post_count <= 0") }
|
||||||
scope :nonempty, -> { where("tags.post_count > 0") }
|
scope :nonempty, -> { where("tags.post_count > 0") }
|
||||||
@@ -163,12 +163,10 @@ class Tag < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update_category_post_counts
|
def update_category_post_counts
|
||||||
Post.with_timeout(30_000, nil, :tags => name) do
|
Post.with_timeout(30_000) do
|
||||||
Post.raw_tag_match(name).where("true /* Tag#update_category_post_counts */").find_each do |post|
|
Post.raw_tag_match(name).find_each do |post|
|
||||||
post.reload
|
post.set_tag_counts
|
||||||
post.set_tag_counts(false)
|
post.save!
|
||||||
args = TagCategory.categories.map {|x| ["tag_count_#{x}", post.send("tag_count_#{x}")]}.to_h.update(:tag_count => post.tag_count)
|
|
||||||
Post.where(:id => post.id).update_all(args)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -265,6 +265,10 @@ class User < ApplicationRecord
|
|||||||
name.match?(/\Auser_[0-9]+~*\z/)
|
name.match?(/\Auser_[0-9]+~*\z/)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def is_restricted?
|
||||||
|
requires_verification? && !is_verified?
|
||||||
|
end
|
||||||
|
|
||||||
def is_anonymous?
|
def is_anonymous?
|
||||||
level == Levels::ANONYMOUS
|
level == Levels::ANONYMOUS
|
||||||
end
|
end
|
||||||
@@ -343,6 +347,26 @@ class User < ApplicationRecord
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def is_appeal_limited?
|
||||||
|
return false if can_upload_free?
|
||||||
|
upload_limit.free_upload_slots < UploadLimit::APPEAL_COST
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_flag_limited?
|
||||||
|
return false if has_unlimited_flags?
|
||||||
|
post_flags.active.count >= 5
|
||||||
|
end
|
||||||
|
|
||||||
|
# Flags are unlimited if you're an approver or you have at least 30 flags
|
||||||
|
# in the last 3 months and have a 70% flag success rate.
|
||||||
|
def has_unlimited_flags?
|
||||||
|
return true if can_approve_posts?
|
||||||
|
|
||||||
|
recent_flags = post_flags.where("created_at >= ?", 3.months.ago)
|
||||||
|
flag_ratio = recent_flags.succeeded.count / recent_flags.count.to_f
|
||||||
|
recent_flags.count >= 30 && flag_ratio >= 0.70
|
||||||
|
end
|
||||||
|
|
||||||
def upload_limit
|
def upload_limit
|
||||||
@upload_limit ||= UploadLimit.new(self)
|
@upload_limit ||= UploadLimit.new(self)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,6 +9,15 @@ class EmailAddressPolicy < ApplicationPolicy
|
|||||||
end
|
end
|
||||||
|
|
||||||
def verify?
|
def verify?
|
||||||
record.valid_key?(request.params[:email_verification_key])
|
if request.params[:email_verification_key].present?
|
||||||
|
record.valid_key?(request.params[:email_verification_key])
|
||||||
|
else
|
||||||
|
record.user_id == user.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_confirmation?
|
||||||
|
# XXX record is a user, not the email address.
|
||||||
|
record.id == user.id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ class PostPolicy < ApplicationPolicy
|
|||||||
user.is_approver? && !record.is_deleted?
|
user.is_approver? && !record.is_deleted?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def destroy?
|
||||||
|
delete?
|
||||||
|
end
|
||||||
|
|
||||||
def ban?
|
def ban?
|
||||||
user.is_approver? && !record.is_banned?
|
user.is_approver? && !record.is_banned?
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ class PostPresenter
|
|||||||
attr_reader :pool, :next_post_in_pool
|
attr_reader :pool, :next_post_in_pool
|
||||||
delegate :tag_list_html, :split_tag_list_html, :split_tag_list_text, :inline_tag_list_html, to: :tag_set_presenter
|
delegate :tag_list_html, :split_tag_list_html, :split_tag_list_text, :inline_tag_list_html, to: :tag_set_presenter
|
||||||
|
|
||||||
def self.preview(post, options = {})
|
def self.preview(post, show_deleted: false, tags: "", **options)
|
||||||
if post.nil?
|
if post.nil?
|
||||||
return "<em>none</em>".html_safe
|
return "<em>none</em>".html_safe
|
||||||
end
|
end
|
||||||
|
|
||||||
if !options[:show_deleted] && post.is_deleted? && options[:tags] !~ /status:(?:all|any|deleted|banned)/
|
if post.is_deleted? && !show_deleted
|
||||||
return ""
|
return ""
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -31,8 +31,8 @@ class PostPresenter
|
|||||||
locals[:link_target] = options[:link_target] || post
|
locals[:link_target] = options[:link_target] || post
|
||||||
|
|
||||||
locals[:link_params] = {}
|
locals[:link_params] = {}
|
||||||
if options[:tags].present? && !CurrentUser.is_anonymous?
|
if tags.present? && !CurrentUser.is_anonymous?
|
||||||
locals[:link_params]["q"] = options[:tags]
|
locals[:link_params]["q"] = tags
|
||||||
end
|
end
|
||||||
if options[:pool_id]
|
if options[:pool_id]
|
||||||
locals[:link_params]["pool_id"] = options[:pool_id]
|
locals[:link_params]["pool_id"] = options[:pool_id]
|
||||||
@@ -116,6 +116,8 @@ class PostPresenter
|
|||||||
"data-pools" => post.pool_string,
|
"data-pools" => post.pool_string,
|
||||||
"data-approver-id" => post.approver_id,
|
"data-approver-id" => post.approver_id,
|
||||||
"data-rating" => post.rating,
|
"data-rating" => post.rating,
|
||||||
|
"data-large-width" => post.large_image_width,
|
||||||
|
"data-large-height" => post.large_image_height,
|
||||||
"data-width" => post.image_width,
|
"data-width" => post.image_width,
|
||||||
"data-height" => post.image_height,
|
"data-height" => post.image_height,
|
||||||
"data-flags" => post.status_flags,
|
"data-flags" => post.status_flags,
|
||||||
|
|||||||
@@ -35,7 +35,10 @@ class TagSetPresenter
|
|||||||
typetags = tags_for_category(category)
|
typetags = tags_for_category(category)
|
||||||
|
|
||||||
if typetags.any?
|
if typetags.any?
|
||||||
html << TagCategory.header_mapping[category] if headers
|
if headers
|
||||||
|
html << %{<h3 class="#{category}-tag-list">#{category.capitalize.pluralize(typetags.size)}</h3>}
|
||||||
|
end
|
||||||
|
|
||||||
html << %{<ul class="#{category}-tag-list">}
|
html << %{<ul class="#{category}-tag-list">}
|
||||||
typetags.each do |tag|
|
typetags.each do |tag|
|
||||||
html << build_list_item(tag, current_query: current_query, show_extra_links: show_extra_links, name_only: name_only, humanize_tags: humanize_tags)
|
html << build_list_item(tag, current_query: current_query, show_extra_links: show_extra_links, name_only: name_only, humanize_tags: humanize_tags)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div style="margin: 1em 0;">
|
<div style="margin: 1em 0;">
|
||||||
<h2>Script</h2>
|
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<%= format_text @bulk_update_request.processor.to_dtext %>
|
<%= format_text @bulk_update_request.processor.to_dtext %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,12 +17,12 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
data-is-voted="<%= comment.voted_by?(CurrentUser.user) %>">
|
data-is-voted="<%= comment.voted_by?(CurrentUser.user) %>">
|
||||||
<div class="author">
|
<div class="author">
|
||||||
<h4>
|
<div class="author-name">
|
||||||
<%= link_to_user comment.creator %>
|
<%= link_to_user comment.creator %>
|
||||||
<% if comment.is_deleted? %>
|
<% if comment.is_deleted? %>
|
||||||
(deleted)
|
(deleted)
|
||||||
<% end %>
|
<% end %>
|
||||||
</h4>
|
</div>
|
||||||
<%= link_to time_ago_in_words_tagged(comment.created_at), post_path(comment.post, anchor: "comment_#{comment.id}"), class: "message-timestamp" %>
|
<%= link_to time_ago_in_words_tagged(comment.created_at), post_path(comment.post, anchor: "comment_#{comment.id}"), class: "message-timestamp" %>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
<div id="c-dmails">
|
<div id="c-dmails">
|
||||||
<div id="a-show">
|
<div id="a-show">
|
||||||
<div class="dmail">
|
<div class="dmail">
|
||||||
<h1>Show Message</h1>
|
<h1><%= @dmail.title %></h1>
|
||||||
<h2><%= @dmail.title %></h2>
|
|
||||||
|
|
||||||
<ul style="margin-bottom: 1em;">
|
<ul style="margin-bottom: 1em;">
|
||||||
<li><strong>Sender</strong>: <%= link_to_user @dmail.from %></li>
|
<li><strong>Sender</strong>: <%= link_to_user @dmail.from %></li>
|
||||||
@@ -13,7 +12,6 @@
|
|||||||
<li><strong>Date</strong>: <%= compact_time(@dmail.created_at) %></li>
|
<li><strong>Date</strong>: <%= compact_time(@dmail.created_at) %></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>Body</h3>
|
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<%= format_text(@dmail.body) %>
|
<%= format_text(@dmail.body) %>
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,30 @@
|
|||||||
<% page_title "Change Email" %>
|
|
||||||
|
|
||||||
<div id="c-emails">
|
<div id="c-emails">
|
||||||
<div id="a-edit">
|
<div id="a-edit" class="fixed-width-container">
|
||||||
<h1>Change Email</h1>
|
<% if @user.email_address.present? %>
|
||||||
|
<% page_title "Change Email" %>
|
||||||
|
<h1>Change Email</h1>
|
||||||
|
|
||||||
<p>You must confirm your password in order to change your email address.</p>
|
<p>Your current email address is <strong><%= @user.email_address.address %></strong>.
|
||||||
|
You must re-enter your password in order to update your email address.</p>
|
||||||
|
<% else %>
|
||||||
|
<% page_title "Add Email" %>
|
||||||
|
<h1>Add Email</h1>
|
||||||
|
|
||||||
|
<p>Add a new email address below. You must re-enter your password in
|
||||||
|
order to update your email address.</p>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if @user.is_restricted? %>
|
||||||
|
<p>Your account is restricted because you signed up from a proxy or VPN.
|
||||||
|
You can still use the site, but you won't be able to leave comments, edit
|
||||||
|
tags, or upload posts until you add a verified email address to your
|
||||||
|
account. Disposable or throwaway email addresses can't be used to verify
|
||||||
|
your account.</p>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<%= edit_form_for(@user, url: user_email_path(@user)) do |f| %>
|
<%= edit_form_for(@user, url: user_email_path(@user)) do |f| %>
|
||||||
|
<%= f.input :email, as: :email, label: "New Email", input_html: { value: "" } %>
|
||||||
<%= f.input :password %>
|
<%= f.input :password %>
|
||||||
<%= f.input :email, as: :email, input_html: { value: "" } %>
|
|
||||||
<%= f.submit "Save" %>
|
<%= f.submit "Save" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
20
app/views/emails/verify.html.erb
Normal file
20
app/views/emails/verify.html.erb
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<% page_title "Verify account" %>
|
||||||
|
|
||||||
|
<div id="c-emails">
|
||||||
|
<div id="a-verify" class="fixed-width-container">
|
||||||
|
<h1>Verify account</h1>
|
||||||
|
|
||||||
|
<% if @user.is_restricted? %>
|
||||||
|
<p>Your account is restricted because you signed up from a VPN or proxy.
|
||||||
|
You can still use the site, but you won't be able to leave comments, edit
|
||||||
|
tags, or upload posts until you verify your account.</p>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<p>Click below to send an email to <strong><%= @email_address.address %></strong>
|
||||||
|
to verify your account.</p>
|
||||||
|
|
||||||
|
<%= edit_form_for(@user, method: :post, url: send_confirmation_user_email_path(@user)) do |f| %>
|
||||||
|
<%= f.submit "Send confirmation email" %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -6,12 +6,12 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
data-creator="<%= forum_post.creator.name %>">
|
data-creator="<%= forum_post.creator.name %>">
|
||||||
<div class="author">
|
<div class="author">
|
||||||
<h4>
|
<div class="author-name">
|
||||||
<%= link_to_user forum_post.creator %>
|
<%= link_to_user forum_post.creator %>
|
||||||
<% if forum_post.is_deleted? %>
|
<% if forum_post.is_deleted? %>
|
||||||
(deleted)
|
(deleted)
|
||||||
<% end %>
|
<% end %>
|
||||||
</h4>
|
</div>
|
||||||
<%= link_to time_ago_in_words_tagged(forum_post.created_at), forum_post, class: "message-timestamp" %>
|
<%= link_to time_ago_in_words_tagged(forum_post.created_at), forum_post, class: "message-timestamp" %>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
|
$("article[data-forum-post-id=<%= @forum_post.id %>] div.author div.author-name").append(" (deleted)");
|
||||||
$("article[data-forum-post-id=<%= @forum_post.id %>] div.author h4").append(" (deleted)");
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
<%= render "news_updates/listing" %>
|
<%= render "news_updates/listing" %>
|
||||||
|
|
||||||
<header id="top">
|
<header id="top">
|
||||||
<h1 id="app-name-header"><%= link_to Danbooru.config.app_name, "/" %></h1>
|
<%= link_to Danbooru.config.app_name, root_path, id: "app-name-header", class: "heading" %>
|
||||||
|
|
||||||
<div id="maintoggle" class="mobile-only">
|
<div id="maintoggle" class="mobile-only">
|
||||||
<a href="#"><i id="maintoggle-on" class="fas fa-bars"></i></a>
|
<a href="#"><i id="maintoggle-on" class="fas fa-bars"></i></a>
|
||||||
@@ -68,6 +68,8 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div id="page">
|
<div id="page">
|
||||||
|
<%= render "users/verification_notice" %>
|
||||||
|
|
||||||
<% if !CurrentUser.is_anonymous? && !CurrentUser.is_gold? && cookies[:hide_upgrade_account_notice].blank? && params[:action] != "upgrade_information" %>
|
<% if !CurrentUser.is_anonymous? && !CurrentUser.is_gold? && cookies[:hide_upgrade_account_notice].blank? && params[:action] != "upgrade_information" %>
|
||||||
<%= render "users/upgrade_notice" %>
|
<%= render "users/upgrade_notice" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -88,6 +90,11 @@
|
|||||||
<%= yield :layout %>
|
<%= yield :layout %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="tooltips">
|
||||||
|
<div id="post-tooltips"></div>
|
||||||
|
<div id="user-tooltips"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script type="application/javascript">
|
<script type="application/javascript">
|
||||||
if (typeof window.Danbooru !== "object") {
|
if (typeof window.Danbooru !== "object") {
|
||||||
window.Danbooru = {};
|
window.Danbooru = {};
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
<table class="striped">
|
|
||||||
<caption>Appeals</caption>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Post</th>
|
|
||||||
<th>User</th>
|
|
||||||
<th>Flags</th>
|
|
||||||
<th>Appeals</th>
|
|
||||||
<th>Score</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<% @dashboard.appeals.each do |post| %>
|
|
||||||
<tr>
|
|
||||||
<td><%= PostPresenter.preview(post, show_deleted: true) %></td>
|
|
||||||
<td><%= mod_link_to_user post.uploader, :negative %></td>
|
|
||||||
<td><%= render "post_flags/reasons", flags: post.flags %></td>
|
|
||||||
<td><%= render "post_appeals/reasons", appeals: post.appeals %></td>
|
|
||||||
<td><%= post.score %></td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<p><%= link_to "View all appeals", post_appeals_path %></p>
|
|
||||||
@@ -11,14 +11,12 @@
|
|||||||
<div id="column-left" class="column column-expand">
|
<div id="column-left" class="column column-expand">
|
||||||
<div class="activity"><%= render "activity_upload" %></div>
|
<div class="activity"><%= render "activity_upload" %></div>
|
||||||
<div class="activity"><%= render "activity_note" %></div>
|
<div class="activity"><%= render "activity_note" %></div>
|
||||||
<div class="activity"><%= render "activity_tag" %></div>
|
|
||||||
<div class="activity"><%= render "activity_wiki_page" %></div>
|
<div class="activity"><%= render "activity_wiki_page" %></div>
|
||||||
<div class="activity"><%= render "activity_artist" %></div>
|
<div class="activity"><%= render "activity_artist" %></div>
|
||||||
<div class="activity"><%= render "activity_comment" %></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="column-right" class="column column-expand">
|
<div id="column-right" class="column column-expand">
|
||||||
<div class="activity"><%= render "activity_appeal" %></div>
|
<div class="activity"><%= render "activity_comment" %></div>
|
||||||
<div class="activity"><%= render "activity_user_feedback" %></div>
|
<div class="activity"><%= render "activity_user_feedback" %></div>
|
||||||
<div class="activity"><%= render "activity_mod_action" %></div>
|
<div class="activity"><%= render "activity_mod_action" %></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
<h1>Delete Post</h1>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<%= PostPresenter.preview(@post, show_deleted: true) %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= form_tag(delete_moderator_post_post_path, :style => "clear: both;", :class => "simple_form") do %>
|
|
||||||
<% if @post.parent_id %>
|
|
||||||
<div class="input">
|
|
||||||
<label for="move_favorites">
|
|
||||||
<%= check_box_tag "move_favorites" %>
|
|
||||||
Move favorites to parent?
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<p style="font-weight: bold;">Note: If the reason you are planning to delete this post is because it is from a banned artist, please <%= link_to "ban", confirm_ban_moderator_post_post_path(@post) %> this post instead of deleting it.</p>
|
|
||||||
|
|
||||||
<div class="input">
|
|
||||||
<label for="reason">Reason</label>
|
|
||||||
<%= text_area_tag "reason" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= submit_tag "Delete" %>
|
|
||||||
<%= submit_tag "Cancel" %>
|
|
||||||
<% end %>
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<%= content_tag(:div, { id: "post-#{post.id}", class: ["post", "mod-queue-preview", "column-container", *PostPresenter.preview_class(post)].join(" ") }.merge(PostPresenter.data_attributes(post))) do %>
|
<%= content_tag(:div, { id: "post-#{post.id}", class: ["post", "mod-queue-preview", "column-container", *PostPresenter.preview_class(post)].join(" ") }.merge(PostPresenter.data_attributes(post))) do %>
|
||||||
<aside class="column column-shrink">
|
<aside class="column column-shrink">
|
||||||
<%= PostPresenter.preview(post, size: true) %>
|
<%= PostPresenter.preview(post, size: true, show_deleted: true) %>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<section class="column column-expand">
|
<section class="column column-expand">
|
||||||
@@ -64,14 +64,14 @@
|
|||||||
<% if post.is_flagged? %>
|
<% if post.is_flagged? %>
|
||||||
<span class="info">
|
<span class="info">
|
||||||
<strong>Flagged</strong>
|
<strong>Flagged</strong>
|
||||||
<%= render "post_flags/reasons", flags: post.flags %>
|
<%= render "post_flags/reasons", flag: post.flags.select(&:pending?).last %>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if (post.is_flagged? || post.is_deleted?) && post.appeals.any? %>
|
<% if post.is_appealed? %>
|
||||||
<span class="info">
|
<span class="info">
|
||||||
<strong>Appeals</strong>
|
<strong>Appeals</strong>
|
||||||
<%= render "post_appeals/reasons", appeals: post.appeals %>
|
<%= render "post_appeals/reasons", appeal: post.appeals.select(&:pending?).last %>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<%= render "posts/partials/index/blacklist" %>
|
<%= render "posts/partials/index/blacklist" %>
|
||||||
|
|
||||||
<p id="modqueue-sidebar-status" class="sidebar-section">
|
<p id="modqueue-sidebar-status" class="sidebar-section">
|
||||||
<h6>Status</h6>
|
<h2>Status</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to "status:pending", modqueue_index_path(search: { tags: "status:pending" }) %>
|
<%= link_to "status:pending", modqueue_index_path(search: { tags: "status:pending" }) %>
|
||||||
@@ -22,6 +22,10 @@
|
|||||||
<%= link_to "status:flagged", modqueue_index_path(search: { tags: "status:flagged" }) %>
|
<%= link_to "status:flagged", modqueue_index_path(search: { tags: "status:flagged" }) %>
|
||||||
<span class="post-count"><%= @flagged_post_count %></span>
|
<span class="post-count"><%= @flagged_post_count %></span>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<%= link_to "status:appealed", modqueue_index_path(search: { tags: "status:appealed" }) %>
|
||||||
|
<span class="post-count"><%= @appealed_post_count %></span>
|
||||||
|
</li>
|
||||||
|
|
||||||
<% @disapproval_reasons.each do |reason, count| %>
|
<% @disapproval_reasons.each do |reason, count| %>
|
||||||
<li>
|
<li>
|
||||||
@@ -33,7 +37,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p id="modqueue-sidebar-uploaders" class="sidebar-section">
|
<p id="modqueue-sidebar-uploaders" class="sidebar-section">
|
||||||
<h6>Uploaders</h6>
|
<h2>Uploaders</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<% @uploaders.each do |uploader, count| %>
|
<% @uploaders.each do |uploader, count| %>
|
||||||
<li>
|
<li>
|
||||||
@@ -46,7 +50,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p id="modqueue-sidebar-tags" class="sidebar-section">
|
<p id="modqueue-sidebar-tags" class="sidebar-section">
|
||||||
<h6>Tags</h6>
|
<h2>Tags</h2>
|
||||||
|
|
||||||
<%= render "tag_list", tags: @artist_tags %>
|
<%= render "tag_list", tags: @artist_tags %>
|
||||||
<%= render "tag_list", tags: @copyright_tags %>
|
<%= render "tag_list", tags: @copyright_tags %>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
<%= edit_form_for(post_appeal, format: :js, remote: true) do |f| %>
|
<%= edit_form_for(post_appeal, format: :js, remote: true) do |f| %>
|
||||||
<%= f.hidden_field :post_id %>
|
<%= f.hidden_field :post_id %>
|
||||||
<%= f.input :reason, as: :dtext, inline: true %>
|
<%= f.input :reason, as: :dtext, inline: true, placeholder: "Optional" %>
|
||||||
<%= dtext_preview_button "post_appeal_reason" %>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<ul class="post-appeal-reasons list-bulleted">
|
<ul class="post-appeal-reason list-bulleted">
|
||||||
<% appeals.each do |appeal| %>
|
<li>
|
||||||
<li class="post-appeal-reason">
|
<% if appeal.reason.present? %>
|
||||||
<span class="prose"><%= format_text(appeal.reason, inline: true) %></span>
|
<span class="prose"><%= format_text(appeal.reason, inline: true) %></span>
|
||||||
- <%= link_to_user(appeal.creator) %>
|
<% else %>
|
||||||
- <%= time_ago_in_words_tagged(appeal.created_at) %>
|
<span class="prose"><em>no reason</em></span>
|
||||||
</li>
|
<% end %>
|
||||||
<% end %>
|
|
||||||
|
(<%= link_to_user(appeal.creator) %>, <%= time_ago_in_words_tagged(appeal.created_at) %>)
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
<%= f.input :post_tags_match, label: "Tags", input_html: { value: params[:search][:post_tags_match], data: { autocomplete: "tag-query" } } %>
|
<%= f.input :post_tags_match, label: "Tags", input_html: { value: params[:search][:post_tags_match], data: { autocomplete: "tag-query" } } %>
|
||||||
<%= f.input :post_id, label: "Post ID", input_html: { value: params[:search][:post_id] } %>
|
<%= f.input :post_id, label: "Post ID", input_html: { value: params[:search][:post_id] } %>
|
||||||
<%= f.input :creator_name, label: "Creator", input_html: { value: params[:search][:creator_name], data: { autocomplete: "user" } } %>
|
<%= f.input :creator_name, label: "Creator", input_html: { value: params[:search][:creator_name], data: { autocomplete: "user" } } %>
|
||||||
<%= f.input :is_resolved, label: "Resolved?", collection: [["Yes", true], ["No", false]], include_blank: true, selected: params[:search][:is_resolved] %>
|
<%= f.input :status, collection: PostAppeal.statuses, include_blank: true, selected: params[:search][:status] %>
|
||||||
<%= f.submit "Search" %>
|
<%= f.submit "Search" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
<% t.column "Appeals", width: "1%" do |post_appeal| %>
|
<% t.column "Appeals", width: "1%" do |post_appeal| %>
|
||||||
<%= link_to post_appeal.post.appeals.size, post_appeals_path(search: { post_id: post_appeal.post_id }) %>
|
<%= link_to post_appeal.post.appeals.size, post_appeals_path(search: { post_id: post_appeal.post_id }) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% t.column "Resolved?", width: "5%" do |post_appeal| %>
|
<% t.column "Status", width: "5%" do |post_appeal| %>
|
||||||
<%= link_to post_appeal.is_resolved.to_s, post_appeals_path(search: params[:search].merge(is_resolved: post_appeal.is_resolved)) %>
|
<%= link_to post_appeal.status, post_appeals_path(search: { status: post_appeal.status }) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% t.column "Uploaded", width: "15%" do |post_appeal| %>
|
<% t.column "Uploaded", width: "15%" do |post_appeal| %>
|
||||||
<%= compact_time post_appeal.post.created_at %>
|
<%= compact_time post_appeal.post.created_at %>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<% if (CurrentUser.can_approve_posts? || post.created_at < 3.days.ago) && disapprovals.length > 0 %>
|
<% if (CurrentUser.can_approve_posts? || post.created_at < Danbooru.config.moderation_period.ago) && disapprovals.length > 0 %>
|
||||||
<% if disapprovals.map(&:reason).grep("breaks_rules").count > 0 %>
|
<% if disapprovals.map(&:reason).grep("breaks_rules").count > 0 %>
|
||||||
(breaks rules: <%= disapprovals.map(&:reason).grep("breaks_rules").count %>)
|
(breaks rules: <%= disapprovals.map(&:reason).grep("breaks_rules").count %>)
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<% if (CurrentUser.can_approve_posts? || post.created_at < 3.days.ago) && disapprovals.length > 0 %>
|
<% if (CurrentUser.can_approve_posts? || post.created_at < Danbooru.config.moderation_period.ago) && disapprovals.length > 0 %>
|
||||||
<p>
|
<p>
|
||||||
It has been reviewed by <%= pluralize disapprovals.length, "approver" %>.
|
It has been reviewed by <%= pluralize disapprovals.length, "approver" %>.
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,14 @@
|
|||||||
|
|
||||||
<%= table_for @events, class: "striped autofit", width: "100%" do |t| %>
|
<%= table_for @events, class: "striped autofit", width: "100%" do |t| %>
|
||||||
<% t.column :type_name, name: "Type" %>
|
<% t.column :type_name, name: "Type" %>
|
||||||
|
<% t.column "Description", td: { class: "col-expand" } do |event| %>
|
||||||
|
<div class="prose">
|
||||||
|
<%= format_text event.reason %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% t.column "Status" do |event| %>
|
||||||
|
<%= event.status %>
|
||||||
|
<% end %>
|
||||||
<% t.column "User" do |event| %>
|
<% t.column "User" do |event| %>
|
||||||
<% if event.is_creator_visible? %>
|
<% if event.is_creator_visible? %>
|
||||||
<%= link_to_user event.creator %>
|
<%= link_to_user event.creator %>
|
||||||
@@ -12,12 +20,6 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<br><%= time_ago_in_words_tagged event.created_at %>
|
<br><%= time_ago_in_words_tagged event.created_at %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% t.column "Description", td: { class: "col-expand" } do |event| %>
|
|
||||||
<div class="prose">
|
|
||||||
<%= format_text event.reason %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% t.column :is_resolved, name: "Resolved" %>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
<ul class="post-flag-reasons list-bulleted">
|
<ul class="post-flag-reason list-bulleted">
|
||||||
<% flags.each do |flag| %>
|
<li>
|
||||||
<li class="post-flag-reason">
|
<span class="prose"><%= format_text(flag.reason, inline: true) %></span>
|
||||||
<span class="prose"><%= format_text(flag.reason, inline: true) %></span>
|
|
||||||
|
|
||||||
<% if policy(flag).can_view_flagger? %>
|
<% if policy(flag).can_view_flagger? %>
|
||||||
- <%= link_to_user(flag.creator) %>
|
(<%= link_to_user(flag.creator) %>, <%= time_ago_in_words_tagged(flag.created_at) %>)
|
||||||
<% end %>
|
<% else %>
|
||||||
|
(<%= time_ago_in_words_tagged(flag.created_at) %>)
|
||||||
- <%= time_ago_in_words_tagged(flag.created_at) %>
|
<% end %>
|
||||||
|
</li>
|
||||||
<% if flag.is_resolved? %>
|
|
||||||
<span class="resolved">RESOLVED</span>
|
|
||||||
<% end %>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<% if policy(PostFlag).can_search_flagger? %>
|
<% if policy(PostFlag).can_search_flagger? %>
|
||||||
<%= f.input :creator_name, label: "Creator", input_html: { value: params[:search][:creator_name], data: { autocomplete: "user" } } %>
|
<%= f.input :creator_name, label: "Creator", input_html: { value: params[:search][:creator_name], data: { autocomplete: "user" } } %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= f.input :is_resolved, label: "Resolved?", collection: [["Yes", true], ["No", false]], include_blank: true, selected: params[:search][:is_resolved] %>
|
|
||||||
<%= f.input :category, label: "Category", collection: ["normal", "unapproved", "rejected", "deleted"], include_blank: true, selected: params[:search][:category] %>
|
<%= f.input :category, label: "Category", collection: ["normal", "unapproved", "rejected", "deleted"], include_blank: true, selected: params[:search][:category] %>
|
||||||
|
<%= f.input :status, collection: PostFlag.statuses, include_blank: true, selected: params[:search][:status] %>
|
||||||
<%= f.submit "Search" %>
|
<%= f.submit "Search" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -19,8 +19,8 @@
|
|||||||
<% t.column "Category", width: "1%" do |post_flag| %>
|
<% t.column "Category", width: "1%" do |post_flag| %>
|
||||||
<%= link_to post_flag.category.to_s, post_flags_path(search: params[:search].merge(category: post_flag.category)) %>
|
<%= link_to post_flag.category.to_s, post_flags_path(search: params[:search].merge(category: post_flag.category)) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% t.column "Resolved?", width: "1%" do |post_flag| %>
|
<% t.column "Status", width: "5%" do |post_flag| %>
|
||||||
<%= link_to post_flag.is_resolved?.to_s, post_flags_path(search: params[:search].merge(is_resolved: post_flag.is_resolved?)) %>
|
<%= link_to post_flag.status, post_flags_path(search: { status: post_flag.status }) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% t.column "Uploaded", width: "15%" do |post_flag| %>
|
<% t.column "Uploaded", width: "15%" do |post_flag| %>
|
||||||
<%= compact_time post_flag.post.created_at %>
|
<%= compact_time post_flag.post.created_at %>
|
||||||
|
|||||||
@@ -13,7 +13,8 @@
|
|||||||
<%= f.input :updater_name, label: "Updater", input_html: { "data-autocomplete": "user", value: params.dig(:search, :updater_name) } %>
|
<%= f.input :updater_name, label: "Updater", input_html: { "data-autocomplete": "user", value: params.dig(:search, :updater_name) } %>
|
||||||
<%= f.input :added_tags_include_all, label: "Added Tags", input_html: { "data-autocomplete": "tag-query", value: params.dig(:search, :added_tags_include_all) } %>
|
<%= f.input :added_tags_include_all, label: "Added Tags", input_html: { "data-autocomplete": "tag-query", value: params.dig(:search, :added_tags_include_all) } %>
|
||||||
<%= f.input :removed_tags_include_all, label: "Removed Tags", input_html: { "data-autocomplete": "tag-query", value: params.dig(:search, :removed_tags_include_all) } %>
|
<%= f.input :removed_tags_include_all, label: "Removed Tags", input_html: { "data-autocomplete": "tag-query", value: params.dig(:search, :removed_tags_include_all) } %>
|
||||||
<%= f.input :changed_tags, label: "Changed Tags", input_html: { "data-autocomplete": "tag-query", value: params.dig(:search, :changed_tags) } %>
|
<%= f.input :all_changed_tags, label: "All Changed Tags", input_html: { "data-autocomplete": "tag-query", value: params.dig(:search, :all_changed_tags) }, hint: "All tags must appear in either tag adds or removes" %>
|
||||||
|
<%= f.input :any_changed_tags, label: "Any Changed Tags", input_html: { "data-autocomplete": "tag-query", value: params.dig(:search, :any_changed_tags) }, hint: "Any tag must appear in either tag adds or removes" %>
|
||||||
<%= f.submit "Search" %>
|
<%= f.submit "Search" %>
|
||||||
<%= link_to "Advanced", search_post_versions_path(params.except(:controller, :action, :index, :commit, :type).permit!), class: "advanced-search-link" %>
|
<%= link_to "Advanced", search_post_versions_path(params.except(:controller, :action, :index, :commit, :type).permit!), class: "advanced-search-link" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<%= f.input :updater_name, label: "Updater", input_html: { value: params.dig(:search, :updater_name), "data-autocomplete": "user" } %>
|
<%= f.input :updater_name, label: "Updater", input_html: { value: params.dig(:search, :updater_name), "data-autocomplete": "user" } %>
|
||||||
<%= f.input :added_tags_include_all, label: "Added tags", input_html: { value: params.dig(:search, :added_tags_include_all), "data-autocomplete": "tag-query" } %>
|
<%= f.input :added_tags_include_all, label: "Added tags", input_html: { value: params.dig(:search, :added_tags_include_all), "data-autocomplete": "tag-query" } %>
|
||||||
<%= f.input :removed_tags_include_all, label: "Removed tags", input_html: { value: params.dig(:search, :removed_tags_include_all), "data-autocomplete": "tag-query" } %>
|
<%= f.input :removed_tags_include_all, label: "Removed tags", input_html: { value: params.dig(:search, :removed_tags_include_all), "data-autocomplete": "tag-query" } %>
|
||||||
<%= f.input :changed_tags, label: "Changed tags", input_html: { value: params.dig(:search, :changed_tags), "data-autocomplete": "tag-query" } %>
|
<%= f.input :changed_tags, label: "Changed Tags", input_html: { "data-autocomplete": "tag-query", value: params.dig(:search, :changed_tags) }, hint: "Added or removed tags" %>
|
||||||
<%= f.input :post_id, input_html: { value: params.dig(:search, :post_id) } %>
|
<%= f.input :post_id, input_html: { value: params.dig(:search, :post_id) } %>
|
||||||
<%= f.input :parent_id, input_html: { value: params.dig(:search, :parent_id) } %>
|
<%= f.input :parent_id, input_html: { value: params.dig(:search, :parent_id) } %>
|
||||||
<%= f.input :rating, input_html: { value: params.dig(:search, :rating) } %>
|
<%= f.input :rating, input_html: { value: params.dig(:search, :rating) } %>
|
||||||
|
|||||||
5
app/views/posts/destroy.js.erb
Normal file
5
app/views/posts/destroy.js.erb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<% if params[:commit] == "Delete" %>
|
||||||
|
location.reload();
|
||||||
|
<% else %>
|
||||||
|
Danbooru.Utility.dialog("Delete Post", "<%= j render "posts/partials/show/delete_dialog", post: @post %>");
|
||||||
|
<% end %>
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<%= render "posts/partials/index/blacklist" %>
|
<%= render "posts/partials/index/blacklist" %>
|
||||||
|
|
||||||
<section id="tag-box">
|
<section id="tag-box">
|
||||||
<h1>Tags</h1>
|
<h2>Tags</h2>
|
||||||
<%= @post_set.tag_list_html(current_query: params[:tags], show_extra_links: policy(Post).show_extra_links?) %>
|
<%= @post_set.tag_list_html(current_query: params[:tags], show_extra_links: policy(Post).show_extra_links?) %>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<%# path, tags %>
|
<%# path, tags %>
|
||||||
|
|
||||||
<section id="search-box">
|
<section id="search-box">
|
||||||
<h1>Search</h1>
|
<h2>Search</h2>
|
||||||
<%= form_tag(path, method: "get", id: "search-box-form") do %>
|
<%= form_tag(path, method: "get", id: "search-box-form") do %>
|
||||||
<% if params[:random] %>
|
<% if params[:random] %>
|
||||||
<%= hidden_field_tag :random, params[:random] %>
|
<%= hidden_field_tag :random, params[:random] %>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div id="blacklist-box" class="sidebar-blacklist">
|
<div id="blacklist-box" class="sidebar-blacklist">
|
||||||
<h1>Blacklisted (<%= link_to_wiki "help", "help:blacklists" %>)</h1>
|
<h2>Blacklisted (<%= link_to_wiki "help", "help:blacklists" %>)</h2>
|
||||||
<ul id="blacklist-list" class="list-bulleted"></ul>
|
<ul id="blacklist-list" class="list-bulleted"></ul>
|
||||||
<%= link_to "Disable all", "#", :id => "disable-all-blacklists", :style => "display: none;" %>
|
<%= link_to "Disable all", "#", :id => "disable-all-blacklists", :style => "display: none;" %>
|
||||||
<%= link_to "Re-enable all", "#", :id => "re-enable-all-blacklists", :style => "display: none;" %>
|
<%= link_to "Re-enable all", "#", :id => "re-enable-all-blacklists", :style => "display: none;" %>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div id="quick-edit-div" style="display: none;">
|
<div id="quick-edit-div" style="display: none;">
|
||||||
<h1>Edit</h1>
|
<h2>Edit</h2>
|
||||||
|
|
||||||
<%= edit_form_for(:post, html: { id: "quick-edit-form" }) do |f| %>
|
<%= edit_form_for(:post, html: { id: "quick-edit-form" }) do |f| %>
|
||||||
<%= f.input :tag_string, label: "Tags", as: :text, input_html: { "data-autocomplete": "tag-edit" } %>
|
<%= f.input :tag_string, label: "Tags", as: :text, input_html: { "data-autocomplete": "tag-edit" } %>
|
||||||
|
|||||||
@@ -40,13 +40,13 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% elsif post_set.pool.present? %>
|
<% elsif post_set.pool.present? %>
|
||||||
<% post_set.pool.tap do |pool| %>
|
<% post_set.pool.tap do |pool| %>
|
||||||
<h4>
|
<h2>
|
||||||
<%= pool.pretty_category %>:
|
<%= pool.pretty_category %>:
|
||||||
<%= link_to pool.pretty_name, pool_path(pool), :class => "pool-category-#{pool.category}" %>
|
<%= link_to pool.pretty_name, pool_path(pool), :class => "pool-category-#{pool.category}" %>
|
||||||
<% if pool.is_deleted? %>
|
<% if pool.is_deleted? %>
|
||||||
<span class="inactive">(deleted)</span>
|
<span class="inactive">(deleted)</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</h4>
|
</h2>
|
||||||
|
|
||||||
<div id="description" class="prose">
|
<div id="description" class="prose">
|
||||||
<%= format_text(post_set.pool.description) %>
|
<%= format_text(post_set.pool.description) %>
|
||||||
@@ -57,10 +57,10 @@
|
|||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% elsif post_set.favgroup.present? %>
|
<% elsif post_set.favgroup.present? %>
|
||||||
<h4>
|
<h2>
|
||||||
Favorite Group:
|
Favorite Group:
|
||||||
<%= link_to post_set.favgroup.pretty_name, favorite_group_path(post_set.favgroup) %>
|
<%= link_to post_set.favgroup.pretty_name, favorite_group_path(post_set.favgroup) %>
|
||||||
</h4>
|
</h2>
|
||||||
Creator: <%= link_to_user post_set.favgroup.creator %>
|
Creator: <%= link_to_user post_set.favgroup.creator %>
|
||||||
<% elsif post_set.has_blank_wiki? %>
|
<% elsif post_set.has_blank_wiki? %>
|
||||||
<p>There is currently no wiki page for the tag <%= link_to_wiki post_set.tag.pretty_name %>. You can <%= link_to "create one", new_wiki_page_path(wiki_page: { title: post_set.tag.name }), rel: "nofollow" %>.</p>
|
<p>There is currently no wiki page for the tag <%= link_to_wiki post_set.tag.pretty_name %>. You can <%= link_to "create one", new_wiki_page_path(wiki_page: { title: post_set.tag.name }), rel: "nofollow" %>.</p>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<% if policy(Post).can_use_mode_menu? %>
|
<% if policy(Post).can_use_mode_menu? %>
|
||||||
<section id="mode-box">
|
<section id="mode-box">
|
||||||
<h1>Mode</h1>
|
<h2>Mode</h2>
|
||||||
<form action="/">
|
<form action="/">
|
||||||
<select name="mode">
|
<select name="mode">
|
||||||
<option value="view">View</option>
|
<option value="view">View</option>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<section id="options-box">
|
<section id="options-box">
|
||||||
<h1>Options</h1>
|
<h2>Options</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<% if policy(SavedSearch).create? %>
|
<% if policy(SavedSearch).create? %>
|
||||||
<li><%= button_tag(tag.i(class: "fas fa-bookmark") + " Save search", id: "save-search", class: "ui-button ui-widget ui-corner-all sub") %></li>
|
<li><%= button_tag(tag.i(class: "fas fa-bookmark") + " Save search", id: "save-search", class: "ui-button ui-widget ui-corner-all sub") %></li>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<section id="related-box">
|
<section id="related-box">
|
||||||
<h1>Related</h1>
|
<h2>Related</h2>
|
||||||
<ul id="related-list">
|
<ul id="related-list">
|
||||||
<% if discover_mode? %>
|
<% if discover_mode? %>
|
||||||
<li id="secondary-links-posts-hot"><%= link_to "Hot", posts_path(:tags => "order:rank") %></li>
|
<li id="secondary-links-posts-hot"><%= link_to "Hot", posts_path(:tags => "order:rank") %></li>
|
||||||
|
|||||||
9
app/views/posts/partials/show/_delete_dialog.html.erb
Normal file
9
app/views/posts/partials/show/_delete_dialog.html.erb
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<div class="delete-post-dialog-body">
|
||||||
|
<%= edit_form_for(post, method: :delete, remote: true) do |f| %>
|
||||||
|
<input type="hidden" name="commit" value="Delete">
|
||||||
|
<%= f.input :reason, as: :dtext, inline: true, input_html: { value: "" } %>
|
||||||
|
<% if post.parent_id.present? %>
|
||||||
|
<%= f.input :move_favorites, label: "Move favorites to parent", as: :boolean, input_html: { checked: false } %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user