diff --git a/Gemfile b/Gemfile index eed0df543..dc935c551 100644 --- a/Gemfile +++ b/Gemfile @@ -45,6 +45,7 @@ gem 'http-cookie', git: "https://github.com/danbooru/http-cookie" gem 'pundit' gem 'mail' gem 'nokogiri' +gem 'view_component', require: 'view_component/engine' group :production, :staging do gem 'unicorn', :platforms => :ruby diff --git a/Gemfile.lock b/Gemfile.lock index 503ee789a..5267140b2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -381,6 +381,8 @@ GEM unicorn-worker-killer (0.4.4) get_process_mem (~> 0) unicorn (>= 4, < 6) + view_component (2.24.0) + activesupport (>= 5.0.0, < 7.0) webpacker (5.2.1) activesupport (>= 5.2) rack-proxy (>= 0.6.1) @@ -467,6 +469,7 @@ DEPENDENCIES stripe-ruby-mock unicorn unicorn-worker-killer + view_component webpacker (>= 4.0.x) whenever diff --git a/app/components/application_component.rb b/app/components/application_component.rb new file mode 100644 index 000000000..3db4d0451 --- /dev/null +++ b/app/components/application_component.rb @@ -0,0 +1,2 @@ +class ApplicationComponent < ViewComponent::Base +end diff --git a/app/components/post_preview_component.rb b/app/components/post_preview_component.rb new file mode 100644 index 000000000..e1e955368 --- /dev/null +++ b/app/components/post_preview_component.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +class PostPreviewComponent < ApplicationComponent + with_collection_parameter :post + + attr_reader :post, :tags, :show_deleted, :show_cropped, :link_target, :pool, :pool_id, :favgroup_id, :similarity, :recommended, :compact, :size, :current_user, :options + delegate :external_link_to, :time_ago_in_words_tagged, to: :helpers + + def initialize(post:, tags: "", show_deleted: false, show_cropped: true, link_target: post, pool: nil, pool_id: nil, favgroup_id: nil, similarity: nil, recommended: nil, compact: nil, size: nil, current_user: CurrentUser.user, **options) + @post = post + @tags = tags + @show_deleted = show_deleted + @show_cropped = show_cropped + @link_target = link_target + @pool = pool + @pool_id = pool_id + @favgroup_id = favgroup_id + @similarity = similarity.round(1) if similarity.present? + @recommended = recommended.round(1) if recommended.present? + @compact = compact + @size = post.file_size if size.present? + @current_user = current_user + @options = options + end + + def render? + post.present? && post.visible?(current_user) && (!post.is_deleted? || show_deleted) + end + + def article_attrs(classes = nil) + { class: [classes, *preview_class].compact.join(" "), **data_attributes } + end + + def link_params + link_params = {} + + link_params["q"] = tags if tags.present? && !current_user.is_anonymous? + link_params["pool_id"] = pool_id if pool_id + link_params["favgroup_id"] = favgroup_id if favgroup_id + + link_params + end + + def cropped_url + if show_cropped && post.has_cropped? && !current_user.disable_cropped_thumbnails? + post.crop_file_url + else + post.preview_file_url + end + end + + def preview_dimensions + # XXX work around ancient bad posts with null or zero dimensions. + if post.image_width.to_i > 0 && post.image_height.to_i > 0 + downscale_ratio = Danbooru.config.small_image_width.to_f / [post.image_width, post.image_height].max + + { + width: [(downscale_ratio * post.image_width).floor, post.image_width].min, + height: [(downscale_ratio * post.image_height).floor, post.image_height].min + } + else + { width: 0, height: 0 } + end + end + + def tooltip + "#{post.tag_string} rating:#{post.rating} score:#{post.score}" + end + + def preview_class + klass = ["post-preview"] + klass << "captioned" if pool || size || similarity || recommended + klass << "post-status-pending" if post.is_pending? + klass << "post-status-flagged" if post.is_flagged? + klass << "post-status-deleted" if post.is_deleted? + klass << "post-status-has-parent" if post.parent_id + klass << "post-status-has-children" if post.has_visible_children? + klass << "post-preview-compact" if compact + klass + end + + def data_attributes + attributes = { + "data-id" => post.id, + "data-has-sound" => post.has_tag?('video_with_sound|flash_with_sound'), + "data-tags" => post.tag_string, + "data-pools" => post.pool_string, + "data-approver-id" => post.approver_id, + "data-rating" => post.rating, + "data-large-width" => post.large_image_width, + "data-large-height" => post.large_image_height, + "data-width" => post.image_width, + "data-height" => post.image_height, + "data-flags" => post.status_flags, + "data-parent-id" => post.parent_id, + "data-has-children" => post.has_children?, + "data-score" => post.score, + "data-views" => post.view_count, + "data-fav-count" => post.fav_count, + "data-pixiv-id" => post.pixiv_id, + "data-file-ext" => post.file_ext, + "data-source" => post.source, + "data-uploader-id" => post.uploader_id, + "data-normalized-source" => post.normalized_source, + } + + if post.visible?(current_user) + attributes["data-md5"] = post.md5 + attributes["data-file-url"] = post.file_url + attributes["data-large-file-url"] = post.large_file_url + attributes["data-preview-file-url"] = post.preview_file_url + end + + attributes + end +end diff --git a/app/views/posts/partials/index/_preview.html.erb b/app/components/post_preview_component/post_preview_component.html.erb similarity index 72% rename from app/views/posts/partials/index/_preview.html.erb rename to app/components/post_preview_component/post_preview_component.html.erb index 0e03f2613..e2d7ebdc2 100644 --- a/app/views/posts/partials/index/_preview.html.erb +++ b/app/components/post_preview_component/post_preview_component.html.erb @@ -1,10 +1,10 @@ -<%= content_tag(:article, article_attrs) do -%> +<%= tag.article id: "post_#{post.id}", **article_attrs do -%> <%= link_to polymorphic_path(link_target, link_params) do -%> - <%= content_tag(:picture) do -%> + <%= tag.source media: "(max-width: 660px)", srcset: cropped_url -%> - <%= tag.source media: "(min-width: 660px)", srcset: preview_url -%> - <%= tag.img class: "has-cropped-#{has_cropped}", src: preview_url, style: "min-width: #{preview_width}px; min-height: #{preview_height}px;", title: tooltip, alt: alt_text -%> - <% end -%> + <%= tag.source media: "(min-width: 660px)", srcset: post.preview_file_url -%> + <%= tag.img class: "has-cropped-#{post.has_cropped?}", src: post.preview_file_url, style: "min-width: #{preview_dimensions[:width]}px; min-height: #{preview_dimensions[:height]}px;", title: tooltip, alt: "post ##{post.id}" -%> + <% end -%> <% if pool -%>

@@ -24,7 +24,7 @@ <% if size -%>

<%= link_to number_to_human_size(size), post.file_url %> - (<%= width %>x<%= height %>) + (<%= post.image_width %>x<%= post.image_height %>)

<% end -%> <% if similarity -%> diff --git a/app/components/post_preview_component/post_preview_component.scss b/app/components/post_preview_component/post_preview_component.scss new file mode 100644 index 000000000..90b3a49e2 --- /dev/null +++ b/app/components/post_preview_component/post_preview_component.scss @@ -0,0 +1,162 @@ +@import "../../javascript/src/styles/base/000_vars.scss"; + +article.post-preview { + height: 154px; + width: 154px; + margin: 0 10px 10px 0; + overflow: hidden; + text-align: center; + display: inline-block; + position: relative; + + a { + display: inline-block; + } + + &.captioned { + height: auto; + vertical-align: text-top; + } + + &.post-preview-compact { + width: auto; + height: auto; + } + + .desc { + font-size: 80%; + margin-bottom: 0; + } + + img { + margin: auto; + } + + &[data-tags~=animated]::before, &[data-file-ext=swf]::before, &[data-file-ext=webm]::before, &[data-file-ext=mp4]::before, &[data-file-ext=zip]::before { + @include animated-icon; + } + + &[data-has-sound=true]::before { + @include sound-icon; + } +} + +/* Avoid dead space around thumbnails in tables. */ +table article.post-preview { + height: auto; + width: auto; + margin: 0; +} + +.post-preview { + img { + border: 2px solid transparent; + } + + &.post-status-has-children img { + border-color: var(--preview-has-children-color); + } + + &.post-status-has-parent img { + border-color: var(--preview-has-parent-color); + } + + &.post-status-has-children.post-status-has-parent img { + border-color: var(--preview-has-children-color) var(--preview-has-parent-color) var(--preview-has-parent-color) var(--preview-has-children-color); + } + + &.post-status-deleted img { + border-color: var(--preview-deleted-color); + } + + &.post-status-has-children.post-status-deleted img { + border-color: var(--preview-has-children-color) var(--preview-deleted-color) var(--preview-deleted-color) var(--preview-has-children-color); + } + + &.post-status-has-parent.post-status-deleted img { + border-color: var(--preview-has-parent-color) var(--preview-deleted-color) var(--preview-deleted-color) var(--preview-has-parent-color); + } + + &.post-status-has-children.post-status-has-parent.post-status-deleted img { + border-color: var(--preview-has-children-color) var(--preview-deleted-color) var(--preview-deleted-color) var(--preview-has-parent-color); + } + + /* Pending and flagged posts have blue borders (except in the modqueue). */ + &.post-status-pending:not(.mod-queue-preview) img, + &.post-status-flagged:not(.mod-queue-preview) img { + border-color: var(--preview-pending-color); + } + + &.post-status-has-children.post-status-pending:not(.mod-queue-preview) img, + &.post-status-has-children.post-status-flagged:not(.mod-queue-preview) img { + border-color: var(--preview-has-children-color) var(--preview-pending-color) var(--preview-pending-color) var(--preview-has-children-color); + } + + &.post-status-has-parent.post-status-pending:not(.mod-queue-preview) img, + &.post-status-has-parent.post-status-flagged:not(.mod-queue-preview) img { + border-color: var(--preview-has-parent-color) var(--preview-pending-color) var(--preview-pending-color) var(--preview-has-parent-color); + } + + &.post-status-has-children.post-status-has-parent.post-status-pending:not(.mod-queue-preview) img, + &.post-status-has-children.post-status-has-parent.post-status-flagged:not(.mod-queue-preview) img { + border-color: var(--preview-has-children-color) var(--preview-pending-color) var(--preview-pending-color) var(--preview-has-parent-color); + } +} + +/* Flagged posts have red borders for approvers. */ +body[data-current-user-can-approve-posts="true"] .post-preview { + &.post-status-flagged img { + border-color: var(--preview-flagged-color); + } + + &.post-status-has-children.post-status-flagged img { + border-color: var(--preview-has-children-color) var(--preview-flagged-color) var(--preview-flagged-color) var(--preview-has-children-color); + } + + &.post-status-has-parent.post-status-flagged img { + border-color: var(--preview-has-parent-color) var(--preview-flagged-color) var(--preview-flagged-color) var(--preview-has-parent-color); + } + + &.post-status-has-children.post-status-has-parent.post-status-flagged img { + border-color: var(--preview-has-children-color) var(--preview-flagged-color) var(--preview-flagged-color) var(--preview-has-parent-color); + } +} + +.post-preview.current-post { + background-color: var(--preview-current-post-background); +} + +@media screen and (max-width: 660px) { + article.post-preview { + margin: 0; + text-align: center; + vertical-align: middle; + display: inline-block; + + a { + margin: 0 auto; + } + + img { + width: 33.3vw; + border: none !important; + } + } + + .user-disable-cropped-false { + article.post-preview { + width: 33.3%; + height: 33.3vw; + overflow: hidden; + } + + img { + width: 33.3vw; + height: 33.3vw; + + &.has-cropped-false { + object-fit: cover; + } + } + } +} diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb index a521b20e8..12f4c23aa 100644 --- a/app/helpers/posts_helper.rb +++ b/app/helpers/posts_helper.rb @@ -1,8 +1,10 @@ module PostsHelper + def post_preview(post, **options) + render PostPreviewComponent.new(post: post, **options) + end + def post_previews_html(posts, **options) - posts.map do |post| - PostPresenter.preview(post, **options) - end.join("").html_safe + render PostPreviewComponent.with_collection(posts, **options) end def reportbooru_enabled? diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 3d7236286..bab66e46c 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -28,6 +28,8 @@ require("@fortawesome/fontawesome-free/css/regular.css"); importAll(require.context('../src/javascripts', true, /\.js(\.erb)?$/)); importAll(require.context('../src/styles', true, /\.s?css(?:\.erb)?$/)); +importAll(require.context('../../components', true, /\.js(\.erb)?$/)); +importAll(require.context('../../components', true, /\.s?css(?:\.erb)?$/)); export { default as jQuery } from "jquery"; export { default as Autocomplete } from '../src/javascripts/autocomplete.js.erb'; diff --git a/app/javascript/src/styles/specific/posts.scss b/app/javascript/src/styles/specific/posts.scss index 1eca2209a..c9e94df39 100644 --- a/app/javascript/src/styles/specific/posts.scss +++ b/app/javascript/src/styles/specific/posts.scss @@ -1,5 +1,3 @@ -@import "../base/000_vars.scss"; - @keyframes heartbeat { 0% { transform:scale(1); @@ -24,54 +22,6 @@ } } -article.post-preview { - height: 154px; - width: 154px; - margin: 0 10px 10px 0; - overflow: hidden; - text-align: center; - display: inline-block; - position: relative; - - a { - display: inline-block; - } - - &.captioned { - height: auto; - vertical-align: text-top; - } - - &.post-preview-compact { - width: auto; - height: auto; - } - - .desc { - font-size: 80%; - margin-bottom: 0; - } - - img { - margin: auto; - } - - &[data-tags~=animated]::before, &[data-file-ext=swf]::before, &[data-file-ext=webm]::before, &[data-file-ext=mp4]::before, &[data-file-ext=zip]::before { - @include animated-icon; - } - - &[data-has-sound=true]::before { - @include sound-icon; - } -} - -/* Avoid dead space around thumbnails in tables. */ -table article.post-preview { - height: auto; - width: auto; - margin: 0; -} - #saved-searches-nav { margin-top: 1em; } @@ -89,84 +39,6 @@ table article.post-preview { } } -.post-preview { - img { - border: 2px solid transparent; - } - - &.post-status-has-children img { - border-color: var(--preview-has-children-color); - } - - &.post-status-has-parent img { - border-color: var(--preview-has-parent-color); - } - - &.post-status-has-children.post-status-has-parent img { - border-color: var(--preview-has-children-color) var(--preview-has-parent-color) var(--preview-has-parent-color) var(--preview-has-children-color); - } - - &.post-status-deleted img { - border-color: var(--preview-deleted-color); - } - - &.post-status-has-children.post-status-deleted img { - border-color: var(--preview-has-children-color) var(--preview-deleted-color) var(--preview-deleted-color) var(--preview-has-children-color); - } - - &.post-status-has-parent.post-status-deleted img { - border-color: var(--preview-has-parent-color) var(--preview-deleted-color) var(--preview-deleted-color) var(--preview-has-parent-color); - } - - &.post-status-has-children.post-status-has-parent.post-status-deleted img { - border-color: var(--preview-has-children-color) var(--preview-deleted-color) var(--preview-deleted-color) var(--preview-has-parent-color); - } - - /* Pending and flagged posts have blue borders (except in the modqueue). */ - &.post-status-pending:not(.mod-queue-preview) img, - &.post-status-flagged:not(.mod-queue-preview) img { - border-color: var(--preview-pending-color); - } - - &.post-status-has-children.post-status-pending:not(.mod-queue-preview) img, - &.post-status-has-children.post-status-flagged:not(.mod-queue-preview) img { - border-color: var(--preview-has-children-color) var(--preview-pending-color) var(--preview-pending-color) var(--preview-has-children-color); - } - - &.post-status-has-parent.post-status-pending:not(.mod-queue-preview) img, - &.post-status-has-parent.post-status-flagged:not(.mod-queue-preview) img { - border-color: var(--preview-has-parent-color) var(--preview-pending-color) var(--preview-pending-color) var(--preview-has-parent-color); - } - - &.post-status-has-children.post-status-has-parent.post-status-pending:not(.mod-queue-preview) img, - &.post-status-has-children.post-status-has-parent.post-status-flagged:not(.mod-queue-preview) img { - border-color: var(--preview-has-children-color) var(--preview-pending-color) var(--preview-pending-color) var(--preview-has-parent-color); - } -} - -/* Flagged posts have red borders for approvers. */ -body[data-current-user-can-approve-posts="true"] .post-preview { - &.post-status-flagged img { - border-color: var(--preview-flagged-color); - } - - &.post-status-has-children.post-status-flagged img { - border-color: var(--preview-has-children-color) var(--preview-flagged-color) var(--preview-flagged-color) var(--preview-has-children-color); - } - - &.post-status-has-parent.post-status-flagged img { - border-color: var(--preview-has-parent-color) var(--preview-flagged-color) var(--preview-flagged-color) var(--preview-has-parent-color); - } - - &.post-status-has-children.post-status-has-parent.post-status-flagged img { - border-color: var(--preview-has-children-color) var(--preview-flagged-color) var(--preview-flagged-color) var(--preview-has-parent-color); - } -} - -.post-preview.current-post { - background-color: var(--preview-current-post-background); -} - #has-parent-relationship-preview, #has-children-relationship-preview { overflow-x: auto; white-space: nowrap; diff --git a/app/javascript/src/styles/specific/z_responsive.scss b/app/javascript/src/styles/specific/z_responsive.scss index 8c9a61ed2..f83d1f3ee 100644 --- a/app/javascript/src/styles/specific/z_responsive.scss +++ b/app/javascript/src/styles/specific/z_responsive.scss @@ -66,37 +66,4 @@ align-items: center; justify-content: flex-start; } - - article.post-preview { - margin: 0; - text-align: center; - vertical-align: middle; - display: inline-block; - - a { - margin: 0 auto; - } - - img { - width: 33.3vw; - border: none !important; - } - } - - .user-disable-cropped-false { - article.post-preview { - width: 33.3%; - height: 33.3vw; - overflow: hidden; - } - - img { - width: 33.3vw; - height: 33.3vw; - - &.has-cropped-false { - object-fit: cover; - } - } - } } diff --git a/app/logical/post_sets/post.rb b/app/logical/post_sets/post.rb index 5ca2b9c78..fff5581ac 100644 --- a/app/logical/post_sets/post.rb +++ b/app/logical/post_sets/post.rb @@ -140,20 +140,6 @@ module PostSets @pending_bulk_update_requests ||= BulkUpdateRequest.pending.where_array_includes_any(:tags, tag.name) end - def post_previews_html(template) - html = "" - if shown_posts.empty? - return template.render("post_sets/blank") - end - - shown_posts.each do |post| - html << PostPresenter.preview(post, show_deleted: show_deleted?, show_cropped: true, tags: tag_string) - html << "\n" - end - - html.html_safe - end - def show_deleted? query.select_metatags("status").any? do |metatag| metatag.value.in?(%w[all any active unmoderated modqueue deleted appealed]) diff --git a/app/presenters/post_presenter.rb b/app/presenters/post_presenter.rb index c9534a7ab..23fee57c1 100644 --- a/app/presenters/post_presenter.rb +++ b/app/presenters/post_presenter.rb @@ -2,147 +2,6 @@ class PostPresenter 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 - def self.preview(post, show_deleted: false, tags: "", **options) - if post.nil? - return "none".html_safe - end - - if post.is_deleted? && !show_deleted - return "" - end - - if !post.visible? - return "" - end - - if post.is_ugoira? && !post.has_ugoira_webm? - # ugoira preview gen is async so dont render it immediately - return "" - end - - locals = {} - locals[:post] = post - - locals[:article_attrs] = { - "id" => "post_#{post.id}", - "class" => preview_class(post, **options).join(" ") - }.merge(data_attributes(post)) - - locals[:link_target] = options[:link_target] || post - - locals[:link_params] = {} - if tags.present? && !CurrentUser.is_anonymous? - locals[:link_params]["q"] = tags - end - if options[:pool_id] - locals[:link_params]["pool_id"] = options[:pool_id] - end - if options[:favgroup_id] - locals[:link_params]["favgroup_id"] = options[:favgroup_id] - end - - locals[:tooltip] = "#{post.tag_string} rating:#{post.rating} score:#{post.score}" - - locals[:cropped_url] = if options[:show_cropped] && post.has_cropped? && !CurrentUser.user.disable_cropped_thumbnails? - post.crop_file_url - else - post.preview_file_url - end - - locals[:preview_url] = post.preview_file_url - - locals[:alt_text] = "post ##{post.id}" - - locals[:has_cropped] = post.has_cropped? - - if options[:pool] - locals[:pool] = options[:pool] - else - locals[:pool] = nil - end - - locals[:width] = post.image_width - locals[:height] = post.image_height - - # XXX work around ancient bad posts with null or zero dimensions. - if post.image_width.to_i > 0 && post.image_height.to_i > 0 - downscale_ratio = Danbooru.config.small_image_width.to_f / [post.image_width, post.image_height].max - locals[:preview_width] = [(downscale_ratio * post.image_width).floor, post.image_width].min - locals[:preview_height] = [(downscale_ratio * post.image_height).floor, post.image_height].min - else - locals[:preview_width] = 0 - locals[:preview_height] = 0 - end - - if options[:similarity] - locals[:similarity] = options[:similarity].round(1) - else - locals[:similarity] = nil - end - - if options[:size] - locals[:size] = post.file_size - else - locals[:size] = nil - end - - if options[:recommended] - locals[:recommended] = options[:recommended].round(1) - else - locals[:recommended] = nil - end - - ApplicationController.render(partial: "posts/partials/index/preview", locals: locals) - end - - def self.preview_class(post, pool: nil, size: nil, similarity: nil, recommended: nil, compact: nil, **options) - klass = ["post-preview"] - # klass << " large-cropped" if post.has_cropped? && options[:show_cropped] - klass << "captioned" if pool || size || similarity || recommended - klass << "post-status-pending" if post.is_pending? - klass << "post-status-flagged" if post.is_flagged? - klass << "post-status-deleted" if post.is_deleted? - klass << "post-status-has-parent" if post.parent_id - klass << "post-status-has-children" if post.has_visible_children? - klass << "post-preview-compact" if compact - klass - end - - def self.data_attributes(post) - attributes = { - "data-id" => post.id, - "data-has-sound" => post.has_tag?('video_with_sound|flash_with_sound'), - "data-tags" => post.tag_string, - "data-pools" => post.pool_string, - "data-approver-id" => post.approver_id, - "data-rating" => post.rating, - "data-large-width" => post.large_image_width, - "data-large-height" => post.large_image_height, - "data-width" => post.image_width, - "data-height" => post.image_height, - "data-flags" => post.status_flags, - "data-parent-id" => post.parent_id, - "data-has-children" => post.has_children?, - "data-score" => post.score, - "data-views" => post.view_count, - "data-fav-count" => post.fav_count, - "data-pixiv-id" => post.pixiv_id, - "data-file-ext" => post.file_ext, - "data-source" => post.source, - "data-uploader-id" => post.uploader_id, - "data-normalized-source" => post.normalized_source, - } - - if post.visible? - attributes["data-md5"] = post.md5 - attributes["data-file-url"] = post.file_url - attributes["data-large-file-url"] = post.large_file_url - attributes["data-preview-file-url"] = post.preview_file_url - end - - attributes - end - def initialize(post) @post = post end diff --git a/app/views/artist_commentaries/index.html.erb b/app/views/artist_commentaries/index.html.erb index 33a6ff4a0..2d88e6688 100644 --- a/app/views/artist_commentaries/index.html.erb +++ b/app/views/artist_commentaries/index.html.erb @@ -6,7 +6,7 @@ <%= table_for @commentaries, width: "100%" do |t| %> <% t.column "Post", width: "1%" do |commentary| %> - <%= PostPresenter.preview(commentary.post, show_deleted: true) %> + <%= post_preview(commentary.post, show_deleted: true) %> <% end %> <% t.column "Original" do |commentary| %> <%= format_commentary_title(commentary.original_title) %> diff --git a/app/views/artist_commentary_versions/_listing.html.erb b/app/views/artist_commentary_versions/_listing.html.erb index 4e0dbd2ed..60aa9c7d4 100644 --- a/app/views/artist_commentary_versions/_listing.html.erb +++ b/app/views/artist_commentary_versions/_listing.html.erb @@ -1,13 +1,13 @@
<% if listing_type(:post_id) == :revert && @commentary_versions.present? %> - <%= PostPresenter.preview(@commentary_versions.first.post, show_deleted: true) %> + <%= post_preview(@commentary_versions.first.post, show_deleted: true) %> <% end %> <%= table_for @commentary_versions, class: "striped" do |t| %> <% if listing_type(:post_id) == :standard %> <% t.column "Post", width: "1%" do |commentary_version| %> - <%= PostPresenter.preview(commentary_version.post, show_deleted: true) %> + <%= post_preview(commentary_version.post, show_deleted: true) %> <% end %> <% end %> <% if listing_type(:post_id) == :standard %> diff --git a/app/views/comment_votes/index.html.erb b/app/views/comment_votes/index.html.erb index 5151af561..728ba5e52 100644 --- a/app/views/comment_votes/index.html.erb +++ b/app/views/comment_votes/index.html.erb @@ -14,7 +14,7 @@ <%= table_for @comment_votes, class: "striped autofit" do |t| %> <% t.column "Post" do |vote| %> - <%= PostPresenter.preview(vote.comment.post, show_deleted: true) %> + <%= post_preview(vote.comment.post, show_deleted: true) %> <% end %> <% t.column "Comment", td: {class: "col-expand"} do |vote| %>
diff --git a/app/views/comments/_index_by_comment.html.erb b/app/views/comments/_index_by_comment.html.erb index 43bbb6bb6..3a2f5cc2d 100644 --- a/app/views/comments/_index_by_comment.html.erb +++ b/app/views/comments/_index_by_comment.html.erb @@ -2,7 +2,7 @@
<% @comments.each do |comment| %> <% if CurrentUser.is_moderator? || (params[:search] && params[:search][:is_deleted] =~ /t/) || !comment.is_deleted? %> - <%= content_tag(:div, { id: "post_#{comment.post.id}", class: ["post", *PostPresenter.preview_class(comment.post)].join(" ") }.merge(PostPresenter.data_attributes(comment.post))) do %> + <%= tag.div id: "post_#{comment.post.id}", **PostPreviewComponent.new(post: comment.post).article_attrs("post") do %>
<% if policy(comment.post).visible? %> <%= link_to(image_tag(comment.post.preview_file_url), post_path(comment.post)) %> diff --git a/app/views/comments/_index_by_post.html.erb b/app/views/comments/_index_by_post.html.erb index cc217a7a9..d04d9c175 100644 --- a/app/views/comments/_index_by_post.html.erb +++ b/app/views/comments/_index_by_post.html.erb @@ -5,7 +5,7 @@ <% @posts.select(&:visible?).each do |post| %> <% if post.comments.unhidden(CurrentUser.user).any? || post.comments.hidden(CurrentUser.user).any? %> - <%= content_tag(:div, { id: "post_#{post.id}", class: ["post", *PostPresenter.preview_class(post)].join(" ") }.merge(PostPresenter.data_attributes(post))) do %> + <%= tag.div id: "post_#{post.id}", **PostPreviewComponent.new(post: post).article_attrs("post") do %>
<%= link_to(image_tag(post.preview_file_url), post_path(post)) %>
diff --git a/app/views/favorite_group_orders/edit.html.erb b/app/views/favorite_group_orders/edit.html.erb index b5517a587..5a6ded13c 100644 --- a/app/views/favorite_group_orders/edit.html.erb +++ b/app/views/favorite_group_orders/edit.html.erb @@ -8,7 +8,7 @@
    <% @favorite_group.posts.limit(1_000).each do |post| %>
  • - <%= PostPresenter.preview(post, show_deleted: true).presence || "Hidden: Post ##{post.id}" %> + <%= post_preview(post, show_deleted: true).presence || "Hidden: Post ##{post.id}" %>
  • <% end %>
diff --git a/app/views/iqdb_queries/_matches.html.erb b/app/views/iqdb_queries/_matches.html.erb index ae85b30a4..88998910a 100644 --- a/app/views/iqdb_queries/_matches.html.erb +++ b/app/views/iqdb_queries/_matches.html.erb @@ -14,13 +14,13 @@ <% @high_similarity_matches.each do |match| %> - <%= PostPresenter.preview(match["post"], show_deleted: true, similarity: match["score"], size: true) %> + <%= post_preview(match["post"], show_deleted: true, similarity: match["score"], size: true) %> <% end %>
diff --git a/app/views/moderator/post/posts/confirm_move_favorites.html.erb b/app/views/moderator/post/posts/confirm_move_favorites.html.erb index c3808538f..55f9be171 100644 --- a/app/views/moderator/post/posts/confirm_move_favorites.html.erb +++ b/app/views/moderator/post/posts/confirm_move_favorites.html.erb @@ -1,7 +1,7 @@

Move Favorites to Parent

- <%= PostPresenter.preview(@post, show_deleted: true) %> + <%= post_preview(@post, show_deleted: true) %>

This will move all the post's favorites to its parent post. Are you sure?

diff --git a/app/views/modqueue/_post.html.erb b/app/views/modqueue/_post.html.erb index 242d7f550..e03ade512 100644 --- a/app/views/modqueue/_post.html.erb +++ b/app/views/modqueue/_post.html.erb @@ -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 %> +<%= tag.div id: "post-#{post.id}", **PostPreviewComponent.new(post: post).article_attrs("post mod-queue-preview column-container") do %>
diff --git a/app/views/pool_orders/edit.html.erb b/app/views/pool_orders/edit.html.erb index 4f0bb1093..9f150742c 100644 --- a/app/views/pool_orders/edit.html.erb +++ b/app/views/pool_orders/edit.html.erb @@ -14,7 +14,7 @@
    <% @pool.posts.each do |post| %>
  • - <%= PostPresenter.preview(post, show_deleted: true).presence || "Hidden: Post ##{post.id}" %> + <%= post_preview(post, show_deleted: true).presence || "Hidden: Post ##{post.id}" %>
  • <% end %>
diff --git a/app/views/pools/gallery.html.erb b/app/views/pools/gallery.html.erb index 1ffae1497..720d8074b 100644 --- a/app/views/pools/gallery.html.erb +++ b/app/views/pools/gallery.html.erb @@ -11,7 +11,7 @@
<% @pools.each do |pool| %> - <%= PostPresenter.preview(pool.cover_post, link_target: pool, pool: pool, show_deleted: true) %> + <%= post_preview(pool.cover_post, link_target: pool, pool: pool, show_deleted: true) %> <% end %> <%= numbered_paginator(@pools) %> diff --git a/app/views/post_appeals/index.html.erb b/app/views/post_appeals/index.html.erb index 48d16c180..1ed4b5be9 100644 --- a/app/views/post_appeals/index.html.erb +++ b/app/views/post_appeals/index.html.erb @@ -6,7 +6,7 @@ <%= table_for @post_appeals, width: "100%" do |t| %> <% t.column "Post", width: "1%" do |post_appeal| %> - <%= PostPresenter.preview(post_appeal.post, show_deleted: true) %> + <%= post_preview(post_appeal.post, show_deleted: true) %> <% end %> <% t.column "Reason" do |post_appeal| %> diff --git a/app/views/post_approvals/index.html.erb b/app/views/post_approvals/index.html.erb index bf6a9afd7..17b5aa52e 100644 --- a/app/views/post_approvals/index.html.erb +++ b/app/views/post_approvals/index.html.erb @@ -11,7 +11,7 @@ <%= table_for @post_approvals, width: "100%" do |t| %> <% t.column "Post", width: "1%" do |post_approval| %> - <%= PostPresenter.preview(post_approval.post, show_deleted: true) %> + <%= post_preview(post_approval.post, show_deleted: true) %> <% end %> <% t.column "Approver", width: "15%" do |post_approval| %> <%= link_to_user post_approval.user %> diff --git a/app/views/post_flags/index.html.erb b/app/views/post_flags/index.html.erb index 876e9ad7e..9fb05177b 100644 --- a/app/views/post_flags/index.html.erb +++ b/app/views/post_flags/index.html.erb @@ -6,7 +6,7 @@ <%= table_for @post_flags, width: "100%" do |t| %> <% t.column "Post", width: "1%" do |post_flag| %> - <%= PostPresenter.preview(post_flag.post, show_deleted: true) %> + <%= post_preview(post_flag.post, show_deleted: true) %> <% end %> <% t.column "Reason" do |post_flag| %> diff --git a/app/views/post_replacements/index.html.erb b/app/views/post_replacements/index.html.erb index 59a766341..b48ea30d9 100644 --- a/app/views/post_replacements/index.html.erb +++ b/app/views/post_replacements/index.html.erb @@ -12,7 +12,7 @@ <%= table_for @post_replacements, class: "striped autofit", width: "100%" do |t| %> <% t.column "Post", width: "1%" do |post_replacement| %> - <%= PostPresenter.preview(post_replacement.post, show_deleted: true) %> + <%= post_preview(post_replacement.post, show_deleted: true) %> <% end %> <% t.column "Source" do |post_replacement| %>
diff --git a/app/views/post_versions/_listing.html.erb b/app/views/post_versions/_listing.html.erb index f8ff320de..87b04a8a6 100644 --- a/app/views/post_versions/_listing.html.erb +++ b/app/views/post_versions/_listing.html.erb @@ -1,6 +1,6 @@
<% if listing_type(:post_id) == :revert %> - <%= PostPresenter.preview(@post_versions.first.post, show_deleted: true) %> + <%= post_preview(@post_versions.first.post, show_deleted: true) %> <% end %> <%= table_for @post_versions, id: "post-versions-table", class: "striped autofit", width: "100%" do |t| %> @@ -11,7 +11,7 @@ <% end %> <% if listing_type(:post_id) == :standard %> <% t.column "Post", width: "1%" do |post_version| %> - <%= PostPresenter.preview(post_version.post, show_deleted: true) %> + <%= post_preview(post_version.post, show_deleted: true) %> <% end %> <% end %> <% t.column "Version", width: "1%" do |post_version| %> diff --git a/app/views/post_votes/index.html.erb b/app/views/post_votes/index.html.erb index a9ba0be9e..2b87427b8 100644 --- a/app/views/post_votes/index.html.erb +++ b/app/views/post_votes/index.html.erb @@ -10,7 +10,7 @@ <%= table_for @post_votes, class: "striped autofit" do |t| %> <% t.column "Post" do |vote| %> - <%= PostPresenter.preview(vote.post, show_deleted: true) %> + <%= post_preview(vote.post, show_deleted: true) %> <% end %> <% t.column "Tags", td: {class: "col-expand"} do |vote| %> <%= TagSetPresenter.new(vote.post.tag_array).inline_tag_list_html %> diff --git a/app/views/posts/partials/index/_posts.html.erb b/app/views/posts/partials/index/_posts.html.erb index cb555b7dc..26c911433 100644 --- a/app/views/posts/partials/index/_posts.html.erb +++ b/app/views/posts/partials/index/_posts.html.erb @@ -1,6 +1,10 @@
- <%= post_set.post_previews_html(self) %> + <% if post_set.shown_posts.empty? %> + <%= render "post_sets/blank" %> + <% else %> + <%= post_previews_html(post_set.posts, show_deleted: post_set.show_deleted?, show_cropped: true, tags: post_set.tag_string) %> + <% end %>
<% if post_set.hidden_posts.present? %> diff --git a/app/views/posts/show.html+tooltip.erb b/app/views/posts/show.html+tooltip.erb index 6e139cefe..1e5409ff9 100644 --- a/app/views/posts/show.html+tooltip.erb +++ b/app/views/posts/show.html+tooltip.erb @@ -39,7 +39,7 @@
">
<% if params[:preview].truthy? %> - <%= PostPresenter.preview(@post, show_deleted: true, compact: true) %> + <%= post_preview(@post, show_deleted: true, compact: true) %> <% end %>
diff --git a/app/views/posts/show.html.erb b/app/views/posts/show.html.erb index a2f202f27..7e7edfb14 100644 --- a/app/views/posts/show.html.erb +++ b/app/views/posts/show.html.erb @@ -44,7 +44,7 @@ <%= render "posts/partials/show/notices", :post => @post %> - <%= content_tag(:section, class: "image-container note-container", **PostPresenter.data_attributes(@post)) do -%> + <%= content_tag(:section, class: "image-container note-container", **PostPreviewComponent.new(post: @post).data_attributes) do -%> <%= render "posts/partials/show/embedded", post: @post %>
<% end -%> diff --git a/app/views/posts/update.js.erb b/app/views/posts/update.js.erb index 606380c4a..226bbecc9 100644 --- a/app/views/posts/update.js.erb +++ b/app/views/posts/update.js.erb @@ -1,6 +1,6 @@ <% if @post.valid? %> var $post = $("#post_<%= @post.id %>"); - var $new_post = $("<%= j PostPresenter.preview(@post, show_deleted: true) %>"); + var $new_post = $("<%= j post_preview(@post, show_deleted: true) %>"); Danbooru.Blacklist.apply_post($new_post.get(0)); $("#post_<%= @post.id %>").replaceWith($new_post); <% if params[:mode] == "quick-edit" %> diff --git a/app/views/recommended_posts/_index.html.erb b/app/views/recommended_posts/_index.html.erb index 7edf76afb..6ed0294a8 100644 --- a/app/views/recommended_posts/_index.html.erb +++ b/app/views/recommended_posts/_index.html.erb @@ -4,6 +4,6 @@ <% end %> <% @recs.each do |rec| %> - <%= PostPresenter.preview(rec[:post], recommended: 100*rec[:score]) %> + <%= post_preview(rec[:post], recommended: 100*rec[:score]) %> <% end %>
diff --git a/app/views/reports/upload_tags.html.erb b/app/views/reports/upload_tags.html.erb index b5384946e..b3183d879 100644 --- a/app/views/reports/upload_tags.html.erb +++ b/app/views/reports/upload_tags.html.erb @@ -8,7 +8,7 @@ <%= table_for @upload_reports do |t| %> <% t.column "Post ID", width: "10%" do |upload_report| %> - <%= PostPresenter.preview(upload_report.becomes(Post), show_deleted: true, tags: "user:#{upload_report.uploader.name}") %> + <%= post_preview(upload_report.becomes(Post), show_deleted: true, tags: "user:#{upload_report.uploader.name}") %> <% end %> <% t.column "Tags added by uploader", width: "45%" do |upload_report| %> <%= TagSetPresenter.new(upload_report.uploader_tags_array).inline_tag_list_html %> diff --git a/app/views/uploads/_related_posts.html.erb b/app/views/uploads/_related_posts.html.erb index 636d261df..9821ba3ae 100644 --- a/app/views/uploads/_related_posts.html.erb +++ b/app/views/uploads/_related_posts.html.erb @@ -10,7 +10,7 @@
<% source.related_posts.each do |post| %> - <%= PostPresenter.preview(post, show_deleted: true, size: true) %> + <%= post_preview(post, show_deleted: true, size: true) %> <% end %>
diff --git a/app/views/uploads/index.html.erb b/app/views/uploads/index.html.erb index eb4b63946..5ada2e452 100644 --- a/app/views/uploads/index.html.erb +++ b/app/views/uploads/index.html.erb @@ -5,7 +5,7 @@ <%= table_for @uploads, class: "striped autofit", width: "100%" do |t| %> <% t.column "Upload" do |upload| %> - <%= PostPresenter.preview(upload.post, tags: "user:#{upload.uploader.name}", show_deleted: true) %> + <%= post_preview(upload.post, tags: "user:#{upload.uploader.name}", show_deleted: true) %> <% end %> <% t.column "Info", td: {class: "col-expand upload-info"} do |upload| %> diff --git a/app/views/users/_post_summary.html.erb b/app/views/users/_post_summary.html.erb index b8987744e..0987a1748 100644 --- a/app/views/users/_post_summary.html.erb +++ b/app/views/users/_post_summary.html.erb @@ -4,9 +4,7 @@ <%= link_to "Uploads", posts_path(:tags => "user:#{user.name}") %>
- <% presenter.uploads.each do |post| %> - <%= PostPresenter.preview(post, :tags => "user:#{user.name}") %> - <% end %> + <%= post_previews_html(presenter.uploads, tags: "user:#{user.name}") %>
<% end %> @@ -17,9 +15,7 @@ <%= link_to "Favorites", posts_path(tags: "ordfav:#{user.name}") %>
- <% presenter.favorites.each do |post| %> - <%= PostPresenter.preview(post, tags: "ordfav:#{user.name}") %> - <% end %> + <%= post_previews_html(presenter.favorites, tags: "ordfav:#{user.name}") %>
<% end %> @@ -33,9 +29,7 @@
- <% presenter.posts_for_saved_search_category(label).each do |post| %> - <%= PostPresenter.preview(post, :tags => "search:#{label}") %> - <% end %> + <%= post_previews_html(presenter.posts_for_saved_search_category, tags: "search:#{label}") %>
<% end %> diff --git a/config/webpacker.yml b/config/webpacker.yml index e8fb2bc5c..69e1fb72e 100644 --- a/config/webpacker.yml +++ b/config/webpacker.yml @@ -11,7 +11,7 @@ default: &default # Additional paths webpack should lookup modules # ['app/assets', 'engine/foo/app/assets'] - resolved_paths: [] + additional_paths: ["app/components"] # Reload manifest.json on all requests so we reload latest compiled packs cache_manifest: false diff --git a/test/components/post_preview_component_test.rb b/test/components/post_preview_component_test.rb new file mode 100644 index 000000000..6e0c61737 --- /dev/null +++ b/test/components/post_preview_component_test.rb @@ -0,0 +1,115 @@ +require "test_helper" + +class PostPreviewComponentTest < ViewComponent::TestCase + include Rails.application.routes.url_helpers + + def render_preview(post, **options) + render_inline(PostPreviewComponent.new(post: post, **options)) + end + + context "The PostPreviewComponent" do + context "for a post visible to the current user" do + should "render" do + @post = create(:post) + node = render_preview(@post, current_user: User.anonymous) + + assert_equal(post_path(@post), node.css("article a").attr("href").value) + assert_equal(@post.preview_file_url, node.css("article img").attr("src").value) + end + end + + context "for a post with restricted tags" do + setup do + Danbooru.config.stubs(:restricted_tags).returns(["touhou"]) + @post = create(:post, tag_string: "touhou") + end + + should "should be visible to Gold users" do + node = render_preview(@post, current_user: create(:gold_user)) + + assert_equal(post_path(@post), node.css("article a").attr("href").value) + assert_equal(@post.preview_file_url, node.css("article img").attr("src").value) + end + + should "not be visible to Members" do + node = render_preview(@post, current_user: create(:user)) + assert_equal("", node.to_s) + end + end + + context "for a banned post" do + setup do + @post = create(:post, is_banned: true) + end + + should "should be visible to Gold users" do + node = render_preview(@post, current_user: create(:gold_user)) + + assert_equal(post_path(@post), node.css("article a").attr("href").value) + assert_equal(@post.preview_file_url, node.css("article img").attr("src").value) + end + + should "not be visible to Members" do + node = render_preview(@post, current_user: create(:user)) + assert_equal("", node.to_s) + end + end + + context "for a banned paid reward" do + setup do + @post = create(:post, tag_string: "paid_reward", is_banned: true) + end + + should "should be visible to Approver users" do + node = render_preview(@post, current_user: create(:approver)) + + assert_equal(post_path(@post), node.css("article a").attr("href").value) + assert_equal(@post.preview_file_url, node.css("article img").attr("src").value) + end + + should "not be visible to Gold users" do + node = render_preview(@post, current_user: create(:gold_user)) + assert_equal("", node.to_s) + end + end + + context "for a non-safe post" do + setup do + @post = create(:post, rating: "q") + end + + should "should be visible to users with safe mode off" do + node = render_preview(@post, current_user: User.anonymous) + + assert_equal(post_path(@post), node.css("article a").attr("href").value) + assert_equal(@post.preview_file_url, node.css("article img").attr("src").value) + end + + should "not be visible to users with safe mode on" do + CurrentUser.stubs(:safe_mode?).returns(true) + node = render_preview(@post, current_user: User.anonymous) + + assert_equal("", node.to_s) + end + end + + context "for a deleted post" do + setup do + @post = create(:post, is_deleted: true) + end + + should "should be visible when the show_deleted flag is set" do + node = render_preview(@post, current_user: User.anonymous, show_deleted: true) + + assert_equal(post_path(@post), node.css("article a").attr("href").value) + assert_equal(@post.preview_file_url, node.css("article img").attr("src").value) + end + + should "not be visible to users normally" do + node = render_preview(@post, current_user: User.anonymous) + + assert_equal("", node.to_s) + end + end + end +end