Klebender Benutzerkarteninhalt Thema

Ich habe ein ausgearbeiteteres Theme für user-card-contents erstellt und einige benutzerdefinierte Felder hinzugefügt. Ich möchte die Karte sticky machen und mit einer Schließen-Schaltfläche versehen – ähnlich wie bei der Komponente „Neues Thema erstellen".

Habe ich recht mit der Annahme, dass ich user-card-contents.js überschreiben muss, um den Aufruf zum Schließen des Elements zu verhindern? Könnte ich dies im Theme verpacken?

Vielen Dank!

Hey Pete. Willkommen bei Meta :wave:

Die kurze Antwort lautet ja. Wenn deine Änderungen nur das Frontend betreffen, können sie in einem Theme oder einer Komponente umgesetzt werden.

Könntest du bitte genauer beschreiben, was du mit „sticky

Danke für die Begrüßung, Joe!

[quote=“Johani, post:2, topic:159875”]
Könntest du bitte genauer erklären, was du mit „sticky

TL;DR Ich denke, das ist das, wonach du suchst

Theme JS

api.modifyClass("component:user-card-contents", {
  didInsertElement() {
    this._super(...arguments);
    $("html").off(this.clickOutsideEventName);
  },
  actions: {
    closeCard() {
      this._close();
    }
  }
});

Und füge dies dann irgendwo in deine Vorlage ein:

{{d-button
  class="btn-flat"
  action=(action "closeCard")
  icon="times"
}}

Die ausführliche Version

Du kennst das wahrscheinlich schon größtenteils, da du bereits an deinem Theme gearbeitet hast, aber ich werde versuchen, es für ein breiteres Publikum etwas detaillierter zu erklären.

Das Erste, was ich tun würde, ist eine Suche entweder lokal oder auf GitHub. Auf GitHub würdest du so etwas wie dieses Ergebnis erhalten. In den meisten Fällen hat ein Suchbegriff mehr als ein Ergebnis, und du müsstest entweder spezifischer werden oder die Ergebnisse manuell durchsuchen, um etwas zu finden, das dem Gesuchten nahe kommt.

So landen wir schließlich in dieser Datei:

discourse/app/assets/javascripts/discourse/app/mixins/card-contents-base.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

Diese Datei ist ein Mixin. Warum erwähne ich das? Weil du dir bewusst sein musst, dass Mixins an vielen verschiedenen Stellen verwendet werden können. In diesem Fall wird es sowohl für Benutzerkarten als auch für Gruppenkarten verwendet. Daher werden die Änderungen, die du hier vornimmst, sich auf beide auswirken.

Wenn du in der Datei suchst, wirst du feststellen, dass clickOutsideEventName hier erstmals definiert wird:

discourse/app/assets/javascripts/discourse/app/mixins/card-contents-base.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

dann hier als Eigenschaft weitergegeben wird:

discourse/app/assets/javascripts/discourse/app/mixins/card-contents-base.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

und schließlich hier verwendet wird:

discourse/app/assets/javascripts/discourse/app/mixins/card-contents-base.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

Cool, aber was bedeutet all das? Nun, wenn du dir ansiehst, wo all dieser Code hinzugefügt wird, wirst du feststellen, dass er sich innerhalb von didInsertElement befindet:

discourse/app/assets/javascripts/discourse/app/mixins/card-contents-base.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

Die Ember-Anleitungen besagen:

Ember garantiert, dass zum Zeitpunkt des Aufrufs von didInsertElement():

  1. Das Element der Komponente sowohl erstellt als auch in das DOM eingefügt wurde.
  2. Das Element der Komponente über die Eigenschaft this.element der Komponente zugänglich ist.

Warum brauchen wir das? Weil wir einen anderen Mousedown-Handler für Benutzerkarten und Gruppenkarten benötigen. Wenn wir einen Schritt zurückgehen, wirst du nun feststellen, dass die ID des Elements in clickOutsideEventName verwendet wird:

Wie oben besprochen, wird dies dann als Eigenschaft weitergegeben.

Nun kommen wir dazu, wie all dies mit dem zusammenhängt, was du tust.

Du versuchst zu verhindern, dass die Karten geschlossen werden, wenn der Benutzer außerhalb von ihnen klickt. Versuchen wir also herauszufinden, wie das möglich ist. Wenn du dich erinnerst, haben wir besprochen, wie clickOutsideEventName schließlich hier verwendet wird:

Kurz gesagt, dies fügt dem HTML-Element einen Mousedown-Handler hinzu, wenn eine Karte eingefügt wird (beim ersten Seitenaufruf). Wir prüfen dann das Ziel des Mousedown-Ereignisses. Wenn das Ziel irgendwo in der Karte liegt, brechen wir ab. Liegt es außerhalb der Karte, schließen wir sie durch Aufruf von this._close().

_close() ist hier definiert:

discourse/app/assets/javascripts/discourse/app/mixins/card-contents-base.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

Und das ist das, was du aufrufen musst, wenn du deine Schließen-Schaltfläche hinzufügst – aber wir kommen später darauf zurück.

Das Ziel ist es nun, diesen Mousedown-Handler zu entfernen, wenn du möchtest, dass Klicks außerhalb der Karte diese nicht schließen. Wie machen wir das? Nun, wir müssen didInsertElement() ändern, und so kann das geschehen.

Discourse-Themes haben Zugriff auf die Plugin-API, die viele Methoden enthält, die du verwenden kannst. Es gibt hier etwas mehr Details zu den am häufigsten verwendeten Methoden: hier.

Wenn du dich erinnerst, ist die Datei, an der wir arbeiten, ein Mixin. Ein Mixin ist eine Ember-Klasse. Die Methode, die wir verwenden werden, ist also modifyClass:

https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/app/lib/plugin-api.js#L121-L124

Wenn du modifyClass verwendest, kannst du eine Klassenmethode hinzufügen, ändern oder vollständig überschreiben. Wir konzentrieren uns auf das Ändern einer Methode, da das genau das ist, was du tun möchtest.

Wir möchten didInsertElement() ändern, also können wir so etwas tun:

api.modifyClass('mixin:card-contents-base', {
  didInsertElement() {
    console.log("foo");
  }
});

und es ausprobieren…

naja, das hat nicht funktioniert.

Warum ist das so? Es stellt sich heraus, dass die modifyClass-Methode derzeit keine Änderungen an Mixins unterstützt (soweit ich es getestet habe). Ich werde mir notieren, warum das der Fall ist, und prüfen, ob wir das beheben können. Für den Moment kommen wir jedoch zurück zu dem, was du tun möchtest.

Nun, wir können Mixins nicht ändern, also sind wir stuck, oder? Nein. Lassen wir uns ein bisschen tiefer ein.

Wie ich bereits erwähnt habe, wird dieses Mixin sowohl von der Benutzer-user-card-contents als auch von der group-card-contents Ember-Komponente verwendet (wiederum, weil Mixins darauf ausgelegt sind, Code wiederverwendbar zu machen).

Schauen wir uns also die user-card-contents-Komponente hier an:

discourse/app/assets/javascripts/discourse/app/components/user-card-contents.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

Wenn du genau liest, wirst du feststellen, dass wir zuerst das oben besprochene Mixin hier importieren

und dann eine neue Ember-Komponente erstellen und das Mixin an sie übergeben.

Was bedeutet das? Es bedeutet, dass didInsertElement() für user-card-contents tatsächlich vom Mixin geerbt wird. Dasselbe gilt für group-card-contents.

Wo stehen wir nun? Nun, es gibt gute und schlechte Nachrichten. Die gute Nachricht ist, dass du Änderungen an user-card-contents vornehmen kannst, ohne group-card-contents zu beeinflussen! Die schlechte Nachricht ist, dass du, wenn du möchtest, dass deine Änderungen auf beide angewendet werden, etwas Code duplizieren musst.

Kommen wir zurück zu modifyClass und versuchen es erneut mit user-card-contents. Also so etwas:

api.modifyClass('component:user-card-contents', {
  didInsertElement() {
    console.log("foo");
  }
});

und sehen, was passiert…

voalá :tada:

Die Änderung wurde registriert und wir können sie in der Konsole sehen, aber wir sind noch nicht ganz dort.

Also, da wir nun didInsertElement() ändern können, versuchen wir zurück zu dem zu kommen, was du tun möchtest. Wenn du dich erinnerst, ist der Mousedown-Handler hier in didInsertElement definiert:

discourse/app/assets/javascripts/discourse/app/mixins/card-contents-base.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

Was können wir also tun? Nun, es ist so einfach:

$("html").off(clickOutsideEventName)

Wird das funktionieren? Nein. Wenn du das tust:

api.modifyClass('component:user-card-contents', {
  didInsertElement() {
    $("html").off(clickOutsideEventName)
  }
});

wirst du etwas kaputt machen. Warum? Weil das keine Änderung der Methode ist. Das ist eine vollständige Überschreibung der Kernmethode didInsertElement() für diese Komponente. Wenn du das tust, wird also kein Code aus dem Kern tatsächlich angewendet.

Wie beheben wir das? Es stellt sich heraus, dass Ember etwas namens this._super(...arguments) hat.

Was macht das? Es ermöglicht dir, Code hinzuzufügen oder voranzustellen, zusätzlich zu dem, was die Klassenmethode bereits hat. Wenn du zum Beispiel das tust:

api.modifyClass('component:user-card-contents', {
  didInsertElement() {
    // Code, den du hinzufügen möchtest
    $("html").off(clickOutsideEventName);
    // Code aus dem Kern
    this._super(...arguments);
  }
});

dann wird der Code, den du hinzufügen möchtest, vor allem anderen ausgeführt, was hier passiert:

discourse/app/assets/javascripts/discourse/app/mixins/card-contents-base.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

Wenn du jedoch das tust:

api.modifyClass('component:user-card-contents', {
  didInsertElement() {
    // Code aus dem Kern
    this._super(...arguments);
    // Code, den du hinzufügen möchtest
    $("html").off(clickOutsideEventName);
  }
});

dann wird dein Code nach allem ausgeführt, was hier läuft:

discourse/app/assets/javascripts/discourse/app/mixins/card-contents-base.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

Das ist eine großartige Möglichkeit, dein Theme widerstandsfähig gegen Änderungen im Kern zu halten, da alles im Kern zuerst ausgeführt wird und dein Code danach.

Versuchen wir es also erneut und sehen, was passiert:

api.modifyClass('component:user-card-contents', {
  didInsertElement() {
    // Code aus dem Kern
    this._super(...arguments);
    // Code, den du hinzufügen möchtest
    $("html").off(clickOutsideEventName);
  }
});

und…

Warum passiert das? Es liegt an einem unterschiedlichen Code-Kontext. In der Ember-Komponentendatei ist clickOutsideEventName bereits definiert, wenn es verwendet wird. Dein Theme befindet sich in einer anderen Datei, daher ist clickOutsideEventName dort nicht definiert.

Wie beheben wir das? Erinnerst du dich daran?

clickOutsideEventName ist eine Eigenschaft der Komponente. Wenn du also this.clickOutsideEventName verwendest, sollte es funktionieren. Probieren wir das aus.

api.modifyClass('component:user-card-contents', {
  didInsertElement() {
    // Code aus dem Kern
    this._super(...arguments);
    // Code, den du hinzufügen möchtest
    $("html").off(this.clickOutsideEventName);
  },
});

Und tatsächlich funktioniert es :tada:

Die Benutzerkarten öffnen sich nun ohne Fehler, und das Klicken irgendwo außerhalb bewirkt nichts.

Du kannst genau dasselbe für Gruppenkarten tun, wie folgt:

api.modifyClass('component:group-card-contents', {
  didInsertElement() {
    // Code aus dem Kern
    this._super(...arguments);
    // Code, den du hinzufügen möchtest
    $("html").off(this.clickOutsideEventName);
  },
});

Das einzige, was noch fehlt, ist, die Schließen-Schaltfläche mit der _close()-Methode zu verbinden, die wir früher besprochen haben. Dafür gibt es drei Schritte:

  1. Eine closeCard-Aktion hinzufügen (du kannst sie wie immer benennen)
  2. Eine Schaltfläche zur Vorlage der Benutzerkarte hinzufügen
  3. Diese Aktion ausführen, wenn auf die Schaltfläche geklickt wird.

Ich bleibe der Einfachheit halber bei den Benutzerkarten. Also fügen wir dies hinzu (zusammen mit dem, was wir bereits besprochen haben):

api.modifyClass("component:user-card-contents", {
  didInsertElement() {
    this._super(...arguments);
    $("html").off(this.clickOutsideEventName);
  },
  // Neues
  actions: {
    closeCard() {
      this._close();
    }
  }
});

Das einzige, was das tut, ist, die Kernmethode _close() aufzurufen, wenn die benutzerdefinierte Aktion closeCard ausgelöst wird.

Als Nächstes müssen wir eine Schaltfläche zur Vorlage der Benutzerkarte hinzufügen, oder so etwas:

{{d-button
  class="btn-flat"
  action=(action "closeCard")
  icon="times"
}}

Meine groben Ergebnisse sehen so aus:

Und natürlich kannst du Ähnliches für Gruppenkarten tun, wie ich oben erwähnt habe.

Ich lasse die Implementierung für Gruppenkarten und Mobilgeräte als Übung für dich, da du im Wesentlichen genau dieselben Konzepte verwenden würdest, die wir oben besprochen haben, wenn du Änderungen an diesen vornehmen möchtest. Bitte lass mich jedoch wissen, wenn du auf Probleme stößt.

Vielen Dank, das war ein großartiges Tutorial! Ich schätze das wirklich. Mir war die Plugin-API noch nicht bewusst.

Etwas, das oben nicht sofort offensichtlich war, ist, die JavaScript-Änderungen des Themes in Script-Tags zu packen und sie in die Datei common/head_tag.html des Plugins einzufügen:

<script type="text/discourse-plugin" version="0.2">
</script>

Nur aus Neugier: Spielt die Versionsnummer im Tag hier eine Rolle? Und ist es immer am besten, diese in head_tag.html statt in header.html zu platzieren, oder macht das keinen großen Unterschied?

Danke!

@Johani Ich kann gerne einen neuen Thread dafür erstellen, falls du das bevorzugst. Ich versuche jedoch, den clientseitigen user-card-Controller ausfindig zu machen, der:

  • beim ersten Klick die user-card lädt
  • und beim zweiten Klick zur user-summary weiterleitet.

Mein Ziel ist es, die Funktion für den zweiten Klick zu entfernen. Danke!