mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-09-12 16:40:40 +08:00
62b604644c
- 新增本地收藏store与工具函数,支持文章/瞬间收藏持久化 - 重构插件可用性检查hook,统一管理依赖插件状态 - 替换旧数据加载hook为新的状态管理方案 - 优化首页布局与加载逻辑,调整公告组件样式 - 为瞬间详情页添加点赞、评论与收藏功能 - 新增文本处理工具,支持HTML/Markdown转纯文本与摘要截断 - 修复评论弹窗适配瞬间类型的参数问题
105 lines
3.5 KiB
TypeScript
105 lines
3.5 KiB
TypeScript
/**
|
|
* 收藏快照:把文章(IPost)/瞬间(IMoment)组装为统一收藏项 IFavoriteItem
|
|
* 纯函数层,快照自包含(纯文本摘要截断),供 store 与三处收藏按钮共用
|
|
*/
|
|
import type { IMoment, IPost } from '@/api/types/halo'
|
|
import { checkAvatarUrl, checkImageUrl } from '@/utils/url'
|
|
import { extractPlainExcerpt } from '@/utils/text'
|
|
|
|
/** 收藏内容类型 */
|
|
export type FavoriteKind = 'post' | 'moment'
|
|
|
|
/** 收藏项作者快照 */
|
|
export interface IFavoriteOwner {
|
|
/** post: owner.metadata.name;moment: owner.name */
|
|
id?: string
|
|
displayName: string
|
|
avatar: string
|
|
}
|
|
|
|
/** 统一收藏项(文章/瞬间,纯本地) */
|
|
export interface IFavoriteItem {
|
|
kind: FavoriteKind
|
|
/** 详情 id(metadata.name) */
|
|
id: string
|
|
/** 封面(仅文章) */
|
|
cover?: string
|
|
/** 标题(仅文章) */
|
|
title?: string
|
|
/** 纯文本摘要:文章 excerpt/正文抽 120 字;瞬间正文抽 200 字 */
|
|
content: string
|
|
/** 收藏时刻(ISO 字符串) */
|
|
createTime: string
|
|
owner: IFavoriteOwner
|
|
}
|
|
|
|
/** 文章正文/摘要截断字数 */
|
|
const POST_EXCERPT_MAX = 120
|
|
/** 瞬间正文截断字数 */
|
|
const MOMENT_EXCERPT_MAX = 200
|
|
|
|
/** 取文章摘要文本(excerpt 优先,兜底从正文抽取) */
|
|
function getPostExcerptText(post: IPost): string {
|
|
return extractPlainExcerpt(post.spec.excerpt || post.content?.content || post.content?.raw, POST_EXCERPT_MAX)
|
|
}
|
|
|
|
/** 文章 → 收藏快照 */
|
|
export function buildPostFavoriteItem(post: IPost, now: Date = new Date()): IFavoriteItem {
|
|
const owner = post.owner
|
|
const cover = post.spec.cover ? checkImageUrl(post.spec.cover) : undefined
|
|
return {
|
|
kind: 'post',
|
|
id: post.metadata.name,
|
|
cover,
|
|
title: post.spec.title,
|
|
content: getPostExcerptText(post),
|
|
createTime: now.toISOString(),
|
|
owner: {
|
|
id: owner.metadata?.name,
|
|
displayName: owner.displayName || '',
|
|
avatar: checkAvatarUrl(owner.avatar),
|
|
},
|
|
}
|
|
}
|
|
|
|
/** 瞬间 → 收藏快照(无封面/标题) */
|
|
export function buildMomentFavoriteItem(moment: IMoment, now: Date = new Date()): IFavoriteItem {
|
|
const owner = moment.owner
|
|
// moment.owner 含 index signature,metadata 需显式收窄
|
|
const ownerMeta = owner?.metadata as { name?: string } | undefined
|
|
return {
|
|
kind: 'moment',
|
|
id: moment.metadata.name,
|
|
content: extractPlainExcerpt(moment.spec.content?.html || moment.spec.content?.raw, MOMENT_EXCERPT_MAX),
|
|
createTime: now.toISOString(),
|
|
owner: {
|
|
id: owner?.name || ownerMeta?.name,
|
|
displayName: owner?.displayName || '',
|
|
avatar: checkAvatarUrl(owner?.avatar),
|
|
},
|
|
}
|
|
}
|
|
|
|
/** 收藏唯一键(kind 与 id 拼接,两域 metadata.name 可能撞名) */
|
|
export function favoriteKey(kind: FavoriteKind, id: string): string {
|
|
return `${kind}:${id}`
|
|
}
|
|
|
|
/**
|
|
* 持久化读回数据为真边界数据,入库/展示前校验结构
|
|
* 仅校验关键字段,非法项直接丢弃
|
|
*/
|
|
export function isValidFavoriteItem(item: unknown): item is IFavoriteItem {
|
|
if (!item || typeof item !== 'object')
|
|
return false
|
|
const target = item as Partial<IFavoriteItem>
|
|
const kindOk = target.kind === 'post' || target.kind === 'moment'
|
|
const idOk = typeof target.id === 'string' && target.id !== ''
|
|
const contentOk = typeof target.content === 'string'
|
|
const createTimeOk = typeof target.createTime === 'string'
|
|
const ownerOk = !!target.owner
|
|
&& typeof target.owner === 'object'
|
|
&& typeof (target.owner as IFavoriteOwner).displayName === 'string'
|
|
return kindOk && idOk && contentOk && createTimeOk && ownerOk
|
|
}
|