feat(settings): 后台可配置 Markdown 代码块自动折叠与阈值行数
支持开关与 0–100 行阈值(0 为无预览),经站点设置与 WS 热更新;短代码在视口内收起时不再回滚浏览位置。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -27,6 +27,8 @@ type updateSettingsRequest struct {
|
||||
SiteDescription *string `json:"site_description"`
|
||||
AllowRegister *bool `json:"allow_register"`
|
||||
PostCooldownHours *int `json:"post_cooldown_hours"`
|
||||
CodeBlockAutoFold *bool `json:"code_block_auto_fold"`
|
||||
CodeBlockFoldLines *int `json:"code_block_fold_lines"`
|
||||
}
|
||||
|
||||
// PUT /api/admin/settings
|
||||
@@ -37,7 +39,8 @@ func (h *Handlers) UpdateSettings(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if req.Accent == nil && req.TrustReviewedPublish == nil && req.SiteName == nil &&
|
||||
req.SiteDescription == nil && req.AllowRegister == nil && req.PostCooldownHours == nil {
|
||||
req.SiteDescription == nil && req.AllowRegister == nil &&
|
||||
req.PostCooldownHours == nil && req.CodeBlockAutoFold == nil && req.CodeBlockFoldLines == nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "未提供任何可更新的设置项"})
|
||||
return
|
||||
}
|
||||
@@ -94,6 +97,22 @@ func (h *Handlers) UpdateSettings(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if req.CodeBlockAutoFold != nil {
|
||||
if err := h.Setting.SetCodeBlockAutoFold(*req.CodeBlockAutoFold); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"})
|
||||
return
|
||||
}
|
||||
}
|
||||
if req.CodeBlockFoldLines != nil {
|
||||
if err := h.Setting.SetCodeBlockFoldLines(*req.CodeBlockFoldLines); err != nil {
|
||||
if errors.Is(err, service.ErrInvalidSiteSetting) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "折叠行数须为 0–100"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
saved, err := h.Setting.Public()
|
||||
if err != nil {
|
||||
@@ -101,16 +120,19 @@ func (h *Handlers) UpdateSettings(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
payload := gin.H{
|
||||
"accent": saved.Accent,
|
||||
"trust_reviewed_publish": saved.TrustReviewedPublish,
|
||||
"site_name": saved.SiteName,
|
||||
"site_description": saved.SiteDescription,
|
||||
"allow_register": saved.AllowRegister,
|
||||
"post_cooldown_hours": saved.PostCooldownHours,
|
||||
"code_block_auto_fold": saved.CodeBlockAutoFold,
|
||||
"code_block_fold_lines": saved.CodeBlockFoldLines,
|
||||
}
|
||||
h.Hub.BroadcastAll(realtime.Envelope{
|
||||
Type: realtime.EventSettingsChanged,
|
||||
Data: gin.H{
|
||||
"accent": saved.Accent,
|
||||
"trust_reviewed_publish": saved.TrustReviewedPublish,
|
||||
"site_name": saved.SiteName,
|
||||
"site_description": saved.SiteDescription,
|
||||
"allow_register": saved.AllowRegister,
|
||||
"post_cooldown_hours": saved.PostCooldownHours,
|
||||
},
|
||||
Data: payload,
|
||||
})
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"ok": true,
|
||||
@@ -120,5 +142,7 @@ func (h *Handlers) UpdateSettings(c *gin.Context) {
|
||||
"site_description": saved.SiteDescription,
|
||||
"allow_register": saved.AllowRegister,
|
||||
"post_cooldown_hours": saved.PostCooldownHours,
|
||||
"code_block_auto_fold": saved.CodeBlockAutoFold,
|
||||
"code_block_fold_lines": saved.CodeBlockFoldLines,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,16 +20,23 @@ const (
|
||||
SettingKeySiteName = "site_name"
|
||||
SettingKeySiteDescription = "site_description"
|
||||
SettingKeyAllowRegister = "allow_register"
|
||||
SettingKeyPostCooldownHours = "post_cooldown_hours"
|
||||
SettingKeyPostCooldownHours = "post_cooldown_hours"
|
||||
// SettingKeyCodeBlockAutoFold 代码块自动折叠;缺行视为开启
|
||||
SettingKeyCodeBlockAutoFold = "code_block_auto_fold"
|
||||
// SettingKeyCodeBlockFoldLines 超过该行数则折叠;0=无预览;缺行=5
|
||||
SettingKeyCodeBlockFoldLines = "code_block_fold_lines"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultSiteName = "姜十三论坛"
|
||||
DefaultSiteDescription = "姜十三论坛 - 技术分享与讨论社区"
|
||||
DefaultCooldownHours = 24
|
||||
DefaultCodeFoldLines = 5
|
||||
MaxSiteNameRunes = 32
|
||||
MaxSiteDescRunes = 160
|
||||
MaxCooldownHours = 168
|
||||
MinCodeFoldLines = 0
|
||||
MaxCodeFoldLines = 100
|
||||
)
|
||||
|
||||
// ErrInvalidAccent 主题色格式非法
|
||||
@@ -48,6 +55,8 @@ type PublicSiteSettings struct {
|
||||
SiteDescription string `json:"site_description"`
|
||||
AllowRegister bool `json:"allow_register"`
|
||||
PostCooldownHours int `json:"post_cooldown_hours"`
|
||||
CodeBlockAutoFold bool `json:"code_block_auto_fold"`
|
||||
CodeBlockFoldLines int `json:"code_block_fold_lines"`
|
||||
}
|
||||
|
||||
// SettingService 站点级键值设置
|
||||
@@ -108,6 +117,8 @@ func (s *SettingService) Public() (PublicSiteSettings, error) {
|
||||
TrustReviewedPublish: true,
|
||||
AllowRegister: true,
|
||||
PostCooldownHours: DefaultCooldownHours,
|
||||
CodeBlockAutoFold: true,
|
||||
CodeBlockFoldLines: DefaultCodeFoldLines,
|
||||
}
|
||||
accent, err := s.AccentColor()
|
||||
if err != nil {
|
||||
@@ -151,6 +162,18 @@ func (s *SettingService) Public() (PublicSiteSettings, error) {
|
||||
return out, err
|
||||
}
|
||||
out.PostCooldownHours = hours
|
||||
|
||||
fold, err := s.CodeBlockAutoFold()
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
out.CodeBlockAutoFold = fold
|
||||
|
||||
foldLines, err := s.CodeBlockFoldLines()
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
out.CodeBlockFoldLines = foldLines
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -223,6 +246,7 @@ func (s *SettingService) SetAllowRegister(on bool) error {
|
||||
return s.setBoolDefaultTrue(SettingKeyAllowRegister, on)
|
||||
}
|
||||
|
||||
|
||||
// PostCooldownHours 新用户发帖冷静期(小时)。缺行=24;0 表示关闭。
|
||||
func (s *SettingService) PostCooldownHours() (int, error) {
|
||||
v, found, err := s.getValue(SettingKeyPostCooldownHours)
|
||||
@@ -251,3 +275,46 @@ func (s *SettingService) SetPostCooldownHours(hours int) error {
|
||||
}
|
||||
return s.putValue(SettingKeyPostCooldownHours, strconv.Itoa(hours))
|
||||
}
|
||||
|
||||
// CodeBlockAutoFold 是否对超过阈值行数的 Markdown 代码块默认折叠。缺行视为开启。
|
||||
func (s *SettingService) CodeBlockAutoFold() (bool, error) {
|
||||
v, found, err := s.getValue(SettingKeyCodeBlockAutoFold)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
return parseBoolDefaultTrue(v, found), nil
|
||||
}
|
||||
|
||||
// SetCodeBlockAutoFold 持久化代码折叠开关;开启时删键保持「缺行=默认开」。
|
||||
func (s *SettingService) SetCodeBlockAutoFold(on bool) error {
|
||||
return s.setBoolDefaultTrue(SettingKeyCodeBlockAutoFold, on)
|
||||
}
|
||||
|
||||
// CodeBlockFoldLines 超过该行数则折叠(折叠预览约同高;0=无预览)。缺行=5。
|
||||
func (s *SettingService) CodeBlockFoldLines() (int, error) {
|
||||
v, found, err := s.getValue(SettingKeyCodeBlockFoldLines)
|
||||
if err != nil {
|
||||
return DefaultCodeFoldLines, err
|
||||
}
|
||||
if !found {
|
||||
return DefaultCodeFoldLines, nil
|
||||
}
|
||||
n, convErr := strconv.Atoi(strings.TrimSpace(v))
|
||||
if convErr != nil || n < MinCodeFoldLines {
|
||||
return DefaultCodeFoldLines, nil
|
||||
}
|
||||
if n > MaxCodeFoldLines {
|
||||
return MaxCodeFoldLines, nil
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (s *SettingService) SetCodeBlockFoldLines(lines int) error {
|
||||
if lines < MinCodeFoldLines || lines > MaxCodeFoldLines {
|
||||
return ErrInvalidSiteSetting
|
||||
}
|
||||
if lines == DefaultCodeFoldLines {
|
||||
return s.deleteKey(SettingKeyCodeBlockFoldLines)
|
||||
}
|
||||
return s.putValue(SettingKeyCodeBlockFoldLines, strconv.Itoa(lines))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user