Hintergrundvideo zu bestimmten Benutzerprofilen hinzufügen?

Wenn du einer bestimmten Seite Inhalte hinzufügen möchtest, ist ein Plugin-Outlet deine beste Option. Kurz gesagt: Plugin-Outlets sind in Discourse-Templates reservierte Bereiche, die du nutzen kannst, um neue Inhalte einzufügen.

Als Erstes musst du herausfinden, ob auf der Seite, die du ansprechen möchtest, ein Plugin-Outlet existiert. Dafür gibt es eine Theme-Komponente, die du installieren kannst.

(deprecated) Plugin outlet locations theme component

Sobald du diese Komponente installiert und aktiviert hast, gehe zur gewünschten Seite und prüfe, was dir zur Verfügung steht. In deinem Fall existiert ein solches Plugin-Outlet bereits (grün hervorgehoben)

Das gesuchte Outlet ist also above-user-profile.

Angenommen, es gäbe es nicht… was dann? Die beste Option wäre in diesem Fall, die Hinzufügung zu beantragen oder einen PR einzureichen, um es in den Core aufzunehmen. In den meisten Fällen wird dies angenommen, wenn dein Anwendungsfall sinnvoll ist.

Wie auch immer, wie gesagt, existiert es in diesem Fall bereits. Schauen wir uns also an, wie du Markup hinzufügen kannst. Für den Rest dieses Beitrags brauchst du die oben genannte Komponente nicht mehr, du kannst sie also jetzt deaktivieren, da du bereits den Namen des Plugin-Outlets hast.

Alles, was du tun musst, ist, etwas Ähnliches im Reiter „Header“ deines Themes hinzuzufügen.

<script type="text/x-handlebars" data-template-name="/connectors/OUTLET_NAME/SOME_NAME">
  Dein Markup geht hier rein...
</script>

Du musst OUTLET_NAME durch den Namen des gewünschten Outlets ersetzen. Ändere dann SOME_NAME in den Namen, den du dieser Anpassung geben möchtest. Der Name kann beliebig sein, aber versuche, wenn möglich, ihn beschreibend zu wählen. Das ist gute Praxis. So landen wir bei folgendem Code:

<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  Dein Markup geht hier rein... wie
  <h1>Hallo Welt!!</h1>
</script>

Lass uns das ausprobieren und sehen, was passiert… denk dran, das Snippet oben gehört in den Reiter common > header deines Themes.

Und…

Bisher so gut, aber lass uns tiefer einsteigen.

Du möchtest deine Videos nicht auf jedem Profil anzeigen, sondern nur unter bestimmten Bedingungen. Wie machst du das? Du brauchst dafür zwei Dinge: einige Daten zum Auswerten und ein wenig JavaScript.

Lass uns die Daten finden. Erinnerst du dich, als ich sagte, Plugin-Outlets sind reservierte Bereiche? Was bringt es denn, sie ohne Kontext zu haben? Deshalb übergibt Discourse die relevanten Kontextteile an jedes Plugin-Outlet… aber zuerst einen Schritt zurück. Wenn du folgendes hinzufügst:

<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  Dein Markup geht hier rein, wie...
  <h1>Hallo Welt!!</h1>
</script>

Sieht das zwar wie HTML aus – und die Script-Tags sind es auch –, aber der Inhalt darin wird als Handlebars-Code behandelt.

Das bedeutet, du kannst stattdessen etwas wie Folgendes tun:

<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  {{log this}}
</script>

und prüfe die Browserkonsole. Du siehst dies jedes Mal, wenn das Outlet gerendert wird, also z. B. wenn du auf einer Benutzerseite bist.

Ist das hier hilfreich? Ja… aber im Moment nicht. Wir kommen darauf zurück. Lass uns einen weiteren Schritt zurückgehen und sehen, wie Discourse den Kontext an das Outlet übergibt. Wenn du den Outlet-Namen auf GitHub (oder lokal) suchst, erhältst du dies:

Repository search results · GitHub

Öffne diese Datei. Das Erste, was du siehst, ist diese Zeile:

Schau dir das Ende dieser Zeile an:

args=(hash model=model)

Du siehst, dass Discourse model als Argument an das Outlet übergibt. Für alle praktischen Zwecke und um es einfach zu halten: model = data.

Eines der Argumente für unser Outlet ist also model, und dort befinden sich die gewünschten Daten. Kehren wir also zu unserem Snippet zurück:

<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  {{log this}}
</script>

und ändern wir es stattdessen in folgendes:

<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
-  {{log this}}
+  {{log args}}
</script>

Jetzt erhalten wir dies in der Konsole:

Du kannst durch diese Daten blättern und prüfen, ob sie das enthalten, was du brauchst. Das sollte der Fall sein, da sie alle Daten über den Benutzer enthalten, die auch in anderen Elementen auf dieser Seite verwendet werden. Es ist das „Modell“ für die Benutzerseite dieses bestimmten Benutzers.

Eine der verfügbaren Eigenschaften dort ist… Trommelwirbel :drum: … die Gruppen, denen der Benutzer angehört.

Wenn du also:

{{log args.model.groups}}

dienst, erhältst du alle Gruppen, denen der Benutzer angehört, in der Konsole.

Okay, jetzt haben wir die benötigten Daten, also bleibt nur noch, einige Bedingungen basierend darauf hinzuzufügen.

Du bist vielleicht versucht zu denken, dass wir das im selben Snippet tun können, aber leider können wir das nicht. Handlebars ist eine Template-Sprache. Sie bietet sehr, sehr grundlegende Unterstützung für Logik – nichts weiter als einfache true/false-Bedingungen und Schleifen. Vergleiche und ähnliches sind nicht möglich.

Wo genau kannst du das also tun? In einer Connector-Klasse, klingt fancy… ich weiß.

Kurz gesagt ist eine Connector-Klasse im Wesentlichen ein Stück JavaScript, das an das Outlet gebunden ist. Es ist viel nuancierter als das, aber das ist alles, was du vorerst wirklich wissen musst.

Lass uns also eine erstellen. Wir machen das so:

<script type="text/discourse-plugin" version="0.8">
api.registerConnectorClass('OUTLET_NAME', 'SOME_NAME', {

});
</script>

OUTLET_NAME und SOME_NAME hier sollten dieselben sein wie oben. Ändern wir sie also:

<script type="text/discourse-plugin" version="0.8">
api.registerConnectorClass('above-user-profile', 'add-profile-videos', {

});
</script>

Dieses Snippet gehört ebenfalls in den Reiter common > header deines Themes. Du solltest also jetzt etwas haben, das so aussieht:

<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  {{log args.model.groups}}
</script>

<script type="text/discourse-plugin" version="0.8">
  api.registerConnectorClass('above-user-profile', 'add-profile-videos', {

  });
</script>

Innerhalb unserer Connector-Klasse können wir etwas tun… aber… wir müssen bedenken, dass es nicht einfach eine beliebige JavaScript-Datei ist. Aus Mangel an einer besseren Beschreibung… betrachte es als ein Ember-Komponente auf Diät. Das weiter auszuführen, würde hier den Rahmen sprengen, also machen wir weiter.

Standardmäßig sind vier Methoden damit verbunden:

actions ermöglicht es dir, Aktionen wie folgt zu definieren:

api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  actions: {
    myAction() {
      // etwas tun
    }
  }
});

Du kannst diese Aktion dann innerhalb des Outlets aufrufen, z. B. wenn ein Button gedrückt wird. Wir brauchen das hier nicht, also machen wir weiter.

api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  shouldRender(args, component) {
    // hier true oder false zurückgeben
  }
});

Auch diese werden wir nicht verwenden, da das Outlet nur auf Profilseiten gerendert wird und wir vorerst keine weiteren Anforderungen haben. Du kannst dies jedoch nutzen, um Bedingungen hinzuzufügen, die vor dem Rendern des Outlets geprüft werden sollen. Zum Beispiel die Vertrauensebene des aktuellen Benutzers oder ähnliches. Weiter…

api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  setupComponent(args, component) {
    // etwas tun
  }
});

Das ist die Methode, auf die wir uns konzentrieren wollen. Welche JavaScript-Bedingungen oder Variablen du setzen möchtest, gehören hierhin. Bevor wir tiefer darauf eingehen, decken wir der Vollständigkeit halber zuerst die letzte Methode ab:

api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  teardownComponent(args, component) {
    // etwas tun
  }
});

Dies wird ausgelöst, wenn das Outlet entfernt wird. Du kannst also Aufräumarbeiten durchführen, z. B. Event-Listener entfernen und so weiter.

Okay, zurück zu setupComponent:

api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  setupComponent(args, component) {
    // etwas tun
  }
});

Du siehst, dass zwei Dinge übergeben werden: Erstens args und zweitens component.

args hier ist dasselbe, was wir früher betrachtet haben. Es sind die Kontextdaten, die Discourse an das Outlet übergibt. Wenn du also:

api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  setupComponent(args, component) {
    console.log(args.model.groups);
  }
});

machst, siehst du dieselben Informationen in der Browserkonsole wie zuvor: die Gruppen, denen der Profilinhaber angehört. Hier beginnt der Spaß: Du hast jetzt die Daten und den richtigen Hook. Du kannst also hier alles Mögliche tun. Wenn ich also möchte, dass das Video nur auf den Profilen von Mitgliedern angezeigt wird, die einer bestimmten Gruppe angehören, kann ich Folgendes tun:

  api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
    setupComponent(args, component) {
      const inGroup = [...args.model.groups].filter(g => g.name === TARGET_GROUP)
      const showVideo = inGroup.length ? true : false;

      console.log(showVideo);
    }
  });

Wenn du dies auf einer Profilseite eines Benutzers in der Gruppe staff ausprobierst, wird true in der Konsole ausgegeben. Also bleibt uns nur noch, dies an das Outlet-Template weiterzugeben. So geht’s:

component, das an setupComponent übergeben wird, wird zwischen Connector und Outlet geteilt. Du kannst Dinge an das Outlet übergeben, indem du sie als Eigenschaften auf der Komponente setzt, wie folgt:

  const TARGET_GROUP = "staff"

  api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
    setupComponent(args, component) {
      const inGroup = [...args.model.groups].filter(g => g.name === TARGET_GROUP)
      const showVideo = inGroup.length ? true : false;
-     console.log(showVideo);
+     component.setProperties({showVideo})
    }
  });

Wenn wir nun zurück zum Template gehen und etwas wie folgendes tun:

{{log showVideo}}

wird dasselbe Ergebnis ausgegeben. Wir setzen das also nun in eine Handlebars-Bedingung und fügen unser Markup wie folgt hinzu:

<script
  type="text/x-handlebars"
  data-template-name="/connectors/above-user-profile/add-profile-videos"
>
  {{#if showVideo}}
    <video playsinline autoplay muted loop id="myVideo" poster="[LINK EINFÜGEN]">
      <source src="[LINK EINFÜGEN]" type="video/webm">
      <source src="[LINK EINFÜGEN]" type="video/mp4">
    </video>
  {{/if}}
</script>

Prüfe dann eine Profilseite eines Staff-Benutzers. Du wirst sehen, dass das Video geladen wird.

Sobald du die Profilseite des Staff-Mitglieds verlässt, verschwindet das Video. Das Video wird nicht auf Profilen von Benutzern angezeigt, die nicht der Gruppe staff angehören.

Lass uns das alles zusammenfassen. Das ist dasselbe wie oben.

Hier ist das CSS, das ich verwendet habe. Reiter common > css:

#myVideo {
  position: fixed;
  top: var(--header-offset);
  min-height: 100vh;
  left: 0;
  z-index: -1;
}

.user-content {
  background: none;
}

.user-main {
  padding: 0.5em;
  background: rgba(var(--secondary-rgb), 0.8);
}

// wenn du es auch auf Mobile haben willst
.mobile-view {
  body[class*="user-"] {
    background: none;
    .user-main,
    .user-content {
      padding: 0.5em;
      background: rgba(var(--secondary-rgb), 0.8);
    }
  }
}

HTML / JavaScript / Handlebars. Dies gehört in den Reiter common > header deines Themes:

<script
  type="text/x-handlebars"
  data-template-name="/connectors/above-user-profile/add-profile-videos"
>
  {{#if showVideo}}
    <video playsinline autoplay muted loop id="myVideo" poster="[LINK EINFÜGEN]">
      <source src="[LINK EINFÜGEN]" type="video/webm">
      <source src="[LINK EINFÜGEN]" type="video/mp4">
    </video>
  {{/if}}
</script>

<script type="text/discourse-plugin" version="0.8">
  const TARGET_GROUP = "staff"

  api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
    setupComponent(args, component) {
      const inGroup = [...args.model.groups].filter(g => g.name === TARGET_GROUP)
      const showVideo = inGroup.length ? true : false;
      component.setProperties({showVideo})
    }
  });
</script>

Ändere TARGET_GROUP in den Namen der Gruppe, die du ansprechen möchtest, und füge die src-Attribute für deine Videos hinzu.

Dieser Beitrag war etwas länger… lass dich davon nicht abschrecken. Sobald du das Konzept verstanden hast, kann alles, was wir oben gemacht haben, in weniger als 3–5 Minuten erledigt werden.

Das Schöne daran ist, dass alles, worüber wir gesprochen haben, für jedes Plugin-Outlet im Wesentlichen gleich ist. Einzige Änderung ist der Name. Das gilt also für alle Plugin-Outlet-Anpassungen, die du in Zukunft vornehmen möchtest.

  1. Finde den Outlet-Namen
  2. Hole die Daten
  3. Verarbeite die Daten in einem Connector
  4. Gib die Eigenschaften zurück an das Template