mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-22 07:14:18 +08:00
This commit introduces the concept of themeable site settings,
which is a new tool for theme authors that lives alongside theme
modifiers and theme settings. Here is a quick summary:
* Theme settings - These are custom settings used to control UI and functionality within your theme or component and provide configuration options. These cannot change core Discourse functionality.
* Theme modifiers - Allows a theme or a component to modify selected server-side functionality of core Discourse as an alternative to building a plugin.
* Themeable site settings (new) - Allows a theme (not components) to override a small subset of core site settings, which generally control parts of the UI and other minor functionality. This allows themes to have a greater control over the full site experience.
Themeable site settings will be shown for all themes, whether the theme
changes
the value or not, and have a similar UI to custom theme settings.
We are also introducing a new page at
`/admin/config/theme-site-settings` that
allows admins to see all possible themeable site settings, and which
themes
are changing the value from the default.
### Configuration
Theme authors can configure initial values themeable site settings using
a section in the `about.json` file like so:
```json
"theme_site_settings": {
"search_experience": "search_field"
}
```
These values will not change when the theme updates, because we cannot
know if admins have manually changed them.
### Limitations
Themeable site settings are only really intended to control elements of
the UI, and when retrieving their value we require a theme ID, so these
limitations apply:
- Themeable site settings cannot be used in Sidekiq jobs
- Themeable site settings cannot be used in markdown rules
- Themeable site settings will be cached separately to client site
settings using theme ID as a key
- Themeable site settings will override keys on the `siteSettings`
service on the client using the application preloader
- `SiteSetting.client_settings_json` will not include themeable site
settings, instead you can call `SiteSetting.theme_site_settings_json`
with a theme ID
### Initial settings
There are only two site settings that will be themeable to begin with:
* `enable_welcome_banner`
* `search_experience`
And our new Horizon theme will take advantage of both. Over time, more
settings that control elements of the UI will be exposed this way.
391 lines
14 KiB
Ruby
Vendored
391 lines
14 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
describe "Admin Customize Themes", type: :system do
|
|
fab!(:color_scheme)
|
|
fab!(:theme) { Fabricate(:theme, name: "Cool theme 1", user_selectable: true) }
|
|
fab!(:admin) { Fabricate(:admin, locale: "en") }
|
|
|
|
let(:theme_page) { PageObjects::Pages::AdminCustomizeThemes.new }
|
|
let(:dialog) { PageObjects::Components::Dialog.new }
|
|
|
|
before { sign_in(admin) }
|
|
|
|
describe "when visiting the page to customize a single theme" do
|
|
it "should allow admin to update the color scheme of the theme" do
|
|
visit("/admin/customize/themes/#{theme.id}")
|
|
|
|
color_scheme_settings = find(".theme-settings__color-scheme")
|
|
|
|
expect(color_scheme_settings).not_to have_css(".submit-edit")
|
|
expect(color_scheme_settings).not_to have_css(".cancel-edit")
|
|
|
|
color_scheme_settings.find(".color-palettes").click
|
|
color_scheme_settings.find(".color-palettes-row[data-value='#{color_scheme.id}']").click
|
|
color_scheme_settings.find(".submit-edit").click
|
|
|
|
expect(color_scheme_settings.find(".setting-value")).to have_content(color_scheme.name)
|
|
expect(color_scheme_settings).not_to have_css(".submit-edit")
|
|
expect(color_scheme_settings).not_to have_css(".cancel-edit")
|
|
end
|
|
end
|
|
|
|
describe "when editing a local theme" do
|
|
it "The saved value is present in the editor" do
|
|
theme.set_field(target: "common", name: "head_tag", value: "console.log('test')", type_id: 0)
|
|
theme.save!
|
|
|
|
visit("/admin/customize/themes/#{theme.id}/common/head_tag/edit")
|
|
|
|
expect(find(".ace_content")).to have_content("console.log('test')")
|
|
end
|
|
|
|
it "can edit the js field" do
|
|
visit("/admin/customize/themes/#{theme.id}/common/js/edit")
|
|
|
|
expect(find(".ace_content")).to have_content("// Your code here")
|
|
find(".ace_text-input", visible: false).fill_in(with: "console.log('test')\n")
|
|
find(".save-theme").click
|
|
|
|
try_until_success do
|
|
expect(
|
|
theme.theme_fields.find_by(target_id: Theme.targets[:extra_js])&.value,
|
|
).to start_with("console.log('test')\n")
|
|
end
|
|
|
|
# Check content is loaded from db correctly
|
|
theme
|
|
.theme_fields
|
|
.find_by(target_id: Theme.targets[:extra_js])
|
|
.update!(value: "console.log('second test')")
|
|
visit("/admin/customize/themes/#{theme.id}/common/js/edit")
|
|
|
|
expect(find(".ace_content")).to have_content("console.log('second test')")
|
|
end
|
|
end
|
|
|
|
it "cannot edit js, upload files or delete system themes" do
|
|
theme.update_columns(id: -10)
|
|
visit("/admin/customize/themes/#{theme.id}")
|
|
expect(page).not_to have_css(".title button")
|
|
expect(page).not_to have_css(".edit-code")
|
|
expect(page).not_to have_css("button.upload")
|
|
expect(page).not_to have_css(".delete")
|
|
end
|
|
|
|
it "hides unecessary sections and buttons for system themes" do
|
|
theme.theme_fields.create!(
|
|
name: "js",
|
|
target_id: Theme.targets[:extra_js],
|
|
value: "console.log('second test')",
|
|
)
|
|
yaml = <<~YAML
|
|
enable_welcome_banner:
|
|
default: true
|
|
description: "Overrides the core `enable welcome banner` site setting"
|
|
YAML
|
|
theme.set_field(target: :settings, name: "yaml", value: yaml)
|
|
theme.save!
|
|
|
|
visit("/admin/customize/themes/#{theme.id}")
|
|
expect(page).to have_css(".created-by")
|
|
expect(page).to have_css(".export")
|
|
expect(page).to have_css(".extra-files")
|
|
expect(page).to have_css(".theme-settings")
|
|
|
|
theme.stubs(:system?).returns(true)
|
|
visit("/admin/customize/themes/#{theme.id}")
|
|
expect(page).not_to have_css(".created-by")
|
|
expect(page).not_to have_css(".export")
|
|
expect(page).not_to have_css(".extra-files")
|
|
expect(page).not_to have_css(".theme-settings")
|
|
end
|
|
|
|
describe "when editing theme translations" do
|
|
it "should allow admin to edit and save the theme translations" do
|
|
theme.set_field(
|
|
target: :translations,
|
|
name: "en",
|
|
value: { en: { group: { hello: "Hello there!" } } }.deep_stringify_keys.to_yaml,
|
|
)
|
|
|
|
theme.save!
|
|
|
|
visit("/admin/customize/themes/#{theme.id}")
|
|
|
|
theme_translations_settings_editor =
|
|
PageObjects::Components::AdminThemeTranslationsSettingsEditor.new
|
|
|
|
theme_translations_settings_editor.fill_in("Hello World")
|
|
theme_translations_settings_editor.save
|
|
|
|
visit("/admin/customize/themes/#{theme.id}")
|
|
|
|
expect(theme_translations_settings_editor.get_input_value).to have_content("Hello World")
|
|
end
|
|
|
|
it "should allow admin to edit and save the theme translations from other languages" do
|
|
theme.set_field(
|
|
target: :translations,
|
|
name: "en",
|
|
value: { en: { group: { hello: "Hello there!" } } }.deep_stringify_keys.to_yaml,
|
|
)
|
|
theme.set_field(
|
|
target: :translations,
|
|
name: "fr",
|
|
value: { fr: { group: { hello: "Bonjour!" } } }.deep_stringify_keys.to_yaml,
|
|
)
|
|
theme.save!
|
|
|
|
visit("/admin/customize/themes/#{theme.id}")
|
|
|
|
theme_translations_settings_editor =
|
|
PageObjects::Components::AdminThemeTranslationsSettingsEditor.new
|
|
expect(theme_translations_settings_editor.get_input_value).to have_content("Hello there!")
|
|
|
|
theme_translations_picker = PageObjects::Components::SelectKit.new(".translation-selector")
|
|
theme_translations_picker.select_row_by_value("fr")
|
|
|
|
expect(page).to have_css(".translations")
|
|
|
|
expect(theme_translations_settings_editor.get_input_value).to have_content("Bonjour!")
|
|
|
|
theme_translations_settings_editor.fill_in("Hello World in French")
|
|
theme_translations_settings_editor.save
|
|
end
|
|
|
|
it "should match the current user locale translation" do
|
|
SiteSetting.allow_user_locale = true
|
|
SiteSetting.set_locale_from_accept_language_header = true
|
|
SiteSetting.default_locale = "fr"
|
|
|
|
theme.set_field(
|
|
target: :translations,
|
|
name: "en",
|
|
value: { en: { group: { hello: "Hello there!" } } }.deep_stringify_keys.to_yaml,
|
|
)
|
|
theme.set_field(
|
|
target: :translations,
|
|
name: "fr",
|
|
value: { fr: { group: { hello: "Bonjour!" } } }.deep_stringify_keys.to_yaml,
|
|
)
|
|
theme.save!
|
|
|
|
visit("/admin/customize/themes/#{theme.id}")
|
|
|
|
theme_translations_settings_editor =
|
|
PageObjects::Components::AdminThemeTranslationsSettingsEditor.new
|
|
|
|
expect(theme_translations_settings_editor.get_input_value).to have_content("Hello there!")
|
|
|
|
theme_translations_picker = PageObjects::Components::SelectKit.new(".translation-selector")
|
|
|
|
expect(theme_translations_picker.component).to have_content("English (US)")
|
|
end
|
|
end
|
|
|
|
describe "when editing a theme's included components" do
|
|
fab!(:component) { Fabricate(:theme, component: true, name: "Cool component 145") }
|
|
|
|
it "can save the included components" do
|
|
theme_page.visit(theme.id)
|
|
theme_page.included_components_selector.expand
|
|
theme_page.included_components_selector.select_row_by_index(0)
|
|
theme_page.included_components_selector.collapse
|
|
theme_page.relative_themes_save_button.click
|
|
expect(theme_page).to have_reset_button_for_setting(".included-components-setting")
|
|
expect(ChildTheme.exists?(parent_theme_id: theme.id, child_theme_id: component.id)).to eq(
|
|
true,
|
|
)
|
|
end
|
|
end
|
|
|
|
context "when visting a component's page" do
|
|
fab!(:component) { Fabricate(:theme, component: true, name: "Cool component 493") }
|
|
|
|
it "has a link to the components page" do
|
|
visit("/admin/customize/themes/#{component.id}")
|
|
expect(theme_page).to have_back_button_to_components_page
|
|
end
|
|
end
|
|
|
|
describe "theme color palette editor" do
|
|
before { SiteSetting.use_overhauled_theme_color_palette = true }
|
|
|
|
it "allows editing colors of theme-owned palette" do
|
|
theme_page.visit(theme.id)
|
|
theme_page.colors_tab.click
|
|
|
|
expect(theme_page).to have_current_path("/admin/customize/themes/#{theme.id}/colors")
|
|
|
|
original_hex = theme_page.color_palette_editor.get_color_value("primary")
|
|
theme_page.color_palette_editor.change_color("primary", "#ff000e")
|
|
|
|
expect(theme_page.changes_banner).to be_visible
|
|
theme_page.changes_banner.click_save
|
|
|
|
page.refresh
|
|
expect(theme_page).to have_colors_tab_active
|
|
|
|
updated_color = theme_page.color_palette_editor.get_color_value("primary")
|
|
expect(updated_color).to eq("#ff000e")
|
|
end
|
|
|
|
it "allows discarding unsaved color changes" do
|
|
theme_page.visit(theme.id)
|
|
theme_page.colors_tab.click
|
|
|
|
original_hex = theme_page.color_palette_editor.get_color_value("primary")
|
|
|
|
theme_page.color_palette_editor.change_color("primary", "#10ff00")
|
|
|
|
theme_page.changes_banner.click_discard
|
|
|
|
expect(theme_page.changes_banner).to be_hidden
|
|
|
|
updated_color = theme_page.color_palette_editor.get_color_value("primary")
|
|
expect(updated_color).to eq(original_hex)
|
|
end
|
|
|
|
it "allows editing dark mode colors" do
|
|
theme_page.visit(theme.id)
|
|
theme_page.colors_tab.click
|
|
|
|
theme_page.color_palette_editor.switch_to_dark_tab
|
|
|
|
original_dark_hex = theme_page.color_palette_editor.get_color_value("primary")
|
|
theme_page.color_palette_editor.change_color("primary", "#000fff")
|
|
|
|
theme_page.changes_banner.click_save
|
|
|
|
page.refresh
|
|
theme_page.color_palette_editor.switch_to_dark_tab
|
|
|
|
updated_dark_color = theme_page.color_palette_editor.get_color_value("primary")
|
|
expect(updated_dark_color).to eq("#000fff")
|
|
end
|
|
|
|
it "shows count of unsaved colors" do
|
|
theme_page.visit(theme.id)
|
|
theme_page.colors_tab.click
|
|
|
|
theme_page.color_palette_editor.change_color("primary", "#eeff80")
|
|
|
|
expect(theme_page.changes_banner).to have_label(
|
|
I18n.t("admin_js.admin.customize.theme.unsaved_colors", count: 1),
|
|
)
|
|
|
|
theme_page.color_palette_editor.switch_to_dark_tab
|
|
|
|
theme_page.color_palette_editor.change_color("primary", "#ff80ee")
|
|
|
|
expect(theme_page.changes_banner).to have_label(
|
|
I18n.t("admin_js.admin.customize.theme.unsaved_colors", count: 2),
|
|
)
|
|
|
|
theme_page.color_palette_editor.change_color("secondary", "#ee30ab")
|
|
expect(theme_page.changes_banner).to have_label(
|
|
I18n.t("admin_js.admin.customize.theme.unsaved_colors", count: 3),
|
|
)
|
|
end
|
|
|
|
it "doesn't show colors tab or DPageHeader for components" do
|
|
component = Fabricate(:theme, component: true)
|
|
theme_page.visit(component.id)
|
|
expect(theme_page.header).to be_hidden
|
|
|
|
expect(theme_page).to have_no_color_scheme_selector
|
|
end
|
|
|
|
it "shows a confirmation dialog when leaving the page with unsaved changes" do
|
|
theme_page.visit(theme.id)
|
|
theme_page.colors_tab.click
|
|
|
|
theme_page.color_palette_editor.change_color("primary", "#eeff80")
|
|
|
|
expect(theme_page.changes_banner).to be_visible
|
|
|
|
find("#site-logo").click
|
|
|
|
expect(dialog).to be_open
|
|
expect(page).to have_content(
|
|
I18n.t("admin_js.admin.customize.theme.unsaved_colors_leave_route_confirmation"),
|
|
)
|
|
|
|
dialog.click_no
|
|
|
|
expect(dialog).to be_closed
|
|
expect(page).to have_current_path("/admin/customize/themes/#{theme.id}/colors")
|
|
|
|
find("#site-logo").click
|
|
expect(dialog).to be_open
|
|
|
|
dialog.click_yes
|
|
expect(page).to have_current_path("/")
|
|
end
|
|
end
|
|
|
|
describe "editing theme site settings" do
|
|
it "shows all themeable site settings and allows editing values" do
|
|
theme_page.visit(theme.id)
|
|
SiteSetting.themeable_site_settings.each do |setting_name|
|
|
expect(theme_page).to have_theme_site_setting(setting_name)
|
|
end
|
|
theme_page.toggle_theme_site_setting("enable_welcome_banner")
|
|
expect(theme_page).to have_overridden_theme_site_setting("enable_welcome_banner")
|
|
expect(page).to have_content(
|
|
I18n.t("admin_js.admin.customize.theme.theme_site_setting_saved"),
|
|
)
|
|
expect(
|
|
ThemeSiteSetting.exists?(theme: theme, name: "enable_welcome_banner", value: "f"),
|
|
).to be_truthy
|
|
end
|
|
|
|
it "allows resetting themeable site setting values back to site setting default" do
|
|
Fabricate(
|
|
:theme_site_setting_with_service,
|
|
theme: theme,
|
|
name: "enable_welcome_banner",
|
|
value: false,
|
|
)
|
|
theme_page.visit(theme.id)
|
|
expect(theme_page).to have_overridden_theme_site_setting("enable_welcome_banner")
|
|
theme_page.reset_overridden_theme_site_setting("enable_welcome_banner")
|
|
expect(page).to have_content(
|
|
I18n.t("admin_js.admin.customize.theme.theme_site_setting_saved"),
|
|
)
|
|
expect(
|
|
ThemeSiteSetting.exists?(theme: theme, name: "enable_welcome_banner", value: "f"),
|
|
).to be_falsey
|
|
end
|
|
|
|
it "does not show the overridden indicator if the theme site setting value in the DB is the same as the default" do
|
|
Fabricate(
|
|
:theme_site_setting_with_service,
|
|
theme: theme,
|
|
name: "enable_welcome_banner",
|
|
value: true,
|
|
)
|
|
theme_page.visit(theme.id)
|
|
expect(theme_page).to have_theme_site_setting("enable_welcome_banner")
|
|
expect(theme_page).to have_no_overridden_theme_site_setting("enable_welcome_banner")
|
|
end
|
|
|
|
it "alters the UI via MessageBus when a theme site setting changes" do
|
|
SiteSetting.refresh!(refresh_site_settings: false, refresh_theme_site_settings: true)
|
|
banner = PageObjects::Components::WelcomeBanner.new
|
|
other_user = Fabricate(:user)
|
|
other_user.user_option.update!(theme_ids: [theme.id])
|
|
sign_in(other_user)
|
|
visit("/")
|
|
expect(banner).to be_visible
|
|
|
|
using_session(:admin) do
|
|
sign_in(admin)
|
|
theme_page.visit(theme.id)
|
|
theme_page.toggle_theme_site_setting("enable_welcome_banner")
|
|
end
|
|
|
|
expect(banner).to be_hidden
|
|
end
|
|
end
|
|
end
|