diff --git a/app/components/forum_post_component/forum_post_component.html.erb b/app/components/forum_post_component/forum_post_component.html.erb index cd1ce382f..0a8a251a9 100644 --- a/app/components/forum_post_component/forum_post_component.html.erb +++ b/app/components/forum_post_component/forum_post_component.html.erb @@ -43,18 +43,20 @@ <% end %> <% end %> <% end %> + <% end %> - <% if policy(forum_post).destroy? && !forum_post.is_original_post?(original_forum_post_id) %> - <% menu.item do %> - <% if forum_post.is_deleted? %> - <%= link_to undelete_forum_post_path(forum_post.id), method: :post, remote: true do %> - <%= undelete_icon %> Undelete - <% end %> - <% else %> - <%= link_to forum_post_path(forum_post.id), "data-confirm": "Are you sure you want to delete this forum post?", method: :delete, remote: true do %> - <%= delete_icon %> Delete - <% end %> - <% end %> + <% if forum_post.is_deleted? && policy(forum_post).undelete? && !forum_post.is_original_post?(original_forum_post_id) %> + <% menu.item do %> + <%= link_to undelete_forum_post_path(forum_post.id), method: :post, remote: true do %> + <%= undelete_icon %> Undelete + <% end %> + <% end %> + <% end %> + + <% if !forum_post.is_deleted? && policy(forum_post).destroy? && !forum_post.is_original_post?(original_forum_post_id) %> + <% menu.item do %> + <%= link_to forum_post_path(forum_post.id), "data-confirm": "Are you sure you want to delete this forum post?", method: :delete, remote: true do %> + <%= delete_icon %> Delete <% end %> <% end %> <% end %> diff --git a/app/controllers/forum_posts_controller.rb b/app/controllers/forum_posts_controller.rb index e6ef8ea68..0ab945b58 100644 --- a/app/controllers/forum_posts_controller.rb +++ b/app/controllers/forum_posts_controller.rb @@ -55,12 +55,16 @@ class ForumPostsController < ApplicationController def destroy @forum_post = authorize ForumPost.find(params[:id]) @forum_post.delete! + + flash[:notice] = @forum_post.errors.none? ? "Post deleted" : @forum_post.errors.full_messages.join("; ") respond_with(@forum_post) end def undelete @forum_post = authorize ForumPost.find(params[:id]) @forum_post.undelete! + + flash[:notice] = @forum_post.errors.none? ? "Post undeleted" : @forum_post.errors.full_messages.join("; ") respond_with(@forum_post) end end diff --git a/app/controllers/wiki_pages_controller.rb b/app/controllers/wiki_pages_controller.rb index f9f6ece3f..7386018b1 100644 --- a/app/controllers/wiki_pages_controller.rb +++ b/app/controllers/wiki_pages_controller.rb @@ -30,15 +30,6 @@ class WikiPagesController < ApplicationController end def show - if params[:format].present? - request.format = params[:format] - elsif params[:id].ends_with?(".html", ".json", ".xml") - request.format = params[:id].split(".").last - params[:id].delete_suffix!(".#{request.format.symbol}") - else - request.format = "html" - end - @wiki_page, found_by = WikiPage.find_by_id_or_title(params[:id]) if request.format.html? && @wiki_page.blank? && found_by == :title diff --git a/app/javascript/src/styles/base/010_reset.scss b/app/javascript/src/styles/base/010_reset.scss index 30fa1c48f..b928b2506 100644 --- a/app/javascript/src/styles/base/010_reset.scss +++ b/app/javascript/src/styles/base/010_reset.scss @@ -1,8 +1,16 @@ +html { + // Disable font boosting on mobile. By default, when desktop mode is enabled + // on mobile, mobile browsers will automagically increase the size of text. + // Usually they do so poorly, making things like headers smaller than body + // text, which breaks the layout. + text-size-adjust: none; +} + *, ::before, ::after { box-sizing: border-box; } -body, h1, h2, h3, h4, h5, h6, p, ul, ol, li, blockquote, dl, dd, menu { +body, h1, h2, h3, h4, h5, h6, p, ul, ol, li, blockquote, dl, dd, menu, input { margin: 0; } diff --git a/app/javascript/src/styles/base/020_base.scss b/app/javascript/src/styles/base/020_base.scss index 6593d41d8..6b072fc8c 100644 --- a/app/javascript/src/styles/base/020_base.scss +++ b/app/javascript/src/styles/base/020_base.scss @@ -50,7 +50,6 @@ fieldset { img { border: none; vertical-align: middle; - image-rendering: smooth; } input, select, textarea { diff --git a/app/javascript/src/styles/common/simple_form.scss b/app/javascript/src/styles/common/simple_form.scss index f17155525..7ceba5e6e 100644 --- a/app/javascript/src/styles/common/simple_form.scss +++ b/app/javascript/src/styles/common/simple_form.scss @@ -83,6 +83,11 @@ form.simple_form { line-height: 1.5em; } + // Hide "*" in label next to required fields. + &.required abbr[title="required"] { + display: none; + } + &.radio_buttons { span.radio label { font-weight: normal; diff --git a/app/models/forum_post.rb b/app/models/forum_post.rb index 5bd66a95b..17ea1d6f9 100644 --- a/app/models/forum_post.rb +++ b/app/models/forum_post.rb @@ -15,13 +15,14 @@ class ForumPost < ApplicationRecord has_one :bulk_update_request validates :body, presence: true, length: { maximum: 200_000 }, if: :body_changed? + validate :validate_deletion_of_original_post + validate :validate_undeletion_of_post before_create :autoreport_spam before_save :handle_reports_on_deletion after_create :update_topic_updated_at_on_create after_update :update_topic_updated_at_on_update_for_original_posts after_destroy :update_topic_updated_at_on_destroy - after_save :delete_topic_if_original_post after_update(:if => ->(rec) {rec.updater_id != rec.creator_id}) do |rec| ModAction.log("#{CurrentUser.user.name} updated forum ##{rec.id}", :forum_post_update) end @@ -83,6 +84,18 @@ class ForumPost < ApplicationRecord votes.exists?(creator_id: user.id, score: score) end + def validate_deletion_of_original_post + if is_original_post? && is_deleted? && !topic.is_deleted? + errors.add(:base, "Can't delete original post without deleting the topic first") + end + end + + def validate_undeletion_of_post + if topic.is_deleted? && !is_deleted? + errors.add(:base, "Can't undelete post without undeleting the topic first") + end + end + def autoreport_spam if SpamDetector.new(self, user_ip: CurrentUser.ip_addr).spam? moderation_reports << ModerationReport.new(creator: User.system, reason: "Spam.") @@ -153,12 +166,6 @@ class ForumPost < ApplicationRecord end end - def delete_topic_if_original_post - if is_deleted? && is_original_post? - topic.update_attribute(:is_deleted, true) - end - end - def handle_reports_on_deletion return unless moderation_reports.pending.present? && is_deleted_change == [false, true] diff --git a/app/models/forum_topic.rb b/app/models/forum_topic.rb index 76056a957..bdf6cea95 100644 --- a/app/models/forum_topic.rb +++ b/app/models/forum_topic.rb @@ -24,12 +24,13 @@ class ForumTopic < ApplicationRecord has_many :tag_implications validates :title, presence: true, length: { maximum: 200 }, if: :title_changed? - validates_associated :original_post validates :category_id, inclusion: { in: CATEGORIES.keys } validates :min_level, inclusion: { in: MIN_LEVELS.values } accepts_nested_attributes_for :original_post - after_update :update_orignal_post + + after_update :update_posts_on_deletion_or_undeletion + after_update :update_original_post after_save(:if => ->(rec) {rec.is_locked? && rec.saved_change_to_is_locked?}) do |rec| ModAction.log("locked forum topic ##{id} (title: #{title})", :forum_topic_lock) end @@ -179,7 +180,14 @@ class ForumTopic < ApplicationRecord (response_count / Danbooru.config.posts_per_page.to_f).ceil end - def update_orignal_post + # Delete all posts when the topic is deleted. Undelete all posts when the topic is undeleted. + def update_posts_on_deletion_or_undeletion + if saved_change_to_is_deleted? + forum_posts.update!(is_deleted: is_deleted) # XXX depends on current user + end + end + + def update_original_post original_post&.update_columns(:updater_id => updater.id, :updated_at => Time.now) end diff --git a/app/models/upload_media_asset.rb b/app/models/upload_media_asset.rb new file mode 100644 index 000000000..9ff3b3a6c --- /dev/null +++ b/app/models/upload_media_asset.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class UploadMediaAsset < ApplicationRecord + belongs_to :upload + belongs_to :media_asset +end diff --git a/app/policies/forum_post_policy.rb b/app/policies/forum_post_policy.rb index bbcc370c1..641b3e6e8 100644 --- a/app/policies/forum_post_policy.rb +++ b/app/policies/forum_post_policy.rb @@ -18,11 +18,11 @@ class ForumPostPolicy < ApplicationPolicy end def destroy? - unbanned? && show? && user.is_moderator? + unbanned? && show? && !record.is_deleted? && user.is_moderator? end def undelete? - unbanned? && show? && user.is_moderator? + unbanned? && show? && record.is_deleted? && !record.topic.is_deleted? && user.is_moderator? end def reply? diff --git a/app/views/forum_posts/destroy.js.erb b/app/views/forum_posts/destroy.js.erb index 7ccf4d7cd..345366b9b 100644 --- a/app/views/forum_posts/destroy.js.erb +++ b/app/views/forum_posts/destroy.js.erb @@ -1 +1 @@ -$("#forum_post_<%= @forum_post.id %>").replaceWith("<%= j render(ForumPostComponent.new(forum_post: @forum_post, current_user: CurrentUser.user)) %>"); +location.reload(); diff --git a/app/views/forum_posts/undelete.js.erb b/app/views/forum_posts/undelete.js.erb index 7ccf4d7cd..345366b9b 100644 --- a/app/views/forum_posts/undelete.js.erb +++ b/app/views/forum_posts/undelete.js.erb @@ -1 +1 @@ -$("#forum_post_<%= @forum_post.id %>").replaceWith("<%= j render(ForumPostComponent.new(forum_post: @forum_post, current_user: CurrentUser.user)) %>"); +location.reload(); diff --git a/app/views/layouts/blank.html.erb b/app/views/layouts/blank.html.erb index 493cbffdc..8073e813c 100644 --- a/app/views/layouts/blank.html.erb +++ b/app/views/layouts/blank.html.erb @@ -4,11 +4,12 @@ <%= NewRelic::Agent.browser_timing_header rescue "" %> <%= page_title %> - + + <%= csrf_meta_tag %> <%= raw Danbooru.config.custom_html_header_content %> - <%= javascript_pack_tag "application" %> + <%= javascript_pack_tag "application", defer: false %> <%= stylesheet_pack_tag "application" %> <%= yield :html_header %> diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb index 2e99094a1..1a16bb66e 100644 --- a/app/views/layouts/default.html.erb +++ b/app/views/layouts/default.html.erb @@ -5,7 +5,8 @@ <%= page_title %> - + + <%= render_meta_links @current_item if @current_item.respond_to?(:paginate) %> <%= tag.link rel: "canonical", href: canonical_url %> <%= tag.link rel: "search", type: "application/opensearchdescription+xml", href: opensearch_url(format: :xml, version: 2), title: "Search posts" %> @@ -22,9 +23,9 @@ <%# XXX hack to only load Ruffle on Flash posts %> <% if controller_name == "posts" && action_name == "show" && @post&.is_flash? %> - <%= javascript_pack_tag "application", "flash" %> + <%= javascript_pack_tag "application", "flash", defer: false %> <% else %> - <%= javascript_pack_tag "application" %> + <%= javascript_pack_tag "application", defer: false %> <% end %> <%= stylesheet_pack_tag "application" %> diff --git a/app/views/robots/index.text.erb b/app/views/robots/index.text.erb index e3d27eb9e..5566786d6 100644 --- a/app/views/robots/index.text.erb +++ b/app/views/robots/index.text.erb @@ -72,6 +72,7 @@ Allow: <%= privacy_policy_path %> Allow: <%= not_found_path %> Allow: /sitemap.xml Allow: /favicon.ico +Allow: /favicon.svg Sitemap: <%= sitemap_url(format: :xml, sitemap: "artists") %> Sitemap: <%= sitemap_url(format: :xml, sitemap: "forum_topics") %> diff --git a/config/initializers/core_extensions.rb b/config/initializers/core_extensions.rb index 3cb5d9f70..ef828ddde 100644 --- a/config/initializers/core_extensions.rb +++ b/config/initializers/core_extensions.rb @@ -63,6 +63,23 @@ class String include Danbooru::Extensions::String end +module MimeNegotationExtension + # Ignore all file extensions except for .html, .js, .json, and .xml when + # parsing the file extension from the URL. Needed for wiki pages (e.g. + # /wiki_pages/rnd.jpg). + private def format_from_path_extension + mime = super + + if mime&.symbol.in?(%i[html js json xml]) + mime + else + nil + end + end +end + +ActionDispatch::Http::MimeNegotiation.prepend(MimeNegotationExtension) + # Make Symbol#to_s return a frozen string. This reduces allocations, but may be # incompatible with some libraries. # diff --git a/config/routes.rb b/config/routes.rb index 950025f90..0f19afcd5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -280,7 +280,7 @@ Rails.application.routes.draw do resources :webhooks do post :receive, on: :collection end - resources :wiki_pages, id: /.+/, format: false do + resources :wiki_pages, id: /.+?(?=\.json|\.xml|\.html)|.+/ do put :revert, on: :member get :search, on: :collection get :show_or_new, on: :collection diff --git a/db/migrate/20220124195900_add_upload_media_assets.rb b/db/migrate/20220124195900_add_upload_media_assets.rb new file mode 100644 index 000000000..7b6fce66e --- /dev/null +++ b/db/migrate/20220124195900_add_upload_media_assets.rb @@ -0,0 +1,12 @@ +class AddUploadMediaAssets < ActiveRecord::Migration[7.0] + def change + create_table :upload_media_assets do |t| + t.timestamps null: false + t.belongs_to :upload, null: false + t.belongs_to :media_asset, null: false + end + + add_foreign_key :upload_media_assets, :uploads, deferrable: :deferred + add_foreign_key :upload_media_assets, :media_assets, deferrable: :deferred + end +end diff --git a/db/structure.sql b/db/structure.sql index 21e65aa6e..3589e5e61 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1906,6 +1906,38 @@ CREATE SEQUENCE public.tags_id_seq ALTER SEQUENCE public.tags_id_seq OWNED BY public.tags.id; +-- +-- Name: upload_media_assets; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.upload_media_assets ( + id bigint NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + upload_id bigint NOT NULL, + media_asset_id bigint NOT NULL +); + + +-- +-- Name: upload_media_assets_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.upload_media_assets_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: upload_media_assets_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.upload_media_assets_id_seq OWNED BY public.upload_media_assets.id; + + -- -- Name: uploads; Type: TABLE; Schema: public; Owner: - -- @@ -2517,6 +2549,13 @@ ALTER TABLE ONLY public.tag_implications ALTER COLUMN id SET DEFAULT nextval('pu ALTER TABLE ONLY public.tags ALTER COLUMN id SET DEFAULT nextval('public.tags_id_seq'::regclass); +-- +-- Name: upload_media_assets id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.upload_media_assets ALTER COLUMN id SET DEFAULT nextval('public.upload_media_assets_id_seq'::regclass); + + -- -- Name: uploads id; Type: DEFAULT; Schema: public; Owner: - -- @@ -2964,6 +3003,14 @@ ALTER TABLE ONLY public.tags ADD CONSTRAINT tags_pkey PRIMARY KEY (id); +-- +-- Name: upload_media_assets upload_media_assets_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.upload_media_assets + ADD CONSTRAINT upload_media_assets_pkey PRIMARY KEY (id); + + -- -- Name: uploads uploads_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -4493,6 +4540,20 @@ CREATE INDEX index_tags_on_name_trgm ON public.tags USING gin (name public.gin_t CREATE INDEX index_tags_on_post_count ON public.tags USING btree (post_count); +-- +-- Name: index_upload_media_assets_on_media_asset_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_upload_media_assets_on_media_asset_id ON public.upload_media_assets USING btree (media_asset_id); + + +-- +-- Name: index_upload_media_assets_on_upload_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_upload_media_assets_on_upload_id ON public.upload_media_assets USING btree (upload_id); + + -- -- Name: index_uploads_on_referer_url; Type: INDEX; Schema: public; Owner: - -- @@ -4806,6 +4867,14 @@ ALTER TABLE ONLY public.uploads ADD CONSTRAINT fk_rails_127111e6ac FOREIGN KEY (post_id) REFERENCES public.posts(id) DEFERRABLE INITIALLY DEFERRED NOT VALID; +-- +-- Name: upload_media_assets fk_rails_171271f781; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.upload_media_assets + ADD CONSTRAINT fk_rails_171271f781 FOREIGN KEY (upload_id) REFERENCES public.uploads(id) DEFERRABLE INITIALLY DEFERRED; + + -- -- Name: bulk_update_requests fk_rails_1773ada54d; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -5414,6 +5483,14 @@ ALTER TABLE ONLY public.post_votes ADD CONSTRAINT fk_rails_f3edc07390 FOREIGN KEY (user_id) REFERENCES public.users(id) DEFERRABLE INITIALLY DEFERRED; +-- +-- Name: upload_media_assets fk_rails_f6bce0ea3f; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.upload_media_assets + ADD CONSTRAINT fk_rails_f6bce0ea3f FOREIGN KEY (media_asset_id) REFERENCES public.media_assets(id) DEFERRABLE INITIALLY DEFERRED; + + -- -- Name: user_upgrades fk_rails_f9349ed07b; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -5697,6 +5774,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20220110171022'), ('20220110171023'), ('20220110171024'), -('20220120233850'); +('20220120233850'), +('20220124195900'); diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 000000000..b901abd79 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/maintenance.html b/public/maintenance.html index 0c703fb95..90a9ef789 100644 --- a/public/maintenance.html +++ b/public/maintenance.html @@ -5,7 +5,8 @@ - + +