DEV: allows to alter category name/description (#28263)

This commit adds two new getters to the category model:
- `displayName`
- `descriptionText`

These getters are used instead of `name` and `description_text` where appropriate.

On top of this two transformers have been added to allow plugins to alter these getters:

```javascript
api.registerValueTransformer(
  "category-display-name",
  ({ value, context }) =>
    value + "-" + context.category.id + "-transformed"
);
```

```javascript
api.registerValueTransformer(
  "category-description-text",
  ({ value, context }) =>
    value + "-" + context.category.id + "-transformed"
);
```
This commit is contained in:
Joffrey JAFFEUX 2024-08-08 17:33:23 +02:00 committed by GitHub
parent 86bb07f619
commit c197daa04c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 104 additions and 30 deletions

View File

@ -19,7 +19,7 @@
@type="checkbox" @type="checkbox"
@checked={{category.selected}} @checked={{category.selected}}
/> />
<span>{{category.name}}</span> <span>{{category.displayName}}</span>
</label> </label>
{{/each}} {{/each}}
</fieldset> </fieldset>

View File

@ -18,9 +18,9 @@
@route="adminSiteSettingsCategory" @route="adminSiteSettingsCategory"
@model={{category.nameKey}} @model={{category.nameKey}}
class={{category.nameKey}} class={{category.nameKey}}
title={{category.name}} title={{category.displayName}}
> >
{{category.name}} {{category.displayName}}
{{#if category.count}} {{#if category.count}}
<span class="count">({{category.count}})</span> <span class="count">({{category.count}})</span>
{{/if}} {{/if}}

View File

@ -19,7 +19,7 @@
{{#if c.read_restricted}} {{#if c.read_restricted}}
{{d-icon this.lockIcon}} {{d-icon this.lockIcon}}
{{/if}} {{/if}}
{{c.name}} {{c.displayName}}
</h3> </h3>
</a> </a>
</div> </div>

View File

@ -29,7 +29,7 @@
{{#if c.read_restricted}} {{#if c.read_restricted}}
{{d-icon this.lockIcon}} {{d-icon this.lockIcon}}
{{/if}} {{/if}}
{{c.name}} {{c.displayName}}
</h3> </h3>
</a> </a>
</div> </div>

View File

@ -4,7 +4,7 @@
{{#if this.category.read_restricted}} {{#if this.category.read_restricted}}
{{d-icon this.lockIcon}} {{d-icon this.lockIcon}}
{{/if}} {{/if}}
<span class="category-name">{{dir-span this.category.name}}</span> <span class="category-name">{{dir-span this.category.displayName}}</span>
</div> </div>
{{#if this.category.uploaded_logo.url}} {{#if this.category.uploaded_logo.url}}
<CategoryLogo @category={{this.category}} /> <CategoryLogo @category={{this.category}} />

View File

@ -112,7 +112,7 @@ function buildTopicCount(count) {
} }
export function defaultCategoryLinkRenderer(category, opts) { export function defaultCategoryLinkRenderer(category, opts) {
let descriptionText = escapeExpression(get(category, "description_text")); let descriptionText = escapeExpression(get(category, "descriptionText"));
let restricted = get(category, "read_restricted"); let restricted = get(category, "read_restricted");
let url = opts.url let url = opts.url
? opts.url ? opts.url
@ -156,7 +156,7 @@ export function defaultCategoryLinkRenderer(category, opts) {
${descriptionText ? 'title="' + descriptionText + '" ' : ""} ${descriptionText ? 'title="' + descriptionText + '" ' : ""}
>`; >`;
let categoryName = escapeExpression(get(category, "name")); let categoryName = escapeExpression(get(category, "displayName"));
if (siteSettings.support_mixed_text_direction) { if (siteSettings.support_mixed_text_direction) {
categoryDir = 'dir="auto"'; categoryDir = 'dir="auto"';

View File

@ -1537,8 +1537,8 @@ class PluginApi {
* displayName: "bugs" * displayName: "bugs"
* href: "/c/bugs", * href: "/c/bugs",
* init: (navItem, category) => { if (category) { navItem.set("category", category) } } * init: (navItem, category) => { if (category) { navItem.set("category", category) } }
* customFilter: (category, args, router) => { return category && category.name !== 'bug' } * customFilter: (category, args, router) => { return category && category.displayName !== 'bug' }
* customHref: (category, args, router) => { if (category && category.name) === 'not-a-bug') return "/a-feature"; }, * customHref: (category, args, router) => { if (category && category.displayName) === 'not-a-bug') return "/a-feature"; },
* before: "top", * before: "top",
* forceActive: (category, args, router) => router.currentURL === "/a/b/c/d", * forceActive: (category, args, router) => router.currentURL === "/a/b/c/d",
* }) * })

View File

@ -178,11 +178,11 @@ export default class CategorySectionLink {
} }
get title() { get title() {
return this.category.description_text; return this.category.descriptionText;
} }
get text() { get text() {
return this.category.name; return this.category.displayName;
} }
get prefixType() { get prefixType() {

View File

@ -5,6 +5,8 @@ export const BEHAVIOR_TRANSFORMERS = Object.freeze([
export const VALUE_TRANSFORMERS = Object.freeze([ export const VALUE_TRANSFORMERS = Object.freeze([
// use only lowercase names // use only lowercase names
"category-description-text",
"category-display-name",
"header-notifications-avatar-size", "header-notifications-avatar-size",
"home-logo-href", "home-logo-href",
"home-logo-image-url", "home-logo-image-url",

View File

@ -3,6 +3,7 @@ import { computed, get } from "@ember/object";
import { service } from "@ember/service"; import { service } from "@ember/service";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { NotificationLevels } from "discourse/lib/notification-levels"; import { NotificationLevels } from "discourse/lib/notification-levels";
import { applyValueTransformer } from "discourse/lib/transformer";
import PermissionType from "discourse/models/permission-type"; import PermissionType from "discourse/models/permission-type";
import RestModel from "discourse/models/rest"; import RestModel from "discourse/models/rest";
import Site from "discourse/models/site"; import Site from "discourse/models/site";
@ -475,6 +476,22 @@ export default class Category extends RestModel {
} }
} }
get descriptionText() {
return applyValueTransformer(
"category-description-text",
this.get("description_text"),
{
category: this,
}
);
}
get displayName() {
return applyValueTransformer("category-display-name", this.get("name"), {
category: this,
});
}
@computed("parent_category_id", "site.categories.[]") @computed("parent_category_id", "site.categories.[]")
get parentCategory() { get parentCategory() {
if (this.parent_category_id) { if (this.parent_category_id) {

View File

@ -117,11 +117,11 @@ class AbstractCategoryRoute extends DiscourseRoute {
"filters." + this.filter(category).replace("/", ".") + ".title" "filters." + this.filter(category).replace("/", ".") + ".title"
); );
let categoryName = category.name; let categoryName = category.displayName;
if (category.parent_category_id) { if (category.parent_category_id) {
const list = Category.list(); const list = Category.list();
const parentCategory = list.findBy("id", category.parent_category_id); const parentCategory = list.findBy("id", category.parent_category_id);
categoryName = `${parentCategory.name}/${categoryName}`; categoryName = `${parentCategory.displayName}/${categoryName}`;
} }
return I18n.t("filters.with_category", { return I18n.t("filters.with_category", {

View File

@ -179,7 +179,7 @@ export default class TagShowRoute extends DiscourseRoute {
return I18n.t("tagging.filters.with_category", { return I18n.t("tagging.filters.with_category", {
filter: filterText, filter: filterText,
tag: model.tag.id, tag: model.tag.id,
category: model.category.name, category: model.category.displayName,
}); });
} else { } else {
return I18n.t("tagging.filters.without_category", { return I18n.t("tagging.filters.without_category", {
@ -191,7 +191,7 @@ export default class TagShowRoute extends DiscourseRoute {
if (model.category) { if (model.category) {
return I18n.t("tagging.filters.untagged_with_category", { return I18n.t("tagging.filters.untagged_with_category", {
filter: filterText, filter: filterText,
category: model.category.name, category: model.category.displayName,
}); });
} else { } else {
return I18n.t("tagging.filters.untagged_without_category", { return I18n.t("tagging.filters.untagged_without_category", {

View File

@ -73,7 +73,7 @@ export default PreviewBaseComponent.extend({
ctx.font = `Bold ${badgeSize * 1.2}px '${font}'`; ctx.font = `Bold ${badgeSize * 1.2}px '${font}'`;
ctx.fillStyle = colors.primary; ctx.fillStyle = colors.primary;
ctx.fillText( ctx.fillText(
category.name, category.displayName,
afterLogo + badgeSize * 1.5, afterLogo + badgeSize * 1.5,
headerHeight * 0.7 + badgeSize * 0.9 headerHeight * 0.7 + badgeSize * 0.9
); );

View File

@ -85,7 +85,11 @@ export default PreviewBaseComponent.extend({
ctx.font = `Bold ${bodyFontSize * 1.3}em '${font}'`; ctx.font = `Bold ${bodyFontSize * 1.3}em '${font}'`;
ctx.fillStyle = colors.primary; ctx.fillStyle = colors.primary;
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.fillText(category.name, boxStartX + boxWidth / 2, boxStartY + 25); ctx.fillText(
category.displayName,
boxStartX + boxWidth / 2,
boxStartY + 25
);
ctx.textAlign = "left"; ctx.textAlign = "left";
if (opts.topics) { if (opts.topics) {
@ -167,7 +171,7 @@ export default PreviewBaseComponent.extend({
const textPos = y + categoryHeight * 0.35; const textPos = y + categoryHeight * 0.35;
ctx.font = `Bold ${bodyFontSize * 1.1}em '${font}'`; ctx.font = `Bold ${bodyFontSize * 1.1}em '${font}'`;
ctx.fillStyle = colors.primary; ctx.fillStyle = colors.primary;
ctx.fillText(category.name, cols[0], textPos); ctx.fillText(category.displayName, cols[0], textPos);
ctx.font = `${bodyFontSize * 0.8}em '${font}'`; ctx.font = `${bodyFontSize * 0.8}em '${font}'`;
ctx.fillStyle = textColor; ctx.fillStyle = textColor;
@ -263,7 +267,7 @@ export default PreviewBaseComponent.extend({
const textPos = y + categoryHeight * 0.35; const textPos = y + categoryHeight * 0.35;
ctx.font = `Bold ${bodyFontSize * 1.1}em '${font}'`; ctx.font = `Bold ${bodyFontSize * 1.1}em '${font}'`;
ctx.fillStyle = colors.primary; ctx.fillStyle = colors.primary;
ctx.fillText(category.name, cols[0], textPos); ctx.fillText(category.displayName, cols[0], textPos);
ctx.font = `${bodyFontSize * 0.8}em '${font}'`; ctx.font = `${bodyFontSize * 0.8}em '${font}'`;
ctx.fillStyle = textColor; ctx.fillStyle = textColor;
@ -328,7 +332,7 @@ export default PreviewBaseComponent.extend({
ctx.fillStyle = colors.primary; ctx.fillStyle = colors.primary;
ctx.fillText( ctx.fillText(
category.name, category.displayName,
cols[3] + badgeSize * 3, cols[3] + badgeSize * 3,
y + topicHeight * 0.76 y + topicHeight * 0.76
); );
@ -409,7 +413,7 @@ export default PreviewBaseComponent.extend({
ctx.fillStyle = colors.primary; ctx.fillStyle = colors.primary;
ctx.fillText( ctx.fillText(
category.name, category.displayName,
cols[0] + badgeSize * 2, cols[0] + badgeSize * 2,
y + rowHeight * 0.73 y + rowHeight * 0.73
); );

View File

@ -543,9 +543,9 @@ acceptance("Sidebar - Plugin API", function (needs) {
route: "discovery.latestCategory", route: "discovery.latestCategory",
routeQuery: { status: "open" }, routeQuery: { status: "open" },
shouldRegister: ({ category }) => { shouldRegister: ({ category }) => {
if (category.name === category1.name) { if (category.displayName === category1.displayName) {
return true; return true;
} else if (category.name === category2.name) { } else if (category.displayName === category2.displayName) {
return false; return false;
} }
}, },

View File

@ -177,7 +177,7 @@ acceptance("Sidebar - Logged on user - Categories Section", function (needs) {
exists( exists(
`.sidebar-section-link-wrapper[data-category-id=${category.id}]` `.sidebar-section-link-wrapper[data-category-id=${category.id}]`
), ),
`${category.name} section link is shown` `${category.displayName} section link is shown`
); );
}); });
}); });
@ -677,7 +677,7 @@ acceptance("Sidebar - Logged on user - Categories Section", function (needs) {
query( query(
`.sidebar-section-link-wrapper[data-category-id="${category.id}"] a` `.sidebar-section-link-wrapper[data-category-id="${category.id}"] a`
).title, ).title,
category.description_text, category.descriptionText,
"category description without HTML entity is used as the link's title" "category description without HTML entity is used as the link's title"
); );
}); });

View File

@ -0,0 +1,26 @@
import { visit } from "@ember/test-helpers";
import { test } from "qunit";
import { withPluginApi } from "discourse/lib/plugin-api";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("category-description-text transformer", function () {
test("applying a value transformation", async function (assert) {
withPluginApi("1.34.0", (api) => {
api.registerValueTransformer(
"category-description-text",
({ value, context }) =>
value[0] + "-" + context.category.id + "-transformed"
);
});
await visit("/");
assert
.dom("[data-topic-id='11994'] .badge-category")
.hasAttribute(
"title",
"A-1-transformed",
"it transforms the category description text"
);
});
});

View File

@ -0,0 +1,25 @@
import { visit } from "@ember/test-helpers";
import { test } from "qunit";
import { withPluginApi } from "discourse/lib/plugin-api";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("category-display-name transformer", function () {
test("applying a value transformation", async function (assert) {
withPluginApi("1.34.0", (api) => {
api.registerValueTransformer(
"category-display-name",
({ value, context }) =>
value + "-" + context.category.id + "-transformed"
);
});
await visit("/");
assert
.dom("[data-topic-id='11997'] .badge-category__name")
.hasText(
"feature-2-transformed",
"it transforms the category display name"
);
});
});

View File

@ -15,7 +15,7 @@ module("Integration | Helper | category-badge", function (hooks) {
assert.strictEqual( assert.strictEqual(
query(".badge-category__name").innerText.trim(), query(".badge-category__name").innerText.trim(),
this.category.name this.category.displayName
); );
}); });

View File

@ -152,7 +152,7 @@ export default ComboBoxComponent.extend({
return content; return content;
}, },
parentCategoryName: readOnly("selectKit.options.parentCategory.name"), parentCategoryName: readOnly("selectKit.options.parentCategory.displayName"),
allCategoriesLabel: computed( allCategoriesLabel: computed(
"parentCategoryName", "parentCategoryName",

View File

@ -83,11 +83,11 @@ export default class CategoryRow extends Component {
} }
get categoryName() { get categoryName() {
return this.category.name; return this.category.displayName;
} }
get categoryDescriptionText() { get categoryDescriptionText() {
return this.category.description_text; return this.category.descriptionText;
} }
@cached @cached