Verwendung von Plugin Outlet Connectors von einem Theme oder Plugin

Discourse includes hundreds of Plugin Outlets which can be used to inject new content or replace existing contend in the Discourse UI. ‘Outlet arguments’ are made available so that content can be customized based on the context.

Choosing an outlet

To find the name of a plugin outlet, search Discourse core for “<PluginOutlet”, or use the plugin outlet locations theme component. (e.g. topic-above-posts).

Wrapper outlets

Some outlets in core look like <PluginOutlet @name="foo" />. These allow you to inject new content. Other outlets will ‘wrap’ an existing core implementation like this

<PluginOutlet @name="foo">
  core implementation
</PluginOutlet>

Defining a connector for this kind of ‘wrapper’ outlet will replace the core implementation. Only one active theme/plugin can contribute a connector for a wrapper plugin outlet.

For wrapper plugin outlets, you can render the original core implementation using the {{yield}} keyword. This can be helpful if you only want to replace the core implementation under certain conditions, or if you would like to wrap it in something.

Defining the template

Once you’ve chosen an outlet, decide on a name for your connector. This needs to be unique across all themes / plugins installed on a given community. e.g. brand-official-topics

In your theme / plugin, define a new handlebars template with a path formatted like this:

:art: {theme}/javascripts/discourse/connectors/{outlet-name}/{connector-name}.hbs

:electric_plug: {plugin}/assets/javascripts/discourse/connectors/{outlet-name}/{connector-name}.hbs

The content of these files will be rendered as an Ember Component. For general information on Ember / Handlebars, check out the Ember guides.

For our hypothetical “brand official topics” connector, the template might look like

<div class="alert alert-info">
  This topic was created by a member of the
  <a href="https://discourse.org/team">Discourse Team</a>
</div>

Some plugin outlets will automatically wrap your content in an HTML element. The element type is defined by @connectorTagName on the <PluginOutlet>.

Using outlet arguments

Plugin Outlets provide information about the surrounding context via @outletArgs. The arguments passed to each outlet vary. An easy way to view the arguments is to add this to your template:

{{log @outletArgs}}

This will log the arguments to your browser’s developer console. They will appear as a Proxy object - to explore the list of arguments, expand the [[Target]] of the proxy.

In our topic-above-posts example, the rendered topic is available under @outletArgs.model. So we can add the username of the team member like this:

<div class="alert alert-info">
  This topic was created by
  {{@outletArgs.model.details.created_by.username}}
  (a member of the
  <a href="https://discourse.org/team">Discourse Team</a>)
</div>

Adding more complex logic

Sometimes, a simple handlebars template is not enough. To add Javascript logic to your connector, you can define a Javascript file adjacent to your handlebars template. This file should export a component definition. This functions just the same as any other component definition, and can include service injections.

Defining a component like this will remove the automatic connectorTagName wrapper element, so you may want to re-introduce an element of the same type in your hbs file.

In our topic-above-posts example, we may want to render the user differently based on the ‘prioritize username in ux’ site setting. A component definition for that might look something like this:

.../connectors/topic-above-posts/brand-official-topic.js:

import Component from "@glimmer/component";
import { service } from "@ember/service";

export default class BrandOfficialTopics extends Component {
  @service siteSettings;

  get displayName() {
    const user = this.args.outletArgs.model.details.created_by;
    if (this.siteSettings.prioritize_username_in_ux) {
      return user.username;
    } else {
      return user.name;
    }
  }
}

We can then update the template to reference the new getter:

<div class="alert alert-info">
  This topic was created by
  {{this.displayName}}
  (a member of the
  <a href="https://discourse.org/team">Discourse Team</a>)
</div>

Conditional rendering

If you only want your content to be rendered under certain conditions, it’s often enough to wrap your template with a handlebars {{#if}} block. If that’s not enough, you may want to use the shouldRender hook to control whether your connector template is rendered at all.

Firstly, ensure you have a .js connector definition as described above. Then, add a static shouldRender() function. Extending our example:

import Component from "@glimmer/component";
import { getOwner } from "discourse-common/lib/get-owner";

export default class BrandOfficialTopics extends Component {
  static shouldRender(outletArgs, helper) {
    const firstPost = outletArgs.model.postStream.posts[0];
    return firstPost.primary_group_name === "team";
  }
  // ... (any other logic)
}

Now the connector will only be rendered when the first post of the topic was created by a team member.

shouldRender is evaluated in a Glimmer autotracking context. Future changes to any referenced properties (e.g. outletArgs) will cause the function to be re-evaluated.

Introducing new outlets

If you need an outlet that doesn’t yet exist, please feel free to make a pull request, or open a topic in Dev.


This document is version controlled - suggest changes on github.

39 „Gefällt mir“

Dies ist jetzt veraltet, richtig?
Deprecation notice: Defining connector classes via registerConnectorClass is deprecated. See https://meta.discourse.org/t/32727 for more modern patterns. [deprecation id: discourse.register-connector-class-legacy]

Das ist richtig; Sie können stattdessen api.renderInOutlet verwenden. :slight_smile:

https://github.com/discourse/discourse/blob/main/app/assets/javascripts/discourse/app/lib/plugin-api.js#L982-L1008

1 „Gefällt mir“

Ich glaube, es ist ein bisschen komplizierter als das :sweat_smile:
https://github.com/Firepup6500/discourse-custom-profile-link/blob/master/common/head_tag.html

Keine Sorge; ich werde sehen, was ich später für Sie tun kann (ich muss jetzt schlafen). :smile:

1 „Gefällt mir“

Danke! (Entschuldigung, dass der Code ein Chaos ist, ich wollte nur, dass er funktioniert, als ich ihn das letzte Mal angefasst habe :sweat_smile:)

1 „Gefällt mir“

Ich stecke hier etwas fest. Ich habe eine Benachrichtigung für meine Komponente erhalten, die in der HEAD-Datei meines Themes verwendet wird. Ich bin mir nicht sicher, wie ich sie mit api.renderInOutlet neu schreiben soll.

  const ajax = require('discourse/lib/ajax').ajax;
  const Topic = require('discourse/models/topic').default;
  // Wir verwenden ajax und das Topic-Modell von Discourse

  api.registerConnectorClass('above-main-container', 'featured-topics', {
    // above-main-container ist der Plugin-Outlet,
    // featured-topics ist der Name Ihrer benutzerdefinierten Komponente

    setupComponent(args, component) {

   // der Rest des Codes folgt

Ich glaube, ich habe versucht, api.registerConnectorClass durch api.renderInOutlet zu ersetzen, aber es hat nicht funktioniert. Ich bin hier kein Experte für Theme-Coding. Danke für jede Hilfe.

Sie können hier ein Beispiel sehen:

StatBanner ist eine native Klasse, die im Verzeichnis components definiert ist:

In Ihrem Fall wäre das api.renderInOutlet("above-main-container", YourClass)

Ich glaube nicht, dass Sie das in der HEAD-Datei tun können. Sie sollten Ihren Code in mehrere Dateien aufteilen.

Ich ermutige Sie, Discourse Theme CLI zu verwenden, da die Entwicklung einer Theme-Komponente damit viel einfacher ist!

Ist Ihre Theme-Komponente öffentlich?

3 „Gefällt mir“

Gibt es eine Möglichkeit, den aktuellen Auslass in meiner .js-Datei zu erhalten?

Ich glaube nicht, dass das möglich ist.

Früher war es möglich, parentView in Classic Components zu inspizieren.

Aber diese Eigenschaft wurde als veraltet markiert.

1 „Gefällt mir“

Was meinen Sie mit „den aktuellen Auslass erhalten“? Meinen Sie den Namen des Auslasses? Oder etwas anderes?

Ich habe eine Lösung für mein Problem gefunden (hier), aber sie sah ungefähr so aus:

  • Erstellen Sie .hbs- und .js-Dateien für 3 verschiedene Auslässe.
  • Überprüfen Sie in jeder JS-Datei, ob der verwendete Auslass dem Wert der Einstellung banner_location entspricht.
  • Wenn ja, zeigen Sie das Banner an. Wenn nicht, blenden Sie das Banner aus.
1 „Gefällt mir“

Cool! Es sieht so aus, als ob Sie sich für die Verwendung von api.renderInOutlet mit einem dynamischen Wert für den Outlet-Namen entschieden haben :chefs_kiss:

1 „Gefällt mir“