feat: 优化单页编辑、列表留白与友链申请体验
单页全屏编辑并禁用内容门控;对齐 Feed 留白;友链申请区前置并收敛弹框样式;略增大帖子列表标题字号。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,11 +10,23 @@ import (
|
||||
var (
|
||||
membersOnlyBlockRe = regexp.MustCompile(`(?is)<members-only\b[^>]*>([\s\S]*?)</members-only>`)
|
||||
replyOnlyBlockRe = regexp.MustCompile(`(?is)<reply-only\b[^>]*>([\s\S]*?)</reply-only>`)
|
||||
pointsOnlyUnwrapRe = regexp.MustCompile(`(?is)<points-only\b[^>]*>([\s\S]*?)</points-only>`)
|
||||
// style/script 内文本不能进搜索/摘要,否则会出现 "* {color:red}" 之类噪声
|
||||
styleOrScriptRe = regexp.MustCompile(`(?is)<(style|script)\b[^>]*>[\s\S]*?</(style|script)>`)
|
||||
htmlTagRe = regexp.MustCompile(`<[^>]+>`)
|
||||
)
|
||||
|
||||
// UnwrapContentGateTags 剥离登录/回复/积分可见外壳,保留内部正文(单页等场景禁用门控)
|
||||
func UnwrapContentGateTags(html string) string {
|
||||
if html == "" {
|
||||
return html
|
||||
}
|
||||
html = membersOnlyBlockRe.ReplaceAllString(html, "$1")
|
||||
html = replyOnlyBlockRe.ReplaceAllString(html, "$1")
|
||||
html = pointsOnlyUnwrapRe.ReplaceAllString(html, "$1")
|
||||
return html
|
||||
}
|
||||
|
||||
// RedactMembersOnlyHTML 未登录时移除会员专属区块内的正文,保留长度提示供前端展示
|
||||
func RedactMembersOnlyHTML(html string) string {
|
||||
return redactGatedBlocks(html, membersOnlyBlockRe, "members-only")
|
||||
|
||||
@@ -26,3 +26,21 @@ func TestRedactGatedPostHTML(t *testing.T) {
|
||||
t.Fatalf("门控正文应被遮盖,得到: %q", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnwrapContentGateTags(t *testing.T) {
|
||||
in := `<p>公开</p>` +
|
||||
`<members-only data-gate="login"><p>登录密</p></members-only>` +
|
||||
`<reply-only data-gate="reply"><p>回复密</p></reply-only>` +
|
||||
`<points-only data-gate="points" data-cost="10"><p>积分密</p></points-only>`
|
||||
out := UnwrapContentGateTags(in)
|
||||
for _, tag := range []string{"members-only", "reply-only", "points-only"} {
|
||||
if strings.Contains(out, tag) {
|
||||
t.Fatalf("应剥离 %s 外壳,得到: %q", tag, out)
|
||||
}
|
||||
}
|
||||
for _, want := range []string{"公开", "登录密", "回复密", "积分密"} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Fatalf("应保留内部正文 %q,得到: %q", want, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func (s *SitePageService) GetBySlug(slug string, allowUnpublished bool) (*model.
|
||||
if err := q.First(&page).Error; err != nil {
|
||||
return nil, ErrSitePageNotFound
|
||||
}
|
||||
page.Content = SanitizePostHTML(page.Content)
|
||||
page.Content = SanitizePostHTML(UnwrapContentGateTags(page.Content))
|
||||
return &page, nil
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ func (s *SitePageService) GetByID(id uint) (*model.SitePage, error) {
|
||||
if err := model.DB.First(&page, id).Error; err != nil {
|
||||
return nil, ErrSitePageNotFound
|
||||
}
|
||||
page.Content = SanitizePostHTML(UnwrapContentGateTags(page.Content))
|
||||
return &page, nil
|
||||
}
|
||||
|
||||
@@ -145,6 +146,15 @@ func (s *SitePageService) Delete(id uint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPublished 仅切换发布状态(列表快捷操作)
|
||||
func (s *SitePageService) SetPublished(id uint, published bool) error {
|
||||
page, err := s.GetByID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return model.DB.Model(page).Update("published", published).Error
|
||||
}
|
||||
|
||||
func (s *SitePageService) ListSitemap(limit int) ([]model.SitePage, error) {
|
||||
if limit <= 0 {
|
||||
limit = 500
|
||||
@@ -161,7 +171,8 @@ func (s *SitePageService) normalizeInput(in SitePageInput) (*model.SitePage, err
|
||||
if !ok {
|
||||
return nil, errors.New("slug 格式无效(2-64 位小写字母、数字、连字符)")
|
||||
}
|
||||
content := s.filter.Filter(SanitizePostHTML(in.Content))
|
||||
// 单页不支持登录/回复/积分可见:保存前剥离外壳,保留内部正文
|
||||
content := s.filter.Filter(SanitizePostHTML(UnwrapContentGateTags(in.Content)))
|
||||
if title == "" {
|
||||
return nil, errors.New("标题不能为空")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user