feat(settings): 后台可开关全站评论
关闭后禁止新评论与回复,已有内容仍可浏览;设置经 WS 同步到前台。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -26,6 +26,7 @@ type updateSettingsRequest struct {
|
|||||||
SiteName *string `json:"site_name"`
|
SiteName *string `json:"site_name"`
|
||||||
SiteDescription *string `json:"site_description"`
|
SiteDescription *string `json:"site_description"`
|
||||||
AllowRegister *bool `json:"allow_register"`
|
AllowRegister *bool `json:"allow_register"`
|
||||||
|
AllowComments *bool `json:"allow_comments"`
|
||||||
PostCooldownHours *int `json:"post_cooldown_hours"`
|
PostCooldownHours *int `json:"post_cooldown_hours"`
|
||||||
CodeBlockAutoFold *bool `json:"code_block_auto_fold"`
|
CodeBlockAutoFold *bool `json:"code_block_auto_fold"`
|
||||||
CodeBlockFoldLines *int `json:"code_block_fold_lines"`
|
CodeBlockFoldLines *int `json:"code_block_fold_lines"`
|
||||||
@@ -42,6 +43,7 @@ func settingsPayload(saved service.PublicSiteSettings) gin.H {
|
|||||||
"site_name": saved.SiteName,
|
"site_name": saved.SiteName,
|
||||||
"site_description": saved.SiteDescription,
|
"site_description": saved.SiteDescription,
|
||||||
"allow_register": saved.AllowRegister,
|
"allow_register": saved.AllowRegister,
|
||||||
|
"allow_comments": saved.AllowComments,
|
||||||
"post_cooldown_hours": saved.PostCooldownHours,
|
"post_cooldown_hours": saved.PostCooldownHours,
|
||||||
"code_block_auto_fold": saved.CodeBlockAutoFold,
|
"code_block_auto_fold": saved.CodeBlockAutoFold,
|
||||||
"code_block_fold_lines": saved.CodeBlockFoldLines,
|
"code_block_fold_lines": saved.CodeBlockFoldLines,
|
||||||
@@ -60,7 +62,7 @@ func (h *Handlers) UpdateSettings(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if req.Accent == nil && req.TrustReviewedPublish == nil && req.SiteName == nil &&
|
if req.Accent == nil && req.TrustReviewedPublish == nil && req.SiteName == nil &&
|
||||||
req.SiteDescription == nil && req.AllowRegister == nil &&
|
req.SiteDescription == nil && req.AllowRegister == nil && req.AllowComments == nil &&
|
||||||
req.PostCooldownHours == nil && req.CodeBlockAutoFold == nil && req.CodeBlockFoldLines == nil &&
|
req.PostCooldownHours == nil && req.CodeBlockAutoFold == nil && req.CodeBlockFoldLines == nil &&
|
||||||
req.UIAnimations == nil && req.AnimCodeFold == nil && req.AnimSmoothScroll == nil &&
|
req.UIAnimations == nil && req.AnimCodeFold == nil && req.AnimSmoothScroll == nil &&
|
||||||
req.AnimChrome == nil {
|
req.AnimChrome == nil {
|
||||||
@@ -110,6 +112,12 @@ func (h *Handlers) UpdateSettings(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if req.AllowComments != nil {
|
||||||
|
if err := h.Setting.SetAllowComments(*req.AllowComments); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
if req.PostCooldownHours != nil {
|
if req.PostCooldownHours != nil {
|
||||||
if err := h.Setting.SetPostCooldownHours(*req.PostCooldownHours); err != nil {
|
if err := h.Setting.SetPostCooldownHours(*req.PostCooldownHours); err != nil {
|
||||||
if errors.Is(err, service.ErrInvalidSiteSetting) {
|
if errors.Is(err, service.ErrInvalidSiteSetting) {
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ const (
|
|||||||
SettingKeySiteName = "site_name"
|
SettingKeySiteName = "site_name"
|
||||||
SettingKeySiteDescription = "site_description"
|
SettingKeySiteDescription = "site_description"
|
||||||
SettingKeyAllowRegister = "allow_register"
|
SettingKeyAllowRegister = "allow_register"
|
||||||
|
// SettingKeyAllowComments 全站开放评论;缺行视为开启(默认开)
|
||||||
|
SettingKeyAllowComments = "allow_comments"
|
||||||
SettingKeyPostCooldownHours = "post_cooldown_hours"
|
SettingKeyPostCooldownHours = "post_cooldown_hours"
|
||||||
// SettingKeyCodeBlockAutoFold 代码块自动折叠;缺行视为开启
|
// SettingKeyCodeBlockAutoFold 代码块自动折叠;缺行视为开启
|
||||||
SettingKeyCodeBlockAutoFold = "code_block_auto_fold"
|
SettingKeyCodeBlockAutoFold = "code_block_auto_fold"
|
||||||
@@ -62,6 +64,7 @@ type PublicSiteSettings struct {
|
|||||||
SiteName string `json:"site_name"`
|
SiteName string `json:"site_name"`
|
||||||
SiteDescription string `json:"site_description"`
|
SiteDescription string `json:"site_description"`
|
||||||
AllowRegister bool `json:"allow_register"`
|
AllowRegister bool `json:"allow_register"`
|
||||||
|
AllowComments bool `json:"allow_comments"`
|
||||||
PostCooldownHours int `json:"post_cooldown_hours"`
|
PostCooldownHours int `json:"post_cooldown_hours"`
|
||||||
CodeBlockAutoFold bool `json:"code_block_auto_fold"`
|
CodeBlockAutoFold bool `json:"code_block_auto_fold"`
|
||||||
CodeBlockFoldLines int `json:"code_block_fold_lines"`
|
CodeBlockFoldLines int `json:"code_block_fold_lines"`
|
||||||
@@ -128,6 +131,7 @@ func (s *SettingService) Public() (PublicSiteSettings, error) {
|
|||||||
SiteDescription: DefaultSiteDescription,
|
SiteDescription: DefaultSiteDescription,
|
||||||
TrustReviewedPublish: true,
|
TrustReviewedPublish: true,
|
||||||
AllowRegister: true,
|
AllowRegister: true,
|
||||||
|
AllowComments: true,
|
||||||
PostCooldownHours: DefaultCooldownHours,
|
PostCooldownHours: DefaultCooldownHours,
|
||||||
CodeBlockAutoFold: true,
|
CodeBlockAutoFold: true,
|
||||||
CodeBlockFoldLines: DefaultCodeFoldLines,
|
CodeBlockFoldLines: DefaultCodeFoldLines,
|
||||||
@@ -173,6 +177,12 @@ func (s *SettingService) Public() (PublicSiteSettings, error) {
|
|||||||
}
|
}
|
||||||
out.AllowRegister = parseBoolDefaultTrue(reg, found)
|
out.AllowRegister = parseBoolDefaultTrue(reg, found)
|
||||||
|
|
||||||
|
comments, err := s.AllowComments()
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
out.AllowComments = comments
|
||||||
|
|
||||||
hours, err := s.PostCooldownHours()
|
hours, err := s.PostCooldownHours()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return out, err
|
return out, err
|
||||||
@@ -286,6 +296,19 @@ func (s *SettingService) SetAllowRegister(on bool) error {
|
|||||||
return s.setBoolDefaultTrue(SettingKeyAllowRegister, on)
|
return s.setBoolDefaultTrue(SettingKeyAllowRegister, on)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AllowComments 是否允许全站发表评论/回复。缺行视为开启。
|
||||||
|
func (s *SettingService) AllowComments() (bool, error) {
|
||||||
|
v, found, err := s.getValue(SettingKeyAllowComments)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return parseBoolDefaultTrue(v, found), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAllowComments 持久化评论开关;开启时删键保持「缺行=默认开」。
|
||||||
|
func (s *SettingService) SetAllowComments(on bool) error {
|
||||||
|
return s.setBoolDefaultTrue(SettingKeyAllowComments, on)
|
||||||
|
}
|
||||||
|
|
||||||
// PostCooldownHours 新用户发帖冷静期(小时)。缺行=24;0 表示关闭。
|
// PostCooldownHours 新用户发帖冷静期(小时)。缺行=24;0 表示关闭。
|
||||||
func (s *SettingService) PostCooldownHours() (int, error) {
|
func (s *SettingService) PostCooldownHours() (int, error) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
Code2,
|
Code2,
|
||||||
Globe,
|
Globe,
|
||||||
Loader2,
|
Loader2,
|
||||||
|
MessageSquare,
|
||||||
Save,
|
Save,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
UserPlus,
|
UserPlus,
|
||||||
@@ -49,6 +50,7 @@ export default function SettingsAdmin({ initial }: { initial: PublicSettings })
|
|||||||
const [siteName, setSiteName] = useState(initial.site_name);
|
const [siteName, setSiteName] = useState(initial.site_name);
|
||||||
const [siteDescription, setSiteDescription] = useState(initial.site_description);
|
const [siteDescription, setSiteDescription] = useState(initial.site_description);
|
||||||
const [allowRegister, setAllowRegister] = useState(initial.allow_register);
|
const [allowRegister, setAllowRegister] = useState(initial.allow_register);
|
||||||
|
const [allowComments, setAllowComments] = useState(initial.allow_comments !== false);
|
||||||
const [codeAutoFold, setCodeAutoFold] = useState(
|
const [codeAutoFold, setCodeAutoFold] = useState(
|
||||||
initial.code_block_auto_fold !== false
|
initial.code_block_auto_fold !== false
|
||||||
);
|
);
|
||||||
@@ -62,6 +64,7 @@ export default function SettingsAdmin({ initial }: { initial: PublicSettings })
|
|||||||
const [savedDesc, setSavedDesc] = useState(initial.site_description);
|
const [savedDesc, setSavedDesc] = useState(initial.site_description);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [togglingReg, setTogglingReg] = useState(false);
|
const [togglingReg, setTogglingReg] = useState(false);
|
||||||
|
const [togglingComments, setTogglingComments] = useState(false);
|
||||||
const [togglingFold, setTogglingFold] = useState(false);
|
const [togglingFold, setTogglingFold] = useState(false);
|
||||||
const [savingFoldLines, setSavingFoldLines] = useState(false);
|
const [savingFoldLines, setSavingFoldLines] = useState(false);
|
||||||
const [uiAnimations, setUiAnimations] = useState(initial.ui_animations !== false);
|
const [uiAnimations, setUiAnimations] = useState(initial.ui_animations !== false);
|
||||||
@@ -116,6 +119,20 @@ export default function SettingsAdmin({ initial }: { initial: PublicSettings })
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleComments = async () => {
|
||||||
|
if (togglingComments) return;
|
||||||
|
const next = !allowComments;
|
||||||
|
setTogglingComments(true);
|
||||||
|
try {
|
||||||
|
const res = await apiUpdateSiteSettings({ allow_comments: next });
|
||||||
|
setAllowComments(res.allow_comments);
|
||||||
|
toast(res.allow_comments ? "已开放评论" : "已关闭评论", "ok");
|
||||||
|
} catch (e) {
|
||||||
|
toast(e instanceof Error ? e.message : "保存失败");
|
||||||
|
} finally {
|
||||||
|
setTogglingComments(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const toggleCodeFold = async () => {
|
const toggleCodeFold = async () => {
|
||||||
if (togglingFold) return;
|
if (togglingFold) return;
|
||||||
@@ -201,7 +218,7 @@ export default function SettingsAdmin({ initial }: { initial: PublicSettings })
|
|||||||
站点设置
|
站点设置
|
||||||
</h1>
|
</h1>
|
||||||
<p className="meta mt-1.5 text-[12.5px]">
|
<p className="meta mt-1.5 text-[12.5px]">
|
||||||
配置站点名称、简介与注册开关;主题色请到「外观」页调整
|
配置站点名称、简介与注册/评论开关;主题色请到「外观」页调整
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -265,6 +282,23 @@ export default function SettingsAdmin({ initial }: { initial: PublicSettings })
|
|||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section className="j13-module p-5 sm:p-6 mb-5 flex items-start justify-between gap-4 flex-wrap">
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<h2 className="text-[15px] font-semibold flex items-center gap-1.5" style={{ color: "var(--ink)" }}>
|
||||||
|
<MessageSquare size={15} /> 开放评论
|
||||||
|
</h2>
|
||||||
|
<p className="meta text-[12.5px] mt-1 leading-relaxed">
|
||||||
|
关闭后全站禁止发表新评论与回复,已有评论仍可浏览。适用于 ICP/IPC
|
||||||
|
备案审核等合规需要;前台仅提示「暂停评论」,不会展示备案事由。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={allowComments}
|
||||||
|
disabled={togglingComments}
|
||||||
|
onChange={() => void toggleComments()}
|
||||||
|
label="开放评论"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section className="j13-module p-5 sm:p-6 mb-5">
|
<section className="j13-module p-5 sm:p-6 mb-5">
|
||||||
<div className="flex items-start justify-between gap-4 flex-wrap">
|
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||||
|
|||||||
@@ -547,6 +547,8 @@ export interface PublicSettings {
|
|||||||
site_name: string;
|
site_name: string;
|
||||||
site_description: string;
|
site_description: string;
|
||||||
allow_register: boolean;
|
allow_register: boolean;
|
||||||
|
/** 全站是否开放评论/回复;缺省 true */
|
||||||
|
allow_comments: boolean;
|
||||||
post_cooldown_hours: number;
|
post_cooldown_hours: number;
|
||||||
/** 超过 N 行的代码块是否默认折叠;缺省 true */
|
/** 超过 N 行的代码块是否默认折叠;缺省 true */
|
||||||
code_block_auto_fold: boolean;
|
code_block_auto_fold: boolean;
|
||||||
@@ -568,6 +570,7 @@ const DEFAULT_PUBLIC_SETTINGS: PublicSettings = {
|
|||||||
site_name: "姜十三论坛",
|
site_name: "姜十三论坛",
|
||||||
site_description: "姜十三论坛 - 技术分享与讨论社区",
|
site_description: "姜十三论坛 - 技术分享与讨论社区",
|
||||||
allow_register: true,
|
allow_register: true,
|
||||||
|
allow_comments: true,
|
||||||
post_cooldown_hours: 24,
|
post_cooldown_hours: 24,
|
||||||
code_block_auto_fold: true,
|
code_block_auto_fold: true,
|
||||||
code_block_fold_lines: 5,
|
code_block_fold_lines: 5,
|
||||||
@@ -587,6 +590,7 @@ function normalizePublicSettings(data: Partial<PublicSettings> | null | undefine
|
|||||||
? String(data.site_description).trim()
|
? String(data.site_description).trim()
|
||||||
: DEFAULT_PUBLIC_SETTINGS.site_description,
|
: DEFAULT_PUBLIC_SETTINGS.site_description,
|
||||||
allow_register: data?.allow_register !== false,
|
allow_register: data?.allow_register !== false,
|
||||||
|
allow_comments: data?.allow_comments !== false,
|
||||||
post_cooldown_hours:
|
post_cooldown_hours:
|
||||||
typeof data?.post_cooldown_hours === "number" && data.post_cooldown_hours >= 0
|
typeof data?.post_cooldown_hours === "number" && data.post_cooldown_hours >= 0
|
||||||
? data.post_cooldown_hours
|
? data.post_cooldown_hours
|
||||||
@@ -1530,6 +1534,7 @@ export type UpdateSiteSettingsBody = {
|
|||||||
site_name?: string;
|
site_name?: string;
|
||||||
site_description?: string;
|
site_description?: string;
|
||||||
allow_register?: boolean;
|
allow_register?: boolean;
|
||||||
|
allow_comments?: boolean;
|
||||||
post_cooldown_hours?: number;
|
post_cooldown_hours?: number;
|
||||||
code_block_auto_fold?: boolean;
|
code_block_auto_fold?: boolean;
|
||||||
code_block_fold_lines?: number;
|
code_block_fold_lines?: number;
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export interface RtSettingsData {
|
|||||||
site_name?: string;
|
site_name?: string;
|
||||||
site_description?: string;
|
site_description?: string;
|
||||||
allow_register?: boolean;
|
allow_register?: boolean;
|
||||||
|
allow_comments?: boolean;
|
||||||
post_cooldown_hours?: number;
|
post_cooldown_hours?: number;
|
||||||
code_block_auto_fold?: boolean;
|
code_block_auto_fold?: boolean;
|
||||||
code_block_fold_lines?: number;
|
code_block_fold_lines?: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user