Je vois. Je pense que cela a du sens si vous affichez des informations sous forme d’étiquette, mais ici, c’est un bouton pour afficher plus d’étiquettes ; le contexte est différent pour moi. C’est à vous de décider ; je ne pense pas que cela ait beaucoup d’importance.
Pour continuer avec les commentaires :
- La liste des étiquettes peut être affichée à d’autres endroits, tels que : la page des catégories, les activités de l’utilisateur, etc. Je supprimerais probablement le paramètre
collapse_in_topic_view et en créerais un nouveau avec des routes spécifiques ou je l’activerais simplement partout.
Dans mon code de test, j’ai utilisé quelque chose comme ça pour ignorer les autres routes :
JS
function isAllowedRoute(routeName) {
const fullRoutesName = [
"index",
"userActivity.topics",
"userActivity.read",
...siteSettings.top_menu.split("|").map((item) => `discovery.${item}`),
];
const partialRoutesName = ["topic."];
if (
fullRoutesName.includes(routeName) ||
partialRoutesName.some((partial) => routeName.startsWith(partial))
) {
return true;
}
return false;
}
- L’injection CSS peut être remplacée en utilisant l’API pour ajouter une classe à
topic-list-item et à une étiquette, puis vous déplacez le CSS vers common.css.
Par exemple :
JS
```js
import { defaultRenderTag } from "discourse/lib/render-tag";
api.registerValueTransformer(
"topic-list-item-class",
({ value, context }) => {
if (highlightedTagsSet.size === 0) {
return value;
}
if (context.topic?.tags?.some((tag) => highlightedTagsSet.has(tag))) {
return [...value, `highlighted-tag__${settings.highlighted_style}`];
}
return value;
}
);
api.replaceTagRenderer((tag, params) => {
if (highlightedTagsSet.has(tag)) {
params.extraClass = params.extraClass || "";
params.extraClass += "highlighted";
}
return defaultRenderTag(tag, params);
});
```
CSS
/* Hides the last separator before the toggle button */
.discourse-tags__tag-separator:has(+ .reveal-tag-action) {
visibility: hidden;
}
.reveal-tag-action {
color: var(--primary-500);
&.-box {
background-color: var(--primary-50);
outline: 1px solid var(--primary-200);
padding-inline: 8px;
}
}
.latest-topic-list-item,
.topic-list-item {
.discourse-tag.highlighted {
color: var(--tertiary);
border-color: var(--tertiary);
background: color-mix(in srgb, var(--tertiary) 12%, transparent);
font-weight: 600;
}
}
&.highlighted-tag {
&__left-border {
border-left: 3px solid var(--tertiary);
background: color-mix(in srgb, var(--tertiary) 6%, transparent);
transition: box-shadow 160ms ease;
&:hover {
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
}
}
&__outline {
outline: 1px solid var(--tertiary);
outline-offset: -2px;
border-radius: 7px;
background: color-mix(in srgb, var(--tertiary) 5%, transparent);
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.06);
transition: background-color 160ms ease;
}
&__card {
border-left: 3px solid var(--tertiary);
background: var(--tertiary-very-low);
border-radius: var(--border-radius);
padding-block: var(--space-2);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
transition: box-shadow 160ms ease;
&:hover {
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.1);
}
}
}
}
- Vous n’avez pas besoin de définir la route actuelle à partir de
onPageChange, vous pouvez y accéder depuis le routeur.
- Faites attention à la casse des étiquettes. Vous avez des paramètres de site qui n’imposent pas la casse minuscule, je pense donc qu’il est préférable de ne pas modifier l’étiquette.
- Concernant la réinitialisation de l’état, vous pouvez probablement utiliser
onPageChange.
JS
```js
api.onPageChange((url) => {
const route = api.container.lookup("service:router").recognize(url);
if (!isAllowedRoute(route?.name)) {
return;
}
for (const [id, model] of topicModels) {
if (model && model.revealTags) {
model.revealTags = false;
model.notifyPropertyChange("tags");
}
}
});
```
- Si possible, ce serait bien d’ajouter des tests.
Voici le code de test complet (j’ai fait d’autres changements mineurs)
JS
import { computed } from "@ember/object";
import { apiInitializer } from "discourse/lib/api";
import { i18n } from "discourse-i18n";
import { defaultRenderTag } from "discourse/lib/render-tag";
import { service } from "@ember/service";
export default apiInitializer((api) => {
const siteSettings = api.container.lookup("service:site-settings");
const router = api.container.lookup("service:router");
const maxVisibleTags = Math.min(
settings.max_tags_visible,
siteSettings.max_tags_per_topic
);
const highlightedTagsSet = new Set(settings.highlighted_tags.split("|"));
const topicModels = new Map();
function isAllowedRoute(routeName) {
const fullRoutesName = [
"index",
"userActivity.topics",
"userActivity.read",
"tag.show",
...siteSettings.top_menu.split("|").map((item) => `discovery.${item}`),
];
const partialRoutesName = ["topic."];
if (
fullRoutesName.includes(routeName) ||
partialRoutesName.some((partial) => routeName.startsWith(partial))
) {
return true;
}
return false;
}
api.modifyClass(
"model:topic",
(Superclass) =>
class extends Superclass {
@service router;
revealTags = false;
init() {
super.init(...arguments);
topicModels.set(String(this.id), this);
}
willDestroy() {
super.willDestroy(...arguments);
topicModels.delete(String(this.id));
}
@computed("tags")
get visibleListTags() {
const baseTags = super.visibleListTags || [];
if (!isAllowedRoute(this.router.currentRouteName)) {
return baseTags;
}
const highlightedList = [];
const regularList = [];
baseTags.forEach((tag) => {
if (highlightedTagsSet.has(tag)) {
highlightedList.push(tag);
} else {
regularList.push(tag);
}
});
if (this.revealTags) {
return [...highlightedList, ...regularList];
}
return [...highlightedList, ...regularList.slice(0, maxVisibleTags)];
}
}
);
api.addTagsHtmlCallback(
(topic) => {
if (!isAllowedRoute(topic.router.currentRouteName)) {
return "";
}
const allTags = topic.tags || [];
if (allTags.length === 0) {
return "";
}
const highlightedCount = allTags.filter((tag) =>
highlightedTagsSet.has(tag)
).length;
const regularCount = allTags.length - highlightedCount;
const effectiveLimit =
highlightedCount + Math.min(regularCount, maxVisibleTags);
// Only show toggle if there are hidden tags
if (allTags.length <= effectiveLimit) {
return "";
}
const isExpanded = topic.revealTags;
const hiddenCount = allTags.length - effectiveLimit;
const label = isExpanded
? i18n(themePrefix("js.tag_reveal.hide"))
: i18n(themePrefix("js.tag_reveal.more_tags"), {
count: hiddenCount,
});
const classList = ["discourse-tag", "reveal-tag-action"];
if (settings.toggle_tag_style === "box") {
classList.push("-box");
}
return `<a class="${classList.join(" ")}" role="button" aria-expanded="${isExpanded}">${label}</a>`;
},
{
priority: siteSettings.max_tags_per_topic + 1,
}
);
api.registerValueTransformer(
"topic-list-item-class",
({ value, context }) => {
if (highlightedTagsSet.size === 0) {
return value;
}
if (context.topic?.tags?.some((tag) => highlightedTagsSet.has(tag))) {
return [...value, `highlighted-tag__${settings.highlighted_style}`];
}
return value;
}
);
api.replaceTagRenderer((tag, params) => {
let newParams = params;
if (highlightedTagsSet.has(tag)) {
newParams = {
...params,
extraClass: [params.extraClass, "highlighted"]
.filter(Boolean)
.join(" "),
};
}
return defaultRenderTag(tag, newParams);
});
document.addEventListener(
"click",
(event) => {
const target = event.target;
if (!target?.matches(".reveal-tag-action")) {
return;
}
event.preventDefault();
event.stopPropagation();
const element =
target.closest("[data-topic-id]") ||
document.querySelector("h1[data-topic-id]");
const topicId = element?.dataset.topicId;
if (!topicId) {
return;
}
const topicModel = topicModels.get(topicId);
if (!topicModel) {
return;
}
topicModel.revealTags = !topicModel.revealTags;
topicModel.notifyPropertyChange("tags");
},
true
);
api.onPageChange((url) => {
const route = api.container.lookup("service:router").recognize(url);
if (!isAllowedRoute(route?.name)) {
return;
}
for (const [id, model] of topicModels) {
if (model && model.revealTags) {
model.revealTags = false;
model.notifyPropertyChange("tags");
}
}
});
});