用户可定制的主题组件(操作指南)

在我的 论坛 上,我想为用户提供一些默认主题的自定义选项。我想让他们可以选择为论坛设置背景。我可以为此创建不同的主题,但这需要额外的工作。虽然主题有设置,但这些设置是针对所有人的,而不是特定于用户的。但有一种方法可以实现用户可配置的主题设置,那就是“滥用” 用户字段

在我的主题中,用户实际上对壁纸有 3 个设置(图像、与背景色混合以及内容半透明),但在此帖子中,我将只关注一个设置,即壁纸图像。

第 1 步:创建用户字段

用户可以选择不同的壁纸,或不选择壁纸。对于用户字段,我创建了一个带有以下选项的可选下拉字段:

  1. none – 完全没有壁纸,这很重要
  2. Default – 如果用户未进行选择
  3. Alternative One
  4. 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 调用,这可能需要一些时间。一旦返回,我们将获取用户字段值并调用 setWallpapersettings.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,您可能希望将它们放在那里。

9 个赞