跳转到主要内容

主题系统

Qomicex 主题系统采用 primitive → semantic → component 三级语义 token 结构,以 .qtheme 主题包承载,实现启动器与插件 UI 全量即时换肤,无需重建 plugin-ui dist。

三级语义模型

  • v1 落点:.qtheme 直接表达 semantic/component 层(即 --* 平铺变量),primitive 派生与 theme.mjs 计算层留待 v2
  • plugin-ui 组件只消费 var(--*),零内联色值 → 换主题 = 全组件即时换肤,消除「改组件须重建 dist」的陈旧问题

语义 token 命名

点分命名,. 归一化为 -(background.emphasis → --background-emphasis)。

色板 token(标准组件级集合 = plugin-ui 消费全集)

扩展语义(v1 可选,emit 为 --foreground-accent 等,供前瞻):foreground.accent、foreground.muted、foreground.destructive、background.elevated、background.emphasis、background.sunken、border.strong、border.accent、accent.hover、accent.active、status.success、status.warning、status.error。

非色 token

space.*、font.family/size/weight、motion.*、shadow.* 归入 v2 非色扩展。

.qtheme 包格式

theme.json schema v1

非法 theme.json 会被 src/theme/schema.ts 拒绝并给出友好错误。

主题管理器

主题管理器(src/theme/manager.ts)提供:
  • applyTheme(json):校验 + 注入 CSS 变量到 :root → 全组件即时换肤;持久化主题 id 到 localStorage
  • clearTheme():移除自定义主题,回到设置层 light/dark/预设
  • restoreSavedTheme():启动时恢复已注册主题
  • useTheme():React Hook,订阅变更事件驱动重渲染
  • registerTheme():注册打包内置 .qtheme(含 Catppuccin 四预设)

主题贡献(contributes)

插件与主题包可通过 manifests 声明三类贡献,v1 仅颜色主题:

图标主题(icon-theme.json)

图标主题是 .qtheme 包中与 theme.json 同级的新文件,为插件侧边栏、菜单项等图标提供覆盖映射。

格式

字段说明

图标定义对象

渲染优先级

PluginIcon 组件渲染顺序:
  • 当前主题的 icon-theme.json 中查找 src 键 → 匹配则渲染 SVG/char/URL
  • 未命中,src 为 URL → <img>
  • 未命中,src 为 FontAwesome 类名 → <FontAwesomeIcon>
  • 均未命中 → null

注册方式

图标主题通过主题管理器注册,与颜色主题绑定:
  • registerTheme(colorJson, iconJson?) — 同时注册颜色 + 图标主题
  • registerIconTheme(themeId, iconJson) — 单独为已注册的颜色主题绑定图标
当前活跃主题的图标映射可通过 getActiveIconTheme() 或 useIconTheme()(React Hook)获取。主题切换时映射自动更新。

字体/连字贡献

manifest 声明

插件 manifests 通过 contributes 字段声明字体资源:
  • fontLinks(string[]):字体 CSS/CDN URL 列表。插件激活时,启动器自动注入 <link rel="stylesheet"> 到 <head>;插件停用时自动移除。
  • 可用 font-feature-settings 作用域约定,在特定区域启用连字(见下方)。

连字作用域约定

启动器预定义 .font-ligatures 类(插件内等价类 .p-font-ligatures),应用在需要启用字体连字的区域(如控制台日志、版本号 diff 等):
对应 CSS:
插件内可使用 .p-font-ligatures 获得同等效果。

与 plugin-ui 的关系

硬性约定:plugin-ui 组件只允许 var(--*) 消费 token,禁止内联色值。审计确认 packages/plugin-ui/src/ 全量零内联色值 → 换主题 = 即时换肤,无需重建 dist。

预设主题

启动器内置 Catppuccin 四预设(latte / frappe / macchiato / mocha),已迁移到新 token 命名。切换预设或自定义 .qtheme 时,所有消费 var(--*) 的 UI 组件即时响应。

插件驱动主题覆盖

插件可通过 __PLUGIN_API__ 动态覆盖启动器 UI 的 CSS 变量,无需分发 .qtheme 包即可实现运行时换肤。

优先级层级

  • applyThemeOverride 注入的 <style> 特异性高于 .qtheme 和 @layer base
  • 若用户设置了主题色(themeColor.ts 的 inline style),--primary/--ring/--primary-foreground 三者由 inline 接管,override 中这三者不生效

API

使用示例

安全限制

  • token 白名单:仅允许 30 个预定义语义 token(background、foreground、primary、accent、border、status-* 等)
  • HSL 格式校验:值必须严格匹配 H S% L%(如 142 71% 48%),拒绝任意 CSS 注入
  • 不在白名单的 token 和非法格式会被静默忽略

生命周期

  • applyThemeOverride 可重复调用,后者覆盖前者
  • 插件停用时会自动清除 override:停用流程会无条件调用 clearThemeOverride,恢复为 .qtheme 或内置 light/dark 主题。因此无需在停用钩子里手动清理;若希望在插件仍启用时撤销覆盖,再显式调用 clearThemeOverride
  • clearThemeOverride 仅移除 override 样式,不影响 .qtheme 或内置 light/dark 主题