diff --git a/app/assets/javascripts/discourse/app/components/d-styles.gjs b/app/assets/javascripts/discourse/app/components/d-styles.gjs
new file mode 100644
index 00000000000..a56edf4ef78
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/d-styles.gjs
@@ -0,0 +1,87 @@
+import Component from "@glimmer/component";
+import { service } from "@ember/service";
+import { getURLWithCDN } from "discourse-common/lib/get-url";
+
+export default class DStyles extends Component {
+ @service session;
+ @service site;
+
+ get categoryColors() {
+ return [
+ ":root {",
+ ...this.site.categories.map(
+ (category) => `--category-${category.id}-color: #${category.color};`
+ ),
+ "}",
+ ].join("\n");
+ }
+
+ get categoryBackgrounds() {
+ const css = [];
+ const darkCss = [];
+
+ this.site.categories.forEach((category) => {
+ const lightUrl = category.uploaded_background?.url;
+ const darkUrl =
+ this.session.defaultColorSchemeIsDark || this.session.darkModeAvailable
+ ? category.uploaded_background_dark?.url
+ : null;
+ const defaultUrl =
+ darkUrl && this.session.defaultColorSchemeIsDark ? darkUrl : lightUrl;
+
+ if (defaultUrl) {
+ const url = getURLWithCDN(defaultUrl);
+ css.push(
+ `body.category-${category.fullSlug} { background-image: url(${url}); }`
+ );
+ }
+
+ if (darkUrl && defaultUrl !== darkUrl) {
+ const url = getURLWithCDN(darkUrl);
+ darkCss.push(
+ `body.category-${category.fullSlug} { background-image: url(${url}); }`
+ );
+ }
+ });
+
+ if (darkCss.length > 0) {
+ css.push("@media (prefers-color-scheme: dark) {", ...darkCss, "}");
+ }
+
+ return css.join("\n");
+ }
+
+ get categoryBadges() {
+ const css = [];
+
+ this.site.categories.forEach((category) => {
+ css.push(
+ `.badge-category[data-category-id="${category.id}"] { ` +
+ `--category-badge-color: var(--category-${category.id}-color); ` +
+ `--category-badge-text-color: #${category.text_color}; ` +
+ `}`
+ );
+
+ if (category.isParent) {
+ css.push(
+ `.badge-category[data-parent-category-id="${category.id}"] { ` +
+ `--parent-category-badge-color: var(--category-${category.id}-color); ` +
+ `}`
+ );
+ }
+ });
+
+ return css.join("\n");
+ }
+
+
+ {{! template-lint-disable no-forbidden-elements }}
+
+
+}
diff --git a/app/assets/javascripts/discourse/app/instance-initializers/category-background-css-generator.js b/app/assets/javascripts/discourse/app/instance-initializers/category-background-css-generator.js
deleted file mode 100644
index 5243bbbd4da..00000000000
--- a/app/assets/javascripts/discourse/app/instance-initializers/category-background-css-generator.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import { getURLWithCDN } from "discourse-common/lib/get-url";
-
-export default {
- after: "register-hashtag-types",
-
- initialize(owner) {
- this.session = owner.lookup("service:session");
- this.site = owner.lookup("service:site");
-
- if (!this.site.categories?.length) {
- return;
- }
-
- const css = [];
- const darkCss = [];
-
- this.site.categories.forEach((category) => {
- const lightUrl = category.uploaded_background?.url;
- const darkUrl =
- this.session.defaultColorSchemeIsDark || this.session.darkModeAvailable
- ? category.uploaded_background_dark?.url
- : null;
- const defaultUrl =
- darkUrl && this.session.defaultColorSchemeIsDark ? darkUrl : lightUrl;
-
- if (defaultUrl) {
- const url = getURLWithCDN(defaultUrl);
- css.push(
- `body.category-${category.fullSlug} { background-image: url(${url}); }`
- );
- }
-
- if (darkUrl && defaultUrl !== darkUrl) {
- const url = getURLWithCDN(darkUrl);
- darkCss.push(
- `body.category-${category.fullSlug} { background-image: url(${url}); }`
- );
- }
- });
-
- if (darkCss.length > 0) {
- css.push("@media (prefers-color-scheme: dark) {", ...darkCss, "}");
- }
-
- const cssTag = document.createElement("style");
- cssTag.type = "text/css";
- cssTag.id = "category-background-css-generator";
- cssTag.innerHTML = css.join("\n");
- document.head.appendChild(cssTag);
- },
-
- teardown() {
- document.querySelector("#category-background-css-generator")?.remove();
- },
-};
diff --git a/app/assets/javascripts/discourse/app/instance-initializers/category-badge-css-generator.js b/app/assets/javascripts/discourse/app/instance-initializers/category-badge-css-generator.js
deleted file mode 100644
index 4e5a5ead633..00000000000
--- a/app/assets/javascripts/discourse/app/instance-initializers/category-badge-css-generator.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { get } from "@ember/object";
-import Category from "discourse/models/category";
-
-export default {
- after: "category-color-css-generator",
-
- // This generates badge CSS for each category, which is used to render category-specific elements.
- initialize(owner) {
- this.site = owner.lookup("service:site");
-
- // If the site is login_required and the user is anon there will be no categories preloaded.
- if (!this.site.categories?.length) {
- return;
- }
-
- const generatedCss = this.site.categories.map((category) => {
- let parentCategory = Category.findById(
- get(category, "parent_category_id")
- );
- let badgeCss = `.badge-category[data-category-id="${category.id}"] { --category-badge-color: var(--category-${category.id}-color); --category-badge-text-color: #${category.text_color}; }`;
- if (parentCategory) {
- badgeCss += `.badge-category[data-parent-category-id="${parentCategory.id}"] { --parent-category-badge-color: var(--category-${parentCategory.id}-color); }`;
- }
- return badgeCss;
- });
-
- const cssTag = document.createElement("style");
- cssTag.type = "text/css";
- cssTag.id = "category-badge-css-generator";
- cssTag.innerHTML = generatedCss.join("\n");
- document.head.appendChild(cssTag);
- },
-};
diff --git a/app/assets/javascripts/discourse/app/instance-initializers/category-color-css-generator.js b/app/assets/javascripts/discourse/app/instance-initializers/category-color-css-generator.js
deleted file mode 100644
index 18e1d7f08ab..00000000000
--- a/app/assets/javascripts/discourse/app/instance-initializers/category-color-css-generator.js
+++ /dev/null
@@ -1,33 +0,0 @@
-export default {
- after: "register-hashtag-types",
-
- /**
- * This generates CSS variables for each category color,
- * which can be used in themes to style category-specific elements.
- *
- * It is also used when styling hashtag icons, since they are colored
- * based on the category color.
- */
- initialize(owner) {
- this.site = owner.lookup("service:site");
-
- // If the site is login_required and the user is anon there will be no categories preloaded.
- if (!this.site.categories?.length) {
- return;
- }
-
- const generatedCssVariables = [
- ":root {",
- ...this.site.categories.map(
- (category) => `--category-${category.id}-color: #${category.color};`
- ),
- "}",
- ];
-
- const cssTag = document.createElement("style");
- cssTag.type = "text/css";
- cssTag.id = "category-color-css-generator";
- cssTag.innerHTML = generatedCssVariables.join("\n");
- document.head.appendChild(cssTag);
- },
-};
diff --git a/app/assets/javascripts/discourse/app/instance-initializers/hashtag-css-generator.js b/app/assets/javascripts/discourse/app/instance-initializers/hashtag-css-generator.js
index e14fd3e6949..7bfef4fe959 100644
--- a/app/assets/javascripts/discourse/app/instance-initializers/hashtag-css-generator.js
+++ b/app/assets/javascripts/discourse/app/instance-initializers/hashtag-css-generator.js
@@ -1,7 +1,7 @@
import { getHashtagTypeClasses } from "discourse/lib/hashtag-type-registry";
export default {
- after: "category-color-css-generator",
+ after: "register-hashtag-types",
/**
* This generates CSS classes for each hashtag type,
diff --git a/app/assets/javascripts/discourse/app/models/site.js b/app/assets/javascripts/discourse/app/models/site.js
index a6ae21dc1d5..08468b58e9b 100644
--- a/app/assets/javascripts/discourse/app/models/site.js
+++ b/app/assets/javascripts/discourse/app/models/site.js
@@ -1,3 +1,4 @@
+import { tracked } from "@glimmer/tracking";
import EmberObject, { get } from "@ember/object";
import { alias, sort } from "@ember/object/computed";
import { htmlSafe } from "@ember/template";
@@ -103,9 +104,12 @@ export default class Site extends RestModel.extend().reopenClass(Singleton) {
return result;
}
+ @tracked categories;
+
@alias("is_readonly") isReadOnly;
@sort("categories", "topicCountDesc") categoriesByCount;
+
init() {
super.init(...arguments);
diff --git a/app/assets/javascripts/discourse/app/templates/application.hbs b/app/assets/javascripts/discourse/app/templates/application.hbs
index 222cee64d60..9af9116828f 100644
--- a/app/assets/javascripts/discourse/app/templates/application.hbs
+++ b/app/assets/javascripts/discourse/app/templates/application.hbs
@@ -1,3 +1,5 @@
+
+
{{i18n "skip_to_main_content"}}
diff --git a/app/assets/javascripts/discourse/tests/acceptance/css-generator-test.js b/app/assets/javascripts/discourse/tests/acceptance/css-generator-test.js
deleted file mode 100644
index ee994789ea7..00000000000
--- a/app/assets/javascripts/discourse/tests/acceptance/css-generator-test.js
+++ /dev/null
@@ -1,81 +0,0 @@
-import { visit } from "@ember/test-helpers";
-import { test } from "qunit";
-import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
-
-acceptance("CSS Generator", function (needs) {
- needs.user();
-
- needs.site({
- categories: [
- { id: 1, color: "ff0000", text_color: "ffffff", name: "category1" },
- { id: 2, color: "333", text_color: "ffffff", name: "category2" },
- {
- id: 4,
- color: "2B81AF",
- text_color: "ffffff",
- parentCategory: { id: 1 },
- name: "category3",
- },
- ],
- });
-
- test("category CSS variables are generated", async function (assert) {
- await visit("/");
- const cssTag = document.querySelector("style#category-color-css-generator");
- assert.equal(
- cssTag.innerHTML,
- ":root {\n" +
- "--category-1-color: #ff0000;\n" +
- "--category-2-color: #333;\n" +
- "--category-4-color: #2B81AF;\n" +
- "}"
- );
- });
-
- test("hashtag CSS classes are generated", async function (assert) {
- await visit("/");
- const cssTag = document.querySelector("style#hashtag-css-generator");
- assert.equal(
- cssTag.innerHTML,
- ".hashtag-category-badge { background-color: var(--primary-medium); }\n" +
- ".hashtag-color--category-1 { background-color: #ff0000; }\n" +
- ".hashtag-color--category-2 { background-color: #333; }\n" +
- ".hashtag-color--category-4 { background-color: #2B81AF; }"
- );
- });
-
- test("category badge CSS variables are generated", async function (assert) {
- await visit("/");
- const cssTag = document.querySelector("style#category-badge-css-generator");
- assert.equal(
- cssTag.innerHTML,
- '.badge-category[data-category-id="1"] { --category-badge-color: var(--category-1-color); --category-badge-text-color: #ffffff; }\n' +
- '.badge-category[data-category-id="2"] { --category-badge-color: var(--category-2-color); --category-badge-text-color: #ffffff; }\n' +
- '.badge-category[data-category-id="4"] { --category-badge-color: var(--category-4-color); --category-badge-text-color: #ffffff; }'
- );
- });
-});
-
-acceptance(
- "CSS Generator | Anon user in login_required site",
- function (needs) {
- needs.site({ categories: null });
- needs.settings({ login_required: true });
- test("category CSS variables are not generated", async function (assert) {
- await visit("/");
-
- const cssTag = document.querySelector(
- "style#category-color-css-generator"
- );
- assert.notOk(exists(cssTag));
- });
-
- test("category badge CSS variables are not generated", async function (assert) {
- await visit("/");
- const cssTag = document.querySelector(
- "style#category-badge-css-generator"
- );
- assert.notOk(exists(cssTag));
- });
- }
-);
diff --git a/app/assets/javascripts/discourse/tests/acceptance/category-background-css-generator-test.js b/app/assets/javascripts/discourse/tests/acceptance/d-styles-test.js
similarity index 56%
rename from app/assets/javascripts/discourse/tests/acceptance/category-background-css-generator-test.js
rename to app/assets/javascripts/discourse/tests/acceptance/d-styles-test.js
index 7ba43e8578c..a495e76cbf7 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/category-background-css-generator-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/d-styles-test.js
@@ -54,22 +54,21 @@ const SITE_DATA = {
],
};
-acceptance("Category Background CSS Generator", function (needs) {
+acceptance("DStyles - category backgrounds", function (needs) {
needs.user();
needs.site(SITE_DATA);
test("CSS classes are generated", async function (assert) {
await visit("/");
- assert.equal(
- document.querySelector("#category-background-css-generator").innerHTML,
+ const css =
"body.category-foo { background-image: url(/uploads/default/original/1X/c5c84b16ebf745ab848d1498267c541facbf1ff0.png); }\n" +
- "body.category-foo-baz { background-image: url(/uploads/default/original/1X/684c104edc18a7e9cef1fa31f41215f3eec5d92b.png); }"
- );
+ "body.category-foo-baz { background-image: url(/uploads/default/original/1X/684c104edc18a7e9cef1fa31f41215f3eec5d92b.png); }";
+ assert.ok(document.querySelector("#d-styles").innerHTML.includes(css));
});
});
-acceptance("Category Background CSS Generator (dark)", function (needs) {
+acceptance("DStyles - category backgrounds (dark)", function (needs) {
needs.user();
needs.site(SITE_DATA);
@@ -88,20 +87,19 @@ acceptance("Category Background CSS Generator (dark)", function (needs) {
test("CSS classes are generated", async function (assert) {
await visit("/");
- assert.equal(
- document.querySelector("#category-background-css-generator").innerHTML,
+ const css =
"body.category-foo { background-image: url(/uploads/default/original/1X/c5c84b16ebf745ab848d1498267c541facbf1ff0.png); }\n" +
- "body.category-foo-baz { background-image: url(/uploads/default/original/1X/684c104edc18a7e9cef1fa31f41215f3eec5d92b.png); }\n" +
- "@media (prefers-color-scheme: dark) {\n" +
- "body.category-bar { background-image: url(/uploads/default/original/1X/f9fdb0ad108f2aed178c40f351bbb2c7cb2571e3.png); }\n" +
- "body.category-foo-baz { background-image: url(/uploads/default/original/1X/89b1a2641e91604c32b21db496be11dba7a253e6.png); }\n" +
- "}"
- );
+ "body.category-foo-baz { background-image: url(/uploads/default/original/1X/684c104edc18a7e9cef1fa31f41215f3eec5d92b.png); }\n" +
+ "@media (prefers-color-scheme: dark) {\n" +
+ "body.category-bar { background-image: url(/uploads/default/original/1X/f9fdb0ad108f2aed178c40f351bbb2c7cb2571e3.png); }\n" +
+ "body.category-foo-baz { background-image: url(/uploads/default/original/1X/89b1a2641e91604c32b21db496be11dba7a253e6.png); }\n" +
+ "}";
+ assert.ok(document.querySelector("#d-styles").innerHTML.includes(css));
});
});
acceptance(
- "Category Background CSS Generator (dark is default)",
+ "DStyles - category backgrounds (default theme is dark)",
function (needs) {
needs.user();
needs.site(SITE_DATA);
@@ -121,12 +119,51 @@ acceptance(
test("CSS classes are generated", async function (assert) {
await visit("/");
- assert.equal(
- document.querySelector("#category-background-css-generator").innerHTML,
+ const css =
"body.category-foo { background-image: url(/uploads/default/original/1X/c5c84b16ebf745ab848d1498267c541facbf1ff0.png); }\n" +
- "body.category-bar { background-image: url(/uploads/default/original/1X/f9fdb0ad108f2aed178c40f351bbb2c7cb2571e3.png); }\n" +
- "body.category-foo-baz { background-image: url(/uploads/default/original/1X/89b1a2641e91604c32b21db496be11dba7a253e6.png); }"
- );
+ "body.category-bar { background-image: url(/uploads/default/original/1X/f9fdb0ad108f2aed178c40f351bbb2c7cb2571e3.png); }\n" +
+ "body.category-foo-baz { background-image: url(/uploads/default/original/1X/89b1a2641e91604c32b21db496be11dba7a253e6.png); }";
+ assert.ok(document.querySelector("#d-styles").innerHTML.includes(css));
});
}
);
+
+acceptance("DStyles - category badges", function (needs) {
+ needs.user();
+
+ needs.site({
+ categories: [
+ { id: 1, color: "ff0000", text_color: "ffffff", name: "category1" },
+ { id: 2, color: "333", text_color: "ffffff", name: "category2" },
+ {
+ id: 4,
+ color: "2B81AF",
+ text_color: "ffffff",
+ parentCategory: { id: 1 },
+ name: "category3",
+ },
+ ],
+ });
+
+ test("category CSS variables are generated", async function (assert) {
+ await visit("/");
+
+ const css =
+ ":root {\n" +
+ "--category-1-color: #ff0000;\n" +
+ "--category-2-color: #333;\n" +
+ "--category-4-color: #2B81AF;\n" +
+ "}";
+ assert.ok(document.querySelector("style#d-styles").innerHTML.includes(css));
+ });
+
+ test("category badge CSS variables are generated", async function (assert) {
+ await visit("/");
+
+ const css =
+ '.badge-category[data-category-id="1"] { --category-badge-color: var(--category-1-color); --category-badge-text-color: #ffffff; }\n' +
+ '.badge-category[data-category-id="2"] { --category-badge-color: var(--category-2-color); --category-badge-text-color: #ffffff; }\n' +
+ '.badge-category[data-category-id="4"] { --category-badge-color: var(--category-4-color); --category-badge-text-color: #ffffff; }';
+ assert.ok(document.querySelector("style#d-styles").innerHTML.includes(css));
+ });
+});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/hashtag-css-generator-test.js b/app/assets/javascripts/discourse/tests/acceptance/hashtag-css-generator-test.js
new file mode 100644
index 00000000000..e1ba52f49a8
--- /dev/null
+++ b/app/assets/javascripts/discourse/tests/acceptance/hashtag-css-generator-test.js
@@ -0,0 +1,33 @@
+import { visit } from "@ember/test-helpers";
+import { test } from "qunit";
+import { acceptance } from "discourse/tests/helpers/qunit-helpers";
+
+acceptance("Hashtag CSS Generator", function (needs) {
+ needs.user();
+
+ needs.site({
+ categories: [
+ { id: 1, color: "ff0000", text_color: "ffffff", name: "category1" },
+ { id: 2, color: "333", text_color: "ffffff", name: "category2" },
+ {
+ id: 4,
+ color: "2B81AF",
+ text_color: "ffffff",
+ parentCategory: { id: 1 },
+ name: "category3",
+ },
+ ],
+ });
+
+ test("classes are generated", async function (assert) {
+ await visit("/");
+ const cssTag = document.querySelector("style#hashtag-css-generator");
+ assert.equal(
+ cssTag.innerHTML,
+ ".hashtag-category-badge { background-color: var(--primary-medium); }\n" +
+ ".hashtag-color--category-1 { background-color: #ff0000; }\n" +
+ ".hashtag-color--category-2 { background-color: #333; }\n" +
+ ".hashtag-color--category-4 { background-color: #2B81AF; }"
+ );
+ });
+});