Documentation
¶
Overview ¶
Package content 提供文章与独立页面。
两者共用一张 posts 表,以 type 区分;发布、修订、定时发布与可见性逻辑只写一份, 接口按类型分别挂在 /posts 与 /pages 下,权限分别走 posts:* 与 pages:*。 依赖 taxonomy 模块(分类与标签关联),装配时必须排在它之后。
Index ¶
- Constants
- Variables
- func Excerpt(content string, limit int) string
- func Highlight(rendered string) string
- func HookPost(p *Post) hooks.Post
- func NewPolicy() *bluemonday.Policy
- func Path(t Type, slug string) string
- func Render(rawType RawType, raw string) (string, error)
- func Sanitize(html string) string
- func StripTags(s string) string
- type AuthorView
- type Filter
- type Handler
- type Module
- func (m *Module) Migrations() fs.FS
- func (m *Module) Name() string
- func (m *Module) Navigation() app.Navigation
- func (m *Module) Permissions() []app.Permission
- func (m *Module) PublishDue(ctx context.Context) (int64, error)
- func (m *Module) Register(a *app.App) error
- func (m *Module) Routes(r app.Router)
- func (m *Module) Start(ctx context.Context) error
- func (m *Module) Writer() *Writer
- type Post
- type PostCategory
- type PostTag
- type RawType
- type Revision
- type RevisionSummary
- type Slugger
- type Status
- type Store
- func (s *Store) Create(ctx context.Context, p *Post, categoryIDs, tagIDs []int64) error
- func (s *Store) DeletePermanently(ctx context.Context, typ Type, id int64) error
- func (s *Store) Get(ctx context.Context, typ Type, id int64) (*Post, error)
- func (s *Store) GetBySlug(ctx context.Context, typ Type, slug string) (*Post, error)
- func (s *Store) Page(ctx context.Context, f *Filter, params api.PageParams) ([]Post, int, error)
- func (s *Store) PublishDue(ctx context.Context) ([]Post, error)
- func (s *Store) Revision(ctx context.Context, postID, revisionID int64) (*Revision, error)
- func (s *Store) Revisions(ctx context.Context, postID int64) ([]RevisionSummary, error)
- func (s *Store) Update(ctx context.Context, p *Post, categoryIDs, tagIDs []int64, snapshot bool, ...) error
- func (s *Store) UpdateStatus(ctx context.Context, p *Post) error
- type Type
- type Visibility
- type WriteParams
- type Writer
Constants ¶
const ExcerptLength = 200
ExcerptLength 是自动摘要的最大字符数(按 Unicode 字符计)。
const Name = "content"
Name 是模块名,也是迁移版本表的后缀。
const PostsPrefix = hooks.PostsPrefix
PostsPrefix 是文章详情页的路径前缀;页面直接挂在站点根下。
const RevisionsKept = 30
RevisionsKept 是每篇内容保留的修订数上限,超出后最旧的被清理。
Variables ¶
var ( // ErrNotFound 表示内容或修订不存在。 ErrNotFound = errors.New("对象不存在") // ErrSlugTaken 表示同类型下 slug 已被占用。 ErrSlugTaken = errors.New("slug 已被占用") // ErrTermNotFound 表示引用的分类或标签不存在。 ErrTermNotFound = errors.New("分类或标签不存在") // ErrNotTrashed 表示内容不在回收站,无法彻底删除。 ErrNotTrashed = errors.New("内容不在回收站") // ErrInvalid 表示字段违反了数据库约束。 ErrInvalid = errors.New("字段不合法") )
错误哨兵。处理器据此映射为 400 / 404 / 409。
var ErrInvalidType = errors.New("内容类型只能是 post 或 page")
ErrInvalidType 表示内容类型不是 post 或 page。
Functions ¶
func Highlight ¶
Highlight 给渲染后的 HTML 里的代码块加上语法高亮与结构外壳。
只改写 <pre>…</pre> 这一段,其余字节原样保留——正文里其他部分不该因为 「顺手过了一遍 HTML 解析器」而被重新序列化。
func NewPolicy ¶ added in v0.2.0
func NewPolicy() *bluemonday.Policy
NewPolicy 返回一份新的正文允许列表策略,供要在它之上再放行几样东西的调用方扩展(插件的前台片段)。
func Render ¶
Render 把原稿渲染为主题消费的 HTML。
HTML 原稿原样返回:编辑器产出的就是规范 HTML;Markdown 经 goldmark 渲染, 标题锚点用 slug 规则生成,中文标题得到可读的中文 ID。
Types ¶
type AuthorView ¶
type AuthorView struct {
ID int64 `json:"id"`
Username string `json:"username"`
DisplayName string `json:"displayName"`
AvatarURL string `json:"avatarUrl"`
}
AuthorView 是嵌入内容响应的作者摘要,只含可公开的字段。
type Filter ¶
type Filter struct {
Type Type
// Status 为空表示回收站之外的全部状态。
Status Status
// AuthorID 非 0 时只取该作者的内容。
AuthorID int64
// CategorySlug / TagSlug 非空时按分类或标签筛选。
CategorySlug string
TagSlug string
// Query 非空时按标题模糊匹配。
Query string
// PublicOnly 为真时只取已发布内容:公开的,或属于 ViewerID 的私密内容。
PublicOnly bool
ViewerID int64
}
Filter 是列表筛选条件。
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler 提供内容的 Console 与 Public 接口。
func NewHandler ¶
NewHandler 构造 Handler;slugify 为 nil 时使用保留中文的缺省策略,events 为 nil 时不派发动作。
type Module ¶
type Module struct {
// contains filtered or unexported fields
}
Module 是内容模块。
func (*Module) Navigation ¶
func (m *Module) Navigation() app.Navigation
Navigation 实现 app.NavigationProvider。
func (*Module) Permissions ¶
func (m *Module) Permissions() []app.Permission
Permissions 实现 app.PermissionProvider。
func (*Module) PublishDue ¶
PublishDue 立即执行一次定时发布扫描,返回推进条数;供后台任务与测试调用。
func (*Module) Register ¶
Register 实现 app.Module:只做装配,不访问数据库。
若 settings 模块先于本模块装配,slug 生成策略跟随站点设置;否则退回保留中文的缺省策略。
type Post ¶
type Post struct {
bun.BaseModel `bun:"table:posts,alias:p"`
ID int64 `bun:"id,pk,autoincrement" json:"id"`
Type Type `bun:"type,notnull" json:"type" enum:"post,page"`
Title string `bun:"title,notnull" json:"title"`
Slug string `bun:"slug,notnull" json:"slug" doc:"URL 片段,同类型内唯一"`
Status Status `bun:"status,notnull" json:"status" enum:"draft,published,scheduled,trashed"`
Visibility Visibility `bun:"visibility,notnull" json:"visibility" enum:"public,private"`
RawType RawType `bun:"raw_type,notnull" json:"rawType" enum:"html,markdown" doc:"原稿格式"`
Raw string `bun:"raw" json:"raw" doc:"原稿:Markdown 源码或规范 HTML"`
Content string `bun:"content" json:"content" doc:"渲染后的 HTML,主题只消费此字段"`
Excerpt string `bun:"excerpt" json:"excerpt"`
ExcerptAuto bool `bun:"excerpt_auto" json:"excerptAuto" doc:"摘要是否由正文自动生成"`
CoverURL string `bun:"cover_url" json:"coverUrl"`
Pinned bool `bun:"pinned" json:"pinned"`
Template string `bun:"template" json:"template" doc:"页面模板名,如 page-about;空为默认"`
AuthorID int64 `bun:"author_id,notnull" json:"authorId"`
PublishedAt *time.Time `bun:"published_at" json:"publishedAt" doc:"发布时间;定时发布时为计划时间"`
TrashedAt *time.Time `bun:"trashed_at" json:"trashedAt"`
Meta map[string]any `bun:"meta,type:jsonb" json:"meta" doc:"扩展元数据"`
CreatedAt time.Time `bun:"created_at,nullzero" json:"createdAt"`
UpdatedAt time.Time `bun:"updated_at,nullzero" json:"updatedAt"`
// 以下为关联数据,由存储层按需填充,不是数据库列。
Author *AuthorView `bun:"-" json:"author,omitempty"`
Categories []taxonomy.Category `bun:"-" json:"categories"`
Tags []taxonomy.Tag `bun:"-" json:"tags"`
}
Post 是文章或页面实体。
type PostCategory ¶
type PostCategory struct {
bun.BaseModel `bun:"table:post_categories,alias:pc"`
PostID int64 `bun:"post_id,pk"`
CategoryID int64 `bun:"category_id,pk"`
}
PostCategory 是文章与分类的关联。
type PostTag ¶
type PostTag struct {
bun.BaseModel `bun:"table:post_tags,alias:pt"`
PostID int64 `bun:"post_id,pk"`
TagID int64 `bun:"tag_id,pk"`
}
PostTag 是文章与标签的关联。
type Revision ¶
type Revision struct {
bun.BaseModel `bun:"table:post_revisions,alias:rv"`
ID int64 `bun:"id,pk,autoincrement" json:"id"`
PostID int64 `bun:"post_id,notnull" json:"postId"`
AuthorID *int64 `bun:"author_id" json:"authorId" doc:"保存该版本的用户;用户已删除时为 null"`
Title string `bun:"title,notnull" json:"title"`
RawType RawType `bun:"raw_type,notnull" json:"rawType" enum:"html,markdown"`
Raw string `bun:"raw" json:"raw"`
Content string `bun:"content" json:"content"`
Excerpt string `bun:"excerpt" json:"excerpt"`
CreatedAt time.Time `bun:"created_at,nullzero" json:"createdAt"`
}
Revision 是一次内容快照。
type RevisionSummary ¶
type RevisionSummary struct {
bun.BaseModel `bun:"table:post_revisions,alias:rv"`
ID int64 `bun:"id,pk" json:"id"`
PostID int64 `bun:"post_id" json:"postId"`
AuthorID *int64 `bun:"author_id" json:"authorId"`
Title string `bun:"title" json:"title"`
RawType RawType `bun:"raw_type" json:"rawType" enum:"html,markdown"`
CreatedAt time.Time `bun:"created_at,nullzero" json:"createdAt"`
}
RevisionSummary 是修订列表项,不含正文以免列表过大。
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store 提供内容的持久化操作。
func (*Store) DeletePermanently ¶
DeletePermanently 彻底删除内容,关联与修订随外键级联消失。
删除条件里带上 status = 'trashed',让「检查是否在回收站」与「删除」成为同一条语句: 先读后删之间存在窗口,用户在这期间点了「恢复」,刚恢复的内容就会被永久删除, 连同它的修订与评论一起不可逆地消失。条件不满足时返回 ErrNotTrashed。
func (*Store) PublishDue ¶
PublishDue 把计划时间已到的定时内容推进为已发布,返回被推进的那些。
单条幂等 UPDATE:多实例同时执行也不会重复发布或互相干扰;只有真正改到行的那个实例 拿得到 RETURNING 的结果,post.published 因此只发一次。
type Visibility ¶
type Visibility string
Visibility 是内容对前台的可见性。
const ( VisibilityPublic Visibility = "public" VisibilityPrivate Visibility = "private" )
可见性。
type WriteParams ¶ added in v0.2.0
type WriteParams struct {
Title string `json:"title"`
// Slug 留空时新建按站点策略由标题生成、修改保留原值。
Slug string `json:"slug"`
// RawType 是原稿格式,缺省 html。
RawType RawType `json:"rawType"`
Raw string `json:"raw"`
Excerpt string `json:"excerpt"`
CategoryIDs []int64 `json:"categoryIds"`
TagIDs []int64 `json:"tagIds"`
}
WriteParams 是新建或修改一条内容的字段。
type Writer ¶ added in v0.2.0
type Writer struct {
// contains filtered or unexported fields
}
Writer 是给插件这类内部调用方用的写入口。
规则与后台接口一致:正文渲染与净化(一律按没有 content:unsafe_html 的作者处理)、 slug 生成与去重、动作派发都走同一套代码。权限由调用方判定——这里不认识调用方是谁。
func (*Writer) Create ¶ added in v0.2.0
func (w *Writer) Create(ctx context.Context, typ Type, authorID int64, p *WriteParams, publish bool) (*Post, error)
Create 新建一条内容,作者为 authorID;publish 为真时直接发布,否则存为草稿。