Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/components/layout/SideBar.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Advertisement from "@/components/widget/Advertisement.astro";
import Announcement from "@/components/widget/Announcement.astro";
import Calendar from "@/components/widget/Calendar.astro";
import Categories from "@/components/widget/Categories.astro";
import LatestShuoshuo from "@/components/widget/LatestShuoshuo.astro";
import Music from "@/components/widget/Music.astro";
import Profile from "@/components/widget/Profile.astro";
import SidebarTOC from "@/components/widget/SidebarTOC.astro";
Expand Down Expand Up @@ -42,6 +43,7 @@ const ANIMATION_DELAY_UNIT = 50; // ms
const componentMap = {
profile: Profile,
announcement: Announcement,
latestShuoshuo: LatestShuoshuo,
categories: Categories,
tags: Tags,
sidebarToc: SidebarTOC,
Expand Down
118 changes: 118 additions & 0 deletions src/components/pages/shuoshuo/ShuoshuoCard.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
import ImageWrapper from "@/components/common/ImageWrapper.astro";

export interface Props {
author: string;
avatar: string;
relativeTime: string;
wordCount: number;
wordUnit: string;
maxCollapsedHeight?: number;
anchorId?: string;
}

const {
author,
avatar,
relativeTime,
wordCount,
wordUnit,
maxCollapsedHeight = 260,
anchorId,
} = Astro.props;
---

<article
id={anchorId}
class="shuoshuo-card card-base p-6 rounded-[var(--radius-large)] overflow-hidden"
data-shuoshuo-card
data-max-height={maxCollapsedHeight}
>
<div class="flex gap-4">
<ImageWrapper
src={avatar}
alt={author}
loading="lazy"
class="h-11 w-11 rounded-full border border-[var(--line-divider)] bg-[var(--card-bg)] object-cover shrink-0"
usePicture={false}
widths={[88]}
sizes="44px"
/>
<div class="min-w-0 flex-1">
<div class="flex items-start justify-between gap-4">
<div class="min-w-0">
<div class="font-bold text-[1.15rem] leading-tight text-black/75 dark:text-white/80 truncate">
{author}
</div>
<div class="mt-1 text-sm text-black/45 dark:text-white/45">
{relativeTime}
</div>
</div>
<div class="text-sm text-black/40 dark:text-white/40 shrink-0">
{wordCount}
{wordUnit}
</div>
</div>

<div class="mt-4">
<div class="shuoshuo-collapsible" data-collapsible>
<div
class="prose dark:prose-invert prose-base !max-w-none custom-md shuoshuo-md"
data-collapsible-content
>
<slot />
</div>
<div class="shuoshuo-fade" data-fade></div>
</div>

<button
type="button"
class="shuoshuo-toggle mt-3 text-sm text-[var(--primary)] hover:opacity-80 active:opacity-60 hidden"
data-toggle
aria-expanded="false"
>
展开全文
</button>
</div>
</div>
</div>
</article>

<style>
.shuoshuo-collapsible {
position: relative;
max-height: var(--shuoshuo-max-height, 260px);
overflow: hidden;
}

.shuoshuo-card.is-open .shuoshuo-collapsible {
max-height: none;
}

.shuoshuo-fade {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 5.5rem;
background: linear-gradient(to bottom, transparent, var(--card-bg));
pointer-events: none;
opacity: 1;
transition: opacity 180ms ease;
}

.shuoshuo-card.is-open .shuoshuo-fade {
opacity: 0;
}

.shuoshuo-md :global(p) {
margin-top: 0.25rem;
margin-bottom: 0.5rem;
}

.shuoshuo-md :global(ul),
.shuoshuo-md :global(ol) {
margin-top: 0.4rem;
margin-bottom: 0.6rem;
}
</style>
77 changes: 77 additions & 0 deletions src/components/widget/LatestShuoshuo.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
import { Icon } from "astro-icon/components";
import WidgetLayout from "@/components/common/WidgetLayout.astro";
import { profileConfig, siteConfig } from "@/config";
import { getSortedShuoshuo } from "@/utils/shuoshuo-utils";
import {
formatRelativeTime,
markdownToPlainText,
toDomId,
} from "@/utils/text-utils";
import { url } from "@/utils/url-utils";

interface Props {
class?: string;
style?: string;
limit?: number;
}

const className = Astro.props.class;
const style = Astro.props.style;
const limit = Astro.props.limit ?? 3;

const isZh = (siteConfig.lang || "").startsWith("zh");
const title = isZh ? "最新说说" : "Latest Shuoshuo";

const entries = (await getSortedShuoshuo()).slice(0, limit);
const items = entries.map((entry) => {
const author = entry.data.author || profileConfig.name;
const relativeTime = formatRelativeTime(entry.data.published);
const excerpt = entry.body ? markdownToPlainText(entry.body) : "";
const anchorId = toDomId("shuoshuo", entry.id);
const href = url(`/shuoshuo/#${anchorId}`);
return { author, relativeTime, excerpt, href };
});
---

<WidgetLayout name={title} id="latest-shuoshuo" class={className} style={style}>
<a
slot="title-icon"
href={url("/shuoshuo/")}
class="btn-plain rounded-lg h-7 px-2 text-xs flex items-center gap-1 text-[var(--primary)] hover:opacity-80 active:opacity-60"
aria-label="Go to shuoshuo page"
>
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
<span>{isZh ? "更多" : "More"}</span>
<Icon name="material-symbols:chevron-right-rounded" class="text-lg" />
</a>

{items.length > 0 ? (
<div class="flex flex-col gap-3">
{items.map((item) => (
<a
href={item.href}
class="group rounded-xl border border-[var(--line-divider)] bg-[var(--card-bg)]/40 hover:bg-[var(--card-bg)] transition-colors p-3"
>
<div class="flex items-center justify-between gap-3 mb-1">
<div class="font-semibold text-sm text-black/70 dark:text-white/70 truncate">
{item.author}
</div>
<div class="text-xs text-black/40 dark:text-white/40 shrink-0">
{item.relativeTime}
</div>
</div>
<div class="text-sm text-black/60 dark:text-white/60 line-clamp-2">
{item.excerpt}
</div>
<div class="mt-2 text-xs text-[var(--primary)] opacity-0 group-hover:opacity-100 transition-opacity">
{isZh ? "查看全文" : "Read more"}
</div>
</a>
))}
</div>
) : (
<div class="text-sm text-[var(--content-meta)] px-1 py-2">
{isZh ? "暂无说说" : "No shuoshuo yet"}
</div>
)}
</WidgetLayout>
17 changes: 16 additions & 1 deletion src/components/widget/SiteStats.astro
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
getSortedPosts,
getTagList,
} from "@/utils/content-utils";
import { getSortedShuoshuo } from "@/utils/shuoshuo-utils";

interface Props {
class?: string;
Expand All @@ -28,6 +29,7 @@ const siteStartDate = siteConfig.siteStartDate || "2025-01-01";
const posts = await getSortedPosts();
const categories = await getCategoryList();
const tags = await getTagList();
const shuoshuo = await getSortedShuoshuo();

// 计算总字数
let totalWords = 0;
Expand Down Expand Up @@ -66,7 +68,15 @@ const lastActivityDate = posts.reduce<Date | null>((latest, post) => {
return latest;
}, null);

const lastPostDate = lastActivityDate ? lastActivityDate.toISOString() : null;
const lastShuoshuoDate = shuoshuo[0]?.data.published ?? null;
const latestActivityDate =
lastActivityDate && lastShuoshuoDate
? new Date(Math.max(lastActivityDate.getTime(), lastShuoshuoDate.getTime()))
: (lastActivityDate ?? lastShuoshuoDate);

const lastPostDate = latestActivityDate
? latestActivityDate.toISOString()
: null;

const todayText = i18n(I18nKey.today);

Expand All @@ -76,6 +86,11 @@ const stats = [
label: i18n(I18nKey.siteStatsPostCount),
value: posts.length,
},
{
icon: "material-symbols:chat-bubble-outline",
label: i18n(I18nKey.siteStatsShuoshuoCount),
value: shuoshuo.length,
},
{
icon: "material-symbols:folder-outline",
label: i18n(I18nKey.siteStatsCategoryCount),
Expand Down
7 changes: 7 additions & 0 deletions src/config/navBarConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const getDynamicNavBarConfig = (): NavBarConfig => {
const links: NavBarLink[] = [
// 主页
LinkPresets.Home,
// 说说
LinkPresets.Shuoshuo,
];

// 文章及其子菜单
Expand Down Expand Up @@ -135,6 +137,11 @@ export const LinkPresets: Record<string, NavBarLink> = {
url: "/archive/",
icon: "material-symbols:archive",
},
Shuoshuo: {
name: "说说",
url: "/shuoshuo/",
icon: "material-symbols:chat-bubble-outline",
},
Categories: {
name: "分类",
url: "/categories/",
Expand Down
8 changes: 8 additions & 0 deletions src/config/sidebarConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ export const sidebarLayoutConfig: SidebarLayoutConfig = {
// 是否在文章详情页显示
showOnPostPage: true,
},
{
// 组件类型:最新说说组件
type: "latestShuoshuo",
// 是否启用该组件
enable: true,
// 组件位置
position: "sticky",
},
{
// 组件类型:公告组件
type: "announcement",
Expand Down
12 changes: 12 additions & 0 deletions src/content.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,19 @@ const specCollection = defineCollection({
schema: z.object({}),
});

const shuoshuoCollection = defineCollection({
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/shuoshuo" }),
schema: z.object({
published: z.date(),
updated: z.date().optional(),
draft: z.boolean().optional().default(false),
author: z.string().optional().default(""),
avatar: z.string().optional().default(""),
}),
});

export const collections = {
posts: postsCollection,
spec: specCollection,
shuoshuo: shuoshuoCollection,
};
8 changes: 8 additions & 0 deletions src/content/shuoshuo/hello.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
published: 2026-01-01
author: Firefly
---

这是第一条说说示例。

说说适合记录简短动态、灵感片段和不需要写成完整文章的内容。
14 changes: 14 additions & 0 deletions src/content/shuoshuo/long-note.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
published: 2026-01-02
author: Firefly
---

这是一条较长的说说示例,用来展示内容折叠效果。

你可以在这里写多段文字、列表或普通 Markdown 内容。页面会在内容超过设定高度时自动折叠,并显示“展开全文”按钮。

- 支持 Markdown 基础语法
- 自动生成锚点链接
- 可在侧栏展示最新说说

继续补充一些文本,让示例更接近真实使用场景。说说并不需要像文章一样结构完整,它更像是一段即时记录,可以是今天的进展、一个想法,也可以是稍后再整理成文章的素材。
1 change: 1 addition & 0 deletions src/i18n/i18nKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ enum I18nKey {
// 站点统计
siteStats = "siteStats",
siteStatsPostCount = "siteStatsPostCount",
siteStatsShuoshuoCount = "siteStatsShuoshuoCount",
siteStatsCategoryCount = "siteStatsCategoryCount",
siteStatsTagCount = "siteStatsTagCount",
siteStatsTotalWords = "siteStatsTotalWords",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ export const en: Translation = {
// Site Statistics
[Key.siteStats]: "Site Statistics",
[Key.siteStatsPostCount]: "Posts",
[Key.siteStatsShuoshuoCount]: "Shuoshuo",
[Key.siteStatsCategoryCount]: "Categories",
[Key.siteStatsTagCount]: "Tags",
[Key.siteStatsTotalWords]: "Total Words",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ export const ja: Translation = {
// サイト統計
[Key.siteStats]: "サイト統計",
[Key.siteStatsPostCount]: "記事",
[Key.siteStatsShuoshuoCount]: "つぶやき",
[Key.siteStatsCategoryCount]: "カテゴリー",
[Key.siteStatsTagCount]: "タグ",
[Key.siteStatsTotalWords]: "総文字数",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ export const ru: Translation = {
// Статистика сайта
[Key.siteStats]: "Статистика сайта",
[Key.siteStatsPostCount]: "Статьи",
[Key.siteStatsShuoshuoCount]: "Шоушо",
[Key.siteStatsCategoryCount]: "Категории",
[Key.siteStatsTagCount]: "Теги",
[Key.siteStatsTotalWords]: "Всего слов",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/zh_CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ export const zh_CN: Translation = {
// 站点统计
[Key.siteStats]: "站点统计",
[Key.siteStatsPostCount]: "文章",
[Key.siteStatsShuoshuoCount]: "说说",
[Key.siteStatsCategoryCount]: "分类",
[Key.siteStatsTagCount]: "标签",
[Key.siteStatsTotalWords]: "总字数",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/zh_TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ export const zh_TW: Translation = {
// 站點統計
[Key.siteStats]: "站點統計",
[Key.siteStatsPostCount]: "文章",
[Key.siteStatsShuoshuoCount]: "說說",
[Key.siteStatsCategoryCount]: "分類",
[Key.siteStatsTagCount]: "標籤",
[Key.siteStatsTotalWords]: "總字數",
Expand Down
Loading