feat: add custom thumb support with url suffix. (#49)

This commit is contained in:
Yufan Sheng 2024-06-21 00:17:18 +08:00
parent 179a2b088f
commit 6b2211ba85
Signed by: syhily
GPG Key ID: 9D18A22A7DCD5A9B
13 changed files with 92 additions and 86 deletions

View File

@ -121,6 +121,7 @@
"tsconfigs", "tsconfigs",
"ultrahtml", "ultrahtml",
"unpic", "unpic",
"unsharp",
"upyun", "upyun",
"urlset", "urlset",
"varchar", "varchar",

View File

@ -13,6 +13,9 @@ export default defineConfig({
security: { security: {
checkOrigin: true, checkOrigin: true,
}, },
image: {
domains: ['localhost', '127.0.0.1'],
},
experimental: { experimental: {
env: { env: {
schema: { schema: {

View File

@ -1,5 +1,7 @@
import { z } from 'astro/zod'; import { z } from 'astro/zod';
const isProd = (): boolean => import.meta.env.MODE === 'production' || process.env.NODE_ENV === 'production';
// The type of the options, use zod for better validation. // The type of the options, use zod for better validation.
const Options = z const Options = z
.object({ .object({
@ -65,9 +67,12 @@ const Options = z
admins: z.array(z.number()), admins: z.array(z.number()),
}), }),
}), }),
thumbnail: z
.function()
.args(z.object({ src: z.string().min(1), width: z.number(), height: z.number() }))
.returns(z.string()),
}) })
.transform((opts) => { .transform((opts) => {
const isProd = (): boolean => import.meta.env.MODE === 'production' || process.env.NODE_ENV === 'production';
const assetsPrefix = (): string => (isProd() ? opts.settings.assetPrefix : opts.local.website); const assetsPrefix = (): string => (isProd() ? opts.settings.assetPrefix : opts.local.website);
return { return {
...opts, ...opts,
@ -81,7 +86,7 @@ const Options = z
}; };
}); });
const options = { const options: z.input<typeof Options> = {
local: { local: {
port: 4321, port: 4321,
}, },
@ -172,6 +177,15 @@ const options = {
admins: [3], admins: [3],
}, },
}, },
thumbnail: ({ src, width, height }) => {
if (isProd()) {
// Add upyun thumbnail support.
return `${src}!upyun520/both/${width}x${height}/quality/100/unsharp/true/progressive/true`;
}
// See https://docs.astro.build/en/reference/image-service-reference/#local-services
// Remember to add the localhost to you image service settings.
return `http://localhost:4321/_image?href=${src}&w=${width}&h=${height}&f=webp&q=100`;
},
}; };
export default Options.parse(options); export default Options.parse(options);

View File

@ -1,5 +1,6 @@
--- ---
import { blurStyle, type Image } from '@/helpers/images'; import { blurStyle, type Image } from '@/helpers/images';
import options from '@/options';
interface Props extends Image { interface Props extends Image {
alt: string; alt: string;
@ -8,4 +9,11 @@ interface Props extends Image {
const { alt, src, width, height } = Astro.props; const { alt, src, width, height } = Astro.props;
--- ---
<img {src} {alt} loading="lazy" {width} {height} style={blurStyle(Astro.props)} /> <img
src={options.thumbnail({ src, width, height })}
{alt}
loading="lazy"
{width}
{height}
style={blurStyle(Astro.props)}
/>

View File

@ -1,14 +0,0 @@
---
import PinnedCategory from '@/components/page/category/PinnedCategory.astro';
import { getCategory } from '@/helpers/schema';
import options from '@/options';
const pinnedSlug = options.settings.post.category ?? [];
const pinnedCategories = pinnedSlug
.map((slug) => getCategory(undefined, slug))
.flatMap((category) => (category !== undefined ? [category] : []));
---
<div class="row g-2 g-md-4 list-grouped mt-3 mt-md-4">
{pinnedCategories.map((category) => <PinnedCategory category={category} />)}
</div>

View File

@ -1,35 +0,0 @@
---
import Image from '@/components/image/Image.astro';
import type { Category } from '@/helpers/schema';
interface Props {
category: Category;
}
const { category } = Astro.props;
---
<div class="col-md-6">
<div class="list-item block">
<div class="media media-3x1">
<a href={category.permalink} class="media-content">
<Image {...category.cover} alt={category.name} />
</a>
</div>
<div class="list-content">
<div class="list-body">
<a href={category.permalink} class="list-title h5">
{category.name}
</a>
<div class="list-subtitle d-none d-md-block text-md text-secondary mt-2">
<div class="h-1x">{category.description}</div>
</div>
</div>
<div class="list-footer mt-2">
<div class="text-muted text-sm">
<span class="d-inline-block">{`${category.counts} 篇文章`}</span>
</div>
</div>
</div>
</div>
</div>

View File

@ -12,7 +12,7 @@ const { post } = Astro.props;
<div class="list-item list-nice-overlay"> <div class="list-item list-nice-overlay">
<div class="media media-3x2"> <div class="media media-3x2">
<a href={post.permalink} class="media-content"> <a href={post.permalink} class="media-content">
<Image {...post.cover} alt={post.title} /> <Image {...post.cover} alt={post.title} width={750} height={500} />
<div class="overlay"></div> <div class="overlay"></div>
</a> </a>
</div> </div>

View File

@ -15,7 +15,7 @@ const category = getCategory(post.category, undefined);
<div class="list-item block"> <div class="list-item block">
<div class="media media-3x2 col-6 col-md-5"> <div class="media media-3x2 col-6 col-md-5">
<a href={post.permalink} class="media-content"> <a href={post.permalink} class="media-content">
<Image {...post.cover} alt={post.title} /> <Image {...post.cover} alt={post.title} width={600} height={400} />
</a> </a>
<div class="media-overlay overlay-top"> <div class="media-overlay overlay-top">
<a class="d-none d-md-inline-block badge badge-md bg-white-overlay" href={category ? category.permalink : ''}> <a class="d-none d-md-inline-block badge badge-md bg-white-overlay" href={category ? category.permalink : ''}>

View File

@ -1,12 +1,25 @@
--- ---
import PostCard from '@/components/page/post/PostCard.astro'; import Pagination from '@/components/page/pagination/Pagination.astro';
import { slicePosts } from '@/helpers/formatter';
import type { Post } from '@/helpers/schema'; import type { Post } from '@/helpers/schema';
import options from '@/options';
import PostCard from './PostCard.astro';
interface Props { interface Props {
posts: Post[]; posts: Post[];
pageNum: number;
} }
const { posts } = Astro.props; const { pageNum, posts } = Astro.props;
const results = slicePosts(posts, pageNum, options.settings.pagination.posts);
if (!results) {
return Astro.redirect('/404');
}
const { currentPosts, totalPage } = results;
--- ---
<div class="list-grid">{posts.map((post) => <PostCard post={post} />)}</div> <div class="content-wrapper content-wrapper col-12 col-xl-9">
<div class="list-grid">{currentPosts.map((post) => <PostCard post={post} />)}</div>
<Pagination current={pageNum} total={totalPage} rootPath={'/'} />
</div>

View File

@ -0,0 +1,41 @@
---
import Image from '@/components/image/Image.astro';
import { getCategory } from '@/helpers/schema';
import options from '@/options';
const pinnedSlug = options.settings.post.category ?? [];
const pinnedCategories = pinnedSlug
.map((slug) => getCategory(undefined, slug))
.flatMap((category) => (category !== undefined ? [category] : []));
---
<div class="row g-2 g-md-4 list-grouped mt-3 mt-md-4">
{
pinnedCategories.map((category) => (
<div class="col-md-6">
<div class="list-item block">
<div class="media media-3x1">
<a href={category.permalink} class="media-content">
<Image {...category.cover} alt={category.name} width={600} height={200} />
</a>
</div>
<div class="list-content">
<div class="list-body">
<a href={category.permalink} class="list-title h5">
{category.name}
</a>
<div class="list-subtitle d-none d-md-block text-md text-secondary mt-2">
<div class="h-1x">{category.description}</div>
</div>
</div>
<div class="list-footer mt-2">
<div class="text-muted text-sm">
<span class="d-inline-block">{`${category.counts} 篇文章`}</span>
</div>
</div>
</div>
</div>
</div>
))
}
</div>

View File

@ -1,25 +0,0 @@
---
import Pagination from '@/components/page/pagination/Pagination.astro';
import PostCards from '@/components/page/post/PostCards.astro';
import { slicePosts } from '@/helpers/formatter';
import type { Post } from '@/helpers/schema';
import options from '@/options';
interface Props {
posts: Post[];
pageNum: number;
}
const { pageNum, posts } = Astro.props;
const results = slicePosts(posts, pageNum, options.settings.pagination.posts);
if (!results) {
return Astro.redirect('/404');
}
const { currentPosts, totalPage } = results;
---
<div class="content-wrapper content-wrapper col-12 col-xl-9">
<PostCards posts={currentPosts} />
<Pagination current={pageNum} total={totalPage} rootPath={'/'} />
</div>

View File

@ -16,7 +16,7 @@ const { post, first } = Astro.props;
<div class="list-item list-nice-overlay"> <div class="list-item list-nice-overlay">
<div class={`media ${first ? 'media-36x17' : ''}`}> <div class={`media ${first ? 'media-36x17' : ''}`}>
<a href={post.permalink} class="media-content"> <a href={post.permalink} class="media-content">
<Image {...post.cover} alt={post.title} /> <Image {...post.cover} alt={post.title} width={first ? 600 : 300} height={300} />
<div class="overlay"></div> <div class="overlay"></div>
</a> </a>
</div> </div>

View File

@ -1,7 +1,7 @@
--- ---
import PinnedCategories from '@/components/page/category/PinnedCategories.astro';
import FeaturePosts from '@/components/page/post/FeaturePosts.astro'; import FeaturePosts from '@/components/page/post/FeaturePosts.astro';
import PostPagination from '@/components/page/post/PostPagination.astro'; import PostCards from '@/components/page/post/PostCards.astro';
import PinnedCategories from '@/components/page/post/PostCategory.astro';
import Sidebar from '@/components/sidebar/Sidebar.astro'; import Sidebar from '@/components/sidebar/Sidebar.astro';
import type { Post, Tag } from '@/helpers/schema'; import type { Post, Tag } from '@/helpers/schema';
@ -27,7 +27,7 @@ if (pageNum < 1) {
<FeaturePosts {posts} /> <FeaturePosts {posts} />
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<PostPagination {pageNum} {posts} /> <PostCards {pageNum} {posts} />
<Sidebar {posts} {tags} /> <Sidebar {posts} {tags} />
</div> </div>
<PinnedCategories /> <PinnedCategories />