feat: 首页门面 Feed 密度与侧栏分组
对齐 SPA 信息密度:头像/摘要/互动、高级搜索收起、浏览/板块分区。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,15 +10,15 @@
|
|||||||
|
|
||||||
| 项 | 值 |
|
| 项 | 值 |
|
||||||
|----|-----|
|
|----|-----|
|
||||||
| **上一刀** | 青主题产品化重绘 + 静态资源 `?v=` 版本号 |
|
| **上一刀** | 首页门面精修(Feed 头像/摘要/互动 + 高级搜索收起 + 侧栏分组) |
|
||||||
| **下一刀** | 需点名(移动侧栏抽屉 / OIDC Admin / S3 / Gitea / 表格表情…) |
|
| **下一刀** | 需点名(移动侧栏抽屉 / 帖详情抛光 / OIDC Admin / S3…) |
|
||||||
| **工作区** | 应干净 |
|
| **工作区** | 应干净 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 一句话状态
|
## 一句话状态
|
||||||
|
|
||||||
核心论坛、Admin、Limits/伪静态、嵌套评论、Markdown 编辑器、主题、公开页青主题产品壳与资源版本号已齐;本分支 `routers/api` 仅保留机器入口。余量多为后置与体验打磨。
|
核心论坛、Admin、Limits/伪静态、嵌套评论、Markdown 编辑器、主题、清青产品壳与资源版本号、首页门面密度已齐;本分支 `routers/api` 仅保留机器入口。余量多为后置与体验打磨。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
| Markdown 工具栏 + preview | 已迁 |
|
| Markdown 工具栏 + preview | 已迁 |
|
||||||
| 浅色 / 暗色 / 跟随系统 | 已迁(CSS 媒体查询,无 head 防闪脚本) |
|
| 浅色 / 暗色 / 跟随系统 | 已迁(CSS 媒体查询,无 head 防闪脚本) |
|
||||||
| 未挂载论坛 JSON API | 已删(对照 `main`) |
|
| 未挂载论坛 JSON API | 已删(对照 `main`) |
|
||||||
| 公开页设计系统 | 已迁(清青产品 chrome + `?v=`;[10-design-system.md](10-design-system.md)) |
|
| 公开页设计系统 | 已迁(清青 chrome + `?v=` + 首页 Feed 门面密度) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -82,16 +82,18 @@
|
|||||||
|
|
||||||
| 组件 | 规则 |
|
| 组件 | 规则 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 顶栏 | 品牌 \| 搜索条 \| 发帖主按钮 + 主题 + 私信 + 用户 +「更多」菜单;告别链接墙 |
|
| 顶栏 | 品牌 \| 搜索条 \|「+ 发帖」主按钮 + 主题 + 私信 + 用户 +「更多」;告别链接墙 |
|
||||||
| 侧栏链 | sm;active = soft 底 + 左边青条 |
|
| 左栏 | 分区「浏览 / 板块」;active = soft 底 + 左边青条 |
|
||||||
| Feed | 排序分段控件;搜索扁平工具条;细线列表;回复数右对齐强调 |
|
| Feed 行 | 头像 · 作者+相对时间 / 标题 / 摘要(最多 2 行)/ 标签 · 右栏回复/赞/阅读;细线分隔非卡片墙 |
|
||||||
| 标签 / 按钮 / 评论 / 门控 | 同青 token;列表仍非卡片墙 |
|
| 高级搜索 | `<details>` 默认收起;有筛选条件或错误时展开;顶栏搜索仍 `/?keyword=` |
|
||||||
|
| 排序 | 分段控件 |
|
||||||
|
| 标签 / 按钮 / 评论 / 门控 | 同青 token |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. 范围边界
|
## 5. 范围边界
|
||||||
|
|
||||||
- **覆盖**:公开页壳层、资源 `?v=`、浅/暗色
|
- **覆盖**:公开页壳层(含首页门面密度)、资源 `?v=`、浅/暗色
|
||||||
- **不覆盖**:Admin 深重绘、移动抽屉、虚拟滚动、TipTap、改站名
|
- **不覆盖**:Admin 深重绘、移动抽屉、虚拟滚动、TipTap、改站名
|
||||||
|
|
||||||
交叉:[06-pages-ux.md](06-pages-ux.md) · [09-ssr-progress.md](09-ssr-progress.md)
|
交叉:[06-pages-ux.md](06-pages-ux.md) · [09-ssr-progress.md](09-ssr-progress.md)
|
||||||
|
|||||||
@@ -303,14 +303,21 @@ html[data-theme="dark"] .j13-brand__mark {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--j13-muted);
|
color: var(--j13-muted);
|
||||||
border-radius: var(--j13-radius);
|
border-radius: var(--j13-radius);
|
||||||
padding: 0.3rem 0.55rem;
|
padding: 0.3rem 0.5rem;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3rem;
|
||||||
transition: color var(--j13-ease), border-color var(--j13-ease), background-color var(--j13-ease);
|
transition: color var(--j13-ease), border-color var(--j13-ease), background-color var(--j13-ease);
|
||||||
}
|
}
|
||||||
|
.j13-theme-toggle__icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
.j13-theme-toggle:hover {
|
.j13-theme-toggle:hover {
|
||||||
color: var(--j13-accent-ink);
|
color: var(--j13-accent-ink);
|
||||||
border-color: var(--j13-accent);
|
border-color: var(--j13-accent);
|
||||||
@@ -415,6 +422,16 @@ html[data-theme="dark"] .j13-brand__mark {
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
top: calc(var(--j13-header-h) + 0.75rem);
|
top: calc(var(--j13-header-h) + 0.75rem);
|
||||||
}
|
}
|
||||||
|
.j13-aside__section {
|
||||||
|
margin: 0.85rem 0 0.35rem;
|
||||||
|
padding: 0 0.55rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 650;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--j13-muted);
|
||||||
|
}
|
||||||
|
.j13-aside__section:first-child { margin-top: 0.15rem; }
|
||||||
.j13-aside__link {
|
.j13-aside__link {
|
||||||
padding: 0.45rem 0.6rem;
|
padding: 0.45rem 0.6rem;
|
||||||
border-radius: var(--j13-radius);
|
border-radius: var(--j13-radius);
|
||||||
@@ -450,7 +467,7 @@ html[data-theme="dark"] .j13-brand__mark {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 0.75rem 1rem;
|
gap: 0.75rem 1rem;
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.65rem;
|
||||||
}
|
}
|
||||||
.j13-feed__title {
|
.j13-feed__title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -485,13 +502,44 @@ html[data-theme="dark"] .j13-brand__mark {
|
|||||||
background: var(--j13-hover);
|
background: var(--j13-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.j13-search-advanced {
|
||||||
|
margin: 0 0 0.75rem;
|
||||||
|
scroll-margin-top: 4.5rem;
|
||||||
|
}
|
||||||
|
.j13-search-advanced__summary {
|
||||||
|
list-style: none;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
color: var(--j13-muted);
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 0.25rem 0;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.j13-search-advanced__summary::-webkit-details-marker { display: none; }
|
||||||
|
.j13-search-advanced__summary::before {
|
||||||
|
content: "";
|
||||||
|
width: 0.4rem;
|
||||||
|
height: 0.4rem;
|
||||||
|
border-right: 1.5px solid currentColor;
|
||||||
|
border-bottom: 1.5px solid currentColor;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
transition: transform var(--j13-ease);
|
||||||
|
}
|
||||||
|
.j13-search-advanced[open] > .j13-search-advanced__summary::before {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
.j13-search-advanced__summary:hover { color: var(--j13-accent); }
|
||||||
|
.j13-search-advanced .j13-search { margin-top: 0.45rem; }
|
||||||
|
|
||||||
.j13-search {
|
.j13-search {
|
||||||
margin: 0 0 0.85rem;
|
margin: 0 0 0.85rem;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
scroll-margin-top: 4.5rem;
|
|
||||||
}
|
}
|
||||||
.j13-search--toolbar .j13-search__row {
|
.j13-search--toolbar .j13-search__row {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -541,38 +589,114 @@ html[data-theme="dark"] .j13-brand__mark {
|
|||||||
|
|
||||||
.j13-post-list { list-style: none; margin: 0; padding: 0; }
|
.j13-post-list { list-style: none; margin: 0; padding: 0; }
|
||||||
.j13-post-item {
|
.j13-post-item {
|
||||||
padding: 0.85rem 0.4rem;
|
display: grid;
|
||||||
margin: 0 -0.4rem;
|
grid-template-columns: 2.5rem minmax(0, 1fr) auto;
|
||||||
|
gap: 0.75rem 0.85rem;
|
||||||
|
padding: 1rem 0.45rem;
|
||||||
|
margin: 0 -0.45rem;
|
||||||
border-top: 1px solid var(--j13-border);
|
border-top: 1px solid var(--j13-border);
|
||||||
border-radius: var(--j13-radius);
|
border-radius: var(--j13-radius);
|
||||||
transition: background-color var(--j13-ease);
|
transition: background-color var(--j13-ease);
|
||||||
|
align-items: start;
|
||||||
}
|
}
|
||||||
.j13-post-item:first-child { border-top: 0; }
|
.j13-post-item:first-child { border-top: 0; }
|
||||||
.j13-post-item:hover { background: var(--j13-hover); }
|
.j13-post-item:hover { background: var(--j13-hover); }
|
||||||
|
.j13-post-item__avatar {
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: var(--j13-soft);
|
||||||
|
}
|
||||||
|
.j13-post-item__avatar img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.j13-post-item__mark {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--j13-accent-ink);
|
||||||
|
background: var(--j13-soft);
|
||||||
|
}
|
||||||
|
.j13-post-item__body { min-width: 0; }
|
||||||
|
.j13-post-item__byline {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.4rem 0.65rem;
|
||||||
|
align-items: baseline;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--j13-muted);
|
||||||
|
margin-bottom: 0.2rem;
|
||||||
|
}
|
||||||
|
.j13-post-item__byline a {
|
||||||
|
color: var(--j13-accent-ink);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.j13-post-item__byline a:hover { color: var(--j13-accent); }
|
||||||
.j13-post-item__title {
|
.j13-post-item__title {
|
||||||
|
display: inline-block;
|
||||||
color: var(--j13-text);
|
color: var(--j13-text);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 1.15rem;
|
font-size: 1.1rem;
|
||||||
letter-spacing: -0.02em;
|
letter-spacing: -0.02em;
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
transition: color var(--j13-ease);
|
transition: color var(--j13-ease);
|
||||||
}
|
}
|
||||||
.j13-post-item__title:hover { color: var(--j13-accent); }
|
.j13-post-item__title:hover { color: var(--j13-accent); }
|
||||||
|
.j13-post-item__excerpt {
|
||||||
|
margin: 0.3rem 0 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--j13-muted);
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
.j13-post-item__meta {
|
.j13-post-item__meta {
|
||||||
margin-top: 0.35rem;
|
margin-top: 0.4rem;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: var(--j13-muted);
|
color: var(--j13-muted);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.45rem;
|
gap: 0.4rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.j13-post-item__replies {
|
.j13-post-item__stats {
|
||||||
margin-left: auto;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.2rem;
|
||||||
|
align-items: flex-end;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
color: var(--j13-muted);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
padding-top: 0.15rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.j13-post-item__stats strong {
|
||||||
font-weight: 650;
|
font-weight: 650;
|
||||||
color: var(--j13-accent-ink);
|
color: var(--j13-accent-ink);
|
||||||
font-variant-numeric: tabular-nums;
|
}
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.j13-post-item {
|
||||||
|
grid-template-columns: 2.25rem minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
.j13-post-item__stats {
|
||||||
|
grid-column: 2;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 0.75rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.j13-widget__title {
|
.j13-widget__title {
|
||||||
|
|||||||
@@ -27,9 +27,15 @@ document.documentElement.dataset.j13Ssr = "1";
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
var btn = document.getElementById("j13-theme-toggle");
|
var btn = document.getElementById("j13-theme-toggle");
|
||||||
if (btn) {
|
if (btn) {
|
||||||
btn.textContent = LABELS[pref] || LABELS.system;
|
var label = btn.querySelector(".j13-theme-toggle__label");
|
||||||
btn.setAttribute("aria-label", "主题:" + (LABELS[pref] || ""));
|
var text = LABELS[pref] || LABELS.system;
|
||||||
btn.title = "当前:" + (LABELS[pref] || "") + "(点击切换)";
|
if (label) {
|
||||||
|
label.textContent = text;
|
||||||
|
} else {
|
||||||
|
btn.textContent = text;
|
||||||
|
}
|
||||||
|
btn.setAttribute("aria-label", "主题:" + text);
|
||||||
|
btn.title = "当前:" + text + "(点击切换)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -169,11 +169,18 @@ type PostListItem struct {
|
|||||||
ID uint
|
ID uint
|
||||||
Title string
|
Title string
|
||||||
Href string
|
Href string
|
||||||
|
AuthorID uint
|
||||||
AuthorName string
|
AuthorName string
|
||||||
|
AuthorHref string
|
||||||
|
AvatarURL string
|
||||||
|
AuthorMark string // 无头像时的单字占位
|
||||||
BoardName string
|
BoardName string
|
||||||
|
Excerpt string
|
||||||
Pinned bool
|
Pinned bool
|
||||||
Featured bool
|
Featured bool
|
||||||
CommentCount int
|
CommentCount int
|
||||||
|
LikeCount int
|
||||||
|
ViewCount int
|
||||||
CreatedLabel string
|
CreatedLabel string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,10 +336,26 @@ func (d Deps) Home(c *gin.Context) {
|
|||||||
if it.Board.ID > 0 {
|
if it.Board.ID > 0 {
|
||||||
bname = it.Board.Name
|
bname = it.Board.Name
|
||||||
}
|
}
|
||||||
|
excerptSrc := strings.TrimSpace(it.ContentPlain)
|
||||||
|
if excerptSrc == "" {
|
||||||
|
excerptSrc = services.ExcerptFromHTML(it.Content, 100)
|
||||||
|
} else {
|
||||||
|
excerptSrc = services.TruncateRunes(excerptSrc, 100)
|
||||||
|
}
|
||||||
|
avatar := strings.TrimSpace(it.User.Avatar)
|
||||||
|
authorID := it.UserID
|
||||||
|
authorHref := ""
|
||||||
|
if authorID > 0 {
|
||||||
|
authorHref = pl.UserPath(authorID)
|
||||||
|
}
|
||||||
posts = append(posts, PostListItem{
|
posts = append(posts, PostListItem{
|
||||||
ID: it.ID, Title: it.Title, Href: pl.PostPath(it.ID), AuthorName: authorName, BoardName: bname,
|
ID: it.ID, Title: it.Title, Href: pl.PostPath(it.ID),
|
||||||
Pinned: it.Pinned, Featured: it.Featured, CommentCount: it.CommentCount,
|
AuthorID: authorID, AuthorName: authorName, AuthorHref: authorHref,
|
||||||
CreatedLabel: it.CreatedAt.Local().Format("2006-01-02 15:04"),
|
AvatarURL: avatar, AuthorMark: firstRuneOr(authorName, "姜"),
|
||||||
|
BoardName: bname, Excerpt: excerptSrc,
|
||||||
|
Pinned: it.Pinned, Featured: it.Featured,
|
||||||
|
CommentCount: it.CommentCount, LikeCount: it.LikeCount, ViewCount: it.ViewCount,
|
||||||
|
CreatedLabel: formatRelativeTime(it.CreatedAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,3 +376,29 @@ func normalizeSort(s string) string {
|
|||||||
func formatTime(t time.Time) string {
|
func formatTime(t time.Time) string {
|
||||||
return t.Local().Format("2006-01-02 15:04")
|
return t.Local().Format("2006-01-02 15:04")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// formatRelativeTime 列表用相对时间
|
||||||
|
func formatRelativeTime(t time.Time) string {
|
||||||
|
if t.IsZero() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
d := now.Sub(t)
|
||||||
|
if d < 0 {
|
||||||
|
d = 0
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case d < time.Minute:
|
||||||
|
return "刚刚"
|
||||||
|
case d < time.Hour:
|
||||||
|
return fmt.Sprintf("%d 分钟前", int(d.Minutes()))
|
||||||
|
case d < 24*time.Hour:
|
||||||
|
return fmt.Sprintf("%d 小时前", int(d.Hours()))
|
||||||
|
case d < 48*time.Hour:
|
||||||
|
return "昨天"
|
||||||
|
case d < 30*24*time.Hour:
|
||||||
|
return fmt.Sprintf("%d 天前", int(d.Hours()/24))
|
||||||
|
default:
|
||||||
|
return t.Local().Format("2006-01-02")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,15 +20,20 @@
|
|||||||
</form>
|
</form>
|
||||||
<div class="j13-header__actions">
|
<div class="j13-header__actions">
|
||||||
{{if .LoggedIn}}
|
{{if .LoggedIn}}
|
||||||
<a class="j13-btn j13-btn--compose" href="/compose">发帖</a>
|
<a class="j13-btn j13-btn--compose" href="/compose">+ 发帖</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
<a class="j13-btn j13-btn--compose" href="/login">登录</a>
|
<a class="j13-btn j13-btn--compose" href="/login">登录</a>
|
||||||
<a class="j13-header__textlink" href="/register">注册</a>
|
<a class="j13-header__textlink" href="/register">注册</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
<button type="button" class="j13-theme-toggle" id="j13-theme-toggle" aria-label="切换主题" title="切换主题">主题</button>
|
<button type="button" class="j13-theme-toggle" id="j13-theme-toggle" aria-label="切换主题" title="切换主题">
|
||||||
|
<svg class="j13-theme-toggle__icon" width="16" height="16" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||||
|
<path fill="currentColor" d="M12 3a1 1 0 0 1 1 1v1.06a7.5 7.5 0 1 1-2 0V4a1 1 0 0 1 1-1Zm0 4.5a5.5 5.5 0 1 0 0 11 5.5 5.5 0 0 0 0-11Z"/>
|
||||||
|
</svg>
|
||||||
|
<span class="j13-theme-toggle__label">主题</span>
|
||||||
|
</button>
|
||||||
{{if .LoggedIn}}
|
{{if .LoggedIn}}
|
||||||
<a class="j13-header__iconlink" href="/messages" title="私信">私信{{if gt .UnreadCount 0}} <span class="j13-badge">{{.UnreadCount}}</span>{{end}}</a>
|
<a class="j13-header__iconlink" href="/messages" title="私信">私信{{if gt .UnreadCount 0}} <span class="j13-badge">{{.UnreadCount}}</span>{{end}}</a>
|
||||||
<a class="j13-header__user" href="/profile" title="个人中心">{{.ViewerName}} · {{.ViewerPoints}}</a>
|
<a class="j13-header__user" href="/profile" title="个人中心">{{.ViewerName}}</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
<details class="j13-more">
|
<details class="j13-more">
|
||||||
<summary>更多</summary>
|
<summary>更多</summary>
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
<a class="{{if eq .Sort "hot"}}is-active{{end}}" href="{{.SortHref "hot"}}">热门讨论</a>
|
<a class="{{if eq .Sort "hot"}}is-active{{end}}" href="{{.SortHref "hot"}}">热门讨论</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<form id="search" class="j13-search j13-search--toolbar" method="get" action="{{.FormAction}}" role="search">
|
<details class="j13-search-advanced" id="search"{{if or .HasSearch .TitleOnly .SearchError}} open{{end}}>
|
||||||
|
<summary class="j13-search-advanced__summary">高级搜索</summary>
|
||||||
|
<form class="j13-search j13-search--toolbar" method="get" action="{{.FormAction}}" role="search">
|
||||||
{{if and .Sort (ne .Sort "latest")}}<input type="hidden" name="sort" value="{{.Sort}}"/>{{end}}
|
{{if and .Sort (ne .Sort "latest")}}<input type="hidden" name="sort" value="{{.Sort}}"/>{{end}}
|
||||||
<div class="j13-search__row">
|
<div class="j13-search__row">
|
||||||
<label class="j13-search__field">
|
<label class="j13-search__field">
|
||||||
@@ -31,20 +33,37 @@
|
|||||||
</div>
|
</div>
|
||||||
{{if .SearchError}}<p class="j13-search__err" role="alert">{{.SearchError}}</p>{{end}}
|
{{if .SearchError}}<p class="j13-search__err" role="alert">{{.SearchError}}</p>{{end}}
|
||||||
</form>
|
</form>
|
||||||
|
</details>
|
||||||
{{if not .Posts}}
|
{{if not .Posts}}
|
||||||
<p class="j13-empty">{{if .SearchError}}请调整搜索条件后重试。{{else if .HasSearch}}未找到匹配帖子。{{else}}暂无帖子。{{end}}</p>
|
<p class="j13-empty">{{if .SearchError}}请调整搜索条件后重试。{{else if .HasSearch}}未找到匹配帖子。{{else}}暂无帖子。{{end}}</p>
|
||||||
{{else}}
|
{{else}}
|
||||||
<ul class="j13-post-list">
|
<ul class="j13-post-list">
|
||||||
{{range .Posts}}
|
{{range .Posts}}
|
||||||
<li class="j13-post-item">
|
<li class="j13-post-item">
|
||||||
|
<div class="j13-post-item__avatar">
|
||||||
|
{{if .AvatarURL}}
|
||||||
|
<img src="{{.AvatarURL}}" alt="" width="40" height="40"/>
|
||||||
|
{{else}}
|
||||||
|
<span class="j13-post-item__mark" aria-hidden="true">{{.AuthorMark}}</span>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div class="j13-post-item__body">
|
||||||
|
<div class="j13-post-item__byline">
|
||||||
|
{{if .AuthorHref}}<a href="{{.AuthorHref}}">{{.AuthorName}}</a>{{else}}<span>{{.AuthorName}}</span>{{end}}
|
||||||
|
<span class="j13-post-item__time">{{.CreatedLabel}}</span>
|
||||||
|
</div>
|
||||||
<a class="j13-post-item__title" href="{{.Href}}"{{if $.OpenPostsInNewTab}} target="_blank" rel="noopener"{{end}}>{{.Title}}</a>
|
<a class="j13-post-item__title" href="{{.Href}}"{{if $.OpenPostsInNewTab}} target="_blank" rel="noopener"{{end}}>{{.Title}}</a>
|
||||||
|
{{if .Excerpt}}<p class="j13-post-item__excerpt">{{.Excerpt}}</p>{{end}}
|
||||||
<div class="j13-post-item__meta">
|
<div class="j13-post-item__meta">
|
||||||
{{if .Pinned}}<span class="j13-tag j13-tag--pin">置顶</span>{{end}}
|
{{if .Pinned}}<span class="j13-tag j13-tag--pin">置顶</span>{{end}}
|
||||||
{{if .Featured}}<span class="j13-tag j13-tag--feat">精华</span>{{end}}
|
{{if .Featured}}<span class="j13-tag j13-tag--feat">精华</span>{{end}}
|
||||||
{{if .BoardName}}<span class="j13-tag">{{.BoardName}}</span>{{end}}
|
{{if .BoardName}}<span class="j13-tag">{{.BoardName}}</span>{{end}}
|
||||||
<span>{{.AuthorName}}</span>
|
</div>
|
||||||
<span>{{.CreatedLabel}}</span>
|
</div>
|
||||||
<span class="j13-post-item__replies">{{.CommentCount}} 回复</span>
|
<div class="j13-post-item__stats" aria-label="互动">
|
||||||
|
<span title="回复"><strong>{{.CommentCount}}</strong> 回复</span>
|
||||||
|
<span title="赞"><strong>{{.LikeCount}}</strong> 赞</span>
|
||||||
|
<span title="阅读"><strong>{{.ViewCount}}</strong> 阅读</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
{{define "shared/board_aside"}}
|
{{define "shared/board_aside"}}
|
||||||
<aside class="j13-aside j13-aside--left">
|
<aside class="j13-aside j13-aside--left">
|
||||||
|
<p class="j13-aside__section">浏览</p>
|
||||||
|
<a class="j13-aside__link{{if and (eq .ActiveBoard 0) (ne .Path "/boards") (ne .Path "/favorites")}} is-active{{end}}" href="/">全部帖子</a>
|
||||||
|
{{if .LoggedIn}}
|
||||||
|
<a class="j13-aside__link{{if eq .Path "/favorites"}} is-active{{end}}" href="/favorites">我的收藏</a>
|
||||||
|
{{end}}
|
||||||
<a class="j13-aside__link{{if eq .Path "/boards"}} is-active{{end}}" href="/boards">全部板块</a>
|
<a class="j13-aside__link{{if eq .Path "/boards"}} is-active{{end}}" href="/boards">全部板块</a>
|
||||||
<a class="j13-aside__link{{if and (eq .ActiveBoard 0) (ne .Path "/boards")}} is-active{{end}}" href="/">全部帖子</a>
|
<p class="j13-aside__section">板块</p>
|
||||||
{{range .Boards}}
|
{{range .Boards}}
|
||||||
<a class="j13-aside__link{{if eq $.ActiveBoard .ID}} is-active{{end}}" href="{{.Href}}">{{.Name}}</a>
|
<a class="j13-aside__link{{if eq $.ActiveBoard .ID}} is-active{{end}}" href="{{.Href}}">{{.Name}}</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -303,14 +303,21 @@ html[data-theme="dark"] .j13-brand__mark {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--j13-muted);
|
color: var(--j13-muted);
|
||||||
border-radius: var(--j13-radius);
|
border-radius: var(--j13-radius);
|
||||||
padding: 0.3rem 0.55rem;
|
padding: 0.3rem 0.5rem;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3rem;
|
||||||
transition: color var(--j13-ease), border-color var(--j13-ease), background-color var(--j13-ease);
|
transition: color var(--j13-ease), border-color var(--j13-ease), background-color var(--j13-ease);
|
||||||
}
|
}
|
||||||
|
.j13-theme-toggle__icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
.j13-theme-toggle:hover {
|
.j13-theme-toggle:hover {
|
||||||
color: var(--j13-accent-ink);
|
color: var(--j13-accent-ink);
|
||||||
border-color: var(--j13-accent);
|
border-color: var(--j13-accent);
|
||||||
@@ -415,6 +422,16 @@ html[data-theme="dark"] .j13-brand__mark {
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
top: calc(var(--j13-header-h) + 0.75rem);
|
top: calc(var(--j13-header-h) + 0.75rem);
|
||||||
}
|
}
|
||||||
|
.j13-aside__section {
|
||||||
|
margin: 0.85rem 0 0.35rem;
|
||||||
|
padding: 0 0.55rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 650;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--j13-muted);
|
||||||
|
}
|
||||||
|
.j13-aside__section:first-child { margin-top: 0.15rem; }
|
||||||
.j13-aside__link {
|
.j13-aside__link {
|
||||||
padding: 0.45rem 0.6rem;
|
padding: 0.45rem 0.6rem;
|
||||||
border-radius: var(--j13-radius);
|
border-radius: var(--j13-radius);
|
||||||
@@ -450,7 +467,7 @@ html[data-theme="dark"] .j13-brand__mark {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 0.75rem 1rem;
|
gap: 0.75rem 1rem;
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.65rem;
|
||||||
}
|
}
|
||||||
.j13-feed__title {
|
.j13-feed__title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -485,13 +502,44 @@ html[data-theme="dark"] .j13-brand__mark {
|
|||||||
background: var(--j13-hover);
|
background: var(--j13-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.j13-search-advanced {
|
||||||
|
margin: 0 0 0.75rem;
|
||||||
|
scroll-margin-top: 4.5rem;
|
||||||
|
}
|
||||||
|
.j13-search-advanced__summary {
|
||||||
|
list-style: none;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
color: var(--j13-muted);
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 0.25rem 0;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.j13-search-advanced__summary::-webkit-details-marker { display: none; }
|
||||||
|
.j13-search-advanced__summary::before {
|
||||||
|
content: "";
|
||||||
|
width: 0.4rem;
|
||||||
|
height: 0.4rem;
|
||||||
|
border-right: 1.5px solid currentColor;
|
||||||
|
border-bottom: 1.5px solid currentColor;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
transition: transform var(--j13-ease);
|
||||||
|
}
|
||||||
|
.j13-search-advanced[open] > .j13-search-advanced__summary::before {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
.j13-search-advanced__summary:hover { color: var(--j13-accent); }
|
||||||
|
.j13-search-advanced .j13-search { margin-top: 0.45rem; }
|
||||||
|
|
||||||
.j13-search {
|
.j13-search {
|
||||||
margin: 0 0 0.85rem;
|
margin: 0 0 0.85rem;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
scroll-margin-top: 4.5rem;
|
|
||||||
}
|
}
|
||||||
.j13-search--toolbar .j13-search__row {
|
.j13-search--toolbar .j13-search__row {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -541,38 +589,114 @@ html[data-theme="dark"] .j13-brand__mark {
|
|||||||
|
|
||||||
.j13-post-list { list-style: none; margin: 0; padding: 0; }
|
.j13-post-list { list-style: none; margin: 0; padding: 0; }
|
||||||
.j13-post-item {
|
.j13-post-item {
|
||||||
padding: 0.85rem 0.4rem;
|
display: grid;
|
||||||
margin: 0 -0.4rem;
|
grid-template-columns: 2.5rem minmax(0, 1fr) auto;
|
||||||
|
gap: 0.75rem 0.85rem;
|
||||||
|
padding: 1rem 0.45rem;
|
||||||
|
margin: 0 -0.45rem;
|
||||||
border-top: 1px solid var(--j13-border);
|
border-top: 1px solid var(--j13-border);
|
||||||
border-radius: var(--j13-radius);
|
border-radius: var(--j13-radius);
|
||||||
transition: background-color var(--j13-ease);
|
transition: background-color var(--j13-ease);
|
||||||
|
align-items: start;
|
||||||
}
|
}
|
||||||
.j13-post-item:first-child { border-top: 0; }
|
.j13-post-item:first-child { border-top: 0; }
|
||||||
.j13-post-item:hover { background: var(--j13-hover); }
|
.j13-post-item:hover { background: var(--j13-hover); }
|
||||||
|
.j13-post-item__avatar {
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: var(--j13-soft);
|
||||||
|
}
|
||||||
|
.j13-post-item__avatar img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.j13-post-item__mark {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--j13-accent-ink);
|
||||||
|
background: var(--j13-soft);
|
||||||
|
}
|
||||||
|
.j13-post-item__body { min-width: 0; }
|
||||||
|
.j13-post-item__byline {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.4rem 0.65rem;
|
||||||
|
align-items: baseline;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--j13-muted);
|
||||||
|
margin-bottom: 0.2rem;
|
||||||
|
}
|
||||||
|
.j13-post-item__byline a {
|
||||||
|
color: var(--j13-accent-ink);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.j13-post-item__byline a:hover { color: var(--j13-accent); }
|
||||||
.j13-post-item__title {
|
.j13-post-item__title {
|
||||||
|
display: inline-block;
|
||||||
color: var(--j13-text);
|
color: var(--j13-text);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 1.15rem;
|
font-size: 1.1rem;
|
||||||
letter-spacing: -0.02em;
|
letter-spacing: -0.02em;
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
transition: color var(--j13-ease);
|
transition: color var(--j13-ease);
|
||||||
}
|
}
|
||||||
.j13-post-item__title:hover { color: var(--j13-accent); }
|
.j13-post-item__title:hover { color: var(--j13-accent); }
|
||||||
|
.j13-post-item__excerpt {
|
||||||
|
margin: 0.3rem 0 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--j13-muted);
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
.j13-post-item__meta {
|
.j13-post-item__meta {
|
||||||
margin-top: 0.35rem;
|
margin-top: 0.4rem;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: var(--j13-muted);
|
color: var(--j13-muted);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.45rem;
|
gap: 0.4rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.j13-post-item__replies {
|
.j13-post-item__stats {
|
||||||
margin-left: auto;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.2rem;
|
||||||
|
align-items: flex-end;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
color: var(--j13-muted);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
padding-top: 0.15rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.j13-post-item__stats strong {
|
||||||
font-weight: 650;
|
font-weight: 650;
|
||||||
color: var(--j13-accent-ink);
|
color: var(--j13-accent-ink);
|
||||||
font-variant-numeric: tabular-nums;
|
}
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.j13-post-item {
|
||||||
|
grid-template-columns: 2.25rem minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
.j13-post-item__stats {
|
||||||
|
grid-column: 2;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 0.75rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.j13-widget__title {
|
.j13-widget__title {
|
||||||
|
|||||||
@@ -27,9 +27,15 @@ document.documentElement.dataset.j13Ssr = "1";
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
var btn = document.getElementById("j13-theme-toggle");
|
var btn = document.getElementById("j13-theme-toggle");
|
||||||
if (btn) {
|
if (btn) {
|
||||||
btn.textContent = LABELS[pref] || LABELS.system;
|
var label = btn.querySelector(".j13-theme-toggle__label");
|
||||||
btn.setAttribute("aria-label", "主题:" + (LABELS[pref] || ""));
|
var text = LABELS[pref] || LABELS.system;
|
||||||
btn.title = "当前:" + (LABELS[pref] || "") + "(点击切换)";
|
if (label) {
|
||||||
|
label.textContent = text;
|
||||||
|
} else {
|
||||||
|
btn.textContent = text;
|
||||||
|
}
|
||||||
|
btn.setAttribute("aria-label", "主题:" + text);
|
||||||
|
btn.title = "当前:" + text + "(点击切换)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user