From 2b4eb36a973d4d91e052fdbd76ca8ed92dc111cc Mon Sep 17 00:00:00 2001 From: Bianca Nenciu Date: Thu, 21 Mar 2024 18:49:41 +0200 Subject: [PATCH] DEV: Refactor CSS generators to Glimmer component (#26202) There is no need to use an initializer and manually update the DOM when a Glimmer component can do it and ensure that the DOM is updated as more categories are being loaded (for example, when lazy loaded categories are enabled). --- .../discourse/app/components/d-styles.gjs | 87 +++++++++++++++++++ .../category-background-css-generator.js | 55 ------------ .../category-badge-css-generator.js | 33 ------- .../category-color-css-generator.js | 33 ------- .../hashtag-css-generator.js | 2 +- .../javascripts/discourse/app/models/site.js | 4 + .../discourse/app/templates/application.hbs | 2 + .../tests/acceptance/css-generator-test.js | 81 ----------------- ...css-generator-test.js => d-styles-test.js} | 77 +++++++++++----- .../acceptance/hashtag-css-generator-test.js | 33 +++++++ 10 files changed, 184 insertions(+), 223 deletions(-) create mode 100644 app/assets/javascripts/discourse/app/components/d-styles.gjs delete mode 100644 app/assets/javascripts/discourse/app/instance-initializers/category-background-css-generator.js delete mode 100644 app/assets/javascripts/discourse/app/instance-initializers/category-badge-css-generator.js delete mode 100644 app/assets/javascripts/discourse/app/instance-initializers/category-color-css-generator.js delete mode 100644 app/assets/javascripts/discourse/tests/acceptance/css-generator-test.js rename app/assets/javascripts/discourse/tests/acceptance/{category-background-css-generator-test.js => d-styles-test.js} (56%) create mode 100644 app/assets/javascripts/discourse/tests/acceptance/hashtag-css-generator-test.js 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"); + } + + +} 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 @@ + + 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; }" + ); + }); +});