discourse/app/serializers/theme_serializer.rb
Kris dbcbd0d896
FEATURE: add modifier to restrict theme color schemes (#38796)
This allows themes to set a modifier in their `about.json` that
restricts a theme's color palettes to only what's defined within the
theme.

```json
"modifiers": {
   "only_theme_color_schemes": true
}
```

Once set, a theme's color palette settings will only list the palettes
included in about.json. A banner is shown indicating that the theme
restricts palettes.

<img width="700" alt="image"
src="https://github.com/user-attachments/assets/cfb4433c-c7c9-4923-a121-fddd572c29ea"
/>

<img width="300" alt="image"
src="https://github.com/user-attachments/assets/67ec1f72-d408-4e3b-911f-a2d0e4db9a37"
/>


If there's only 1 palette defined by the theme, it will be set to both
light and dark settings.

If there are more than 1 palettes defined, we check to see if one is
dark and will use the first dark palette for the dark setting.

If there are no color palettes defined by the theme, the color selection
will be unrestricted.

---------

Co-authored-by: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com>
2026-03-26 09:03:11 -04:00

113 lines
3 KiB
Ruby

# frozen_string_literal: true
require "base64"
class ThemeSerializer < BasicThemeSerializer
attributes :color_scheme_id,
:dark_color_scheme_id,
:user_selectable,
:auto_update,
:remote_theme_id,
:settings,
:themeable_site_settings,
:errors,
:supported?,
:enabled?,
:disabled_at,
:theme_fields,
:screenshot_dark_url,
:screenshot_light_url,
:system,
:only_theme_color_schemes
has_one :color_scheme, serializer: ColorSchemeSerializer, embed: :object
has_one :user, serializer: UserNameSerializer, embed: :object
has_one :disabled_by, serializer: UserNameSerializer, embed: :object
has_many :child_themes, serializer: BasicThemeSerializer, embed: :objects
has_many :parent_themes, serializer: BasicThemeSerializer, embed: :objects
has_one :remote_theme, serializer: RemoteThemeSerializer, embed: :objects
has_many :translations, serializer: ThemeTranslationSerializer, embed: :objects
def initialize(theme, options = {})
super
@include_theme_field_values = options[:include_theme_field_values] || false
@errors = []
object.theme_fields.each { |o| @errors << o.error if o.error }
end
def theme_fields
ActiveModel::ArraySerializer.new(
object.theme_fields,
each_serializer: ThemeFieldSerializer,
include_value: include_theme_field_values?,
).as_json
end
def include_theme_field_values?
# This is passed into each `ThemeFieldSerializer` to determine if `value` will be serialized.
# We only want to serialize if we are viewing staff_action_logs (for diffing changes), or if
# the theme is a local theme, so the saved values appear in the theme field editor.
@include_theme_field_values || object.remote_theme_id.nil?
end
def child_themes
object.child_themes
end
def parent_themes
object.parent_themes
end
def settings
object.settings.map do |_name, setting|
ThemeSettingsSerializer.new(setting, scope:, root: false)
end
rescue ThemeSettingsParser::InvalidYaml => e
@errors << e.message
nil
end
# Components always return an empty array here
def themeable_site_settings
# UI for editing settings always expects the value + default to be a string
# to compare whether the setting has been changed or not.
object.themeable_site_settings.each do |tss|
tss[:default] = tss[:default].to_s
tss[:value] = tss[:value].to_s
end
end
def include_themeable_site_settings?
!object.component?
end
def include_child_themes?
!object.component?
end
def errors
@errors
end
def include_errors?
@errors.present?
end
def include_disabled_at?
object.component? && !object.enabled?
end
def include_disabled_by?
include_disabled_at?
end
def system
object.system?
end
def only_theme_color_schemes
object.theme_modifier_set&.only_theme_color_schemes || false
end
end