FEATURE: Add dark mode option for category backgrounds (#24003)
Adds a new upload field for a dark mode category background that will be used as an alternative when Discourse is using a dark mode theme.
This commit is contained in:
parent
e7afd18155
commit
0cfc42e0e6
|
@ -32,3 +32,14 @@
|
|||
@id="category-background-uploader"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section class="field category-background-image">
|
||||
<label>{{i18n "category.background_image_dark"}}</label>
|
||||
<UppyImageUploader
|
||||
@imageUrl={{this.backgroundDarkImageUrl}}
|
||||
@onUploadDone={{action "backgroundDarkUploadDone"}}
|
||||
@onUploadDeleted={{action "backgroundDarkUploadDeleted"}}
|
||||
@type="category_background_dark"
|
||||
@id="category-dark-background-uploader"
|
||||
/>
|
||||
</section>
|
|
@ -8,6 +8,11 @@ export default buildCategoryPanel("images").extend({
|
|||
return uploadedBackgroundUrl || "";
|
||||
},
|
||||
|
||||
@discourseComputed("category.uploaded_background_dark.url")
|
||||
backgroundDarkImageUrl(uploadedBackgroundDarkUrl) {
|
||||
return uploadedBackgroundDarkUrl || "";
|
||||
},
|
||||
|
||||
@discourseComputed("category.uploaded_logo.url")
|
||||
logoImageUrl(uploadedLogoUrl) {
|
||||
return uploadedLogoUrl || "";
|
||||
|
@ -42,6 +47,14 @@ export default buildCategoryPanel("images").extend({
|
|||
backgroundUploadDeleted() {
|
||||
this._deleteUpload("category.uploaded_background");
|
||||
},
|
||||
|
||||
backgroundDarkUploadDone(upload) {
|
||||
this._setFromUpload("category.uploaded_background_dark", upload);
|
||||
},
|
||||
|
||||
backgroundDarkUploadDeleted() {
|
||||
this._deleteUpload("category.uploaded_background_dark");
|
||||
},
|
||||
},
|
||||
|
||||
_deleteUpload(path) {
|
||||
|
|
|
@ -232,6 +232,7 @@ const Category = RestModel.extend({
|
|||
uploaded_logo_id: this.get("uploaded_logo.id"),
|
||||
uploaded_logo_dark_id: this.get("uploaded_logo_dark.id"),
|
||||
uploaded_background_id: this.get("uploaded_background.id"),
|
||||
uploaded_background_dark_id: this.get("uploaded_background_dark.id"),
|
||||
allow_badges: this.allow_badges,
|
||||
category_setting_attributes: this.category_setting,
|
||||
custom_fields: this.custom_fields,
|
||||
|
|
|
@ -459,6 +459,7 @@ class CategoriesController < ApplicationController
|
|||
:uploaded_logo_id,
|
||||
:uploaded_logo_dark_id,
|
||||
:uploaded_background_id,
|
||||
:uploaded_background_dark_id,
|
||||
:slug,
|
||||
:allow_badges,
|
||||
:topic_template,
|
||||
|
|
|
@ -29,6 +29,7 @@ class Category < ActiveRecord::Base
|
|||
belongs_to :uploaded_logo, class_name: "Upload"
|
||||
belongs_to :uploaded_logo_dark, class_name: "Upload"
|
||||
belongs_to :uploaded_background, class_name: "Upload"
|
||||
belongs_to :uploaded_background_dark, class_name: "Upload"
|
||||
|
||||
has_many :topics
|
||||
has_many :category_users
|
||||
|
@ -115,8 +116,13 @@ class Category < ActiveRecord::Base
|
|||
|
||||
after_save do
|
||||
if saved_change_to_uploaded_logo_id? || saved_change_to_uploaded_logo_dark_id? ||
|
||||
saved_change_to_uploaded_background_id?
|
||||
upload_ids = [self.uploaded_logo_id, self.uploaded_logo_dark_id, self.uploaded_background_id]
|
||||
saved_change_to_uploaded_background_id? || saved_change_to_uploaded_background_dark_id?
|
||||
upload_ids = [
|
||||
self.uploaded_logo_id,
|
||||
self.uploaded_logo_dark_id,
|
||||
self.uploaded_background_id,
|
||||
self.uploaded_background_dark_id,
|
||||
]
|
||||
UploadReference.ensure_exist!(upload_ids: upload_ids, target: self)
|
||||
end
|
||||
end
|
||||
|
@ -1185,6 +1191,7 @@ end
|
|||
# allow_unlimited_owner_edits_on_first_post :boolean default(FALSE), not null
|
||||
# default_slow_mode_seconds :integer
|
||||
# uploaded_logo_dark_id :integer
|
||||
# uploaded_background_dark_id :integer
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
|
|
|
@ -16,6 +16,7 @@ class CategoryList
|
|||
def self.included_associations
|
||||
[
|
||||
:uploaded_background,
|
||||
:uploaded_background_dark,
|
||||
:uploaded_logo,
|
||||
:uploaded_logo_dark,
|
||||
:topic_only_relative_url,
|
||||
|
|
|
@ -81,6 +81,7 @@ class Site
|
|||
:uploaded_logo,
|
||||
:uploaded_logo_dark,
|
||||
:uploaded_background,
|
||||
:uploaded_background_dark,
|
||||
:tags,
|
||||
:tag_groups,
|
||||
:form_templates,
|
||||
|
|
|
@ -35,6 +35,7 @@ class BasicCategorySerializer < ApplicationSerializer
|
|||
has_one :uploaded_logo, embed: :object, serializer: CategoryUploadSerializer
|
||||
has_one :uploaded_logo_dark, embed: :object, serializer: CategoryUploadSerializer
|
||||
has_one :uploaded_background, embed: :object, serializer: CategoryUploadSerializer
|
||||
has_one :uploaded_background_dark, embed: :object, serializer: CategoryUploadSerializer
|
||||
|
||||
def include_parent_category_id?
|
||||
parent_category_id
|
||||
|
|
|
@ -3734,6 +3734,7 @@ en:
|
|||
logo: "Category Logo Image"
|
||||
logo_dark: "Dark Mode Category Logo Image"
|
||||
background_image: "Category Background Image"
|
||||
background_image_dark: "Dark Category Background Image"
|
||||
badge_colors: "Badge colors"
|
||||
background_color: "Background color"
|
||||
foreground_color: "Foreground color"
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddDarkModeBackgroundToCategories < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :categories, :uploaded_background_dark_id, :integer, index: true
|
||||
end
|
||||
end
|
|
@ -34,7 +34,7 @@ module Stylesheet
|
|||
when Stylesheet::Manager::COLOR_SCHEME_STYLESHEET
|
||||
file += importer.import_color_definitions
|
||||
file += importer.import_wcag_overrides
|
||||
file += importer.category_backgrounds
|
||||
file += importer.category_backgrounds(options[:color_scheme_id])
|
||||
file += importer.font
|
||||
end
|
||||
end
|
||||
|
|
|
@ -95,11 +95,16 @@ module Stylesheet
|
|||
contents
|
||||
end
|
||||
|
||||
def category_backgrounds
|
||||
def category_backgrounds(color_scheme_id)
|
||||
is_dark_color_scheme =
|
||||
color_scheme_id.present? && ColorScheme.find_by_id(color_scheme_id)&.is_dark?
|
||||
|
||||
contents = +""
|
||||
Category
|
||||
.where("uploaded_background_id IS NOT NULL")
|
||||
.each { |c| contents << category_css(c) if c.uploaded_background&.url.present? }
|
||||
.each do |c|
|
||||
contents << category_css(c, is_dark_color_scheme) if c.uploaded_background&.url.present?
|
||||
end
|
||||
|
||||
contents
|
||||
end
|
||||
|
@ -215,9 +220,20 @@ module Stylesheet
|
|||
@theme == :nil ? nil : @theme
|
||||
end
|
||||
|
||||
def category_css(category)
|
||||
def category_css(category, is_dark_color_scheme)
|
||||
full_slug = category.full_slug.split("-")[0..-2].join("-")
|
||||
"body.category-#{full_slug} { background-image: url(#{upload_cdn_path(category.uploaded_background.url)}) }\n"
|
||||
|
||||
# in case we're using a dark color scheme, we define the background using the dark image
|
||||
# if one is available. Otherwise, we use the light image by default.
|
||||
if is_dark_color_scheme && category.uploaded_background_dark&.url.present?
|
||||
return category_background_css(full_slug, category.uploaded_background_dark.url)
|
||||
end
|
||||
|
||||
category_background_css(full_slug, category.uploaded_background.url)
|
||||
end
|
||||
|
||||
def category_background_css(full_slug, background_url)
|
||||
"body.category-#{full_slug} { background-image: url(#{upload_cdn_path(background_url)}) }"
|
||||
end
|
||||
|
||||
def font_css(font)
|
||||
|
|
|
@ -220,6 +220,7 @@ task "site:export_structure", [:zip_path] => :environment do |task, args|
|
|||
uploaded_logo_id: data.set_upload(c.uploaded_logo_id),
|
||||
uploaded_logo_dark_id: data.set_upload(c.uploaded_logo_dark_id),
|
||||
uploaded_background_id: data.set_upload(c.uploaded_background_id),
|
||||
uploaded_background_dark_id: data.set_upload(c.uploaded_background_dark_id),
|
||||
topic_featured_link_allowed: c.topic_featured_link_allowed,
|
||||
all_topics_wiki: c.all_topics_wiki,
|
||||
show_subcategory_list: c.show_subcategory_list,
|
||||
|
|
|
@ -965,6 +965,7 @@ def analyze_missing_s3
|
|||
%i[categories uploaded_logo_id],
|
||||
%i[categories uploaded_logo_dark_id],
|
||||
%i[categories uploaded_background_id],
|
||||
%i[categories uploaded_background_dark_id],
|
||||
%i[custom_emojis upload_id],
|
||||
%i[theme_fields upload_id],
|
||||
%i[user_exports upload_id],
|
||||
|
|
|
@ -229,6 +229,16 @@ RSpec.describe Jobs::CleanUpUploads do
|
|||
expect(Upload.exists?(id: category_background_upload.id)).to eq(true)
|
||||
end
|
||||
|
||||
it "does not delete category dark background uploads" do
|
||||
category_background_dark_upload = fabricate_upload
|
||||
Fabricate(:category, uploaded_background_dark: category_background_dark_upload)
|
||||
|
||||
Jobs::CleanUpUploads.new.execute(nil)
|
||||
|
||||
expect(Upload.exists?(id: expired_upload.id)).to eq(false)
|
||||
expect(Upload.exists?(id: category_background_dark_upload.id)).to eq(true)
|
||||
end
|
||||
|
||||
it "does not delete post uploads" do
|
||||
upload = fabricate_upload
|
||||
Fabricate(:post, uploads: [upload])
|
||||
|
|
|
@ -3,33 +3,114 @@
|
|||
require "stylesheet/importer"
|
||||
|
||||
RSpec.describe Stylesheet::Importer do
|
||||
def compile_css(name)
|
||||
Stylesheet::Compiler.compile_asset(name)[0]
|
||||
def compile_css(name, options = {})
|
||||
Stylesheet::Compiler.compile_asset(name, options)[0]
|
||||
end
|
||||
|
||||
describe "#category_backgrounds" do
|
||||
it "applies CDN to background category images" do
|
||||
expect(compile_css("color_definitions")).to_not include("body.category-")
|
||||
|
||||
it "uses the correct background image based in the color scheme" do
|
||||
background = Fabricate(:upload)
|
||||
background_dark = Fabricate(:upload)
|
||||
|
||||
parent_category = Fabricate(:category)
|
||||
category =
|
||||
Fabricate(
|
||||
:category,
|
||||
parent_category_id: parent_category.id,
|
||||
uploaded_background: background,
|
||||
uploaded_background_dark: background_dark,
|
||||
)
|
||||
|
||||
expect(compile_css("color_definitions")).to include(
|
||||
# light color schemes
|
||||
["Neutral", "Shades of Blue", "WCAG", "Summer", "Solarized Light"].each do |scheme_name|
|
||||
scheme = ColorScheme.create_from_base(name: "Light Test", base_scheme_id: scheme_name)
|
||||
|
||||
compiled_css = compile_css("color_definitions", { color_scheme_id: scheme.id })
|
||||
|
||||
expect(compiled_css).to include(
|
||||
"body.category-#{parent_category.slug}-#{category.slug}{background-image:url(#{background.url})}",
|
||||
)
|
||||
expect(compiled_css).not_to include(background_dark.url)
|
||||
end
|
||||
|
||||
# dark color schemes
|
||||
[
|
||||
"Dark",
|
||||
"Grey Amber",
|
||||
"Latte",
|
||||
"Dark Rose",
|
||||
"WCAG Dark",
|
||||
"Dracula",
|
||||
"Solarized Dark",
|
||||
].each do |scheme_name|
|
||||
scheme = ColorScheme.create_from_base(name: "Light Test", base_scheme_id: scheme_name)
|
||||
|
||||
compiled_css = compile_css("color_definitions", { color_scheme_id: scheme.id })
|
||||
|
||||
expect(compiled_css).not_to include(background.url)
|
||||
expect(compiled_css).to include(
|
||||
"body.category-#{parent_category.slug}-#{category.slug}{background-image:url(#{background_dark.url})}",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
it "applies CDN to background category images" do
|
||||
expect(compile_css("color_definitions")).to_not include("body.category-")
|
||||
|
||||
background = Fabricate(:upload)
|
||||
background_dark = Fabricate(:upload)
|
||||
|
||||
parent_category = Fabricate(:category)
|
||||
category =
|
||||
Fabricate(
|
||||
:category,
|
||||
parent_category_id: parent_category.id,
|
||||
uploaded_background: background,
|
||||
uploaded_background_dark: background_dark,
|
||||
)
|
||||
|
||||
compiled_css = compile_css("color_definitions")
|
||||
expect(compiled_css).to include(
|
||||
"body.category-#{parent_category.slug}-#{category.slug}{background-image:url(#{background.url})}",
|
||||
)
|
||||
|
||||
GlobalSetting.stubs(:cdn_url).returns("//awesome.cdn")
|
||||
expect(compile_css("color_definitions")).to include(
|
||||
compiled_css = compile_css("color_definitions")
|
||||
expect(compiled_css).to include(
|
||||
"body.category-#{parent_category.slug}-#{category.slug}{background-image:url(//awesome.cdn#{background.url})}",
|
||||
)
|
||||
end
|
||||
|
||||
it "applies CDN to dark background category images" do
|
||||
scheme = ColorScheme.create_from_base(name: "Dark Test", base_scheme_id: "Dark")
|
||||
expect(compile_css("color_definitions", { color_scheme_id: scheme.id })).to_not include(
|
||||
"body.category-",
|
||||
)
|
||||
|
||||
background = Fabricate(:upload)
|
||||
background_dark = Fabricate(:upload)
|
||||
|
||||
parent_category = Fabricate(:category)
|
||||
category =
|
||||
Fabricate(
|
||||
:category,
|
||||
parent_category_id: parent_category.id,
|
||||
uploaded_background: background,
|
||||
uploaded_background_dark: background_dark,
|
||||
)
|
||||
|
||||
compiled_css = compile_css("color_definitions", { color_scheme_id: scheme.id })
|
||||
expect(compiled_css).to include(
|
||||
"body.category-#{parent_category.slug}-#{category.slug}{background-image:url(#{background_dark.url})}",
|
||||
)
|
||||
|
||||
GlobalSetting.stubs(:cdn_url).returns("//awesome.cdn")
|
||||
compiled_css = compile_css("color_definitions", { color_scheme_id: scheme.id })
|
||||
expect(compiled_css).to include(
|
||||
"body.category-#{parent_category.slug}-#{category.slug}{background-image:url(//awesome.cdn#{background_dark.url})}",
|
||||
)
|
||||
end
|
||||
|
||||
it "applies S3 CDN to background category images" do
|
||||
setup_s3
|
||||
SiteSetting.s3_use_iam_profile = true
|
||||
|
@ -40,7 +121,32 @@ RSpec.describe Stylesheet::Importer do
|
|||
background = Fabricate(:upload_s3)
|
||||
category = Fabricate(:category, uploaded_background: background)
|
||||
|
||||
expect(compile_css("color_definitions")).to include(
|
||||
compiled_css = compile_css("color_definitions")
|
||||
expect(compiled_css).to include(
|
||||
"body.category-#{category.slug}{background-image:url(https://s3.cdn/original",
|
||||
)
|
||||
end
|
||||
|
||||
it "applies S3 CDN to dark background category images" do
|
||||
scheme = ColorScheme.create_from_base(name: "Dark Test", base_scheme_id: "WCAG Dark")
|
||||
|
||||
setup_s3
|
||||
SiteSetting.s3_use_iam_profile = true
|
||||
SiteSetting.s3_upload_bucket = "test"
|
||||
SiteSetting.s3_region = "ap-southeast-2"
|
||||
SiteSetting.s3_cdn_url = "https://s3.cdn"
|
||||
|
||||
background = Fabricate(:upload_s3)
|
||||
background_dark = Fabricate(:upload_s3)
|
||||
category =
|
||||
Fabricate(
|
||||
:category,
|
||||
uploaded_background: background,
|
||||
uploaded_background_dark: background_dark,
|
||||
)
|
||||
|
||||
compiled_css = compile_css("color_definitions", { color_scheme_id: scheme.id })
|
||||
expect(compiled_css).to include(
|
||||
"body.category-#{category.slug}{background-image:url(https://s3.cdn/original",
|
||||
)
|
||||
end
|
||||
|
|
|
@ -22,6 +22,7 @@ RSpec.describe UploadReference do
|
|||
fab!(:upload1) { Fabricate(:upload) }
|
||||
fab!(:upload2) { Fabricate(:upload) }
|
||||
fab!(:upload3) { Fabricate(:upload) }
|
||||
fab!(:upload4) { Fabricate(:upload) }
|
||||
|
||||
it "creates upload references" do
|
||||
category = nil
|
||||
|
@ -32,13 +33,14 @@ RSpec.describe UploadReference do
|
|||
uploaded_logo_id: upload1.id,
|
||||
uploaded_logo_dark_id: upload2.id,
|
||||
uploaded_background_id: upload3.id,
|
||||
uploaded_background_dark_id: upload4.id,
|
||||
)
|
||||
}.to change { UploadReference.count }.by(3)
|
||||
}.to change { UploadReference.count }.by(4)
|
||||
|
||||
upload_reference = UploadReference.last
|
||||
expect(upload_reference.target).to eq(category)
|
||||
|
||||
expect { category.destroy! }.to change { UploadReference.count }.by(-3)
|
||||
expect { category.destroy! }.to change { UploadReference.count }.by(-4)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -260,6 +260,12 @@
|
|||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"uploaded_background_dark": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -310,7 +316,8 @@
|
|||
"search_priority",
|
||||
"uploaded_logo",
|
||||
"uploaded_logo_dark",
|
||||
"uploaded_background"
|
||||
"uploaded_background",
|
||||
"uploaded_background_dark"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
@ -167,6 +167,12 @@
|
|||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"uploaded_background_dark": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -206,7 +212,8 @@
|
|||
"subcategory_ids",
|
||||
"uploaded_logo",
|
||||
"uploaded_logo_dark",
|
||||
"uploaded_background"
|
||||
"uploaded_background",
|
||||
"uploaded_background_dark"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -263,6 +263,12 @@
|
|||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"uploaded_background_dark": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -314,7 +320,8 @@
|
|||
"search_priority",
|
||||
"uploaded_logo",
|
||||
"uploaded_logo_dark",
|
||||
"uploaded_background"
|
||||
"uploaded_background",
|
||||
"uploaded_background_dark"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
@ -701,6 +701,12 @@
|
|||
"null"
|
||||
]
|
||||
},
|
||||
"uploaded_background_dark": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"can_edit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
@ -752,6 +758,7 @@
|
|||
"uploaded_logo",
|
||||
"uploaded_logo_dark",
|
||||
"uploaded_background",
|
||||
"uploaded_background_dark",
|
||||
"can_edit"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue