Wir stellen das veraltete „Widget“-Rendersystem ab und ersetzen es durch moderne Glimmer-Komponenten.
Wir haben den Post-Stream kürzlich mit Glimmer-Komponenten modernisiert. Diese Anleitung führt Sie durch die Migration Ihrer Plugins und Themes vom alten widget-basierten System zur neuen Glimmer-Implementierung.\n
Keine Sorge – diese Migration ist einfacher, als es auf den ersten Blick erscheinen mag. Wir haben das neue System intuitiver und leistungsfähiger als das alte Widget-System gestaltet, und diese Anleitung unterstützt Sie dabei.
Zeitplan
Dies sind Schätzungen, die sich ändern können
Q2 2025:
Implementierung im Kern abgeschlossen
Beginn des Upgrades der offiziellen Plugins und Theme-Komponenten
Aktiviert auf Meta
Upgrade-Empfehlungen veröffentlicht (diese Anleitung)
Q3 2025:
Upgrade der offiziellen Plugins und Theme-Komponenten abgeschlossen
glimmer_post_stream_modestandardmäßig aufautosetzen und Deprecation-Meldungen in der Konsole aktivieren
Deprecation-Meldungen mit einem Admin-Warnbanner aktivieren (geplant für Juli 2025)- Plugins und Themes von Drittanbietern sollten aktualisiert werden
Q4 2025:
Den neuen Post-Stream standardmäßig aktivieren
Die Feature-Flag-Einstellung und den veralteten Code entfernen
Was bedeutet das für mich?
Wenn Ihre Plugins oder Themes „Widget“-APIs zur Anpassung des Post-Streams verwenden, müssen Sie diese für die neue Version aktualisieren.
Wie kann ich den neuen Post-Stream testen?
Um den neuen Post-Stream zu testen, ändern Sie einfach die Einstellung glimmer_post_stream_mode auf auto in Ihren Site-Einstellungen. Dadurch wird der neue Post-Stream aktiviert, sofern keine inkompatiblen Plugins oder Themes vorhanden sind.
Wenn glimmer_post_stream_mode auf auto gesetzt ist, erkennt Discourse automatisch inkompatible Plugins und Themes. Falls welche gefunden werden, sehen Sie hilfreiche Warnmeldungen in der Browserkonsole, die genau identifizieren, welche Plugins oder Themes aktualisiert werden müssen, inklusive Stack-Traces, die Ihnen helfen, den relevanten Code zu lokalisieren.
Diese Meldungen helfen Ihnen dabei, genau zu erkennen, welche Teile Ihrer Plugins oder Themes aktualisiert werden müssen, um mit dem neuen Glimmer-Post-Stream kompatibel zu sein.
Sollten Sie beim Verwenden des neuen Post-Streams auf Probleme stoßen: Keine Sorge. Vorläufig können Sie die Einstellung auf disabled setzen, um zum alten System zurückzukehren. Wenn Sie inkompatible Erweiterungen installiert haben, aber den neuen Post-Stream dennoch testen möchten, können Sie als Administrator die Option auf enabled setzen, um den neuen Post-Stream zu erzwingen. Verwenden Sie dies jedoch mit Vorsicht – Ihre Site funktioniert möglicherweise nicht ordnungsgemäß, je nach Ihren vorhandenen Anpassungen.
Ich habe benutzerdefinierte Plugins oder Themes installiert. Muss ich sie aktualisieren?
Sie müssen Ihre Plugins oder Themes aktualisieren, wenn sie eine der folgenden Anpassungen durchführen:
-
Verwendung von
decorateWidget,changeWidgetSetting,reopenWidgetoderattachWidgetActionan diesen Widgets: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
-
Verwendung einer der folgenden API-Methoden:
addPostTransformCallbackincludePostAttributes
Wenn Sie Erweiterungen haben, die eine der oben genannten Anpassungen verwenden, sehen Sie beim Besuch einer Topic-Seite eine Warnung in der Konsole, die das zu aktualisierende Plugin oder die Komponente identifiziert.
Die Deprecation-ID lautet:
discourse.post-stream-widget-overrides
Wenn Sie mehr als ein Theme in Ihrer Instanz verwenden, überprüfen Sie bitte alle, da Warnungen nur für aktive Plugins und aktuell verwendete Themes sowie Theme-Komponenten angezeigt werden.
Was sind die Ersatzlösungen?
Der neue Glimmer-Post-Stream bietet mehrere Möglichkeiten, um die Darstellung von Beiträgen anzupassen:
- Plugin Outlets: Zum Hinzufügen von Inhalt an bestimmten Stellen im Post-Stream.
- Transformer: Zum Anpassen von Elementen, Ändern von Datenstrukturen oder Modifizieren des Verhaltens von Komponenten.
Ersetzen von includePostAttributes durch addTrackedPostProperties
Wenn Ihr Plugin includePostAttributes verwendet, um Eigenschaften zum Post-Modell hinzuzufügen, müssen Sie es auf addTrackedPostProperties umstellen.
Vorher:
api.includePostAttributes('can_accept_answer', 'accepted_answer', 'topic_accepted_answer');
Nachher:
api.addTrackedPostProperties('can_accept_answer', 'accepted_answer', 'topic_accepted_answer');
Die Funktion addTrackedPostProperties markiert Eigenschaften als verfolgt für Post-Updates. Dies ist wichtig, wenn Ihr Plugin Eigenschaften zu einem Post hinzufügt und diese während des Renderings verwendet. Sie stellt sicher, dass die Benutzeroberfläche automatisch aktualisiert wird, wenn sich diese Eigenschaften ändern.
Häufige Migrationsmuster
Verwendung von Plugin Outlets
Plugin Outlets sind Ihr Hauptwerkzeug zum Hinzufügen von Inhalt an bestimmten Stellen im Post-Stream. Betrachten Sie sie als festgelegte Stellen, an denen Sie Ihren benutzerdefinierten Inhalt einfügen können.
Sie sind der Schlüssel zur Migration weg von Widget-Dekorationen.
Der Glimmer-Post-Stream kapselt häufig angepassten Inhalt in Outlets ein. Verwenden Sie die Plugin-API-Funktionen renderBeforeWrapperOutlet und renderAfterWrapperOutlet, um Inhalt davor oder dahinter einzufügen.
1. Ersetzen von Widget-Dekorationen durch Plugin Outlets
Die häufigste Anpassung ist das Hinzufügen von Inhalt zu Beiträgen. Im Widget-System würden Sie decorateWidget verwenden. Mit Glimmer verwenden Sie stattdessen Plugin Outlets.
Vorher:
// Teil eines Initializers in einem Plugin
import { withPluginApi } from "discourse/lib/plugin-api";
// ... andere 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) => {
// ... andere Anpassungen
customizeWidgetPost(api);
});
}
};
Nachher:
// Teil eines .gjs-Initializers in einem Plugin
import Component from "@glimmer/component";
import { withPluginApi } from "discourse/lib/plugin-api";
import SolvedAcceptedAnswer from "../components/solved-accepted-answer";
// ... andere 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) => {
// ... andere Anpassungen
customizePost(api);
});
}
};
2. Hinzufügen von Inhalt nach dem Namen des Autors
Wenn Ihr Plugin Inhalt nach dem Namen des Autors hinzufügt, verwenden Sie die renderAfterWrapperOutlet-API mit dem Outlet post-meta-data-poster-name.
Vorher:
// Teil eines Initializers in einem Plugin
import { withPluginApi } from "discourse/lib/plugin-api";
// ... andere 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) => {
// ... andere Anpassungen
customizeWidgetPost(api);
});
}
};
Nachher:
// Teil eines .gjs-Initializers in einem Plugin
import Component from "@glimmer/component";
import { withPluginApi } from "discourse/lib/plugin-api";
// ... andere 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) => {
// ... andere Anpassungen
customizePost(api);
});
}
};
3. Hinzufügen von Inhalt vor dem Post-Inhalt
// Teil eines .gjs-Initializers in einem Theme
import Component from "@glimmer/component";
import { withPluginApi } from "discourse/lib/plugin-api";
// ... andere 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">
Dies ist ein angepinntes Topic
</div>
</template>
}
);
}
export default {
name: "pinned-topic-notice",
initialize() {
withPluginApi((api) => {
// ... andere Anpassungen
customizePost(api);
});
}
};
4. Hinzufügen von Inhalt nach dem Post-Inhalt
// Teil eines .gjs-Initializers in einem Theme
import Component from "@glimmer/component";
import { withPluginApi } from "discourse/lib/plugin-api";
// ... andere Imports
function customizePost(api) {
api.renderAfterWrapperOutlet(
"post-article",
class extends Component {
static shouldRender(args) {
return args.post?.wiki;
}
// In einer echten Komponente würden Sie ein Template wie folgt verwenden:
<template>
<div class="wiki-post-notice">
Dieser Beitrag ist ein Wiki
</div>
</template>
}
);
}
export default {
name: "wiki-post-notice",
initialize() {
withPluginApi((api) => {
customizePost(api);
// ... andere Anpassungen
});
}
};
Verwendung von Transformer
Transformer sind eine leistungsstarke Möglichkeit, Discourse-Komponenten anzupassen. Sie ermöglichen es Ihnen, Daten oder das Verhalten von Komponenten zu ändern, ohne gesamte Komponenten überschreiben zu müssen.
Hier sind einige der relevantesten Value-Transformer für die Anpassung des Post-Streams:
| Transformer-Name | Beschreibung | Kontext |
|---|---|---|
post-class |
Anpassung der CSS-Klassen, die auf das Haupt-Post-Element angewendet werden. | { post } |
post-meta-data-infos |
Anpassung der Liste der Metadaten-Komponenten, die für einen Beitrag angezeigt werden. Dies ermöglicht das Hinzufügen, Entfernen oder Neuordnen von Elementen wie dem Post-Datum, dem Bearbeitungs-Indikator usw. | { post, metaDataInfoKeys } |
post-meta-data-poster-name-suppress-similar-name |
Entscheiden, ob die Anzeige des vollständigen Namens des Benutzers unterdrückt werden soll, wenn dieser seinem Benutzernamen ähnlich ist. Rückgabe von true zum Unterdrücken. |
{ post, name } |
post-notice-component |
Anpassung oder Ersetzung der Komponente, die zum Rendern einer Post-Benachrichtigung verwendet wird. | { post, type } |
post-show-topic-map |
Steuerung der Sichtbarkeit der Topic-Map-Komponente im ersten Beitrag. | { post, isPM, isRegular, showWithoutReplies } |
post-small-action-class |
Hinzufügen benutzerdefinierter CSS-Klassen zu einem kleinen Action-Post. | { post, actionCode } |
post-small-action-custom-component |
Ersetzen eines standardmäßigen kleinen Action-Posts durch eine benutzerdefinierte Glimmer-Komponente. | { post, actionCode } |
post-small-action-icon |
Anpassung des Symbols, das für einen kleinen Action-Post verwendet wird. | { post, actionCode } |
poster-name-class |
Hinzufügen benutzerdefinierter CSS-Klassen zum Container des Autorennamens. | { user } |
1. Hinzufügen einer benutzerdefinierten Klasse zu Beiträgen
// Teil eines Initializers in einem Plugin
import { withPluginApi } from "discourse/lib/plugin-api";
function customizePostClasses(api) {
api.registerValueTransformer(
"post-class",
({ value, context }) => {
const { post } = context;
// Füge eine benutzerdefinierte Klasse zu Beiträgen eines bestimmten Benutzers hinzu
if (post.user_id === 1) {
return [...value, "special-user-post"];
}
return value;
}
);
}
export default {
name: "custom-post-classes",
initialize() {
withPluginApi((api) => {
// ... andere Anpassungen
customizePostClasses(api);
});
}
};
2. Hinzufügen benutzerdefinierter Post-Metadaten
Der Transformer post-meta-data-infos ermöglicht es Ihnen, benutzerdefinierte Komponenten zum Abschnitt der Post-Metadaten hinzuzufügen.
// Teil eines Initializers in einem Plugin
import { withPluginApi } from "discourse/lib/plugin-api";
function customizePostMetadata(api) {
// Definieren Sie eine Komponente, die im Metadaten-Abschnitt verwendet werden soll
// Die Komponente sollte außerhalb des Transformer-Callbacks erstellt werden,
// da dies sonst zu Speicherproblemen führen kann.
const CustomMetadataComponent = <template>...</template>;
api.registerValueTransformer(
"post-meta-data-infos",
({ value: metadata, context: { post, metaDataInfoKeys } }) => {
// Füge die Komponente nur für bestimmte Beiträge hinzu
if (post.some_custom_property) {
metadata.add(
"custom-metadata-key",
CustomMetadataComponent,
{
// Positioniere es vor dem Datum
before: metaDataInfoKeys.DATE,
// und nach dem Reply-Tab
after: metaDataInfoKeys.REPLY_TO_TAB,
}
);
}
}
);
}
export default {
name: "custom-post-metadata",
initialize() {
withPluginApi((api) => {
// ... andere Anpassungen
customizePostMetadata(api);
});
}
};
Hier ist ein reales Beispiel aus dem Plugin discourse-activity-pub:
// Teil eines Initializers im 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);
// ... andere Anpassungen
});
}
};
5. Einfügen von Inhalt vor oder nach dem gekochten Inhalt des Posts
Wenn Ihr Plugin Inhalt nach dem Text im Post hinzufügt, verwenden Sie die renderAfterWrapperOutlet-API mit dem Outlet post-content-cooked-html.
Vorher:
// Teil eines Initializers in einem 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 = "Dieser Beitrag ist ein Wiki";
element.prepend(banner);
}
});
}
export default {
name: "wiki-footer",
initialize() {
withPluginApi((api) => {
// ... andere Anpassungen
customizeCooked(api);
});
}
};
Nachher:
// Teil eines Initializers in einem Plugin (.gjs)
import Component from "@glimmer/component";
import { withPluginApi } from "discourse/lib/plugin-api";
// Definieren Sie eine Komponente, die im Plugin-Outlet verwendet werden soll
class WikiBanner extends Component {
static shouldRender(args) {
return args.post.wiki;
}
<template>
<div class="wiki-footer">Dieser Beitrag ist ein Wiki</div>
</template>
}
function customizePost(api) {
// Verwenden Sie renderBeforeWrapperOutlet, um Inhalt vor dem Post-Inhalt hinzuzufügen
api.renderAfterWrapperOutlet(
"post-content-cooked-html",
WikiBanner
);
}
export default {
name: "wiki-footer",
initialize() {
withPluginApi((api) => {
customizePost(api);
// ... andere Anpassungen
});
}
};
Unterstützung beider Systeme während der Übergangsphase
Als Autor eines Plugins oder Themes möchten Sie möglicherweise während der Übergangsphase beide Systeme unterstützen, um sicherzustellen, dass Ihre Erweiterungen sowohl mit dem alten als auch mit dem neuen Post-Stream funktionieren.
Hier ist ein Muster, das von vielen offiziellen Plugins verwendet wird:
// 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) {
// Glimmer-Post-Stream-Anpassungen
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>
}
);
// ...
// Umwickeln des alten Widget-Codes unter Unterdrückung der Deprecation-Warnungen
withSilencedDeprecations("discourse.post-stream-widget-overrides", () =>
customizeWidgetPost(api)
);
}
// alter Widget-Code
function customizeWidgetPost(api) {
api.decorateWidget("post-contents:after-cooked", (helper) => {
let post = helper.getModel();
if (helper.attrs.post_number === 1 && post?.topic?.accepted_answer) {
// Verwenden Sie RenderGlimmer, um eine Glimmer-Komponente im Widget-System zu rendern
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);
// ... andere Anpassungen
});
}
},
};
Beispiele aus der Praxis
Hier sind Links zu tatsächlichen Migrations-Pull-Requests unserer offiziellen Plugins. Diese zeigen, wie wir jede Art von Anpassung aktualisiert haben:
- 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
Fehlerbehebung
Meine Site sieht nach Aktivierung des neuen Post-Streams kaputt aus
- Setzen Sie
glimmer_post_stream_modezurück aufdisabled - Überprüfen Sie die Konsole auf spezifische Fehlermeldungen
- Aktualisieren Sie die problematischen Plugins/Themes, bevor Sie es erneut versuchen
Ich sehe keine Warnungen, aber meine Anpassungen funktionieren nicht
- Überprüfen Sie, ob Ihre Anpassungen auf die oben aufgeführten Widgets abzielen
- Stellen Sie sicher, dass Sie auf der Topic-Seite mit Topics testen, bei denen Ihre Anpassungen sichtbar sind
Brauchen Sie Hilfe?
Wenn Sie einen Fehler gefunden haben oder Ihre Anpassung mit den neuen APIs, die wir eingeführt haben, nicht erreicht werden kann, teilen Sie es uns bitte unten mit.