Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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={isZh ? "前往说说页面" : "Go to shuoshuo page"}
>
<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