Compare commits

..

1 Commits

Author SHA1 Message Date
awesomerobot 216303af46 UX: full-width user directory 2025-06-26 11:57:55 -04:00
10 changed files with 44 additions and 371 deletions
+2 -150
View File
@@ -1,151 +1,3 @@
# discourse_theme_ranHorizon 主题源码)
Horizon is a simple, beautiful theme that improves the out-of-the-box experience for Discourse sites.
> 该仓库是 Discourse `Horizon (beta)` 主题的源码副本。仓库内原 README 已标注该主题已并入 Discourse Core。
## 1. 项目作用
这是一个 Discourse 主题项目,核心目标是:
- 改造论坛整体视觉(卡片化主题列表、圆角、配色系统、侧边栏样式)
- 增强交互(侧边栏新建主题按钮、颜色方案切换、Composer Peek 模式)
- 调整不同页面体验(分类页、主题页、聊天页、登录页、移动端)
- 提供系统测试,验证主题关键行为
---
## 2. 目录结构与作用
### `.github/`
- `workflows/discourse-theme.yml`CI 配置,复用 Discourse 官方主题工作流。
### `assets/`
- `.gitkeep`:占位目录,预留静态资源(当前无实际资源文件)。
### `common/`
- `common.scss`:公共样式入口,按模块聚合 `scss/` 下样式。
- `color_definitions.scss`:主题色变量定义(基于 tertiary 推导背景、链接、侧边栏等)。
- `head_tag.html`:注入浏览器兼容检测脚本,不支持 `hsl(from ...)` 时提示升级浏览器。
### `desktop/`
- `desktop.scss`:桌面端样式入口。
- `desktop-full-width.scss`:全宽布局与头部网格布局规则。
- `desktop-horizon-fixes.scss`:桌面端修复(如 bulk select 样式/布局修正)。
### `javascripts/`
- `.gitkeep`:占位。
- `discourse/api-initializers/`:通过 Discourse API 注入主题行为。
- `horizon.gjs`:主题主初始化器;注册实验装饰层、配色选择器、站点设置覆盖、管理员弃用提示。
- `sidebar-new-topic-button-connector.js`:把自定义“新建主题”按钮渲染到侧边栏。
- `composer-peek-toggle-connector.js`:把 Peek 模式切换按钮挂到 composer 工具区。
- `full-width-logo-behavior.js`:侧边栏展开时控制 Logo 最小化行为。
- `reposition-bulk-select.js`:将批量选择入口放到导航控制区。
- `hamburger-click-outside-transformer.js`:扩展汉堡菜单“点击外部关闭”例外元素。
- `discourse/components/`Glimmer 组件。
- `experimental-screen.gjs`:主内容四角/底部装饰层组件(根据容器位置实时计算)。
- `sidebar-new-topic-button.gjs`:侧边栏新建主题按钮逻辑(权限、分类只读、草稿、关闭汉堡菜单)。
- `composer-peek-mode-toggle.gjs`Composer Peek 模式开关,偏好写入 `keyValueStore`
- `user-color-palette-selector.gjs`:用户配色切换主组件(支持匿名/登录、明暗配套、CSS 热替换)。
- `user-color-palette-menu-item.gjs`:配色菜单项。
- `discourse/components/card/`:主题列表卡片列组件。
- `topic-status-column.gjs`Pinned/Hot 状态徽章。
- `topic-category-column.gjs`:分类列。
- `topic-creator-column.gjs`:主题创建者头像列。
- `topic-replies-column.gjs`:回复数列。
- `topic-likes-column.gjs`:点赞数列(当前主题列表布局中未启用展示)。
- `topic-activity-column.gjs`:最近活动信息(回复/创建/更新与时间)。
- `discourse/initializers/topic-list-columns.gjs`:重排主题列表列、注入卡片列、改写点击行为与移动布局策略。
### `locales/`
- `en.yml`:主题文案与元数据(如 `topic_hot``topic_pinned`、描述文本)。
### `scss/`
按功能拆分的样式模块(由 `common/common.scss``desktop/desktop.scss` 聚合):
- `variables.scss`:全局尺寸/圆角/间距变量。
- `main.scss`:页面主体容器网格与背景结构。
- `topic-cards.scss`:主题列表卡片化布局核心样式。
- `topic.scss`:主题阅读页、时间线、帖子流样式。
- `categories-view.scss`:分类页与分类+最新混合页样式。
- `sidebar.scss`:侧边栏容器/section/链接样式。
- `sidebar-new-topic-button.scss`:侧边栏新建主题按钮视觉与状态。
- `header.scss`:顶部栏、通知菜单、图标交互样式。
- `nav-pills.scss`:列表导航区(sticky、tab/下拉)样式。
- `buttons.scss`:按钮体系(默认/主按钮 hover、focus、active)。
- `composer.scss`:编辑器外观与工具条、翻译 composer 相关样式。
- `composer-peek-mode.scss`Peek 模式下 composer 与主布局联动。
- `chat.scss`:聊天页/聊天抽屉适配。
- `welcome-banner.scss`:欢迎横幅和搜索区域样式。
- `mobile-stuff.scss`:移动端/小屏布局调整。
- `login.scss`:登录类页面布局样式。
- `misc.scss`:分散组件兼容/补丁样式。
- `hiddenstuff.scss`:隐藏部分原生 UI 元素。
- `color-choice.scss`:配色切换菜单样式。
- `color-exploration.scss`:侧边栏等颜色变量实验定义。
- `box-view.scss`:实验装饰层(四角遮罩)与聊天相关视觉细节。
- `desktop-full-width.scss``desktop-horizon-fixes.scss`:虽然在 `scss/` 目录下存在同名文件,实际桌面入口使用 `desktop/` 目录版本。
### `screenshots/`
- `light.png``dark.png`:主题明/暗模式截图,供主题元信息展示。
### `spec/`
系统测试(RSpec + Capybara):
- `system/core_features_spec.rb`:主题与核心能力兼容性基础测试。
- `system/horizon_high_level_spec.rb`:高层端到端流程(主题列表、导航、配色切换)。
- `system/sidebar_topic_button_spec.rb`:侧边栏新建主题按钮行为测试。
- `system/composer_peek_spec.rb`Peek 模式可见性与持久化测试。
- `system/user_color_palette_selector_spec.rb`:配色切换在匿名/登录/深色模式下的行为测试。
- `system/page_objects/components/user_color_palette_selector.rb`:配色选择器的 PageObject 封装。
- `.gitkeep`:占位。
### `test/`
- `acceptance/.gitkeep`:预留前端测试目录(当前无具体测试文件)。
---
## 3. 根目录文件作用
- `about.json`:主题元数据(名称、作者、链接、版本、颜色方案、截图、modifiers)。
- `settings.yml`:主题设置项(欢迎横幅开关、搜索体验模式)。
- `README.md`:项目说明文档(本文件)。
- `LICENSE`MIT 许可证。
- `package.json`:前端开发依赖与 Node/pnpm 版本约束。
- `pnpm-lock.yaml`:前端依赖锁定文件。
- `Gemfile` / `Gemfile.lock`Ruby 工具链依赖(rubocop-discourse、syntax_tree 等)。
- `.discourse-compatibility`:与 Discourse 版本兼容映射。
- `eslint.config.mjs`ESLint 配置(继承 `@discourse/lint-configs`)。
- `stylelint.config.mjs`Stylelint 配置。
- `.template-lintrc.cjs`Ember Template Lint 配置。
- `.prettierrc.cjs`Prettier 配置。
- `.rubocop.yml`Rubocop 配置(Discourse 规则)。
- `.streerc`Syntax Tree 格式化配置。
- `.npmrc`:npm 行为约束(严格引擎、禁自动 peer 安装)。
- `.gitignore`Git 忽略规则。
---
## 4. 关键功能对应关系(便于快速定位)
- 主题配色切换:
- 逻辑:`javascripts/discourse/components/user-color-palette-selector.gjs`
- 菜单样式:`scss/color-choice.scss`
- 颜色定义:`about.json` + `common/color_definitions.scss`
- 侧边栏新建主题按钮:
- 逻辑:`javascripts/discourse/components/sidebar-new-topic-button.gjs`
- 挂载:`javascripts/discourse/api-initializers/sidebar-new-topic-button-connector.js`
- 样式:`scss/sidebar-new-topic-button.scss`
- 主题列表卡片化:
- 列注册:`javascripts/discourse/initializers/topic-list-columns.gjs`
- 各列组件:`javascripts/discourse/components/card/*.gjs`
- 主样式:`scss/topic-cards.scss`
- Composer Peek 模式:
- 逻辑:`javascripts/discourse/components/composer-peek-mode-toggle.gjs`
- 挂载:`javascripts/discourse/api-initializers/composer-peek-toggle-connector.js`
- 样式:`scss/composer-peek-mode.scss`
---
## 5. 说明
- 此仓库 README 原文已声明:该主题已并入 Discourse Core,生产环境优先使用 Core 内置 Horizon 主题。
- 当前仓库更适合作为学习/二次开发参考,用于理解 Discourse 主题的样式组织方式与前端扩展点。
https://meta.discourse.org/t/horizon-theme/360486
+5 -5
View File
@@ -1,9 +1,9 @@
{
"name": "Horizon-shiran",
"authors": "shiran",
"about_url": "",
"license_url": "https://gitea.s1f.ren/shiran/discourse_theme_ran/horizon/blob/main/LICENSE",
"learn_more": "",
"name": "Horizon",
"authors": "Design Team",
"about_url": "https://meta.discourse.org/t/horizon-theme/360486",
"license_url": "https://github.com/discourse/horizon/blob/main/LICENSE",
"learn_more": "https://meta.discourse.org/t/installing-a-theme-or-theme-component/63682",
"theme_version": "0.0.1",
"modifiers": {
"svg_icons": ["fire"],
-1
View File
@@ -18,4 +18,3 @@
@import "topic";
@import "topic-cards";
@import "variables";
@import "self";
+1 -1
View File
@@ -1,6 +1,6 @@
en:
theme_metadata:
description: "自制二次元的半透明主题"
description: "A simple, beautiful theme that improves the out of the box experience for Discourse sites."
topic_pinned: "Pinned"
topic_hot: "Hot"
user_replied: "replied"
+2 -8
View File
@@ -4,8 +4,7 @@
.category-list {
border-collapse: separate;
th.topics,
.category-logo {
th.topics {
display: none;
}
@@ -225,8 +224,7 @@
margin-inline: 1rem;
.num.posts,
.category-topics-count,
.category-logo {
.category-topics-count {
display: none;
}
@@ -263,10 +261,6 @@
}
.category-boxes.with-subcategories {
.category-logo {
display: none;
}
@include viewport.from(sm) {
margin-top: 0;
}
+5 -23
View File
@@ -3,7 +3,6 @@
.topic-list-header {
position: relative;
top: 0;
z-index: 2;
}
.topic-author-avatar-data {
@@ -29,29 +28,18 @@
}
.topic-list-header {
position: relative;
margin-bottom: 0.35rem;
tr {
border: none;
}
.topic-list-data {
padding: 0.25rem 0;
color: var(--primary-600);
font-size: var(--font-down-2);
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
background: transparent;
padding: 0;
&:not(.default) {
display: none;
}
&.default {
border-radius: var(--d-border-radius);
.bulk-select,
span:not(.bulk-select-topics, .d-button-label) {
display: none;
@@ -62,22 +50,16 @@
// bulk select
.bulk-select-topics {
position: absolute;
right: 0.25rem;
top: 50%;
transform: translateY(-50%);
background: color-mix(in srgb, var(--d-content-background) 88%, white 12%);
border: 1px solid var(--primary-200);
border-radius: 999px;
box-shadow: 0 6px 18px -12px rgb(10 18 35 / 35%);
right: 0;
background: var(--secondary);
border-radius: 0 0 0 var(--d-border-radius);
display: flex;
gap: 0.5rem;
margin: 0;
padding: 0.25rem;
margin: 0.5rem;
button {
white-space: nowrap;
margin: 0;
border-radius: 999px;
}
}
}
+7
View File
@@ -98,6 +98,13 @@ body:not(.has-full-page-chat, .wizard) {
}
}
body.users-page {
// very specific to overcome the above rules
#main #main-outlet-wrapper #main-outlet > section {
max-width: unset;
}
}
@include viewport.until(sm) {
.welcome-banner {
display: none;
-127
View File
@@ -1,127 +0,0 @@
@use "lib/viewport";
#main-outlet {
@if $home_bg_image != "" {
background-image: url($home_bg_image);
background-size: cover;
background-position: center;
background-attachment: fixed;
}
}
.container.list-container{
position: relative;
}
.container.list-container::before{
position: absolute;
content: "";
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
background-color: #fff9;
border-radius: var(--d-border-radius);
backdrop-filter: blur(10px);
box-shadow: #00000077 0 0 19px 0;
}
.topic-list-header{
background-color: unset;
}
.list-controls, .search-container{
border-radius: var(--d-border-radius);
}
.welcome-banner {
padding: 0 !important;
box-shadow: #00000094 0 0 11px 0;
}
#list-area, .topic-list-header, .admin-detail{
background-color: unset !important;
}
.user-content, .details{
border-radius: var(--d-border-radius);
}
.regular.ember-view, .user-main, .reviewable,
.admin-content, .contents.clearfix.body-page,
.search-container, .show-badge, .users-directory, #main-outlet>.edit-category,
#main-outlet>.container.groups-index,
.container.group{
background-color: #ffffffa8 !important;
border-radius: var(--d-border-radius);
backdrop-filter: blur(32px);
box-shadow: #00000077 0 0 19px 0;
padding: 24px;
}
.topic-post.clearfix.regular, .post-list-item.user-stream-item,
.search-header, .admin-plugin-config-page__content,
.user-main .about.collapsed-info .details,
.ember-view.group-box, .admin-container>.container.groups-index{
background: var(--d-chat-input-bg-color);
border-radius: var(--d-border-radius);
box-shadow: #00000024 0 0 7px 2px;
margin: 10px 0;
position: relative;
padding: 16px 12px;
overflow: hidden;
transition: background-color 0.2s ease-in-out;
}
.user-content{
background-color: unset;
}
.powered-by-discourse{
display: none;
}
// 默认光标
@if $default_cursor != "" {
* {
cursor: url($default_cursor) 1 1, auto;
}
}
// 悬停光标
@if $hover_cursor != "" {
a,
button,
[role="button"],
input[type="button"],
input[type="submit"],
.btn,
.clickable {
cursor: url($hover_cursor) 1 1, auto;
}
}
// 链接光标
@if $pointer_cursor != "" {
a[href],
button,
[role="button"],
label,
input[type="button"],
input[type="submit"],
.pointer {
cursor: url($pointer_cursor) 1 1, pointer;
}
}
// 文本光标
@if $text_cursor != "" {
input[type="text"],
input[type="email"],
input[type="password"],
input[type="search"],
textarea {
cursor: url($text_cursor) 1 1, text;
}
}
+22 -30
View File
@@ -9,7 +9,11 @@
}
.topic-list > .topic-list-body > .topic-list-item.last-visit {
border-bottom: none;
border-bottom: 1px solid var(--primary-300);
&:hover {
border-color: var(--accent-color);
}
}
.topic-list,
@@ -18,8 +22,8 @@
border-radius: var(--d-border-radius);
padding: 3px 6px;
background-color: light-dark(
oklch(from var(--category-badge-color) 97% calc(c * 0.3) h),
oklch(from var(--category-badge-color) 45% calc(c * 0.5) h)
oklch(from var(--category-badge-color) 97% calc(c * 0.3) h),
oklch(from var(--category-badge-color) 45% calc(c * 0.5) h)
);
@include viewport.until(md) {
@@ -29,8 +33,8 @@
.badge-category__name {
color: light-dark(
oklch(from var(--category-badge-color) 20% calc(c * 1) h),
oklch(from var(--category-badge-color) 100% calc(c * 0.9) h)
oklch(from var(--category-badge-color) 20% calc(c * 1) h),
oklch(from var(--category-badge-color) 100% calc(c * 0.9) h)
);
}
}
@@ -68,11 +72,11 @@
.topic-list-body .topic-list-item {
position: relative;
background: linear-gradient(45deg, var(--active-color), rgb(255 255 255 / 25%));
box-shadow: 4px 4px 6px 0px rgba(10, 18, 35, .38), 0 6px 16px -10px var(--topic-card-shadow);
background: var(--d-content-background);
box-shadow: 0 0 12px 1px var(--topic-card-shadow);
text-overflow: ellipsis;
padding: var(--space-3);
border: none;
border: 1px solid var(--primary-300);
display: grid;
grid-template-columns: min-content min-content min-content auto min-content;
grid-template-areas:
@@ -81,18 +85,6 @@
grid-gap: var(--space-3);
border-radius: var(--d-border-radius);
cursor: pointer;
transition:
all 0.2s ease;
&::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
padding: 1px;
mask-composite: exclude;
pointer-events: none;
}
&.has-replies {
grid-template-areas:
@@ -152,16 +144,16 @@
}
&:hover {
background: var(--active-color);
box-shadow: 4px 5px 3px 1px rgba(0, 0, 0, .2), 0 8px 20px -10px var(--topic-card-shadow);
border-left: 5px solid #0006;
.discourse-no-touch & {
border-color: var(--accent-color);
background: var(--d-content-background);
}
}
&.selected {
box-shadow:
0 14px 32px -14px rgb(10 18 35 / 48%),
0 0 0 2px oklch(from var(--accent-color) l calc(c * 0.45) h / 0.28),
0 0 0 8px oklch(from var(--accent-color) l calc(c * 0.2) h / 0.14);
0 0 0 2px var(--accent-color),
0 0 12px 1px var(--topic-card-shadow);
}
&.excerpt-expanded {
@@ -243,8 +235,8 @@
}
padding: 0.25em 0.5rem;
background-color: light-dark(
oklch(from var(--category-badge-color) 97% calc(c * 0.3) h),
oklch(from var(--category-badge-color) 45% calc(c * 0.5) h)
oklch(from var(--category-badge-color) 97% calc(c * 0.3) h),
oklch(from var(--category-badge-color) 45% calc(c * 0.5) h)
);
@include viewport.until(md) {
@@ -259,8 +251,8 @@
.badge-category__name {
font-size: var(--font-down-1);
color: light-dark(
oklch(from var(--category-badge-color) 20% calc(c * 1) h),
oklch(from var(--category-badge-color) 100% calc(c * 0.9) h)
oklch(from var(--category-badge-color) 20% calc(c * 1) h),
oklch(from var(--category-badge-color) 100% calc(c * 0.9) h)
);
min-width: 0;
overflow: hidden;
-26
View File
@@ -9,29 +9,3 @@ search_experience:
- search_field
- search_icon
description: "Overrides the core `search experience` site setting"
home_bg_image:
type: upload
default: ""
description: "首页背景图"
default_cursor:
type: upload
default: ""
description: "默认光标 SVG 文件"
hover_cursor:
type: upload
default: ""
description: "悬停光标 SVG 文件"
pointer_cursor:
type: upload
default: ""
description: "链接光标 SVG 文件"
text_cursor:
type: upload
default: ""
description: "文本光标 SVG 文件"