Klebender Benutzerkarteninhalt Thema

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.