Commit 478254c
Changed files (14)
src
assets
img
components
pages
posts
styles
types
utils
src/components/aside/siteinfo/Stats.astro
@@ -2,9 +2,9 @@
import { asideConfig, siteConfig } from '@/config';
import I18nKey from '@i18n/I18nKey';
import { i18n } from '@i18n/translation';
-import { getOrCreateRenderResult, getPostsCount } from '@utils/content-utils';
+import { getPostsCount } from '@utils/content-utils';
import { Icon } from 'astro-icon/components';
-import { getCollection } from 'astro:content';
+import { getCollection, render } from 'astro:content';
---
<div class="stats stats-vertical w-full">
@@ -49,7 +49,7 @@ import { getCollection } from 'astro:content';
const entries = await getCollection('posts');
const words = await Promise.all(
entries.map(async (entry) => {
- const { remarkPluginFrontmatter } = await getOrCreateRenderResult(entry);
+ const { remarkPluginFrontmatter } = await render(entry);
return remarkPluginFrontmatter.words as number;
})
);
src/components/widgets/PostCard.astro
@@ -15,9 +15,10 @@ interface Props {
category?: string;
cover?: string;
description: string;
+ basePath: string;
}
-const { title, url, published, updated, tags, category, cover } = Astro.props;
+const { title, url, published, updated, tags, category, cover, basePath } = Astro.props;
const className = Astro.props.class;
const hasCover = cover !== '' && cover !== undefined && cover !== null;
@@ -88,7 +89,7 @@ const metas: ({ icon: string; text: string; link?: string; time?: Date } | undef
{
hasCover ? (
<figure class="md:w-3/4 md:max-w-96">
- <PostCardCover url={url} title={title} cover={cover} />
+ <PostCardCover url={url} title={title} cover={cover} basePath={basePath} />
</figure>
) : (
<figure>
src/components/widgets/PostCardCover.astro
@@ -6,9 +6,10 @@ interface Props {
url: string;
title: string;
cover: string;
+ basePath?: string;
}
-const { url, title, cover } = Astro.props;
+const { url, title, cover, basePath } = Astro.props;
---
<a
@@ -21,5 +22,5 @@ const { url, title, cover } = Astro.props;
>
<Icon name="material-symbols:chevron-right-rounded" class="h-24 w-24 text-white" />
</div>
- <ImageWrapper src={cover} alt={title} />
+ <ImageWrapper src={cover} alt={title} basePath={basePath} />
</a>
src/components/Banner.astro
@@ -5,21 +5,24 @@ import ImageWrapper from './utils/ImageWrapper.astro';
interface Props {
src?: string;
+ basePath?: string;
}
const siteBanner = siteConfig.banner;
if (siteBanner === false) throw Error('Should not show this error');
const src = Astro.props.src || (siteBanner.src as string);
-const customBanner = Boolean(Astro.props.src !== undefined);
---
-<div
- id="banner"
- class="relative max-h-screen scale-105 overflow-hidden opacity-0 duration-1000"
- data-custom-banner={customBanner}
->
- <ImageWrapper id="banner-img" src={src} class="h-full object-cover" />
+<div id="banner" class="relative max-h-screen scale-105 opacity-0 duration-1000">
+ <div class="h-full w-full">
+ <ImageWrapper
+ id="banner-img"
+ src={src}
+ class="swup-transition-parallel absolute! h-full w-full overflow-hidden"
+ basePath={Astro.props.basePath}
+ />
+ </div>
<div
id="banner-mask"
class="from-base-100 to-base-100/0 absolute bottom-0 grid h-1/2 min-h-48 w-full bg-linear-0"
@@ -103,6 +106,21 @@ const customBanner = Boolean(Astro.props.src !== undefined);
const heightExtend = document.getElementById('page-height-extend');
heightExtend?.classList.add('hidden');
});
+
+ // 处理 Banner 图片切换
+ window.swup?.hooks.before('content:insert', (_, { containers }) => {
+ for (const container of containers) {
+ if (container.selector !== '#banner-img') continue;
+ const prevWrapper = container.previous;
+ const nextWrapper = container.next;
+ const prevImg = prevWrapper.querySelector('img') as HTMLImageElement;
+ const nextImg = nextWrapper.querySelector('img') as HTMLImageElement;
+ if (prevImg.src !== nextImg.src) continue;
+ prevWrapper.classList.add('hidden');
+ prevWrapper.classList.remove('swup-transition-parallel');
+ nextWrapper.classList.remove('swup-transition-parallel');
+ }
+ });
}
if (window.swup) {
src/components/PostsPage.astro
@@ -1,14 +1,12 @@
---
import { siteConfig } from '@/config';
-import type { BlogPostData } from '@/types/data';
+import type { BlogPost } from '@/types/data';
+import path from 'path';
import Pagination from './widgets/Pagination.astro';
import PostCard from './widgets/PostCard.astro';
interface Props {
- posts: {
- body: string;
- data: BlogPostData;
- }[];
+ posts: BlogPost[];
currentPage: number;
postsPerPage?: number;
baseUrl: string;
@@ -36,6 +34,7 @@ posts = posts.slice((currentPage - 1) * postsPerPage, currentPage * postsPerPage
category={data.category}
cover={data.cover}
description={data.description}
+ basePath={path.join('content/posts/', post.id)}
/>
);
})
src/layouts/GlobalLayout.astro
@@ -14,10 +14,14 @@ interface Props {
title?: string;
description?: string;
lang?: string;
+ banner?: {
+ src: string;
+ basePath?: string;
+ };
}
let { title, lang } = Astro.props;
-const { description } = Astro.props;
+const { description, banner } = Astro.props;
let pageTitle: string;
if (title) pageTitle = `${title} - ${siteConfig.title}`;
@@ -81,7 +85,7 @@ const favicons: Favicon[] =
<SideToolBar />
<Navbar>
<slot name="header" />
- {siteConfig.banner !== false && <Banner />}
+ {siteConfig.banner !== false && <Banner src={banner?.src} basePath={banner?.basePath} />}
<div id="body-wrap" class="w-full items-center md:px-4">
<slot />
</div>
src/layouts/MainLayout.astro
@@ -6,11 +6,15 @@ interface Props {
title?: string;
description?: string;
lang?: string;
+ banner?: {
+ src: string;
+ basePath?: string;
+ };
}
-const { title, description, lang } = Astro.props;
+const { title, description, lang, banner } = Astro.props;
---
-<GlobalLayout title={title} description={description} lang={lang}>
+<GlobalLayout title={title} description={description} lang={lang} banner={banner}>
<slot slot="head" name="head" />
<slot slot="header" name="header" />
<!-- Main content -->
src/layouts/PostPageLayout.astro
@@ -12,12 +12,16 @@ interface Props {
lang?: string;
headings?: MarkdownHeading[];
comment?: boolean;
+ banner?: {
+ src: string;
+ basePath?: string;
+ };
}
-const { title, description, lang, headings, comment } = Astro.props;
+const { title, description, lang, headings, comment, banner } = Astro.props;
---
-<MainLayout title={title} description={description} lang={lang}>
+<MainLayout title={title} description={description} lang={lang} banner={banner}>
<slot slot="head" name="head" />
<slot slot="header-content" name="header-content" />
<div class="card border-base-300 swup-transition-fade border-2 px-6 py-4">
src/pages/posts/[article].astro
@@ -5,8 +5,8 @@ import PostInfo from '@components/misc/PostInfo.astro';
import ImageWrapper from '@components/utils/ImageWrapper.astro';
import Markdown from '@components/utils/Markdown.astro';
import PostPageLayout from '@layouts/PostPageLayout.astro';
-import { getOrCreateRenderResult } from '@utils/content-utils';
-import { getCollection } from 'astro:content';
+import { getCollection, render } from 'astro:content';
+import path from 'path';
export async function getStaticPaths() {
const articles = await getCollection('posts');
@@ -17,11 +17,12 @@ export async function getStaticPaths() {
}
const { article } = Astro.props;
-const { Content, headings, remarkPluginFrontmatter } = await getOrCreateRenderResult(article);
+const { Content, headings, remarkPluginFrontmatter } = await render(article);
const hasCover =
article.data.cover !== '' && article.data.cover !== undefined && article.data.cover !== null;
const description = article.data.description || remarkPluginFrontmatter.excerpt;
+const basePath = path.join('content/posts/', article.id);
---
<PostPageLayout
@@ -30,6 +31,7 @@ const description = article.data.description || remarkPluginFrontmatter.excerpt;
headings={headings}
comment={article.data.comment}
lang={article.data.lang}
+ banner={hasCover ? { src: article.data.cover, basePath: basePath } : undefined}
>
<Fragment slot="header-content">
<PostInfo
@@ -49,6 +51,7 @@ const description = article.data.description || remarkPluginFrontmatter.excerpt;
src={article.data.cover}
class="mb-6 rounded-xl shadow"
alt={article.data.title}
+ basePath={basePath}
/>
)
}
src/styles/transition.css
@@ -14,6 +14,16 @@ html.is-animating .swup-transition-slide {
@apply -translate-x-20 opacity-0;
}
+html.is-changing .swup-transition-parallel {
+ @apply transition-all duration-500 ease-linear;
+}
+html.is-changing .swup-transition-parallel.is-previous-container {
+ @apply -translate-x-full;
+}
+html.is-changing .swup-transition-parallel.is-next-container {
+ @apply translate-x-full;
+}
+
.onload-animation {
opacity: 0;
animation: 300ms fade-in-up;
src/types/data.ts
@@ -1,3 +1,5 @@
+import type { RenderedContent } from 'astro:content';
+
export type BlogPostData = {
body: string;
title: string;
@@ -10,3 +12,10 @@ export type BlogPostData = {
category?: string;
comment?: boolean;
};
+
+export type BlogPost = {
+ id: string;
+ rendered: RenderedContent;
+ body: string;
+ data: BlogPostData;
+};
src/utils/content-utils.ts
@@ -1,38 +1,11 @@
import type { BlogPostData } from '@/types/data';
+import type { BlogPost } from '@/types/data';
import I18nKey from '@i18n/I18nKey';
import { i18n } from '@i18n/translation';
-import type { MarkdownHeading } from 'astro';
-import type { AstroComponentFactory } from 'astro/runtime/server/index.d.ts';
import { getCollection } from 'astro:content';
-import { type CollectionEntry, render } from 'astro:content';
-interface RenderResult {
- Content: AstroComponentFactory;
- headings: MarkdownHeading[];
- remarkPluginFrontmatter: Record<string, unknown>;
-}
-
-const renderCache = new Map<string, RenderResult>();
-
-export async function getOrCreateRenderResult(article: CollectionEntry<'posts'>) {
- const cacheKey = article.id;
-
- if (renderCache.has(cacheKey)) {
- return renderCache.get(cacheKey)!;
- }
-
- const { Content, headings, remarkPluginFrontmatter } = await render(article);
- const result = { Content, headings, remarkPluginFrontmatter };
-
- renderCache.set(cacheKey, result);
- return result;
-}
-
-export async function getSortedPosts(): Promise<{ body: string; data: BlogPostData }[]> {
- const allBlogPosts = (await getCollection('posts')) as unknown as {
- body: string;
- data: BlogPostData;
- }[];
+export async function getSortedPosts(): Promise<BlogPost[]> {
+ const allBlogPosts = (await getCollection('posts')) as unknown as BlogPost[];
const sortedBlogPosts = allBlogPosts.sort(
(a: { data: BlogPostData }, b: { data: BlogPostData }) => {
const dateA = new Date(a.data.published);
astro.config.mjs
@@ -35,11 +35,12 @@ export default defineConfig({
mdx(),
swup({
theme: false,
- containers: ['main'],
+ containers: ['main', '#banner-img'],
animationClass: 'swup-transition-',
globalInstance: true,
smoothScrolling: true,
progress: true,
+ parallel: ['#banner-img'],
}),
],
markdown: {