Skip to content
Merged
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
17 changes: 12 additions & 5 deletions src/components/atoms/SocialShare.astro
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
---
import { Icon } from 'astro-icon/components';

interface Props {
text?: string;
url?: string | URL;
class?: string;
}

const { text, url, class: className = 'inline-block' } = Astro.props;
---

<div class={className}>
<span class="align-super font-bold">Share:</span>
<button class="ml-2" title="Twitter Share" data-aw-social-share="twitter" data-aw-url={url} data-aw-text={text}
><Icon name="logos:twitter" class="w-6 h-6" />
><Icon name="logos:twitter" class="h-6 w-6" />
</button>
<button class="ml-2" title="Facebook Share" data-aw-social-share="facebook" data-aw-url={url}
><Icon name="logos:facebook" class="w-6 h-6" />
><Icon name="logos:facebook" class="h-6 w-6" />
</button>
<button class="ml-2" title="Linkedin Share" data-aw-social-share="linkedin" data-aw-url={url} data-aw-text={text}
><Icon name="logos:linkedin-icon" class="w-6 h-6" />
><Icon name="logos:linkedin-icon" class="h-6 w-6" />
</button>
<button class="ml-2" title="Whatsapp Share" data-aw-social-share="whatsapp" data-aw-url={url} data-aw-text={text}
><Icon name="logos:whatsapp" class="w-6 h-6" />
><Icon name="logos:whatsapp" class="h-6 w-6" />
</button>
<button class="ml-2" title="Email Share" data-aw-social-share="mail" data-aw-url={url} data-aw-text={text}
><Icon name="tabler:mail" class="w-6 h-6" />
><Icon name="tabler:mail" class="h-6 w-6" />
</button>
</div>
7 changes: 6 additions & 1 deletion src/components/atoms/Tags.astro
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
---
import { getPermalink } from '~/utils/permalinks';

interface Props {
tags?: string[];
class?: string;
}

const { tags, class: className = 'text-sm' } = Astro.props;
---

{
tags && Array.isArray(tags) && (
<ul class={className}>
{tags.map((tag) => (
<li class="bg-gray-100 dark:bg-slate-700 inline-block mr-2 mb-2 py-0.5 px-2">
<li class="mb-2 mr-2 inline-block bg-gray-100 px-2 py-0.5 dark:bg-slate-700">
<a href={getPermalink(tag, 'tag')}>{tag}</a>
</li>
))}
Expand Down
35 changes: 26 additions & 9 deletions src/components/blog/GridItem.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { Picture } from 'astro:assets';

import { findImage } from '~/utils/images';
import { getPermalink } from '~/utils/permalinks';
import type { BlogPost } from '~/types/BlogPost';

interface Props {
post: BlogPost;
}

const { post } = Astro.props;

Expand All @@ -11,38 +16,50 @@ const image = await findImage(post.image);

<article class="group mb-6 transition-all duration-300 hover:-translate-y-1">
<a href={getPermalink(post.slug, 'post')} class="block">
<div class="relative h-0 pb-[56.25%] lg:h-64 overflow-hidden bg-gray-400 dark:bg-slate-700 rounded-xl shadow-lg mb-6 group-hover:shadow-xl transition-shadow duration-300">
<div
class="relative mb-6 h-0 overflow-hidden rounded-xl bg-gray-400 pb-[56.25%] shadow-lg transition-shadow duration-300 group-hover:shadow-xl dark:bg-slate-700 lg:h-64"
>
{
image && (
<Picture
src={image}
class="absolute inset-0 object-cover w-full h-full rounded-xl bg-gray-400 dark:bg-slate-700 transform group-hover:scale-105 transition-transform duration-500 ease-out"
class="absolute inset-0 h-full w-full transform rounded-xl bg-gray-400 object-cover transition-transform duration-500 ease-out group-hover:scale-105 dark:bg-slate-700"
widths={[400, 900]}
sizes="(max-width: 900px) 400px, 900px"
alt={post.title}
/>
)
}
<!-- Overlay on hover -->
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 rounded-xl"></div>
<div
class="absolute inset-0 rounded-xl bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100"
>
</div>
<!-- Read more indicator -->
<div class="absolute bottom-4 left-4 right-4 transform translate-y-4 opacity-0 group-hover:translate-y-0 group-hover:opacity-100 transition-all duration-300">
<span class="inline-flex items-center gap-2 text-white text-sm font-semibold">
<div
class="absolute bottom-4 left-4 right-4 translate-y-4 transform opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100"
>
<span class="inline-flex items-center gap-2 text-sm font-semibold text-white">
阅读更多
<svg class="w-4 h-4 transform group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg
class="h-4 w-4 transform transition-transform group-hover:translate-x-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"></path>
</svg>
</span>
</div>
</div>
</a>
<h3 class="mb-2 text-xl font-bold leading-snug sm:text-2xl font-heading">
<h3 class="font-heading mb-2 text-xl font-bold leading-snug sm:text-2xl">
<a
href={getPermalink(post.slug, 'post')}
class="text-slate-900 dark:text-white hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200"
class="text-slate-900 transition-colors duration-200 hover:text-primary-600 dark:text-white dark:hover:text-primary-400"
>
{post.title}
</a>
</h3>
<p class="text-gray-700 dark:text-gray-400 line-clamp-2">{post.excerpt || post.description}</p>
<p class="line-clamp-2 text-gray-700 dark:text-gray-400">{post.excerpt || post.description}</p>
</article>
57 changes: 37 additions & 20 deletions src/components/blog/ListItem.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,42 @@ import PostTags from '~/components/atoms/Tags.astro';
import { getPermalink } from '~/utils/permalinks';
import { findImage } from '~/utils/images';
import { getFormattedDate } from '~/utils/utils';
import type { BlogPost } from '~/types/BlogPost';

interface Props {
post: BlogPost;
}

const { post } = Astro.props;

const image = await findImage(post.image);
const publishDateString = post.publishDate instanceof Date ? post.publishDate.toISOString() : post.publishDate;
---

<article class={`max-w-md mx-auto md:max-w-none grid gap-6 md:gap-8 ${image ? 'md:grid-cols-2' : ''} group`}>
{
image && (
<a class="relative block" href={getPermalink(post.slug, 'post')}>
<div class="relative h-0 pb-[56.25%] md:pb-[75%] md:h-80 lg:pb-[56.25%] overflow-hidden bg-gray-400 dark:bg-slate-700 rounded-xl shadow-lg group-hover:shadow-xl transition-shadow duration-300">
<div class="relative h-0 overflow-hidden rounded-xl bg-gray-400 pb-[56.25%] shadow-lg transition-shadow duration-300 group-hover:shadow-xl dark:bg-slate-700 md:h-80 md:pb-[75%] lg:pb-[56.25%]">
{image && (
<Picture
src={image}
class="absolute inset-0 object-cover w-full h-full rounded-xl bg-gray-400 dark:bg-slate-700 transform group-hover:scale-105 transition-transform duration-500 ease-out"
class="absolute inset-0 h-full w-full transform rounded-xl bg-gray-400 object-cover transition-transform duration-500 ease-out group-hover:scale-105 dark:bg-slate-700"
widths={[400, 900]}
sizes="(max-width: 900px) 400px, 900px"
alt={post.title}
/>
)}
<!-- Overlay gradient on hover -->
<div class="absolute inset-0 bg-gradient-to-t from-black/50 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 rounded-xl"></div>
<!-- Play/Read indicator -->
<div class="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<div class="w-14 h-14 rounded-full bg-white/90 dark:bg-slate-800/90 flex items-center justify-center shadow-lg transform scale-90 group-hover:scale-100 transition-transform duration-300">
<svg class="w-6 h-6 text-primary-600 dark:text-primary-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
<div class="absolute inset-0 rounded-xl bg-gradient-to-t from-black/50 via-transparent to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
<div class="absolute inset-0 flex items-center justify-center opacity-0 transition-opacity duration-300 group-hover:opacity-100">
<div class="flex h-14 w-14 scale-90 transform items-center justify-center rounded-full bg-white/90 shadow-lg transition-transform duration-300 group-hover:scale-100 dark:bg-slate-800/90">
<svg
class="h-6 w-6 text-primary-600 dark:text-primary-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</div>
</div>
Expand All @@ -41,32 +50,40 @@ const image = await findImage(post.image);
}
<div class="flex flex-col justify-center">
<header>
<h2 class="text-xl sm:text-2xl font-bold leading-snug mb-2 font-heading">
<h2 class="font-heading mb-2 text-xl font-bold leading-snug sm:text-2xl">
<a
class="text-slate-900 dark:text-white hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200"
class="text-slate-900 transition-colors duration-200 hover:text-primary-600 dark:text-white dark:hover:text-primary-400"
href={getPermalink(post.slug, 'post')}
>
{post.title}
</a>
</h2>
</header>
<p class="text-md sm:text-lg flex-grow text-gray-600 dark:text-gray-400 line-clamp-3">
<p class="text-md line-clamp-3 flex-grow text-gray-600 dark:text-gray-400 sm:text-lg">
{post.excerpt || post.description}
</p>
<footer class="mt-4">
<div class="flex items-center gap-2">
<span class="text-gray-500 dark:text-slate-400 text-sm flex items-center gap-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
<span class="flex items-center gap-1 text-sm text-gray-500 dark:text-slate-400">
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
<time datetime={post.publishDate}>{getFormattedDate(post.publishDate)}</time>
<time datetime={publishDateString}>{getFormattedDate(post.publishDate)}</time>
</span>
<span class="text-gray-300 dark:text-slate-600">•</span>
<span class="text-gray-500 dark:text-slate-400 text-sm flex items-center gap-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
<span class="flex items-center gap-1 text-sm text-gray-500 dark:text-slate-400">
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
{Math.ceil(post.readingTime)} min
{Math.ceil(post.readingTime ?? 0)} min
</span>
</div>
<div class="mt-4">
Expand Down
35 changes: 20 additions & 15 deletions src/components/blog/SinglePost.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,58 @@ import PostTags from '~/components/atoms/Tags.astro';
import SocialShare from '~/components/atoms/SocialShare.astro';

import { getFormattedDate } from '~/utils/utils';
import type { BlogPost } from '~/types/BlogPost';

interface Props {
post: BlogPost & { body?: string };
url?: string | URL;
}

const { post, url } = Astro.props;

const publishDateString = post.publishDate instanceof Date ? post.publishDate.toISOString() : post.publishDate;
---

<section class="py-8 sm:py-16 lg:py-20 mx-auto">
<section class="mx-auto py-8 sm:py-16 lg:py-20">
<article>
<header class={post.image ? 'text-center' : ''}>
<p class="px-4 sm:px-6 max-w-3xl mx-auto">
<time datetime={post.publishDate}>{getFormattedDate(post.publishDate)}</time> ~ {Math.ceil(post.readingTime)} min
read
<p class="mx-auto max-w-3xl px-4 sm:px-6">
<time datetime={publishDateString}>{getFormattedDate(post.publishDate)}</time> ~ {
Math.ceil(post.readingTime ?? 0)
} min read
</p>
<h1
class="px-4 sm:px-6 max-w-3xl mx-auto text-4xl md:text-5xl font-bold leading-tighter tracking-tighter mb-8 font-heading"
class="leading-tighter font-heading mx-auto mb-8 max-w-3xl px-4 text-4xl font-bold tracking-tighter sm:px-6 md:text-5xl"
>
{post.title}
</h1>
{
post.image ? (
<Picture
src={post.image}
class="max-w-full lg:max-w-6xl mx-auto mt-4 mb-6 sm:rounded-md bg-gray-400 dark:bg-slate-700"
class="mx-auto mb-6 mt-4 max-w-full bg-gray-400 dark:bg-slate-700 sm:rounded-md lg:max-w-6xl"
widths={[400, 900]}
sizes="(max-width: 900px) 400px, 900px"
alt={post.description}
loading="eager"
width={900}
width={900}
height={506}
/>
) : (
<div class="max-w-3xl mx-auto px-4 sm:px-6">
<div class="mx-auto max-w-3xl px-4 sm:px-6">
<div class="border-t dark:border-slate-700" />
</div>
)
}
</header>
<div
class="container mx-auto px-6 sm:px-6 max-w-3xl prose prose-lg lg:prose-xl dark:prose-invert dark:prose-headings:text-slate-300 prose-md prose-headings:font-heading prose-headings:leading-tighter prose-headings:tracking-tighter prose-headings:font-bold prose-a:text-primary-600 dark:prose-a:text-primary-400 prose-img:rounded-md prose-img:shadow-lg mt-8"
class="prose-md prose-headings:font-heading prose-headings:leading-tighter container prose prose-lg mx-auto mt-8 max-w-3xl px-6 dark:prose-invert lg:prose-xl prose-headings:font-bold prose-headings:tracking-tighter prose-a:text-primary-600 prose-img:rounded-md prose-img:shadow-lg dark:prose-headings:text-slate-300 dark:prose-a:text-primary-400 sm:px-6"
>
{post.Content ? <post.Content /> : <Fragment set:html={post.body} />}
</div>
<div class="container mx-auto px-6 sm:px-6 max-w-3xl mt-8 flex justify-between flex-col sm:flex-row">
<div class="container mx-auto mt-8 flex max-w-3xl flex-col justify-between px-6 sm:flex-row sm:px-6">
<PostTags tags={post.tags} class="mr-5" />
<SocialShare
url={url}
text={post.title}
class="mt-5 sm:mt-1 align-middle text-gray-500 dark:text-slate-600"
/>
<SocialShare url={url} text={post.title} class="mt-5 align-middle text-gray-500 dark:text-slate-600 sm:mt-1" />
</div>
</article>
</section>
Loading