Nous abandonnons progressivement l’ancien système de rendu des « widgets » pour le remplacer par des composants Glimmer modernes.
Nous avons récemment modernisé le flux de publications en utilisant des composants Glimmer. Ce guide vous accompagnera dans la migration de vos plugins et thèmes de l’ancien système basé sur les widgets vers la nouvelle implémentation Glimmer.
Ne vous inquiétez pas : cette migration est plus simple qu’il n’y paraît à première vue. Nous avons conçu le nouveau système pour qu’il soit plus intuitif et puissant que l’ancien système de widgets, et ce guide vous aidera tout au long du processus.
Calendrier
Ces estimations sont susceptibles d’évoluer
T2 2025 :
Implémentation de base terminée
Début de la mise à niveau des plugins et des composants de thème officiels
Activé sur Meta
Publication des conseils de mise à niveau (ce guide)
T3 2025 :
Fin de la mise à niveau des plugins et des composants de thème officiels
Définition de glimmer_post_stream_modepar défaut surautoet activation des messages de dépréciation dans la console
Activation des messages de dépréciation avec une bannière d’avertissement pour les administrateurs (prévue pour juillet 2025)- Les plugins et thèmes tiers doivent être mis à jour
T4 2025 :
Activation du nouveau flux de publications par défaut
Suppression du paramètre de drapeau de fonctionnalité et du code hérité
Qu’est-ce que cela signifie pour moi ?
Si l’un de vos plugins ou thèmes utilise des API « widget » pour personnaliser le flux de publications, vous devrez les mettre à jour pour qu’ils fonctionnent avec la nouvelle version.
Comment puis-je essayer le nouveau flux de publications ?
Pour essayer le nouveau flux de publications, modifiez simplement le paramètre glimmer_post_stream_mode en auto dans les paramètres de votre site. Cela activera le nouveau flux de publications si vous n’avez aucun plugin ou thème incompatible.
Lorsque glimmer_post_stream_mode est défini sur auto, Discourse détecte automatiquement les plugins et thèmes incompatibles. S’il en trouve, vous verrez des messages d’avertissement utiles dans la console du navigateur qui identifient exactement quels plugins ou thèmes doivent être mis à jour, ainsi que des traces de pile pour vous aider à localiser le code concerné.
Ces messages vous aideront à identifier exactement quelles parties de vos plugins ou thèmes doivent être mises à jour pour être compatibles avec le nouveau flux de publications Glimmer.
Si vous rencontrez des problèmes avec le nouveau flux de publications, ne vous inquiétez pas : pour l’instant, vous pouvez toujours définir le paramètre sur disabled pour revenir à l’ancien système. Si vous avez des extensions incompatibles installées mais que vous souhaitez tout de même essayer, en tant qu’administrateur, vous pouvez définir l’option sur enabled pour forcer le nouveau flux de publications. Utilisez cette option avec précaution : votre site pourrait ne pas fonctionner correctement selon les personnalisations que vous avez mises en place.
J’ai des plugins ou des thèmes personnalisés installés. Dois-je les mettre à jour ?
Vous devrez mettre à jour vos plugins ou thèmes s’ils effectuent l’une des personnalisations suivantes :
-
Utiliser
decorateWidget,changeWidgetSetting,reopenWidgetouattachWidgetActionsur les widgets suivants :actions-summaryavatar-flairembedded-postexpand-hiddenexpand-post-buttonfilter-jump-to-postfilter-show-allpost-articlepost-avatar-user-infopost-avatarpost-bodypost-contentspost-datepost-edits-indicatorpost-email-indicatorpost-gappost-group-requestpost-linkspost-locked-indicatorpost-meta-datapost-noticepost-placeholderpost-streampostposter-nameposter-name-titleposts-filtered-noticereply-to-tabselect-posttopic-post-visited-line
-
Utiliser l’une des méthodes API ci-dessous :
addPostTransformCallbackincludePostAttributes
Si vous avez des extensions utilisant l’une des personnalisations ci-dessus, vous verrez un avertissement dans la console indiquant quel plugin ou composant doit être mis à niveau lorsque vous visitez une page de sujet.
L’ID de dépréciation est :
discourse.post-stream-widget-overrides
Si vous utilisez plus d’un thème dans votre instance, assurez-vous de vérifier tous les thèmes, car les avertissements n’apparaîtront que pour les plugins actifs et les thèmes et composants de thème actuellement utilisés.
Quelles sont les alternatives ?
Le nouveau flux de publications Glimmer vous offre plusieurs façons de personnaliser l’apparence des publications :
- Plugin Outlets (Sorties de plugin) : Pour ajouter du contenu à des points spécifiques du flux de publications.
- Transformers (Transformateurs) : Pour personnaliser des éléments, modifier des structures de données ou changer le comportement des composants.
Remplacer includePostAttributes par addTrackedPostProperties
Si votre plugin utilise includePostAttributes pour ajouter des propriétés au modèle de publication, vous devrez le mettre à jour pour utiliser addTrackedPostProperties à la place.
Avant :
api.includePostAttributes('can_accept_answer', 'accepted_answer', 'topic_accepted_answer');
Après :
api.addTrackedPostProperties('can_accept_answer', 'accepted_answer', 'topic_accepted_answer');
La fonction addTrackedPostProperties marque les propriétés comme suivies pour les mises à jour des publications. C’est important si votre plugin ajoute des propriétés à une publication et les utilise lors du rendu. Cela garantit que l’interface utilisateur se met à jour automatiquement lorsque ces propriétés changent.
Modèles courants de migration
Utilisation des Plugin Outlets
Les Plugin Outlets sont votre outil principal pour ajouter du contenu à des points spécifiques du flux de publications. Considérez-les comme des emplacements désignés où vous pouvez injecter votre contenu personnalisé.
Ils sont la clé pour migrer loin des décorations de widgets.
Le flux de publications Glimmer enveloppe souvent le contenu personnalisé dans des sorties. Utilisez les fonctions de l’API de plugin renderBeforeWrapperOutlet et renderAfterWrapperOutlet pour insérer du contenu avant ou après ces sorties.
1. Remplacer les décorations de widgets par des Plugin Outlets
La personnalisation la plus courante consiste à ajouter du contenu aux publications. Dans le système de widgets, vous utilisiez decorateWidget. Avec Glimmer, vous utiliserez des Plugin Outlets à la place.
Avant :
// Partie d'un initialiseur dans un plugin
import { withPluginApi } from "discourse/lib/plugin-api";
// ... autres imports
function customizeWidgetPost(api) {
api.decorateWidget("post-contents:after-cooked", (helper) => {
const post = helper.getModel();
if (post.post_number === 1 && post.topic.accepted_answer) {
return helper.attach("solved-accepted-answer", { post });
}
});
}
export default {
name: "extend-for-solved-button",
initialize() {
withPluginApi((api) => {
// ... autres personnalisations
customizeWidgetPost(api);
});
}
};
Après :
// Partie d'un initialiseur .gjs dans un plugin
import Component from "@glimmer/component";
import { withPluginApi } from "discourse/lib/plugin-api";
import SolvedAcceptedAnswer from "../components/solved-accepted-answer";
// ... autres imports
function customizePost(api) {
api.renderAfterWrapperOutlet(
"post-content-cooked-html",
class extends Component {
static shouldRender(args) {
return args.post?.post_number === 1 && args.post?.topic?.accepted_answer;
}
<template>
<SolvedAcceptedAnswer
@post={{@post}}
/>
</template>
}
);
}
export default {
name: "extend-for-solved-button",
initialize() {
withPluginApi((api) => {
// ... autres personnalisations
customizePost(api);
});
}
};
2. Ajouter du contenu après le nom de l’auteur
Si votre plugin ajoute du contenu après le nom de l’auteur, vous utiliserez l’API renderAfterWrapperOutlet avec la sortie post-meta-data-poster-name.
Avant :
// Partie d'un initialiseur dans un plugin
import { withPluginApi } from "discourse/lib/plugin-api";
// ... autres imports
function customizeWidgetPost(api) {
api.decorateWidget(`poster-name:after`, (dec) => {
if (!isGPTBot(dec.attrs.user)) {
return;
}
return dec.widget.attach("persona-flair", {
personaName: dec.model?.topic?.ai_persona_name,
});
});
}
export default {
name: "ai-bot-replies",
initialize() {
withPluginApi((api) => {
// ... autres personnalisations
customizeWidgetPost(api);
});
}
};
Après :
// Partie d'un initialiseur .gjs dans un plugin
import Component from "@glimmer/component";
import { withPluginApi } from "discourse/lib/plugin-api";
// ... autres imports
function customizePost(api) {
api.renderAfterWrapperOutlet(
"post-meta-data-poster-name",
class extends Component {
static shouldRender(args) {
return isGPTBot(args.post?.user);
}
<template>
<span class="persona-flair">{{@post.topic.ai_persona_name}}</span>
</template>
}
);
}
export default {
name: "ai-bot-replies",
initialize() {
withPluginApi((api) => {
// ... autres personnalisations
customizePost(api);
});
}
};
3. Ajouter du contenu avant le contenu de la publication
// Partie d'un initialiseur .gjs dans un thème
import Component from "@glimmer/component";
import { withPluginApi } from "discourse/lib/plugin-api";
// ... autres imports
function customizePost(api) {
api.renderBeforeWrapperOutlet(
"post-article",
class extends Component {
static shouldRender(args) {
return args.post?.topic?.pinned;
}
<template>
<div class="pinned-post-notice">
Ceci est un sujet épinglé
</div>
</template>
}
);
}
export default {
name: "pinned-topic-notice",
initialize() {
withPluginApi((api) => {
// ... autres personnalisations
customizePost(api);
});
}
};
4. Ajouter du contenu après le contenu de la publication
// Partie d'un initialiseur .gjs dans un thème
import Component from "@glimmer/component";
import { withPluginApi } from "discourse/lib/plugin-api";
// ... autres imports
function customizePost(api) {
api.renderAfterWrapperOutlet(
"post-article",
class extends Component {
static shouldRender(args) {
return args.post?.wiki;
}
// Dans un vrai composant, vous utiliseriez un template comme ceci :
<template>
<div class="wiki-post-notice">
Cette publication est un wiki
</div>
</template>
}
);
}
export default {
name: "wiki-post-notice",
initialize() {
withPluginApi((api) => {
customizePost(api);
// ... autres personnalisations
});
}
};
Utilisation des Transformers
Les Transformers sont un moyen puissant de personnaliser les composants Discourse. Ils vous permettent de modifier des données ou le comportement des composants sans avoir à remplacer des composants entiers.
Voici certains des transformateurs de valeurs les plus pertinents pour la personnalisation du flux de publications :
| Nom du Transformateur | Description | Contexte |
|---|---|---|
post-class |
Personnaliser les classes CSS appliquées à l’élément de publication principal. | { post } |
post-meta-data-infos |
Personnaliser la liste des composants de métadonnées affichés pour une publication. Cela permet d’ajouter, supprimer ou réorganiser des éléments comme la date de publication, l’indicateur de modification, etc. | { post, metaDataInfoKeys } |
post-meta-data-poster-name-suppress-similar-name |
Décider s’il faut supprimer l’affichage du nom complet de l’utilisateur lorsqu’il est similaire à son nom d’utilisateur. Retourner true pour supprimer. |
{ post, name } |
post-notice-component |
Personnaliser ou remplacer le composant utilisé pour afficher un avis de publication. | { post, type } |
post-show-topic-map |
Contrôler la visibilité du composant de carte de sujet sur la première publication. | { post, isPM, isRegular, showWithoutReplies } |
post-small-action-class |
Ajouter des classes CSS personnalisées à une petite action de publication. | { post, actionCode } |
post-small-action-custom-component |
Remplacer une petite action de publication standard par un composant Glimmer personnalisé. | { post, actionCode } |
post-small-action-icon |
Personnaliser l’icône utilisée pour une petite action de publication. | { post, actionCode } |
poster-name-class |
Ajouter des classes CSS personnalisées au conteneur du nom de l’auteur. | { user } |
1. Ajouter une classe personnalisée aux publications
// Partie d'un initialiseur dans un plugin
import { withPluginApi } from "discourse/lib/plugin-api";
function customizePostClasses(api) {
api.registerValueTransformer(
"post-class",
({ value, context }) => {
const { post } = context;
// Ajouter une classe personnalisée aux publications d'un utilisateur spécifique
if (post.user_id === 1) {
return [...value, "special-user-post"];
}
return value;
}
);
}
export default {
name: "custom-post-classes",
initialize() {
withPluginApi((api) => {
// ... autres personnalisations
customizePostClasses(api);
});
}
};
2. Ajouter des métadonnées de publication personnalisées
Le transformateur post-meta-data-infos vous permet d’ajouter des composants personnalisés à la section des métadonnées de la publication.
// Partie d'un initialiseur dans un plugin
import { withPluginApi } from "discourse/lib/plugin-api";
function customizePostMetadata(api) {
// Définir un composant à utiliser dans la section des métadonnées
// Le composant doit être créé en dehors de la fonction de rappel du transformateur,
// sinon cela peut causer des problèmes de mémoire.
const CustomMetadataComponent = <template>...</template>;
api.registerValueTransformer(
"post-meta-data-infos",
({ value: metadata, context: { post, metaDataInfoKeys } }) => {
// Ajouter le composant uniquement pour certaines publications
if (post.some_custom_property) {
metadata.add(
"custom-metadata-key",
CustomMetadataComponent,
{
// Le placer avant la date
before: metaDataInfoKeys.DATE,
// et après l'onglet de réponse
after: metaDataInfoKeys.REPLY_TO_TAB,
}
);
}
}
);
}
export default {
name: "custom-post-metadata",
initialize() {
withPluginApi((api) => {
// ... autres personnalisations
customizePostMetadata(api);
});
}
};
Voici un exemple concret tiré du plugin discourse-activity-pub :
// Partie d'un initialiseur dans le plugin discourse-activity-pub
import { withPluginApi } from "discourse/lib/plugin-api";
import ActivityPubPostStatus from "../components/activity-pub-post-status";
import {
activityPubPostStatus,
showStatusToUser,
} from "../lib/activity-pub-utilities";
function customizePost(api, container) {
const currentUser = api.getCurrentUser();
const PostMetadataActivityPubStatus = <template>
<div class="post-info activity-pub">
<ActivityPubPostStatus @post={{@post}} />
</div>
</template>;
api.registerValueTransformer(
"post-meta-data-infos",
({ value: metadata, context: { post, metaDataInfoKeys } }) => {
const site = container.lookup("service:site");
const siteSettings = container.lookup("service:site-settings");
if (
site.activity_pub_enabled &&
post.activity_pub_enabled &&
post.post_number !== 1 &&
showStatusToUser(currentUser, siteSettings)
) {
const status = activityPubPostStatus(post);
if (status) {
metadata.add(
"activity-pub-indicator",
PostMetadataActivityPubStatus,
{
before: metaDataInfoKeys.DATE,
after: metaDataInfoKeys.REPLY_TO_TAB,
}
);
}
}
}
);
}
export default {
name: "activity-pub",
initialize(container) {
withPluginApi((api) => {
customizePost(api, container);
// ... autres personnalisations
});
}
};
5. Insérer du contenu avant ou après le contenu cuisiné de la publication
Si votre plugin ajoute du contenu après le texte de la publication, vous utiliserez l’API renderAfterWrapperOutlet avec la sortie post-content-cooked-html.
Avant :
// Partie d'un initialiseur dans un plugin
import { withPluginApi } from "discourse/lib/plugin-api";
function customizeCooked(api) {
api.decorateWidget("post-contents:after-cooked", (helper) => {
const post = helper.getModel();
if (post.wiki) {
const banner = document.createElement("div");
banner.classList.add("wiki-footer");
banner.textContent = "Cette publication est un wiki";
element.prepend(banner);
}
});
}
export default {
name: "wiki-footer",
initialize() {
withPluginApi((api) => {
// ... autres personnalisations
customizeCooked(api);
});
}
};
Après :
// Partie d'un initialiseur dans un plugin (.gjs)
import Component from "@glimmer/component";
import { withPluginApi } from "discourse/lib/plugin-api";
// Définir un composant à utiliser dans la sortie du plugin
class WikiBanner extends Component {
static shouldRender(args) {
return args.post.wiki;
}
<template>
<div class="wiki-footer">Cette publication est un wiki</div>
</template>
}
function customizePost(api) {
// Utiliser renderBeforeWrapperOutlet pour ajouter du contenu avant le contenu de la publication
api.renderAfterWrapperOutlet(
"post-content-cooked-html",
WikiBanner
);
}
export default {
name: "wiki-footer",
initialize() {
withPluginApi((api) => {
customizePost(api);
// ... autres personnalisations
});
}
};
Prise en charge des deux systèmes (ancien et nouveau) pendant la transition
En tant qu’auteur de plugin ou de thème, vous pouvez souhaiter prendre en charge les deux systèmes pendant la transition pour vous assurer que vos extensions fonctionnent avec les anciens et nouveaux flux de publications.
Voici un modèle utilisé par de nombreux plugins officiels :
// solved-button.js
import Component from "@glimmer/component";
import { withSilencedDeprecations } from "discourse/lib/deprecated";
import { withPluginApi } from "discourse/lib/plugin-api";
import RenderGlimmer from "discourse/widgets/render-glimmer";
import SolvedAcceptedAnswer from "../components/solved-accepted-answer";
function customizePost(api) {
// personnalisations du flux de publications glimmer
api.renderAfterWrapperOutlet(
"post-content-cooked-html",
class extends Component {
static shouldRender(args) {
return args.post?.post_number === 1 && args.post?.topic?.accepted_answer;
}
<template>
<SolvedAcceptedAnswer
@post={{@post}}
@decoratorState={{@decoratorState}}
/>
</template>
}
);
// ...
// envelopper l'ancien code de widget en silence les avertissements de dépréciation
withSilencedDeprecations("discourse.post-stream-widget-overrides", () =>
customizeWidgetPost(api)
);
}
// ancien code de widget
function customizeWidgetPost(api) {
api.decorateWidget("post-contents:after-cooked", (helper) => {
let post = helper.getModel();
if (helper.attrs.post_number === 1 && post?.topic?.accepted_answer) {
// Utiliser RenderGlimmer pour afficher un composant Glimmer dans le système de widgets
return new RenderGlimmer(
helper.widget,
"div",
<template><SolvedAcceptedAnswer @post={{@data.post}} /></template>
null,
{ post }
);
}
});
}
export default {
name: "extend-for-solved-button",
initialize(container) {
const siteSettings = container.lookup("service:site-settings");
if (siteSettings.solved_enabled) {
withPluginApi((api) => {
customizePost(api);
// ... autres personnalisations
});
}
},
};
Exemples concrets
Voici des liens vers des demandes de tirage (pull requests) réelles de migration de nos plugins officiels. Elles montrent comment nous avons mis à jour chaque type de personnalisation :
- discourse-ai
- discourse-assign
- discourse-cakeday
- discourse-post-voting
- discourse-reactions
- discourse-shared-edits
- discourse-solved
- discourse-topic-voting
- discourse-user-notes
- discourse-activity-pub
Dépannage
Mon site semble cassé après avoir activé le nouveau flux de publications
- Réglez
glimmer_post_stream_modesurdisabled - Vérifiez la console pour des messages d’erreur spécifiques
- Mettez à jour les plugins/thèmes problématiques avant de réessayer
Je ne vois aucun avertissement, mais mes personnalisations ne fonctionnent pas
- Vérifiez que vos personnalisations ciblent les widgets listés ci-dessus
- Assurez-vous de tester sur la page de sujet avec des sujets où vos personnalisations sont visibles
Besoin d’aide ?
Si vous avez trouvé un bug ou si votre personnalisation ne peut pas être réalisée avec les nouvelles API que nous avons introduites, veuillez nous en informer ci-dessous.