在我的 论坛 上,我想为用户提供一些默认主题的自定义选项。我想让他们可以选择为论坛设置背景。我可以为此创建不同的主题,但这需要额外的工作。虽然主题有设置,但这些设置是针对所有人的,而不是特定于用户的。但有一种方法可以实现用户可配置的主题设置,那就是“滥用” 用户字段。
在我的主题中,用户实际上对壁纸有 3 个设置(图像、与背景色混合以及内容半透明),但在此帖子中,我将只关注一个设置,即壁纸图像。
第 1 步:创建用户字段
用户可以选择不同的壁纸,或不选择壁纸。对于用户字段,我创建了一个带有以下选项的可选下拉字段:
- none – 完全没有壁纸,这很重要
- Default – 如果用户未进行选择
- Alternative One
- Alternative Two
这是一个可选字段,因此默认值将为空白。对于空白值,我希望使用默认壁纸。
第 2 步:CSS
在 common/common.scss 中,我定义了以下内容:
:root {
--wallpaper-default: url(#{$img-wallpaper-default});
--wallpaper-alternative-one: url(#{$img-wallpaper-alternative-one});
--wallpaper-alternative-two: url(#{$img-wallpaper-alternative-two});
/* 默认设置为 none 以减少可能的初始图像切换 */
--wallpaper: none;
}
body {
background-image: var(--wallpaper);
}
:root 包含各种壁纸图像的变量,这些变量是 主题资产。背景图像设置为 --wallpaper 变量,该变量最初为 none。因此,默认情况下,当 CSS 加载时,将没有壁纸。这是为了消除用户选择与原始壁纸不同的壁纸时可能出现的“闪烁”。
每个可选择的壁纸都有一个变量 --wallpaper-xx-yy-zz。用户字段值是纯文本。根据该值,我将选择正确的 CSS 变量作为背景图像。
第 3 步:主题设置
在 JavaScript 代码中,我需要知道包含壁纸设置的用户字段。最简单的方法是直接引用用户字段 ID。因此,您需要创建一个主题设置来配置此用户字段 ID。
wallpaper_user_field:
default: -1
type: integer
第 4 步:设置壁纸
为此,您需要编写一些 JavaScript。由于此代码需要尽早执行,因此您将创建一个初始化程序 javascripts/discourse/initializer/your-theme-component.js。
让我们从基础开始:
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;
// 获取 `html` 元素
this.styleRoot = document.body.parentElement.style;
this.loadWallpaperSettings();
}
// 稍后将添加更多逻辑
}
export default {
name: PLUGIN_ID,
initialize(owner) {
withPluginApi('1.34.0', (api) => {
this.instance = new YourThemeWallpaper(owner, api);
});
}
}
这是用于添加一些主题组件 JavaScript 的大量样板代码。styleRoot 变量应引用文档的 html 元素,我们将使用它来更改有效的 CSS。
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] || '';
this.setWallpaper(wp);
});
} else {
this.setWallpaper('');
}
}
为了获取用户字段值,需要进行额外的 API 调用,这可能需要一些时间。一旦返回,我们将获取用户字段值并调用 setWallpaper。settings.wallpaper_user_field 是对第 3 步中主题设置的引用。如果未配置设置,wp 字段将默认为空字符串。
如果没有当前用户,我们将调用 setWallpaper 函数并传入一个空字符串来加载默认壁纸。
setWallpaper 函数将实际更新壁纸:
setWallpaper(wallpaper) {
if (!wallpaper || wallpaper === '') {
// 如果未设置,则使用默认值
wallpaper = 'default';
}
// 规范化值以匹配 CSS 变量
wallpaper = wallpaper.toLowerCase().replaceAll(/[^a-z0-9]+/g, '-');
if (wallpaper === 'none') {
this.styleRoot.setProperty('--wallpaper', 'none');
} else {
this.styleRoot.setProperty('--wallpaper', 'var(--wallpaper-'+wallpaper+')');
}
}
如果配置的壁纸设置为 "none",我们将删除背景图像。否则,该值将被规范化以类似于 CSS 变量。“Alternative One”将导致将 CSS 变量 --wallpaper 设置为 var(--wallpaper-alternative-one)。如果用户字段值未映射到 CSS 变量,它实际上将导致没有壁纸,这已经足够了。
第 5 步:更快的加载
上述设置效果很好。但是,额外的 API 调用可能导致壁纸设置得相当晚。这不会产生很好的结果。为了解决这个问题,我利用浏览器的 本地存储 来存储和检索设置。
在构造函数中,在加载用户壁纸设置之前,先尝试根据本地存储中的数据设置壁纸。加载用户设置时,使用配置的内容更新本地存储。
const WALLPAPER_KEY = 'your_theme_wallpaper';
constructor(owner, api) {
setOwner(this, owner);
this.api = api;
// 获取 `html` 元素
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);
}
}
这大大改善了在加载 Discourse 时尽早设置壁纸。实际上,setWallpaper 函数将被调用两次。但由于值很少更改,因此不会注意到。只有当用户更改壁纸时,在下次加载时才会闪烁。这也是为什么 CSS 中的初始壁纸设置为 none 而不是默认值的原因。
第 6 步:实时预览
在 Discourse 中更改配色方案时,您会获得此新配色方案的实时预览,无需保存和重新加载即可查看其外观。这对于壁纸设置也很有用,以便用户可以检查他们最喜欢哪一个。
要实现这一点,您需要一些额外的技巧,通过修改用户字段组件。
在构造函数的末尾,我添加了一个对 this.setupLivePreview() 的调用,其中包含以下内容:
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);
}
}
});
}
在第一部分,我们监听页面更改事件。如果 wallpaperPreviewed 为 true,我们将重新加载用户最初配置的设置。如果用户未保存用户字段的新值,则壁纸将恢复为原始设置。
接下来是真正的魔法。我们修改下拉用户字段组件,在 didUpdateAttrs 函数中添加一些额外的代码。当用户字段获得新值时,将调用此函数。发生这种情况时,我们将 wallpaperPreviewed 变量设置为 true,以便在页面更改时加载保存的设置。接下来,我们调用 setWallpaper 函数来显示当前选定的壁纸。
对于其他用户字段类型也可以进行同样的操作。例如,对于纯文本字段,您将修改类 component:user-fields/text。
结论
通过这些方法,您可以让用户控制主题组件如何通过用户字段影响他们的体验。
唯一的问题是用户字段位于 Preferences > Profile 下,而不是 Preferences > Interface,您可能希望将它们放在那里。