Wie man benutzerdefinierte Felder zu Modellen hinzufügt

Dies ist eine Sammlung von Bildungs-Plugins, die zeigen, wie man benutzerdefinierte Felder zu verschiedenen Modellen in Discourse hinzufügt. Sie dienen als Lernwerkzeuge für alle, die lernen möchten, wie man Discourse-Plugins entwickelt.

GitHub-Mark So fügt man ein benutzerdefiniertes Feld zu einem Thema hinzu
GitHub-Mark So fügt man ein benutzerdefiniertes Feld zu einer Kategorie hinzu

Für wen sind sie gedacht?

Diese Plugins richten sich an Personen, die mehr über die Erstellung von Discourse-Plugins erfahren möchten. Bevor du mit diesen Plugins beginnst, solltest du den Anfängerleitfaden zur Erstellung von Discourse-Plugins durchgearbeitet haben.

Du könntest diese Plugins zwar nutzen, um benutzerdefinierte Felder auf deiner Discourse-Instanz hinzuzufügen, jedoch müsstest du dafür den Code dennoch leicht anpassen. Sie sind nicht für den Plug-and-Play-Einsatz auf einem Live-Server konzipiert.

Wie sie funktionieren

Neben dem funktionierenden Code enthält jedes Plugin eine schrittweise Beschreibung dessen, was der Code tut, in Form von Kommentaren. Zum Beispiel:

## 
# type:        step
# number:      1
# title:       Register the field
# description: Wo wir Discourse mitteilen, welche Art von Feld wir hinzufügen.
#              Du kannst ein String-, Integer-, Boolean- oder JSON-Feld registrieren.
# references:  lib/plugins/instance.rb,
#              app/models/concerns/has_custom_fields.rb
##
register_topic_custom_field_type(FIELD_NAME, FIELD_TYPE.to_sym)

Hoffentlich sind die Schritte und Kommentare selbsterklärend. Die references dienen dazu, dir zu zeigen, wo du nachschauen kannst, wenn du mehr erfahren möchtest.

Lass es mich wissen, wenn du sie nützlich findest, wenn etwas nicht funktioniert oder die Hinweise unklar sind :slight_smile:

28 „Gefällt mir“

Danke für das Erstellen des Plugins für uns.
Ich habe diesen Fehler nach der Installation des Plugins:

Oops
Die Software, die dieses Diskussionsforum betreibt, ist auf ein unerwartetes Problem gestoßen. Wir entschuldigen uns für die Unannehmlichkeiten.
Detaillierte Informationen über den Fehler wurden protokolliert und eine automatische Benachrichtigung generiert. Wir werden uns das ansehen.
Es sind keine weiteren Maßnahmen erforderlich. Sollte der Fehler jedoch weiterhin auftreten, können Sie weitere Details bereitstellen, einschließlich der Schritte zur Reproduktion des Fehlers, indem Sie ein Diskussionsthema in der Feedback-Kategorie der Website posten.

Hey, läuft das in deiner lokalen Entwicklungsumgebung? Wenn ja, könntest du mir deine Entwicklungs-Logs per PN schicken? Wenn deine Dev-Umgebung korrekt eingerichtet ist, funktioniert dieses Plugin.

1 „Gefällt mir“

Ich habe es gelesen, aber ich kann es nicht verstehen. Könntest du dir bitte Zeit nehmen und ein Video-Tutorial erstellen, das die Installation dieses Plugins von der Einrichtung einer lokalen Entwicklungsumgebung bis zum schrittweisen Hinzufügen eines benutzerdefinierten Feldes erklärt? Der Zugang zu diesem Plugin ist sehr schwierig.

Falls möglich, könntest du die Suchfunktion für das Feldtyp „Zahl

Diese Ressource, die @angus zusammengestellt hat, ist noch hilfreicher, als ich zunächst dachte.

Nicht nur ist der Code zum Hinzufügen eines benutzerdefinierten Feldes mit klaren Erklärungen vorhanden, sondern ein Großteil davon kann direkt in jedes Plugin integriert werden, da der Code hauptsächlich Variablen wie FIELD_NAME und FIELD_VALUE verwendet, die Sie in plugin/config/settings.yml definieren können (stellen Sie außerdem sicher, dass Ihre Plugin-Dateistruktur der im GitHub-Code von @angus bereitgestellten entspricht). Das Durchgehen des Codes hat mir auch ein besseres Verständnis einiger Discourse-Funktionen und -Methoden vermittelt, die ich zwar schon gesehen, aber bis jetzt nie wirklich verstanden habe.

Bisher funktioniert der Code hervorragend, um benutzerdefinierte Themenfelder zu erstellen und zu speichern. Es haben sich jedoch zwei Fragen ergeben:

  1. Fehler in der Themenliste: Es scheint einen Fehler zu geben, wenn ich versuche, eine Kategorienliste (d. h. eine Themenliste für eine Kategorie) zu laden, in der Themen erstellt wurden, bevor das benutzerdefinierte Feld hinzugefügt wurde. Es wird die Ausnahmeseite angezeigt, und dieser Fehler wird aufgelistet:
    Attempting to access a non preloaded custom field, this is disallowed to prevent N+1 queries. Was ist der empfohlene Weg, um dieses Problem zu lösen?

  2. Gibt es eine Möglichkeit, das benutzerdefinierte Feld nur für Themen in bestimmten Kategorien anzuwenden? Nehmen wir also an, ich habe Kategorie 1, Kategorie 2 und Kategorie 3, und ich möchte, dass die Eingabe für das benutzerdefinierte Feld nur angezeigt wird und das Feld nur gespeichert wird, wenn das Thema zur Kategorie 3 gehört. Gibt es eine Möglichkeit, das zu tun?

1 „Gefällt mir“

Wir (Pavilion) werden in Zukunft etwas Ähnliches tun, aber vorerst gibt es nur den Code und die Schritte. Falls du bei einem bestimmten Problem feststeckst, erstelle einen Beitrag im #dev-Bereich und beschreibe das Problem im Detail.

Ich habe dem Thema „Benutzerdefinierte Felder für Themen

2 „Gefällt mir“

Das ist fantastisch, @angus. Vielen Dank.

Ein Video ist immer schön – ich bin grundsätzlich dafür, Dinge so einfach wie möglich zu gestalten. Ich bin jedoch nicht der Meinung, dass ein Video erforderlich ist, um den Schlüsselwert aus diesen von @angus zusammengestellten Ressourcen zu erhalten. Diese Ressourcen liefern den Code, den du benötigst, um das spezifische Ziel der jeweiligen Ressource zu erreichen (ein funktionierendes benutzerdefiniertes Themenfeld oder ein benutzerdefiniertes Kategorienfeld). Ein Video würde wahrscheinlich nur @angus oder jemand anderem zeigen, wie man die Ressource implementiert, aber das ist unkompliziert, und wir können dies wahrscheinlich einfach hier darlegen.

Um es klarzustellen: Diese Ressourcen sind keine Plugins, die du einfach als Plug-and-Play zu deiner Site hinzufügst, um dein Forum anzupassen. Stattdessen vermitteln sie dir effizient das Verständnis, das du benötigst, um deine eigenen benutzerdefinierten Felder in deinem Plugin zu programmieren.


So habe ich diese Ressourcen verwendet:

Du musst den Namen und den Typ des gewünschten Feldes in config/settings hinzufügen. Der Code in diesen Ressourcen verwendet Variablen, die dort definiert sind. Du musst den Code also danach kaum noch anpassen, damit er in deinem eigenen Plugin funktioniert – die Variablen in plugin.rb und anderswo verweisen auf config/settings und sollten dann funktionieren.

Nachdem du config/settings aktualisiert hast, kannst du einfach dem Code folgen und ihn zu deinem Plugin hinzufügen:

  • Beginne mit dem Code in plugin.rb und füge ihn in die plugin.rb deines eigenen Plugins ein, um das benutzerdefinierte Feld zu erstellen.

  • Gehe dann zum Initializer (unter assets/javascripts/discourse/[custom-field-initializer]), um den Code zu erhalten, der das benutzerdefinierte Feld initialisiert und dir erlaubt, es auf dem Server zu speichern.

  • Erstelle dann das Formular in der View-Schicht, wo der Benutzer (oder deine App, falls die App das Feld automatisch hinzufügt) den Wert für das benutzerdefinierte Feld eingeben kann, hier (assets/discourse/connectors/[plugin-outlet-name]/[deine spezielle Vorlage].hbs).

  • @angus hat dies so eingerichtet, dass du die Formulare für die benutzerdefinierten Felder in einem Plugin-Outlet hinzufügst, das in die Discourse-Vorlage eingefügt wird. Die Einstellungen für dieses Formular findest du hier (assets/javascripts/discourse/lib/[name-des-benutzerdefinierten-felds].js.es6), sodass du dies wahrscheinlich auch anpassen möchtest, damit das Formular funktioniert.

@angus, bitte korrigiere gerne alles, was ich hier gesagt habe.

Sobald ich durch die oben genannten Schritte ein Gefühl dafür bekommen hatte, wie man ein benutzerdefiniertes Feld einrichtet, begann ich, die Dinge etwas weiter anzupassen (zum Beispiel, indem ich kreativer wurde, wie das Formular funktioniert). Dies war jedoch ein äußerst hilfreicher Ausgangspunkt, der mir Stunden an Arbeit erspart hat.

Nachdem ich mich damit beschäftigt hatte, hatte ich einige Fragen (wie ich bereits früher gefragt habe), aber Antworten in Development zu erhalten scheint der hilfreichste Weg zu sein, um von dort aus voranzukommen.

3 „Gefällt mir“

Tolle Beschreibung! Ja, genau dafür sind sie gedacht :+1:

1 „Gefällt mir“

Edit: Ich habe meine Frage zur Abfrage von Elementen basierend auf einem benutzerdefinierten Feld ursprünglich hier gepostet, habe mich aber entschieden, dass die Frage sich ausreichend unterscheidet, um einen eigenen Beitrag zu rechtfertigen. Daher habe ich sie hier separat gepostet.

2 „Gefällt mir“

Ich erlebe ein seltsames Verhalten des Composers nach dem Beispiel für benutzerdefinierte Themenfelder.

Wenn ich auf den Button „Thema erstellen“ klicke (zum Beispiel auf der Kategorie-Ansicht – aber überall auf der Website), öffnet sich der Composer nicht, und ich erhalte diesen Fehler:

Uncaught Error: Assertion Failed: The key provided to set must be a string or number, you passed undefined
    at assert (index.js:172)
    at set (index.js:2802)
    at Class.set (observable.js:176)
    at composer.js:769
    at Array.forEach (<anonymous>)
    at Class.open (composer.js:768)
    at composer.js:898
    at invokeCallback (rsvp.js:493)
    at rsvp.js:558
    at rsvp.js:19

Ich habe diesen Fehler erstmals bemerkt, als ich versuchte, einen neuen „Thema erstellen“-Button auf einer neuen Seite hinzuzufügen. Seitdem tritt der Fehler jedoch weiterhin auf, selbst wenn ich diesen neuen Button und jeglichen damit verbundenen Code entfernt habe.

Ich vermute, dass der folgende Code aus dem topic-custom-field-initializer das Problem verursacht:

api.serializeOnCreate(fieldName);
api.serializeToDraft(fieldName);
api.serializeToTopic(fieldName, `topic.${fieldName}`);

Wenn ich diesen Code entferne, funktionieren die „Thema erstellen“-Buttons wieder (der Composer öffnet sich ordnungsgemäß). Sobald ich den Code jedoch wieder hinzufüge, taucht der Composer-Fehler erneut auf.

Dieser Code war zuvor in meinem Plugin ohne Probleme enthalten. Jetzt verursacht er jedoch den Composer-Fehler – selbst wenn ich jeglichen mit dem Composer oder „Thema erstellen“-Buttons verbundenen Code in meinem Plugin entfernt habe.

Natürlich ist dieser Code wichtig, da er das benutzerdefinierte Feld serialisiert. Es scheint jedoch, als würde er mit dem Composer kollidieren. Haben Sie eine Idee, wie man das Problem lösen kann?

Ich habe die Ursache herausgefunden: Ich habe versucht, zwei separate benutzerdefinierte Felder zum Initialisierer für benutzerdefinierte Themenfelder hinzuzufügen. Aus irgendeinem Grund hat das zu Störungen geführt. Es gibt wahrscheinlich eine Möglichkeit, zwei benutzerdefinierte Felder ordnungsgemäß in diese Datei einzufügen, aber mein Code, der denselben Code für zwei separate benutzerdefinierte Felder wiederholte, hat Probleme verursacht. Als ich das zweite benutzerdefinierte Feld aus dieser Datei entfernte, funktionierte es wieder einwandfrei.

Müsste jedes Feld mit diesem Skeleton-Code ein eigenständiges Plugin sein, wenn ich mehrere Felder hinzufügen wollte?

Ich freue mich sehr, dieses Tutorial gefunden zu haben, und frage mich, wie viel Anpassung ich an diesen Vorlagen vornehmen müsste, damit sie für benutzerdefinierte Benutzerfelder funktionieren.

Nein, Sie müssen nur zusätzlichen Code für das zusätzliche Feld hinzufügen. In den meisten Fällen durch einfaches Duplizieren des vorhandenen Codes, z. B.

add_preloaded_topic_list_custom_field(FIELD_NAME_1)
add_preloaded_topic_list_custom_field(FIELD_NAME_2)

Der erste Anlaufpunkt für benutzerdefinierte Benutzerfelder ist /admin/customize/user_fields, das Ihnen eine Benutzeroberfläche zum Hinzufügen bietet. Wenn Sie eine detailliertere Kontrolle wünschen, ähnelt der Prozess dem für Themen und Kategorien, aber Sie benötigen keine Frontend-Elemente für Benutzerfelder.

Tatsächlich denken wir (Pavilion) darüber nach, ein Plugin für benutzerdefinierte Felder zu entwickeln (analog zu ACF für WordPress), das zunächst der Benutzeroberfläche für benutzerdefinierte Felder im Custom Wizard Plugin ähneln würde.

Tatsächlich verwenden einige Leute bereits das Custom Wizard Plugin als Manager für benutzerdefinierte Felder. Es listet alle benutzerdefinierten Felder auf Ihrer Instanz (aus beliebiger Quelle) auf und ermöglicht Ihnen, ein Feld eines beliebigen Typs zu jedem Modell hinzuzufügen, das sie unterstützt.

Es fügt keine Frontend-Unterstützung hinzu, z. B. wie im Bildungs-Plugin für benutzerdefinierte Themenfelder gezeigt (und das würde im Kontext des Custom Wizard Plugins nicht funktionieren), weshalb wir darüber nachdenken, dies in ein separates Plugin auszulagern.

3 „Gefällt mir“

@angus, ich glaube, das würde mir sehr gefallen.

Besonders, wenn es die Frontend-Unterstützung hinzufügt.

Ich hätte gerne eine einfache Möglichkeit für Administratoren, benutzerdefinierte Felder zu verschiedenen Klassen hinzuzufügen, Benutzern die Eingabe zu ermöglichen (z. B. für Themen, Beiträge, Benutzerprofile) und eine Möglichkeit für das Frontend, diese anzuzeigen.

Das Hauptproblem, das ich mit den aktuellen benutzerdefinierten Benutzerfeldern nicht lösen kann, ist die Vielfalt der Feldtypen. Derzeit ist die Auswahl auf 4 beschränkt, glaube ich, und ich hätte gerne die Optionen, die mit dem Custom Wizards Plugin verfügbar sind.

Idealerweise möchte ich ein ziemlich fortschrittliches, durchsuchbares/filterbares/sortierbares Benutzerverzeichnis mit vielen benutzerdefinierten Feldern verschiedener Typen erstellen. Ich werde mit Custom Wizards experimentieren, um zu sehen, ob es vorerst funktioniert, und hoffe, dass Sie in das Custom Fields Plugin investieren werden.

Danke!

@angus

Zuerst einmal vielen Dank für dieses Plugin.

Haben Sie vielleicht ein funktionierendes Beispiel dieses Plugins (benutzerdefiniertes Feld zu Thema), um mehrere benutzerdefinierte Felder zu berücksichtigen? Ich konnte erfolgreich ein benutzerdefiniertes Feld hinzufügen und ein paar Modifikationen ohne Probleme vornehmen.

Ich habe versucht, den Code zu duplizieren, zu modifizieren und ein zusätzliches Plugin hinzuzufügen usw.

Hat jemand ein Code-Repository oder ein Beispiel, das er mit mir teilen möchte? Jede Hilfe wäre sehr dankbar.

1 „Gefällt mir“

Hallo @Joe_Stanton,

Ich habe das schon ein paar Mal gemacht und die Art und Weise, wie ich es gemacht habe, war, die benutzerdefinierten Felder in einem Array mit einem Objekt mit einem Namen und Typ für das benutzerdefinierte Feld zu speichern.

Zum Beispiel:

fields = [
  { name: 'isClassifiedListing', type: 'boolean' },
  { name: 'listingStatus', type: 'string' },
  { name: "listingDetails", type: 'json' }
]

Dann durchlaufe ich die Felder, um die Logik anzuwenden, die in diesem Thema erwähnt wird. Sie können ein Beispiel dafür sehen, wie es in einem Plugin verwendet wird, an dem ich arbeite hier. Der relevante Code ist jedoch unten aufgeführt.

Beispiel

Auf der Serverseite:

  # Registrierung benutzerdefinierter Felder
  fields.each do |field|
    # Registriere die Felder
    register_topic_custom_field_type(field[:name], field[:type].to_sym)

    # Getter-Methoden
    add_to_class(:topic, field[:name].to_sym) do
      if !custom_fields[field[:name]].nil?
        custom_fields[field[:name]]
      else
        nil
      end
    end

    # Setter-Methoden
    add_to_class(:topic, "#{field[:name]}=") do |value|
      custom_fields[field[:name]] = value
    end

    # Aktualisierung bei Themen-Erstellung
    on(:topic_created) do |topic, opts, user|
      topic.send("#{field[:name]}=".to_sym, opts[field[:name].to_sym])
      topic.save!
    end

    # Aktualisierung bei Themen-Bearbeitung
    PostRevisor.track_topic_field(field[:name].to_sym) do |tc, value|
      tc.record_change(field[:name], tc.topic.send(field[:name]), value)
      tc.topic.send("#{field[:name]}=".to_sym, value.present? ? value : nil)
    end

    # Serialisierung zu Thema
    add_to_serializer(:topic_view, field[:name].to_sym) do
      object.topic.send(field[:name])
    end

    # Vorladen der Felder
    add_preloaded_topic_list_custom_field(field[:name])

    # Serialisierung zur Themenliste
    add_to_serializer(:topic_list_item, field[:name].to_sym) do
      object.send(field[:name])
    end
  end

Ähnlich auf der Clientseite, um die Felder zu serialisieren:


 const CUSTOM_FIELDS = [
  { name: "isClassifiedListing", type: "boolean" },
  { name: "listingStatus", type: "string" },
  { name: "listingDetails", type: "json" },
];

  // Benutzerdefinierte Felder serialisieren:
  CUSTOM_FIELDS.forEach((field) => {
    api.serializeOnCreate(field.name);
    api.serializeToDraft(field.name);
    api.serializeToTopic(field.name, `topic.${field.name}`);
  });
2 „Gefällt mir“

Vielen Dank, @keegan!

Ich glaube, ich habe die Datei plugin.rb richtig eingerichtet, die Schleife sieht korrekt aus.

Ich habe jedoch Schwierigkeiten mit der Datei topic-custom-field-initializer.js. Hier ist mein Code für beide Dateien. Irgendwelche Hinweise für die Initialisierungsdatei initializer.js? Ich glaube, ich bin hier wirklich nah dran. Wenn ich ein neues Thema erstelle, erhalte ich 1 von 3 Feldern, das listingDetails-Feld, aber die Felder isClassifiedListing und listingStatus fehlen immer noch.

enabled_site_setting :topic_custom_field_enabled
register_asset 'stylesheets/common.scss'

after_initialize do
  fields = [
  { name: 'isClassifiedListing', type: 'boolean' },
  { name: 'listingStatus', type: 'string' },
  { name: "listingDetails", type: 'json' }
]

 fields.each do |field|

  register_topic_custom_field_type(field[:name], field[:type].to_sym)

   add_to_class(:topic, field[:name].to_sym) do
      if !custom_fields[field[:name]].nil?
        custom_fields[field[:name]]
      else
        nil
      end
    end

   add_to_class(:topic, "#{field[:name]}=") do |value|
      custom_fields[field[:name]] = value
   end

   on(:topic_created) do |topic, opts, user|
      topic.send("#{field[:name]}=".to_sym, opts[field[:name].to_sym])
      topic.save!
   end

    PostRevisor.track_topic_field(field[:name].to_sym) do |tc, value|
      tc.record_change(field[:name], tc.topic.send(field[:name]), value)
      tc.topic.send("#{field[:name]}=".to_sym, value.present? ? value : nil)
    end

    add_to_serializer(:topic_view, field[:name].to_sym) do
      object.topic.send(field[:name])
    end

  add_preloaded_topic_list_custom_field(field[:name])

    # Serialize to the topic list
    add_to_serializer(:topic_list_item, field[:name].to_sym) do
      object.send(field[:name])
    end

end

end

Initializer.js

import { withPluginApi } from 'discourse/lib/plugin-api';
import discourseComputed from "discourse-common/utils/decorators";
import { alias } from '@ember/object/computed';
import { isDefined, fieldInputTypes } from '../lib/topic-custom-field';

export default {
  name: "topic-custom-field-intializer",
  initialize(container) {


    const CUSTOM_FIELDS = [
      { name: "isClassifiedListing", type: "boolean" },
      { name: "listingStatus", type: "string" },
      { name: "listingDetails", type: "json" },
    ];

    CUSTOM_FIELDS.forEach(field => {

    withPluginApi('0.11.2', api => {

      api.registerConnectorClass('composer-fields', 'composer-topic-custom-field-container', {
        setupComponent(attrs, component) {
          const model = attrs.model;

          if (!isDefined(model[field.name]) && model.topic && model.topic[field.name]) {
            model.set(field.name, model.topic[field.name]);
          }

          let props = {
            fieldName: field.name,
            fieldValue: model.get(field.name)
          }
          component.setProperties(Object.assign(props, fieldInputTypes(field.type)));
        },

        actions: {
          onChangeField(fieldValue) {
            this.set(`model.${field.name}`, fieldValue);
          }
        }
      });

      api.registerConnectorClass('edit-topic', 'edit-topic-custom-field-container', {
        setupComponent(attrs, component) {
          const model = attrs.model;

          let props = {
            fieldName: field.name,
            fieldValue: model.get(field.name)
          }
          component.setProperties(Object.assign(props, fieldInputTypes(field.type)));
        },

        actions: {
          onChangeField(fieldValue) {
            this.set(`buffered.${field.name}`, fieldValue);
          }
        }
      });

      api.serializeOnCreate(field.name);
      api.serializeToDraft(field.name);
      api.serializeToTopic(field.name, `topic.${field.name}`);

      api.registerConnectorClass('topic-title', 'topic-title-custom-field-container', {
        setupComponent(attrs, component) {
          const model = attrs.model;
          const controller = container.lookup('controller:topic');

          component.setProperties({
            fieldName: field.name,
            fieldValue: model.get(field.name),
            showField: !controller.get('editingTopic') && isDefined(model.get(field.name))
          });

          controller.addObserver('editingTopic', () => {
            if (this._state === 'destroying') return;
            component.set('showField', !controller.get('editingTopic') && isDefined(model.get(field.name)));
          });

          model.addObserver(field.name, () => {
            if (this._state === 'destroying') return;
            component.set('fieldValue', model.get(field.name));
          });
        }
      });

      api.modifyClass('component:topic-list-item', {
        customFieldName: field.name,
        customFieldValue: alias(`topic.${field.name}`),

        @discourseComputed('customFieldValue')
        showCustomField: (value) => (isDefined(value))
      });

    });


    });
  }
}

1 „Gefällt mir“

Ich habe es nicht getestet, aber ich glaube, der Grund, warum Sie nur 1/3 der Felder sehen, ist, dass es über die benutzerdefinierten Felder schleift und eine nicht eindeutige Connector-Klasse registriert und die vorherige überschreibt.

Im Allgemeinen schlage ich für die Client-Seite vor, anstatt durch die benutzerdefinierten Felder zu schleifen und die API-Methoden zu deklarieren, Komponenten für jedes Feld separat zu definieren oder zumindest separate Aktionen, da Sie wahrscheinlich unterschiedliche Logik für jedes Feld benötigen werden?

Der einzige Teil, über den ich schleifen und deklarieren würde, ist dieser:

  api.serializeOnCreate(field.name);
      api.serializeToDraft(field.name);
      api.serializeToTopic(field.name, `topic.${field.name}`);

Für den Rest der Komponenten ist es wahrscheinlich am besten, für jeden Fall eine separate Logik zu erstellen.

3 „Gefällt mir“

@keegan das hat funktioniert! Vielen Dank für all die Einblicke. Ohne deine Hilfe hätte ich es nicht geschafft.

4 „Gefällt mir“