Auf meinem Forum wollte ich den Benutzern einige Anpassungsoptionen für das Standardthema geben. Ich wollte ihnen die Möglichkeit geben, einen Hintergrund für das Forum festzulegen. Ich könnte dafür verschiedene Themen erstellen, aber das ist ziemlich viel zusätzliche Arbeit. Und obwohl Themen Einstellungen haben, sind diese für jeden und nicht speziell für einen Benutzer. Aber es gibt eine Möglichkeit, benutzerspezifische Einstellungen für Themen zu erreichen, und das ist durch das “Missbrauchen” von Benutzerfeldern.
In meinem Thema hat der Benutzer tatsächlich 3 Einstellungen für die Tapete (Bild, Überblendung mit der Hintergrundfarbe und Inhaltstransparenz), aber für diesen Beitrag werde ich mich nur auf eine einzige Einstellung konzentrieren, das Tapetenbild.
Schritt 1: Erstellen des Benutzerfeldes
Der Benutzer hat die Möglichkeit, eine Reihe verschiedener Hintergrundbilder oder gar kein Hintergrundbild auszuwählen. Für das Benutzerfeld habe ich ein optionales Dropdown-Feld mit den folgenden Optionen erstellt:
- keine – kein Hintergrundbild, das ist wichtig
- Standard – wenn der Benutzer keine Auswahl getroffen hat
- Alternative Eins
- Alternative Zwei
Dies ist ein optionales Feld, daher wäre der Standardwert leer. Für einen leeren Wert würde ich gerne das Standard-Hintergrundbild verwenden.
Schritt 2: CSS
In common/common.scss habe ich Folgendes definiert:
:root {
--wallpaper-default: url(#{$img-wallpaper-default});
--wallpaper-alternative-one: url(#{$img-wallpaper-alternative-one});
--wallpaper-alternative-two: url(#{$img-wallpaper-alternative-two});
/* Standardmäßig auf keine gesetzt, um mögliche anfängliche Bildwechsel zu reduzieren */
--wallpaper: none;
}
body {
background-image: var(--wallpaper);
}
Das :root enthält die Variablen für die verschiedenen Hintergrundbilder, die Theme-Assets sind. Das Hintergrundbild ist auf die Variable --wallpaper gesetzt, die anfänglich none ist. Standardmäßig wird also beim Laden des CSS kein Hintergrundbild angezeigt. Dies geschieht, um ein mögliches “Flimmern” zu vermeiden, wenn der Benutzer ein anderes Hintergrundbild als das Original ausgewählt hat.
Jedes wählbare Hintergrundbild erhält eine Variable --wallpaper-xx-yy-zz. Benutzerfeldwerte sind einfacher Text. Basierend auf diesem Wert wähle ich die richtige CSS-Variable als Hintergrundbild aus.
Schritt 3: Theme-Einstellungen
Im JavaScript-Code muss ich das Benutzerfeld kennen, das die Hintergrundbildeinstellung enthält. Der einfachste Weg, dies zu tun, ist, direkt auf die ID des Benutzerfeldes zu verweisen. Sie müssen also eine Theme-Einstellung erstellen, um diese Benutzerfeld-ID zu konfigurieren.
wallpaper_user_field:
default: -1
type: integer
Schritt 4: Festlegen des Hintergrundbildes
Dafür müssen Sie etwas JavaScript schreiben. Da dieser Code so früh wie möglich ausgeführt werden muss, würden Sie einen Initialisierer javascripts/discourse/initializer/your-theme-component.js erstellen.
Beginnen wir mit den Grundlagen:
import { setOwner } from '@ember/owner';
import { withPluginApi } from 'discourse/lib/plugin-api';
const PLUGIN_ID = 'your-theme-component';
class YourThemeWallpaper {
api;
styleRoot;
constructor(owner, api) {
setOwner(this, owner);
this.api = api;
// Ruft das `html`-Element ab
this.styleRoot = document.body.parentElement.style;
this.loadWallpaperSettings();
}
// Weitere Logik wird später hinzugefügt
}
export default {
name: PLUGIN_ID,
initialize(owner) {
withPluginApi('1.34.0', (api) => {
this.instance = new YourThemeWallpaper(owner, api);
});
}
}
Dies ist für den großen Boilerplate-Code, um etwas Theme-Komponenten-JavaScript hinzuzufügen. Die Variable styleRoot sollte sich auf das html-Element des Dokuments beziehen, das wir verwenden werden, um das effektive CSS zu ändern.
Die Funktion loadWallpaperSettings lädt die Benutzereinstellung und sieht ungefähr so aus:
loadWallpaperSettings() {
let user = this.api.getCurrentUser();
if (user) {
this.api.container.lookup('store:main').find('user', user.username).then((user) => {
let wp = user.user_fields[settings.wallpaper_user_field] || '';
this.setWallpaper(wp);
});
} else {
this.setWallpaper('');
}
}
Um die Werte der Benutzerfelder zu erhalten, muss ein zusätzlicher API-Aufruf erfolgen, der eine Weile dauern kann. Sobald dieser zurückkehrt, erhalten wir den Wert des Benutzerfeldes und rufen setWallpaper mit diesem Wert auf. settings.wallpaper_user_field ist ein Verweis auf die Theme-Einstellung aus Schritt #3. Wenn die Einstellung nicht konfiguriert wurde, wird das Feld wp standardmäßig auf einen leeren String gesetzt.
Wenn kein aktueller Benutzer vorhanden ist, rufen wir die Funktion setWallpaper mit einem leeren String auf, um das Standard-Hintergrundbild zu laden.
Die Funktion setWallpaper aktualisiert tatsächlich das Hintergrundbild:
setWallpaper(wallpaper) {
if (!wallpaper || wallpaper === '') {
// Standard verwenden, wenn keine gesetzt wurde
wallpaper = 'default';
}
// Normalisieren Sie den Wert, damit er zur CSS-Variable passt
wallpaper = wallpaper.toLowerCase().replaceAll(/[^a-z0-9]+/g, '-');
if (wallpaper === 'none') {
this.styleRoot.setProperty('--wallpaper', 'none');
} else {
this.styleRoot.setProperty('--wallpaper', 'var(--wallpaper-'+wallpaper+')');
}
}
Wenn die konfigurierte Hintergrundbild-Einstellung "none" war, entfernen wir das Hintergrundbild. Andernfalls wird der Wert normalisiert, um einer CSS-Variable zu ähneln. “Alternative One” führt also dazu, dass die CSS-Variable --wallpaper auf var(--wallpaper-alternative-one) gesetzt wird. Wenn der Wert des Benutzerfeldes keiner CSS-Variable zugeordnet werden kann, führt dies effektiv zu keinem Hintergrundbild, was gut genug ist.
Schritt 5: Schnelleres Laden
Die obige Einrichtung funktioniert hervorragend. Der zusätzliche API-Aufruf kann jedoch dazu führen, dass das Hintergrundbild recht spät gesetzt wird. Das ergibt kein gutes Ergebnis. Um dies zu lösen, nutze ich den Local Storage des Browsers, um die Einstellung zu speichern und abzurufen.
Im Konstruktor, bevor die Hintergrundbildeinstellungen des Benutzers geladen werden, versuchen Sie zuerst, das Hintergrundbild basierend auf Daten aus dem Local Storage festzulegen. Beim Laden der Benutzereinstellungen wird der Local Storage mit dem konfigurierten Wert aktualisiert.
const WALLPAPER_KEY = 'your_theme_wallpaper';
constructor(owner, api) {
setOwner(this, owner);
this.api = api;
// Ruft das `html`-Element ab
this.styleRoot = document.body.parentElement.style;
this.loadLocalStorage();
this.loadWallpaperSettings();
}
loadWallpaperSettings() {
let user = this.api.getCurrentUser();
if (user) {
this.api.container.lookup('store:main').find('user', user.username).then((user) => {
let wp = user.user_fields[settings.wallpaper_user_field] || '';
localStorage.setItem(WALLPAPER_KEY, wp);
this.setWallpaper(wp);
});
} else {
localStorage.removeItem(WALLPAPER_KEY);
this.setWallpaper('');
}
}
loadLocalStorage() {
let data = localStorage.getItem(WALLPAPER_KEY);
if (data) {
this.setWallpaper(data);
}
}
Dies verbessert die frühe Einstellung des Hintergrundbildes beim Laden von Discourse erheblich. Effektiv würde die Funktion setWallpaper zweimal aufgerufen. Da sich der Wert jedoch selten ändert, wird dies nicht bemerkbar sein. Nur wenn der Benutzer das Hintergrundbild geändert hat, würde es beim nächsten Laden flackern. Dies ist auch der Grund, warum das anfängliche Hintergrundbild in CSS auf none anstelle des Standardwerts gesetzt ist.
Schritt 6: Live-Vorschau
Beim Ändern des Farbschemas in Discourse erhalten Sie eine Live-Vorschau dieses neuen Farbschemas, Sie müssen nicht speichern und neu laden, um zu sehen, wie es aussehen würde. Das wäre auch für die Hintergrundbildeinstellungen sehr schön, damit die Benutzer sehen können, welches sie am meisten mögen.
Um dies zu erreichen, benötigen Sie einige zusätzliche Tricks, indem Sie die Komponenten der Benutzerfelder ändern.
Am Ende des Konstruktors habe ich einen Aufruf zu this.setupLivePreview() hinzugefügt, der Folgendes enthält:
setupLivePreview() {
api.onPageChange((url, title) => {
if (this.wallpaperPreviewed) {
this.wallpaperPreviewed = false;
this.loadWallpaperSettings();
}
});
let _this = this;
api.modifyClass('component:user-fields/dropdown', {
pluginId: PLUGIN_ID,
didUpdateAttrs() {
if (this.field.id == settings.wallpaper_user_field) {
_this.wallpaperPreviewed = true;
_this.setWallpaper(this.value);
}
}
});
}
Im ersten Teil lauschen wir auf Seitenwechselereignisse. Wenn wallpaperPreviewed wahr war, laden wir die ursprünglich konfigurierten Einstellungen des Benutzers neu. Wenn der Benutzer die neuen Werte der Benutzerfelder nicht gespeichert hat, wird das Hintergrundbild auf die ursprüngliche Einstellung zurückgesetzt.
Als nächstes kommt die eigentliche Magie. Wir modifizieren die Dropdown-Benutzerfeldkomponente, um der didUpdateAttrs-Funktion zusätzlichen Code hinzuzufügen. Diese wird aufgerufen, wenn das Benutzerfeld einen neuen Wert erhält. Wenn dies geschieht, setzen wir die Variable wallpaperPreviewed auf true, damit beim Seitenwechsel die gespeicherten Einstellungen geladen werden. Als nächstes rufen wir die Funktion setWallpaper auf, um stattdessen das aktuell ausgewählte Hintergrundbild anzuzeigen.
Dasselbe kann für andere Benutzertypen erfolgen. Für das einfache Textfeld würden Sie beispielsweise die Klasse component:user-fields/text modifizieren.
Fazit
Damit geben Sie Ihren Benutzern die Kontrolle darüber, wie Theme-Komponenten ihre Erfahrung durch Benutzerfelder beeinflussen können.
Das einzige Problem ist, dass sich die Benutzerfelder unter Einstellungen > Profil und nicht unter Einstellungen > Benutzeroberfläche befinden, wo Sie sie wahrscheinlich haben möchten.