feat: 发评限流与品牌 Logo/Favicon/OG 上传
发评挂 RateLimiter comment;设置页支持品牌图上传/清除,并写入 favicon 与 og:image。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -189,7 +189,8 @@
|
||||
- [x] 敏感词:`forum_settings.filter_words` 读写 + 热更 — SSR `/admin/settings`
|
||||
- [x] 基础限流(post/comment/register/login/window)— SSR;完整 Limits 字数等未迁
|
||||
- [x] SMTP 配置与测试信 — SSR `/admin/settings` 邮件区
|
||||
- [x] 站点品牌文案:名称、标语、简介、keywords、Logo 字标、ICP — SSR(Logo/Favicon/OG 上传未迁)
|
||||
- [x] 站点品牌文案:名称、标语、简介、keywords、Logo 字标、ICP — SSR `/admin/settings`
|
||||
- [x] Logo / Favicon / 默认 OG 图上传与清除 — SSR `/admin/settings/brand/upload|clear`;`<head>` favicon + og:image
|
||||
- [x] SQLite 一键备份与下载 — SSR `/admin/settings`(仅 sqlite;写入 data 目录后下载)
|
||||
|
||||
---
|
||||
@@ -199,7 +200,7 @@
|
||||
- [x] `GET /health` — `routers/setup.go`
|
||||
- [x] `robots.txt` / `sitemap.xml` — SSR 同源路径
|
||||
- [x] 静态上传文件可达 — `Static /uploads` → data/uploads
|
||||
- [x] 限流:发帖、注册、登录、举报、私信、友链 — SSR 写路径已挂 `RateLimiter`(评论动作键服务层有、web 发评尚未挂,可后补)
|
||||
- [x] 限流:发帖、评论、注册、登录、举报、私信、友链 — SSR 写路径已挂 `RateLimiter`
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
| `/admin/dashboard` | 概览计数 | 已迁 |
|
||||
| `/admin/boards` | 板块 CRUD | 已迁 |
|
||||
| `/admin/moderation` | 待审帖/评 通过/拒绝 | 已迁 |
|
||||
| `/admin/settings` | 品牌 + 基础限流 + 敏感词 + SMTP | 已迁 |
|
||||
| `/admin/settings` | 品牌(含 Logo/Favicon/OG 上传)+ 基础限流 + 敏感词 + SMTP + 备份 | 已迁 |
|
||||
| `/admin/friend-links` | 品牌友链、申请审核、入口开关 | 已迁 |
|
||||
| `/admin/pages` | 站点单页 CRUD / 发布 | 已迁 |
|
||||
| `/admin/reports` | 举报处理 | 已迁 |
|
||||
@@ -200,7 +200,7 @@
|
||||
| Badges | 定义自动/限定徽章;颁发/收回限定 — SSR `/admin/badges` |
|
||||
| Media | 分类浏览;单删/批量删;同步索引 — SSR `/admin/media` |
|
||||
| Media | 分类浏览;批量删 |
|
||||
| Settings | Tab:论坛限制、侧栏组件、伪静态、邮件、OIDC+客户端、Gitea、存储、品牌、敏感词、备份 — SSR `/admin/settings` 已迁品牌/限流/侧栏/敏感词/邮件/SQLite 备份(OIDC/Gitea/存储/伪静态未迁) |
|
||||
| Settings | Tab:论坛限制、侧栏组件、伪静态、邮件、OIDC+客户端、Gitea、存储、品牌、敏感词、备份 — SSR `/admin/settings` 已迁品牌(含 Logo/Favicon/OG)/限流/侧栏/敏感词/邮件/SQLite 备份(OIDC/Gitea/存储/伪静态未迁) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -12,59 +12,36 @@
|
||||
|
||||
| 项 | 值 |
|
||||
|----|-----|
|
||||
| **上一刀** | P2:Admin 设等级 + 用户页徽章;勾选 Exp / 防刷 / 自动徽章 |
|
||||
| **下一刀** | **P3 体验**(主题 / 伪静态 / 编辑器)或 **后置**(OIDC / 存储热切换 / Gitea);需点名 |
|
||||
| **工作区** | 应干净;有未提交改动时先处理再开新刀 |
|
||||
| **上一刀** | P0:发评限流 + 品牌 Logo/Favicon/OG 上传与 head meta |
|
||||
| **下一刀** | **P3 / 后置**(需点名:伪静态、完整 Limits、OIDC、S3 热切换、编辑器、嵌套评论等) |
|
||||
| **工作区** | 应干净 |
|
||||
|
||||
---
|
||||
|
||||
## 一句话状态
|
||||
|
||||
核心论坛 + Admin P1 + §H 成长/防刷(服务层 + Admin 设等级)**已齐**。余量:P3 体验打磨与后置产品化(OIDC / S3 热切换 / Gitea)。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
core[核心论坛已迁]
|
||||
admin[Admin用户徽章媒体]
|
||||
growth[等级徽章防刷]
|
||||
polish[主题伪静态编辑器]
|
||||
deferred[OIDC存储Gitea]
|
||||
core --> admin --> growth --> polish
|
||||
deferred -.-> polish
|
||||
```
|
||||
核心论坛、Admin P1、§H 成长/防刷、P0 品牌图与发评限流 **已齐**。余量主要是体验打磨与后置产品化。
|
||||
|
||||
---
|
||||
|
||||
## 已完成里程碑
|
||||
|
||||
### 公开站 / Admin / §H
|
||||
## 已完成(摘)
|
||||
|
||||
| 域 | 状态 |
|
||||
|----|------|
|
||||
| 五种帖类型、评论、私信、友链、单页、§O | 已迁 |
|
||||
| Admin:users / badges / media / settings 基础 | 已迁 |
|
||||
| Exp→Lv、Admin 设等级、用户页徽章展示 | 已迁 |
|
||||
| 短龄同 IP 互刷拒绝分成 | 已落地(unlock 服务) |
|
||||
| 自动徽章 EvaluateAuto | 已落地(访问用户页触发) |
|
||||
|
||||
### 近期提交(摘)
|
||||
|
||||
media → **等级设定 + 徽章展示 / §H 勾选**
|
||||
| 五种帖 + Admin users/badges/media | 已迁 |
|
||||
| §H Exp/等级/徽章/防刷 | 已迁 |
|
||||
| 发评 `RateLimiter` comment | 已挂 |
|
||||
| 品牌 Logo/Favicon/OG 上传 + favicon/og meta | 已迁 |
|
||||
|
||||
---
|
||||
|
||||
## 未完成分层
|
||||
|
||||
### P0 — 小清理(可夹带)
|
||||
### P0 余量
|
||||
|
||||
- 发评路径挂 `RateLimiter` `comment` 动作
|
||||
- Logo/Favicon/OG 上传、头像裁剪
|
||||
- 头像裁剪等边角
|
||||
|
||||
### P2 — 成长与防刷
|
||||
|
||||
~~已完成主路径~~;无独立「自动徽章定时任务」Admin UI(可接受)。
|
||||
|
||||
### P3 — 体验 / 编辑器 ← 当前候选
|
||||
### P3 — 体验 / 编辑器
|
||||
|
||||
- 主题、侧栏折叠、虚拟滚动、`feed_list_style`、伪静态 Admin
|
||||
- TipTap / Markdown 双模
|
||||
@@ -79,19 +56,9 @@ media → **等级设定 + 徽章展示 / §H 勾选**
|
||||
|
||||
---
|
||||
|
||||
## 默认「继续」刀序
|
||||
## 默认「继续」
|
||||
|
||||
| 序 | 刀 | 产出 |
|
||||
|----|----|------|
|
||||
| ✓ | 核心闭环 + Admin P1 | … |
|
||||
| ✓ | §H 等级/防刷/徽章展示 | … |
|
||||
| → | 需点名 P3 或后置 | … |
|
||||
|
||||
---
|
||||
|
||||
## 已完成计划
|
||||
|
||||
- [admin-users](plans/admin-users.md) · [admin-badges](plans/admin-badges.md) · [admin-media](plans/admin-media.md)
|
||||
需点名下一刀;未点名时优先小清理或明确产品偏好。
|
||||
|
||||
---
|
||||
|
||||
@@ -99,7 +66,6 @@ media → **等级设定 + 徽章展示 / §H 勾选**
|
||||
|
||||
| 问题 | 看哪里 |
|
||||
|------|--------|
|
||||
| 某功能做没做? | [02-features.md](02-features.md) 复选框 |
|
||||
| 某功能做没做? | [02-features.md](02-features.md) |
|
||||
| 现在该干什么? | 本文「当前指针」 |
|
||||
| 路由迁没迁? | [06-pages-ux.md](06-pages-ux.md) §1 |
|
||||
| 目录约定? | [08-gitea-ssr-architecture.md](08-gitea-ssr-architecture.md) |
|
||||
|
||||
@@ -53,6 +53,12 @@ body.j13-body {
|
||||
place-items: center;
|
||||
font-weight: 700;
|
||||
}
|
||||
.j13-brand__logo {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
object-fit: contain;
|
||||
border-radius: var(--j13-radius);
|
||||
}
|
||||
.j13-brand__text { display: flex; flex-direction: column; }
|
||||
.j13-brand__text strong { font-size: 1rem; }
|
||||
.j13-brand__slogan { font-size: 0.75rem; color: var(--j13-muted); }
|
||||
@@ -1073,6 +1079,27 @@ points-only[data-locked="false"] {
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
.j13-admin-brand-assets {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
|
||||
gap: 1rem;
|
||||
margin: 0 0 1.5rem;
|
||||
}
|
||||
.j13-admin-brand-asset {
|
||||
border: 1px solid var(--j13-border);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
.j13-admin-brand-preview {
|
||||
max-width: 8rem;
|
||||
max-height: 4rem;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.j13-admin-brand-preview--sm {
|
||||
max-width: 2rem;
|
||||
max-height: 2rem;
|
||||
}
|
||||
.j13-admin-media-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -23,6 +23,14 @@ type pageData struct {
|
||||
SiteName string
|
||||
Slogan string
|
||||
LogoMark string
|
||||
LogoURL string
|
||||
FaviconURL string
|
||||
OGImageURL string
|
||||
ShowFriendLinksFooter bool
|
||||
FooterPages []struct {
|
||||
Title string
|
||||
Slug string
|
||||
}
|
||||
CSRF string
|
||||
Error string
|
||||
Flash string
|
||||
|
||||
@@ -520,6 +520,90 @@ func (d Deps) AdminSettingsBrandPost(c *gin.Context) {
|
||||
ctx.Redirect("/admin/settings")
|
||||
}
|
||||
|
||||
// AdminSettingsBrandUploadPost 上传 Logo / Favicon / OG 图
|
||||
func (d Deps) AdminSettingsBrandUploadPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if !ctx.CheckCSRF() {
|
||||
d.renderAdminSettings(ctx, "无效请求,请重试")
|
||||
return
|
||||
}
|
||||
if d.Store == nil {
|
||||
d.renderAdminSettings(ctx, "上传存储未就绪")
|
||||
return
|
||||
}
|
||||
kind := strings.TrimSpace(c.PostForm("kind"))
|
||||
if kind != "logo" && kind != "favicon" && kind != "og_image" {
|
||||
d.renderAdminSettings(ctx, "请选择 Logo、Favicon 或 OG 图")
|
||||
return
|
||||
}
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
d.renderAdminSettings(ctx, "请选择图片文件")
|
||||
return
|
||||
}
|
||||
const maxBytes = 2 * 1024 * 1024
|
||||
if file.Size > maxBytes {
|
||||
d.renderAdminSettings(ctx, "图片不能超过 2MB")
|
||||
return
|
||||
}
|
||||
url, err := services.SaveUploadedImage(d.Store, file, services.UploadCategorySite, kind)
|
||||
if err != nil {
|
||||
d.renderAdminSettings(ctx, err.Error())
|
||||
return
|
||||
}
|
||||
prev := d.Settings.SiteBranding()
|
||||
switch kind {
|
||||
case "logo":
|
||||
_ = d.Settings.SetSiteLogo(url)
|
||||
d.Store.DeleteByURL(prev.Logo)
|
||||
ctx.SetFlash("Logo 已上传")
|
||||
case "favicon":
|
||||
_ = d.Settings.SetSiteFavicon(url)
|
||||
d.Store.DeleteByURL(prev.Favicon)
|
||||
ctx.SetFlash("Favicon 已上传")
|
||||
case "og_image":
|
||||
_ = d.Settings.SetSiteOGImage(url)
|
||||
d.Store.DeleteByURL(prev.OGImage)
|
||||
ctx.SetFlash("默认 OG 图已上传")
|
||||
}
|
||||
ctx.Redirect("/admin/settings")
|
||||
}
|
||||
|
||||
// AdminSettingsBrandClearPost 清除品牌图片
|
||||
func (d Deps) AdminSettingsBrandClearPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if !ctx.CheckCSRF() {
|
||||
d.renderAdminSettings(ctx, "无效请求,请重试")
|
||||
return
|
||||
}
|
||||
kind := strings.TrimSpace(c.PostForm("kind"))
|
||||
brand := d.Settings.SiteBranding()
|
||||
switch kind {
|
||||
case "logo":
|
||||
_ = d.Settings.SetSiteLogo("")
|
||||
if d.Store != nil {
|
||||
d.Store.DeleteByURL(brand.Logo)
|
||||
}
|
||||
ctx.SetFlash("已清除 Logo")
|
||||
case "favicon":
|
||||
_ = d.Settings.SetSiteFavicon("")
|
||||
if d.Store != nil {
|
||||
d.Store.DeleteByURL(brand.Favicon)
|
||||
}
|
||||
ctx.SetFlash("已清除 Favicon")
|
||||
case "og_image":
|
||||
_ = d.Settings.SetSiteOGImage("")
|
||||
if d.Store != nil {
|
||||
d.Store.DeleteByURL(brand.OGImage)
|
||||
}
|
||||
ctx.SetFlash("已清除 OG 图")
|
||||
default:
|
||||
d.renderAdminSettings(ctx, "无效的资源类型")
|
||||
return
|
||||
}
|
||||
ctx.Redirect("/admin/settings")
|
||||
}
|
||||
|
||||
// AdminSettingsLimitsPost 限流
|
||||
func (d Deps) AdminSettingsLimitsPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
|
||||
@@ -108,6 +108,9 @@ type PageChrome struct {
|
||||
SiteName string
|
||||
Slogan string
|
||||
LogoMark string
|
||||
LogoURL string
|
||||
FaviconURL string
|
||||
OGImageURL string
|
||||
LoggedIn bool
|
||||
IsAdmin bool
|
||||
ViewerName string
|
||||
@@ -170,6 +173,9 @@ func (d Deps) chrome(ctx *webctx.Context, title, desc, inner string) PageChrome
|
||||
SiteName: brand.Name,
|
||||
Slogan: brand.Slogan,
|
||||
LogoMark: firstRuneOr(brand.LogoMark, "姜"),
|
||||
LogoURL: strings.TrimSpace(brand.Logo),
|
||||
FaviconURL: strings.TrimSpace(brand.Favicon),
|
||||
OGImageURL: strings.TrimSpace(brand.DefaultShareImage()),
|
||||
LoggedIn: ctx.IsSigned(),
|
||||
IsAdmin: ctx.IsAdmin(),
|
||||
ViewerName: name,
|
||||
|
||||
@@ -75,6 +75,8 @@ func Register(r *gin.Engine, deps Deps, authMW *auth.AuthMiddleware) {
|
||||
admin.POST("/comments/:id/reject", deps.AdminCommentReject)
|
||||
admin.GET("/settings", deps.AdminSettingsGet)
|
||||
admin.POST("/settings/brand", deps.AdminSettingsBrandPost)
|
||||
admin.POST("/settings/brand/upload", deps.AdminSettingsBrandUploadPost)
|
||||
admin.POST("/settings/brand/clear", deps.AdminSettingsBrandClearPost)
|
||||
admin.POST("/settings/limits", deps.AdminSettingsLimitsPost)
|
||||
admin.POST("/settings/filter-words", deps.AdminSettingsFilterWordsPost)
|
||||
admin.POST("/settings/mail", deps.AdminSettingsMailPost)
|
||||
|
||||
@@ -391,6 +391,11 @@ func (d Deps) PostComment(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
isPrivate := c.PostForm("is_private") == "1" || c.PostForm("is_private") == "on"
|
||||
if d.Limiter != nil && !d.Limiter.Allow("comment", fmt.Sprintf("%d", ctx.UserID())) {
|
||||
ctx.SetFlash("操作过于频繁,请稍后再试")
|
||||
ctx.Redirect(fmt.Sprintf("/post/%d#comments", id))
|
||||
return
|
||||
}
|
||||
safe := "<p>" + html.EscapeString(content) + "</p>"
|
||||
cm, err := d.Comment.Create(services.CommentCreateInput{
|
||||
PostID: id, UserID: ctx.UserID(), Content: safe,
|
||||
|
||||
@@ -19,6 +19,69 @@
|
||||
<button type="submit">保存品牌</button>
|
||||
</form>
|
||||
|
||||
<h3>品牌图片</h3>
|
||||
<p class="j13-muted">Logo / Favicon / 默认 Open Graph 图;单文件不超过 2MB。</p>
|
||||
<div class="j13-admin-brand-assets">
|
||||
<div class="j13-admin-brand-asset">
|
||||
<strong>Logo</strong>
|
||||
{{if .Brand.Logo}}
|
||||
<p><img class="j13-admin-brand-preview" src="{{.Brand.Logo}}" alt="Logo"/></p>
|
||||
<p class="j13-muted"><code>{{.Brand.Logo}}</code></p>
|
||||
<form method="post" action="/admin/settings/brand/clear" class="j13-inline-form" onsubmit="return confirm('清除 Logo?');">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
|
||||
<input type="hidden" name="kind" value="logo"/>
|
||||
<button type="submit" class="j13-linkbtn">清除</button>
|
||||
</form>
|
||||
{{else}}
|
||||
<p class="j13-muted">未设置(导航使用字标)</p>
|
||||
{{end}}
|
||||
<form method="post" action="/admin/settings/brand/upload" enctype="multipart/form-data" class="j13-form j13-admin-form">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
|
||||
<input type="hidden" name="kind" value="logo"/>
|
||||
<label>上传 <input type="file" name="file" accept="image/*" required/></label>
|
||||
<button type="submit">上传 Logo</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="j13-admin-brand-asset">
|
||||
<strong>Favicon</strong>
|
||||
{{if .Brand.Favicon}}
|
||||
<p><img class="j13-admin-brand-preview j13-admin-brand-preview--sm" src="{{.Brand.Favicon}}" alt="Favicon"/></p>
|
||||
<form method="post" action="/admin/settings/brand/clear" class="j13-inline-form" onsubmit="return confirm('清除 Favicon?');">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
|
||||
<input type="hidden" name="kind" value="favicon"/>
|
||||
<button type="submit" class="j13-linkbtn">清除</button>
|
||||
</form>
|
||||
{{else}}
|
||||
<p class="j13-muted">未设置</p>
|
||||
{{end}}
|
||||
<form method="post" action="/admin/settings/brand/upload" enctype="multipart/form-data" class="j13-form j13-admin-form">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
|
||||
<input type="hidden" name="kind" value="favicon"/>
|
||||
<label>上传 <input type="file" name="file" accept="image/*" required/></label>
|
||||
<button type="submit">上传 Favicon</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="j13-admin-brand-asset">
|
||||
<strong>默认 OG 图</strong>
|
||||
{{if .Brand.OGImage}}
|
||||
<p><img class="j13-admin-brand-preview" src="{{.Brand.OGImage}}" alt="OG"/></p>
|
||||
<form method="post" action="/admin/settings/brand/clear" class="j13-inline-form" onsubmit="return confirm('清除 OG 图?');">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
|
||||
<input type="hidden" name="kind" value="og_image"/>
|
||||
<button type="submit" class="j13-linkbtn">清除</button>
|
||||
</form>
|
||||
{{else}}
|
||||
<p class="j13-muted">未设置(回落 Logo / Favicon)</p>
|
||||
{{end}}
|
||||
<form method="post" action="/admin/settings/brand/upload" enctype="multipart/form-data" class="j13-form j13-admin-form">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
|
||||
<input type="hidden" name="kind" value="og_image"/>
|
||||
<label>上传 <input type="file" name="file" accept="image/*" required/></label>
|
||||
<button type="submit">上传 OG 图</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>基础限流</h2>
|
||||
<p class="j13-muted">窗口内允许的操作次数(秒)。</p>
|
||||
<form method="post" action="/admin/settings/limits" class="j13-form j13-admin-form">
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>{{.Title}}</title>
|
||||
{{if .Description}}<meta name="description" content="{{.Description}}"/>{{end}}
|
||||
{{if .FaviconURL}}<link rel="icon" href="{{.FaviconURL}}"/>{{end}}
|
||||
<meta property="og:title" content="{{.Title}}"/>
|
||||
{{if .Description}}<meta property="og:description" content="{{.Description}}"/>{{end}}
|
||||
{{if .OGImageURL}}<meta property="og:image" content="{{.OGImageURL}}"/>{{end}}
|
||||
<link rel="stylesheet" href="/ssr-assets/site.css"/>
|
||||
</head>
|
||||
<body class="j13-body">
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
<header class="j13-header">
|
||||
<div class="j13-header__inner">
|
||||
<a class="j13-brand" href="/">
|
||||
{{if .LogoURL}}
|
||||
<img class="j13-brand__logo" src="{{.LogoURL}}" alt="{{.SiteName}}"/>
|
||||
{{else}}
|
||||
<span class="j13-brand__mark">{{.LogoMark}}</span>
|
||||
{{end}}
|
||||
<span class="j13-brand__text">
|
||||
<strong>{{.SiteName}}</strong>
|
||||
{{if .Slogan}}<span class="j13-brand__slogan">{{.Slogan}}</span>{{end}}
|
||||
|
||||
@@ -53,6 +53,12 @@ body.j13-body {
|
||||
place-items: center;
|
||||
font-weight: 700;
|
||||
}
|
||||
.j13-brand__logo {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
object-fit: contain;
|
||||
border-radius: var(--j13-radius);
|
||||
}
|
||||
.j13-brand__text { display: flex; flex-direction: column; }
|
||||
.j13-brand__text strong { font-size: 1rem; }
|
||||
.j13-brand__slogan { font-size: 0.75rem; color: var(--j13-muted); }
|
||||
@@ -1073,6 +1079,27 @@ points-only[data-locked="false"] {
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
.j13-admin-brand-assets {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
|
||||
gap: 1rem;
|
||||
margin: 0 0 1.5rem;
|
||||
}
|
||||
.j13-admin-brand-asset {
|
||||
border: 1px solid var(--j13-border);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
.j13-admin-brand-preview {
|
||||
max-width: 8rem;
|
||||
max-height: 4rem;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.j13-admin-brand-preview--sm {
|
||||
max-width: 2rem;
|
||||
max-height: 2rem;
|
||||
}
|
||||
.j13-admin-media-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
Reference in New Issue
Block a user