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:
Sérgio Saquetim 2023-10-20 09:48:06 -03:00 committed by GitHub
parent e7afd18155
commit 0cfc42e0e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 228 additions and 20 deletions

View File

@ -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>

View File

@ -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) {

View File

@ -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,

View File

@ -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,

View File

@ -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
#

View File

@ -16,6 +16,7 @@ class CategoryList
def self.included_associations
[
:uploaded_background,
:uploaded_background_dark,
:uploaded_logo,
:uploaded_logo_dark,
:topic_only_relative_url,

View File

@ -81,6 +81,7 @@ class Site
:uploaded_logo,
:uploaded_logo_dark,
:uploaded_background,
:uploaded_background_dark,
:tags,
:tag_groups,
:form_templates,

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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],

View File

@ -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])

View File

@ -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

View File

@ -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

View File

@ -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"
]
}
},

View File

@ -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"
]
}
}

View File

@ -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"
]
}
},

View File

@ -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"
]
}