This commit starts to introduce services to replace actions in the
ThemesController. We will start with the low langing fruit:
* Create
* Destroy
* BulkDestroy
* GetTranslations
Endpoints like #import and #update will be much harder.
Also, the https://github.com/discourse/discourse-theme-creator plugin
overrides some of these controller actions directly, so we need
to be careful.
Introduction of system themes. System themes are local themes which:
- Cannot be deleted;
- Cannot have “custom code” added, components only;
- Cannot have uploads;
- Cannot edit color palettes;
- Are updated on deploy, like core plugins.
This PR added the Foundation system theme, which is an empty theme like
Default. The Foundation theme will be added in the next PR.
In a development environment, when system theme files are
changed/added/deleted, the theme is reuploaded and the page is reloaded
to make it a good experience for the engineer working on improvements.
System themes are not visible until
`SiteSetting.experimental_system_theme` is enabled.
We will be moving towards `type="module"` for all of Discourse's JS
bundles in the near future. This commit makes a start by applying the
change to translation bundles.
This was previously merged, but the lack of `apply_cdn_headers` on the
locales controller led to CORS errors on sites with CDNs.
We will be moving towards `type="module"` for all of Discourse's JS
bundles in the near future. This commit makes a start by applying the
change to translation bundles.
Without this wrapper, `data` is defined in the global scope and clashes
when there are multiple themes with translations.
Followup to 033cccdf17a56b43f9f243bb549abe0a41a0de63
We can remove the wrapper once we land
https://github.com/discourse/discourse/pull/33107
Theme translations are very simple JS, and do not need to be run through
the theme transpiler. This brings their format in-line with core/plugin
translations.
Extracted from https://github.com/discourse/discourse/pull/33103
This commit removes the color palette dropdown from the theme page and replaces it with a new "Colors" tab where the theme's color palette can be edited directly in that tab on the theme page. With this change, a theme's color palette is strongly tied to its theme and can't be linked to other themes and it can't be selected by users without using the theme as well.
All of the changes are behind a feature flag. To enable it, turn on the `use_overhauled_theme_color_palette` setting.
Co-authored-by: Ella <ella.estigoy@gmail.com>
`strict_loading` was added to prevent it happening in the future. Few
adjustments had to be made:
- include color_scheme and color_scheme_colors, also for parent and
child themes;
- internal translations were using preload_fields, but it was too deep
to correctly use preloaded tables. I had to pass
`preloaded_locale_fields` manually;
- include theme in color_scheme.
Before:
<img width="663" alt="Screenshot 2025-05-15 at 3 43 47 pm"
src="https://github.com/user-attachments/assets/b55ce11e-80cb-43eb-8e31-940b0e9859f3"
/>
After:
<img width="665" alt="Screenshot 2025-05-16 at 11 29 00 am"
src="https://github.com/user-attachments/assets/f00bac19-f64b-4048-b220-4d0a9d90a929"
/>
The new themes listing page at `/admin/config/customize/themes`
currently has poor performance compared to the components page
(`/admin/config/customize/components`) due to various N+1 issues,
loading all themes and components from the server when only themes are
needed, and serializing data/attributes that aren't needed for rendering
the themes grid.
This commit improves the performance by eliminating all N+1 that are
currently present, excluding components from the page payload, and
reducing the amount of data transmitted for each theme when loading the
page.
Previously we were compiling core and theme CSS into two targets:
Desktop and Mobile. The majority of both files was the 'common' css.
This commit splits those common styles into their own targets so that
there is less duplication. This should improve compilation times + cache
reuse, as well as opening the door for experiments with
media-query-based mobile-modes.
The only functional change is that we can no longer use `@extend` to
copy 'common' rules in core to mobile/desktop. This is probably for the
best. Duplication and/or mixins are a more native-css pattern for this.
Plugins already have a common / mobile / desktop pattern, so are
unchanged by this commit.
Theme/component description is fetched via the `locale_fields` and
`theme_translation_overrides` associations on the `Theme` model. We're
currently not preloading these associations when serializing
themes/components, which causes an N+1 performance issue when accessing
the themes/components listing pages.
Follow-up to https://github.com/discourse/discourse/pull/31887
This commit introduces a new design for the components listing page, which
is not linked from anywhere in the UI at the moment, but it can be
accessed by heading to the `/admin/config/customize/components` path
directly. We'll make this new design available from the sidebar and
remove the old page once we've tested and validated the new design
internally.
Internal topic: t/146007.
---------
Co-authored-by: Ella <ella.estigoy@gmail.com>
Previously we would prepend extra content to developer-authored files,
which means adding `@use` in some files would throw an error because
`@use` must be at the top of any compiled file.
Instead, we can ensure any developer-authored files are on the load
path, and then `@import` them into the synthetic entrypoint.
Plugin color_definitions stylesheets are an edge case here, and will
need to be handled separately (or... wait until we move to native css
relative-color syntax, then we can drop color-definition stylesheets
altogether)
Related to https://github.com/discourse/discourse/pull/30893
As part of the theme overhauling project, we're making each theme fully
own/control its color palette which can be edited directly on the theme
page. To make this possible, we need to introduce a special type of
color palettes that are marked as "owned by a theme" in the database
which aren't displayed in the admin color palettes page and can't be
edited from it. This commit is the first step of this change; it adds a new
join table to associate a color palette with a theme. For now, we're
keeping the relationship one-to-one (hence the `UNIQUE` indexes), but we
may later change it to one-to-many.
Internal topic: t/141648.
This commit contains various quality improvements to
our site setup wizard, along with some rearrangement of
steps to improve the admin setup experience and encourage
admins to customize the site early to avoid "all sites look the
same" sentiment.
#### Step rearrangement
* “Your site is ready” from 3 → 4
* “Logos” from 4 → 5
* “Look and feel” from 5 → 3
#### Font selector improvements
Changes the wizard font selector dropdown to show
a preview of all fonts with a CSS class so you don't
have to choose the font to get a preview.
Also makes the fonts appear in alphabetical order.
#### Preview improvements
Placeholder text changed from lorem ipsum to actual topic titles,
category names, and post content. This makes it feel more "real".
Fixes "undefined" categories. Added a date to the topic timeline.
Fixes button rectangles and other UI elements not changing in
size when the font changed, leading to cut off text which looked super
messy. Also fixed some font color issues.
Fixed table header alignment for Latest topic list.
#### Homepage style selector improvements
Limited the big list of homepage styles to Latest, Hot, Categories with latest topics,
and Category boxes based on research into the most common options.
#### Preview header
Changed the preview header to move the hamburger to the left
and add a chat icon
#### And more!
Changed the background of the wizard to use our branded blob style.
Previously, theme hbr files were compiled to an IIFE, which would be executed before the app is booted. That is causing silenced deprecations to be printed, because the deprecation-workflow isn't set up when the IIFE is run.
This commit updates the theme compiler so that it matches the ember-cli-based raw-hbs compiler. Templates are output to normal modules, which will then be loaded by the existing `eager-load-raw-templates` initializer. This runs after the app has started booting.
Theme modifiers can now be defined as theme settings, this allows for
site operators to override behavior of theme modifiers.
New syntax is:
```
{
...
"modifiers": {
"modifier_name": {
"type": "setting",
"value": "setting_name"
}
}
}
```
This also introduces a new theme modifier for serialize_post_user_badges. Name of badge must match the name of the badge in the badges table. The client-side is updated to load this new data from the post-stream serializer.
Co-authored-by: David Taylor <david@taylorhq.com>
* PERF: Fix N+1 issue for javascript_cache
* FIX: missing upload fields should still appear in stylesheets
Sass is still expected to compile successfully even without uploads.
Revert a blank upload to have a blank URL
* DEV: remove unneeded test comment
---------
Co-authored-by: Jeff Wong <awole20@gmail.com>
decorator-transforms (https://github.com/ef4/decorator-transforms) is a modern replacement for babel's plugin-proposal-decorators. It provides a decorator implementation using modern browser features, without needing to enable babel's full suite of class feature transformations. This improves the developer experience and performance.
In local testing with Google's 'tachometer' tool, this reduces Discourse's 'init-to-render' time by around 3-4% (230ms -> 222ms).
It reduces our initial gzip'd JS payloads by 3.2% (2.43MB -> 2.35MB), or 7.5% (14.5MB -> 13.4MB) uncompressed.
This was previously reverted in 97847f6. This version includes a babel transformation which works around the bug in Safari <= 15.
For Cloudflare compatibility issues, check https://meta.discourse.org/t/311390
This reverts commit 0f4520867b565c1f114293ccda547615fed36ab5.
This has led to two problems:
1. An incompatibility with Cloudflare's "auto minify" feature. They've deprecated this feature because of incompatibility with modern JS syntax. But unfortunately it will remain enabled on existing properties until 2024-08-05.
2. Discourse fails to boot in Safari 15. This is strange, because Safari does support all the required features in our production JS bundles. Even more strangely, things start working as soon as you open the developer tools. That suggests the cause could be a Safari bug rather than a simple incompatibility.
Reverting while we work out a path forward on both those issues.
decorator-transforms (https://github.com/ef4/decorator-transforms) is a modern replacement for babel's plugin-proposal-decorators. It provides a decorator implementation using modern browser features, without needing to enable babel's full suite of class feature transformations. This improves the developer experience and performance.
In local testing with Google's 'tachometer' tool, this reduces Discourse's 'init-to-render' time by around 3-4% (230ms -> 222ms).
It reduces our initial gzip'd JS payloads by 3.2% (2.43MB -> 2.35MB), or 7.5% (14.5MB -> 13.4MB) uncompressed.
This commit introduces the `run_theme_migration` spec helper to allow
theme developers to write RSpec tests for theme migrations. For example,
this allows the following RSpec test to be written in themes:
```
RSpec.describe "0003-migrate-small-links-setting migration" do
let!(:theme) { upload_theme_component }
it "should set target property to `_blank` if previous target component is not valid or empty" do
theme.theme_settings.create!(
name: "small_links",
theme: theme,
data_type: ThemeSetting.types[:string],
value: "some text, #|some text 2, #, invalid target",
)
run_theme_migration(theme, "0003-migrate-small-links-setting")
expect(theme.settings[:small_links].value).to eq(
[
{ "text" => "some text", "url" => "#", "target" => "_blank" },
{ "text" => "some text 2", "url" => "#", "target" => "_blank" },
],
)
end
end
```
This change is being introduced because we realised that writting just
javascript tests for the migrations is insufficient since javascript
tests do not ensure that the migrated theme settings can actually be
successfully saved into the database. Hence, we are introduce this
helper as a way for theme developers to write "end-to-end" migrations
tests.
This is a follow up to e2da72b76c7f49c2c3a159ce48051c4050f3dc3e.
Why this change?
According to https://web.dev/articles/preload-critical-assets,
> By preloading a certain resource, you are telling the browser that you would like to fetch it sooner than the browser would otherwise discover it because you are certain that it is important for the current page.
The preload resource hint is meant to tell the browser to fetch
resources that it would not discover upfront or early. However, we are
not using it the right way because we are literally adding the resource
hint right before a `<script>` tag which means the browser would have
discovered the resource even without the resource hint.
What does this change do?
This commit removes the preload resource hint which are added right
before script tags since the optimization here is highly questionable at the expense of making
our initial DOM larger.
The strict-dynamic CSP directive is supported in all our target browsers, and makes for a much simpler configuration. Instead of allowlisting paths, we use a per-request nonce to authorize `<script>` tags, and then those scripts are allowed to load additional scripts (or add additional inline scripts) without restriction.
This becomes especially useful when admins want to add external scripts like Google Tag Manager, or advertising scripts, which then go on to load a ton of other scripts.
All script tags introduced via themes will automatically have the nonce attribute applied, so it should be zero-effort for theme developers. Plugins *may* need some changes if they are inserting their own script tags.
This commit introduces a strict-dynamic-based CSP behind an experimental `content_security_policy_strict_dynamic` site setting.
Why this change?
This is caused by a regression in
59839e428f3dc49213fedfd8b67acb9f2a5ec69a, where we stopped saving the
`Theme` object because it was unnecessary. However, it resulted in the
`after_save` callback not being called and hence
`Theme#update_javascript_cache!` not being called. As a result, some
sites were reporting that after runing a theme migration, the defaults
for the theme settings were used instead of the settings overrides
stored in the database.
What does this change do?
Add a call to `Theme#update_javascript_cache!` after running theme
migrations.
Why this change?
Returning an array makes it hard to immediately retrieve a setting by
name and makes the retrieval an O(N) operation. By returning an array,
we make it easier for us to lookup a setting by name and retrieval is
O(1) as well.
Why this change?
Currently, is it hard to iteratively write a theme settings migrations
because our theme migrations system does not rollback. Therefore, we
want to allow theme developers to be able to write QUnit tests for their
theme migrations files enabling them to iteratively write their theme
migrations.
What does this change do?
1. Update `Theme#baked_js_tests_with_digest` to include all `ThemeField`
records of `ThemeField#target` equal to `migrations`. Note that we do
not include the `settings` and `themePrefix` variables for migration files.
2. Don't minify JavaScript test files becasue it makes debugging in
development hard.
Why this change?
Importing theme with the `bundle` params is used mainly by
`discourse_theme` CLI in the development environment. However, we do not
want migrations to automatically run in the development environment
and instead want the developer to be intentional about running theme
migrations. As such, this commit adds support for a
`skip_migrations` param when importing a theme with the `bundle` params.
This commit also adds a `migrated` attribute for migrations theme fields
to indicate whether a migrations theme field has been migrated or not.
Previously we would only recompile a theme locale when its own data changes. However, the output also includes fallback data from other locales, so we need to invalidate all locales when fallback locale data is changed. Building a list of dependent locales is tricky, so let's just invalidate them all.
This commit introduces a new feature that allows theme developers to manage the transformation of theme settings over time. Similar to Rails migrations, the theme settings migration system enables developers to write and execute migrations for theme settings, ensuring a smooth transition when changes are required in the format or structure of setting values.
Example use cases for the theme settings migration system:
1. Renaming a theme setting.
2. Changing the data type of a theme setting (e.g., transforming a string setting containing comma-separated values into a proper list setting).
3. Altering the format of data stored in a theme setting.
All of these use cases and more are now possible while preserving theme setting values for sites that have already modified their theme settings.
Usage:
1. Create a top-level directory called `migrations` in your theme/component, and then within the `migrations` directory create another directory called `settings`.
2. Inside the `migrations/settings` directory, create a JavaScript file using the format `XXXX-some-name.js`, where `XXXX` is a unique 4-digit number, and `some-name` is a descriptor of your choice that describes the migration.
3. Within the JavaScript file, define and export (as the default) a function called `migrate`. This function will receive a `Map` object and must also return a `Map` object (it's acceptable to return the same `Map` object that the function received).
4. The `Map` object received by the `migrate` function will include settings that have been overridden or changed by site administrators. Settings that have never been changed from the default will not be included.
5. The keys and values contained in the `Map` object that the `migrate` function returns will replace all the currently changed settings of the theme.
6. Migrations are executed in numerical order based on the XXXX segment in the migration filenames. For instance, `0001-some-migration.js` will be executed before `0002-another-migration.js`.
Here's a complete example migration script that renames a setting from `setting_with_old_name` to `setting_with_new_name`:
```js
// File name: 0001-rename-setting.js
export default function migrate(settings) {
if (settings.has("setting_with_old_name")) {
settings.set("setting_with_new_name", settings.get("setting_with_old_name"));
}
return settings;
}
```
Internal topic: t/109980
Why this change?
Currently, we do not have a method to easily retrieve a theme setting's
value on the server side. Such a method can be useful in the test
environment where we need to retrieve the theme's setting and use its
value in assertions.
What does this change do?
This change introduces the `Theme#get_setting` instance method.
We run the ember-this-fallback transformation on plugin and theme code so that they can continue omitting `this.` in `.hbs` templates. A bug in the implementation meant that it was incorrectly transforming things like `{{dir/some-component}}` into `<DirSomeComponent />` (rather than `<Dir::SomeComponent />`).
This commit uses patch-package to apply the fix from https://github.com/tildeio/ember-this-fallback/pull/56