feat: SSR 三栏右栏热门帖与 aside widgets
EOF Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
## A. 浏览与布局
|
## A. 浏览与布局
|
||||||
|
|
||||||
- [ ] 三栏布局:左导航 / 中 Feed / 右栏小组件
|
- [x] 三栏布局:左导航 / 中 Feed / 右栏小组件 — SSR(桌面;≤900px 隐藏右栏)
|
||||||
- [ ] 浅色 / 暗色主题;跟随系统偏好并本地记忆
|
- [ ] 浅色 / 暗色主题;跟随系统偏好并本地记忆
|
||||||
- [ ] 响应式:平板/手机收起侧栏
|
- [ ] 响应式:平板/手机收起侧栏
|
||||||
- [ ] 长列表虚拟滚动或等价流畅方案
|
- [ ] 长列表虚拟滚动或等价流畅方案
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
- [x] 板块筛选:全部 + 单板块
|
- [x] 板块筛选:全部 + 单板块
|
||||||
- [ ] 列表样式可配:`title` | `excerpt` | `thumbnail`
|
- [ ] 列表样式可配:`title` | `excerpt` | `thumbnail`
|
||||||
- [x] 搜索:关键词、标签、作者、仅标题(`title_only`)— SSR Feed 面板(`/` / `/board/:id`)
|
- [x] 搜索:关键词、标签、作者、仅标题(`title_only`)— SSR Feed 面板(`/` / `/board/:id`)
|
||||||
- [ ] 右栏:热门帖、标签云、最新评论、最新用户、友链(可开关排序)
|
- [x] 右栏:热门帖、标签云、最新评论、最新用户、友链(可开关排序)— SSR 热门固定 + `aside_widgets`;Admin 仅友链侧栏开关
|
||||||
- [ ] 登录用户右栏/侧边:签到与抽奖入口(入口暂在 `/profile` 钱包区)
|
- [ ] 登录用户右栏/侧边:签到与抽奖入口(入口暂在 `/profile` 钱包区)
|
||||||
- [ ] 下拉刷新(移动端)
|
- [ ] 下拉刷新(移动端)
|
||||||
- [ ] 可选伪静态:`/post/123.html` 等形式(后缀后台可配)
|
- [ ] 可选伪静态:`/post/123.html` 等形式(后缀后台可配)
|
||||||
|
|||||||
@@ -86,9 +86,10 @@
|
|||||||
|
|
||||||
### 2.3 右栏 RightPanel
|
### 2.3 右栏 RightPanel
|
||||||
|
|
||||||
- 登录用户:`AsideCheckInStrip`(签到 + 抽奖 + 积分入口)
|
- 登录用户:`AsideCheckInStrip`(签到 + 抽奖 + 积分入口)— **未迁**(入口仍在 `/profile`)
|
||||||
- 热门帖
|
- 热门帖 — **已迁** SSR(固定块)
|
||||||
- 可配置 widgets:`tag_cloud` / `recent_comments` / `recent_users` / `friend_links`(顺序与开关来自 settings)
|
- 可配置 widgets:`tag_cloud` / `recent_comments` / `recent_users` / `friend_links`(顺序与开关来自 `aside_widgets`)— **已迁** SSR;Admin 完整排序编辑器未迁,友链侧栏开关在 `/admin/friend-links`
|
||||||
|
- 桌面三列;≤900px 隐藏右栏
|
||||||
|
|
||||||
### 2.4 移动端
|
### 2.4 移动端
|
||||||
|
|
||||||
@@ -184,7 +185,7 @@
|
|||||||
| Dashboard | 看计数与待办;点进对应列表 |
|
| Dashboard | 看计数与待办;点进对应列表 |
|
||||||
| Boards | 拖拽或数字排序;图标/色板选择;增删改 |
|
| Boards | 拖拽或数字排序;图标/色板选择;增删改 |
|
||||||
| Pages | 列表发布开关;进编辑器写正文;nav/footer 勾选 |
|
| Pages | 列表发布开关;进编辑器写正文;nav/footer 勾选 |
|
||||||
| Links | 品牌友链增删;申请通过/拒绝;回链检测开关;nav/footer 开关 — SSR `/admin/friend-links`(aside / 复检按钮未迁) |
|
| Links | 品牌友链增删;申请通过/拒绝;回链检测开关;nav/footer/aside 开关 — SSR `/admin/friend-links`(完整侧栏组件编辑 / 复检按钮未迁) |
|
||||||
| Posts | 按状态筛;通过/拒绝;置顶/版顶/精华/锁编/锁评;进回收站恢复/清除 |
|
| Posts | 按状态筛;通过/拒绝;置顶/版顶/精华/锁编/锁评;进回收站恢复/清除 |
|
||||||
| Comments | 审核;修订查看;回收站 |
|
| Comments | 审核;修订查看;回收站 |
|
||||||
| Reports | 处理动作四选一 |
|
| Reports | 处理动作四选一 |
|
||||||
|
|||||||
@@ -92,6 +92,8 @@ web_src/ → public/assets/
|
|||||||
|
|
||||||
Feed 搜索:`/` / `/board/:id` 面板(`keyword` / `tag` / `author` / `title_only`);排序与分页 URL 保参;顶栏链到 `/#search`。
|
Feed 搜索:`/` / `/board/:id` 面板(`keyword` / `tag` / `author` / `title_only`);排序与分页 URL 保参;顶栏链到 `/#search`。
|
||||||
|
|
||||||
友链:`/links`(列表、登录申请/取消、Logo 上传);Admin `/admin/friend-links`(品牌增删、审核、入口开关)。
|
右栏:首页与帖详情三栏;固定热门帖 + `aside_widgets`(标签云 / 最新评论 / 最新用户 / 友链);Admin 友链页可开关侧栏友链。
|
||||||
|
|
||||||
|
友链:`/links`(列表、登录申请/取消、Logo 上传);Admin `/admin/friend-links`(品牌增删、审核、nav/footer/aside 入口开关)。
|
||||||
|
|
||||||
Admin:`/admin/dashboard`、`/admin/boards`、`/admin/moderation`、`/admin/settings`(品牌/限流/敏感词/SMTP)、`/admin/friend-links`。
|
Admin:`/admin/dashboard`、`/admin/boards`、`/admin/moderation`、`/admin/settings`(品牌/限流/敏感词/SMTP)、`/admin/friend-links`。
|
||||||
@@ -54,6 +54,7 @@ func funcMap() template.FuncMap {
|
|||||||
"postURL": func(id uint) string {
|
"postURL": func(id uint) string {
|
||||||
return fmt.Sprintf("/post/%d", id)
|
return fmt.Sprintf("/post/%d", id)
|
||||||
},
|
},
|
||||||
|
"queryEscape": url.QueryEscape,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,13 +61,17 @@ body.j13-body {
|
|||||||
.j13-nav a:hover { text-decoration: underline; }
|
.j13-nav a:hover { text-decoration: underline; }
|
||||||
|
|
||||||
.j13-layout {
|
.j13-layout {
|
||||||
max-width: 1100px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 200px 1fr;
|
grid-template-columns: 200px 1fr 240px;
|
||||||
gap: 1.25rem;
|
gap: 1.25rem;
|
||||||
}
|
}
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.j13-layout { grid-template-columns: 200px 1fr; }
|
||||||
|
.j13-aside--right { display: none; }
|
||||||
|
}
|
||||||
@media (max-width: 800px) {
|
@media (max-width: 800px) {
|
||||||
.j13-layout { grid-template-columns: 1fr; }
|
.j13-layout { grid-template-columns: 1fr; }
|
||||||
}
|
}
|
||||||
@@ -77,6 +81,9 @@ body.j13-body {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
}
|
}
|
||||||
|
.j13-aside--right {
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
.j13-aside__link {
|
.j13-aside__link {
|
||||||
padding: 0.45rem 0.65rem;
|
padding: 0.45rem 0.65rem;
|
||||||
border-radius: var(--j13-radius);
|
border-radius: var(--j13-radius);
|
||||||
@@ -90,6 +97,76 @@ body.j13-body {
|
|||||||
color: var(--j13-accent);
|
color: var(--j13-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.j13-widget__title {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--j13-muted);
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
.j13-widget__list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
.j13-widget__list a {
|
||||||
|
color: var(--j13-text);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
.j13-widget__list a:hover { color: var(--j13-accent); }
|
||||||
|
.j13-widget__meta {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--j13-muted);
|
||||||
|
margin-top: 0.1rem;
|
||||||
|
}
|
||||||
|
.j13-widget__empty {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--j13-muted);
|
||||||
|
}
|
||||||
|
.j13-widget__tags {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.35rem 0.5rem;
|
||||||
|
}
|
||||||
|
.j13-widget__tags a {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--j13-accent);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.j13-widget__tags a:hover { text-decoration: underline; }
|
||||||
|
.j13-widget__count {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--j13-muted);
|
||||||
|
margin-left: 0.15rem;
|
||||||
|
}
|
||||||
|
.j13-widget__links a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
.j13-widget__logo {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.j13-widget__more {
|
||||||
|
margin: 0.5rem 0 0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.j13-widget__more a { color: var(--j13-accent); }
|
||||||
|
|
||||||
.j13-main {
|
.j13-main {
|
||||||
background: var(--j13-surface);
|
background: var(--j13-surface);
|
||||||
border: 1px solid var(--j13-border);
|
border: 1px solid var(--j13-border);
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ type adminFriendLinksData struct {
|
|||||||
PendingCount int64
|
PendingCount int64
|
||||||
NavShow bool
|
NavShow bool
|
||||||
FooterShow bool
|
FooterShow bool
|
||||||
|
AsideShow bool
|
||||||
ReciprocalCheck bool
|
ReciprocalCheck bool
|
||||||
BrandName string
|
BrandName string
|
||||||
BrandURL string
|
BrandURL string
|
||||||
@@ -60,6 +61,7 @@ func (d Deps) renderAdminFriendLinks(ctx *webctx.Context, errMsg string) {
|
|||||||
AdminChrome: chrome,
|
AdminChrome: chrome,
|
||||||
NavShow: d.Settings.NavShowFriendLinks(),
|
NavShow: d.Settings.NavShowFriendLinks(),
|
||||||
FooterShow: d.Settings.FooterShowFriendLinks(),
|
FooterShow: d.Settings.FooterShowFriendLinks(),
|
||||||
|
AsideShow: d.Settings.AsideShowFriendLinks(),
|
||||||
ReciprocalCheck: d.Settings.FriendLinkReciprocalCheckEnabled(),
|
ReciprocalCheck: d.Settings.FriendLinkReciprocalCheckEnabled(),
|
||||||
}
|
}
|
||||||
brand := d.Settings.SiteBranding()
|
brand := d.Settings.SiteBranding()
|
||||||
@@ -105,6 +107,7 @@ func (d Deps) AdminFriendLinksSettingsPost(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
_ = d.Settings.SetNavShowFriendLinks(c.PostForm("nav_show") == "1" || c.PostForm("nav_show") == "on")
|
_ = d.Settings.SetNavShowFriendLinks(c.PostForm("nav_show") == "1" || c.PostForm("nav_show") == "on")
|
||||||
_ = d.Settings.SetFooterShowFriendLinks(c.PostForm("footer_show") == "1" || c.PostForm("footer_show") == "on")
|
_ = d.Settings.SetFooterShowFriendLinks(c.PostForm("footer_show") == "1" || c.PostForm("footer_show") == "on")
|
||||||
|
_ = d.Settings.SetAsideFriendLinksEnabled(c.PostForm("aside_show") == "1" || c.PostForm("aside_show") == "on")
|
||||||
_ = d.Settings.SetFriendLinkReciprocalCheckEnabled(c.PostForm("reciprocal_check") == "1" || c.PostForm("reciprocal_check") == "on")
|
_ = d.Settings.SetFriendLinkReciprocalCheckEnabled(c.PostForm("reciprocal_check") == "1" || c.PostForm("reciprocal_check") == "on")
|
||||||
ctx.SetFlash("友链入口设置已保存")
|
ctx.SetFlash("友链入口设置已保存")
|
||||||
ctx.Redirect("/admin/friend-links")
|
ctx.Redirect("/admin/friend-links")
|
||||||
|
|||||||
@@ -34,6 +34,54 @@ type BoardView struct {
|
|||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RightAsideHotPost 右栏热门帖
|
||||||
|
type RightAsideHotPost struct {
|
||||||
|
ID uint
|
||||||
|
Title string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RightAsideTag 右栏标签
|
||||||
|
type RightAsideTag struct {
|
||||||
|
Name string
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
// RightAsideComment 右栏最新评论
|
||||||
|
type RightAsideComment struct {
|
||||||
|
PostID uint
|
||||||
|
Author string
|
||||||
|
Excerpt string
|
||||||
|
PostTitle string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RightAsideUser 右栏最新用户
|
||||||
|
type RightAsideUser struct {
|
||||||
|
ID uint
|
||||||
|
Nickname string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RightAsideFriendLink 右栏友链
|
||||||
|
type RightAsideFriendLink struct {
|
||||||
|
Name string
|
||||||
|
URL string
|
||||||
|
Logo string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RightAsideWidget 按 aside_widgets 顺序的一块(仅已开启)
|
||||||
|
type RightAsideWidget struct {
|
||||||
|
Kind string // tag_cloud | recent_comments | recent_users | friend_links
|
||||||
|
Tags []RightAsideTag
|
||||||
|
Comments []RightAsideComment
|
||||||
|
Users []RightAsideUser
|
||||||
|
FriendLinks []RightAsideFriendLink
|
||||||
|
}
|
||||||
|
|
||||||
|
// RightAsideData 右栏:固定热门 + 可配置 widgets
|
||||||
|
type RightAsideData struct {
|
||||||
|
HotPosts []RightAsideHotPost
|
||||||
|
Widgets []RightAsideWidget
|
||||||
|
}
|
||||||
|
|
||||||
// PageChrome 布局公共字段
|
// PageChrome 布局公共字段
|
||||||
type PageChrome struct {
|
type PageChrome struct {
|
||||||
Title string
|
Title string
|
||||||
@@ -55,6 +103,7 @@ type PageChrome struct {
|
|||||||
ViewerPoints int // 登录用户当前积分;未登录为 0
|
ViewerPoints int // 登录用户当前积分;未登录为 0
|
||||||
ShowFriendLinksNav bool
|
ShowFriendLinksNav bool
|
||||||
ShowFriendLinksFooter bool
|
ShowFriendLinksFooter bool
|
||||||
|
RightAside RightAsideData
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Deps) ctx(c *gin.Context) *webctx.Context {
|
func (d Deps) ctx(c *gin.Context) *webctx.Context {
|
||||||
@@ -111,9 +160,73 @@ func (d Deps) chrome(ctx *webctx.Context, title, desc, inner string) PageChrome
|
|||||||
ViewerPoints: viewerPoints,
|
ViewerPoints: viewerPoints,
|
||||||
ShowFriendLinksNav: d.Settings.NavShowFriendLinks(),
|
ShowFriendLinksNav: d.Settings.NavShowFriendLinks(),
|
||||||
ShowFriendLinksFooter: d.Settings.FooterShowFriendLinks(),
|
ShowFriendLinksFooter: d.Settings.FooterShowFriendLinks(),
|
||||||
|
RightAside: d.loadRightAside(brand),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
rightAsideHotLimit = 5
|
||||||
|
rightAsideTagLimit = 24
|
||||||
|
rightAsideCommentLimit = 8
|
||||||
|
rightAsideUserLimit = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d Deps) loadRightAside(brand services.SiteBranding) RightAsideData {
|
||||||
|
out := RightAsideData{
|
||||||
|
HotPosts: []RightAsideHotPost{},
|
||||||
|
Widgets: []RightAsideWidget{},
|
||||||
|
}
|
||||||
|
if d.Post != nil {
|
||||||
|
if items, err := d.Post.HotPosts(rightAsideHotLimit); err == nil {
|
||||||
|
for _, it := range items {
|
||||||
|
out.HotPosts = append(out.HotPosts, RightAsideHotPost{ID: it.ID, Title: it.Title})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, w := range d.Settings.AsideWidgets() {
|
||||||
|
if !w.Enabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
block := RightAsideWidget{Kind: w.ID}
|
||||||
|
switch w.ID {
|
||||||
|
case services.AsideWidgetTagCloud:
|
||||||
|
if d.Post != nil {
|
||||||
|
if tags, err := d.Post.PopularTags(rightAsideTagLimit); err == nil {
|
||||||
|
for _, t := range tags {
|
||||||
|
block.Tags = append(block.Tags, RightAsideTag{Name: t.Name, Count: t.Count})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case services.AsideWidgetRecentComments:
|
||||||
|
if d.Comment != nil {
|
||||||
|
if list, err := d.Comment.ListRecentPublic(rightAsideCommentLimit); err == nil {
|
||||||
|
for _, c := range list {
|
||||||
|
block.Comments = append(block.Comments, RightAsideComment{
|
||||||
|
PostID: c.PostID, Author: c.Author, Excerpt: c.Excerpt, PostTitle: c.PostTitle,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case services.AsideWidgetRecentUsers:
|
||||||
|
if d.User != nil {
|
||||||
|
if list, err := d.User.ListRecentRegistered(rightAsideUserLimit); err == nil {
|
||||||
|
for _, u := range list {
|
||||||
|
block.Users = append(block.Users, RightAsideUser{ID: u.ID, Nickname: u.Nickname})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case services.AsideWidgetFriendLinks:
|
||||||
|
for _, l := range brand.FriendLinks {
|
||||||
|
block.FriendLinks = append(block.FriendLinks, RightAsideFriendLink{Name: l.Name, URL: l.URL, Logo: l.Logo})
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out.Widgets = append(out.Widgets, block)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func firstRuneOr(s, fallback string) string {
|
func firstRuneOr(s, fallback string) string {
|
||||||
s = strings.TrimSpace(s)
|
s = strings.TrimSpace(s)
|
||||||
if s == "" {
|
if s == "" {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
|
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
|
||||||
<label class="j13-check"><input type="checkbox" name="nav_show" value="1"{{if .NavShow}} checked{{end}}/> 导航显示友链入口</label>
|
<label class="j13-check"><input type="checkbox" name="nav_show" value="1"{{if .NavShow}} checked{{end}}/> 导航显示友链入口</label>
|
||||||
<label class="j13-check"><input type="checkbox" name="footer_show" value="1"{{if .FooterShow}} checked{{end}}/> 页脚显示友链入口</label>
|
<label class="j13-check"><input type="checkbox" name="footer_show" value="1"{{if .FooterShow}} checked{{end}}/> 页脚显示友链入口</label>
|
||||||
|
<label class="j13-check"><input type="checkbox" name="aside_show" value="1"{{if .AsideShow}} checked{{end}}/> 侧栏显示友链</label>
|
||||||
<label class="j13-check"><input type="checkbox" name="reciprocal_check" value="1"{{if .ReciprocalCheck}} checked{{end}}/> 启用回链检测</label>
|
<label class="j13-check"><input type="checkbox" name="reciprocal_check" value="1"{{if .ReciprocalCheck}} checked{{end}}/> 启用回链检测</label>
|
||||||
<button type="submit">保存设置</button>
|
<button type="submit">保存设置</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<main class="j13-main">
|
<main class="j13-main">
|
||||||
{{template "home/feed" .}}
|
{{template "home/feed" .}}
|
||||||
</main>
|
</main>
|
||||||
|
{{template "shared/right_aside" .}}
|
||||||
</div>
|
</div>
|
||||||
{{template "base/footer" .}}
|
{{template "base/footer" .}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<main class="j13-main">
|
<main class="j13-main">
|
||||||
{{template "post/body" .}}
|
{{template "post/body" .}}
|
||||||
</main>
|
</main>
|
||||||
|
{{template "shared/right_aside" .}}
|
||||||
</div>
|
</div>
|
||||||
{{template "base/footer" .}}
|
{{template "base/footer" .}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
74
templates/shared/right_aside.tmpl
Normal file
74
templates/shared/right_aside.tmpl
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
{{define "shared/right_aside"}}
|
||||||
|
<aside class="j13-aside j13-aside--right" aria-label="侧栏">
|
||||||
|
<section class="j13-widget">
|
||||||
|
<h2 class="j13-widget__title">热门讨论</h2>
|
||||||
|
{{if .RightAside.HotPosts}}
|
||||||
|
<ul class="j13-widget__list">
|
||||||
|
{{range .RightAside.HotPosts}}
|
||||||
|
<li><a href="{{postURL .ID}}">{{.Title}}</a></li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
{{else}}
|
||||||
|
<p class="j13-widget__empty">暂无热门帖</p>
|
||||||
|
{{end}}
|
||||||
|
</section>
|
||||||
|
{{range .RightAside.Widgets}}
|
||||||
|
<section class="j13-widget">
|
||||||
|
{{if eq .Kind "tag_cloud"}}
|
||||||
|
<h2 class="j13-widget__title">标签</h2>
|
||||||
|
{{if .Tags}}
|
||||||
|
<ul class="j13-widget__tags">
|
||||||
|
{{range .Tags}}
|
||||||
|
<li><a href="/?tag={{queryEscape .Name}}">{{.Name}}</a><span class="j13-widget__count">{{.Count}}</span></li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
{{else}}
|
||||||
|
<p class="j13-widget__empty">暂无标签</p>
|
||||||
|
{{end}}
|
||||||
|
{{else if eq .Kind "recent_comments"}}
|
||||||
|
<h2 class="j13-widget__title">最新评论</h2>
|
||||||
|
{{if .Comments}}
|
||||||
|
<ul class="j13-widget__list">
|
||||||
|
{{range .Comments}}
|
||||||
|
<li>
|
||||||
|
<a href="{{postURL .PostID}}">{{.Excerpt}}</a>
|
||||||
|
<span class="j13-widget__meta">{{.Author}} · {{.PostTitle}}</span>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
{{else}}
|
||||||
|
<p class="j13-widget__empty">暂无评论</p>
|
||||||
|
{{end}}
|
||||||
|
{{else if eq .Kind "recent_users"}}
|
||||||
|
<h2 class="j13-widget__title">新用户</h2>
|
||||||
|
{{if .Users}}
|
||||||
|
<ul class="j13-widget__list">
|
||||||
|
{{range .Users}}
|
||||||
|
<li><a href="/user/{{.ID}}">{{.Nickname}}</a></li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
{{else}}
|
||||||
|
<p class="j13-widget__empty">暂无用户</p>
|
||||||
|
{{end}}
|
||||||
|
{{else if eq .Kind "friend_links"}}
|
||||||
|
<h2 class="j13-widget__title">友链</h2>
|
||||||
|
{{if .FriendLinks}}
|
||||||
|
<ul class="j13-widget__list j13-widget__links">
|
||||||
|
{{range .FriendLinks}}
|
||||||
|
<li>
|
||||||
|
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">
|
||||||
|
{{if .Logo}}<img src="{{.Logo}}" alt="" class="j13-widget__logo"/>{{end}}
|
||||||
|
{{.Name}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
<p class="j13-widget__more"><a href="/links">全部友链</a></p>
|
||||||
|
{{else}}
|
||||||
|
<p class="j13-widget__empty">暂无友链 · <a href="/links">去看看</a></p>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</section>
|
||||||
|
{{end}}
|
||||||
|
</aside>
|
||||||
|
{{end}}
|
||||||
@@ -61,13 +61,17 @@ body.j13-body {
|
|||||||
.j13-nav a:hover { text-decoration: underline; }
|
.j13-nav a:hover { text-decoration: underline; }
|
||||||
|
|
||||||
.j13-layout {
|
.j13-layout {
|
||||||
max-width: 1100px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 200px 1fr;
|
grid-template-columns: 200px 1fr 240px;
|
||||||
gap: 1.25rem;
|
gap: 1.25rem;
|
||||||
}
|
}
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.j13-layout { grid-template-columns: 200px 1fr; }
|
||||||
|
.j13-aside--right { display: none; }
|
||||||
|
}
|
||||||
@media (max-width: 800px) {
|
@media (max-width: 800px) {
|
||||||
.j13-layout { grid-template-columns: 1fr; }
|
.j13-layout { grid-template-columns: 1fr; }
|
||||||
}
|
}
|
||||||
@@ -77,6 +81,9 @@ body.j13-body {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
}
|
}
|
||||||
|
.j13-aside--right {
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
.j13-aside__link {
|
.j13-aside__link {
|
||||||
padding: 0.45rem 0.65rem;
|
padding: 0.45rem 0.65rem;
|
||||||
border-radius: var(--j13-radius);
|
border-radius: var(--j13-radius);
|
||||||
@@ -90,6 +97,76 @@ body.j13-body {
|
|||||||
color: var(--j13-accent);
|
color: var(--j13-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.j13-widget__title {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--j13-muted);
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
.j13-widget__list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
.j13-widget__list a {
|
||||||
|
color: var(--j13-text);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
.j13-widget__list a:hover { color: var(--j13-accent); }
|
||||||
|
.j13-widget__meta {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--j13-muted);
|
||||||
|
margin-top: 0.1rem;
|
||||||
|
}
|
||||||
|
.j13-widget__empty {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--j13-muted);
|
||||||
|
}
|
||||||
|
.j13-widget__tags {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.35rem 0.5rem;
|
||||||
|
}
|
||||||
|
.j13-widget__tags a {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--j13-accent);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.j13-widget__tags a:hover { text-decoration: underline; }
|
||||||
|
.j13-widget__count {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--j13-muted);
|
||||||
|
margin-left: 0.15rem;
|
||||||
|
}
|
||||||
|
.j13-widget__links a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
.j13-widget__logo {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.j13-widget__more {
|
||||||
|
margin: 0.5rem 0 0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.j13-widget__more a { color: var(--j13-accent); }
|
||||||
|
|
||||||
.j13-main {
|
.j13-main {
|
||||||
background: var(--j13-surface);
|
background: var(--j13-surface);
|
||||||
border: 1px solid var(--j13-border);
|
border: 1px solid var(--j13-border);
|
||||||
|
|||||||
Reference in New Issue
Block a user