| ---
|
| import type { CollectionEntry } from "astro:content";
|
| import { render } from "astro:content";
|
| import { Icon } from "astro-icon/components";
|
| import CoverImage from "@/components/common/CoverImage.astro";
|
| import PostMetadata from "@/components/layout/PostMeta.astro";
|
| import I18nKey from "@/i18n/i18nKey";
|
| import { i18n } from "@/i18n/translation";
|
| import { processCoverImageSync } from "@/utils/image-utils";
|
| import { getFileDirFromPath, getTagUrl } from "@/utils/url-utils";
|
|
|
| interface Props {
|
| class?: string;
|
| entry: CollectionEntry<"posts">;
|
| title: string;
|
| url: string;
|
| published: Date;
|
| updated?: Date;
|
| tags: string[];
|
| category: string | null;
|
| image: string;
|
| description: string;
|
| draft: boolean;
|
| pinned?: boolean;
|
| style: string;
|
| loading?: "lazy" | "eager";
|
| }
|
| const {
|
| entry,
|
| title,
|
| url,
|
| published,
|
| updated,
|
| tags,
|
| category,
|
| image,
|
| description,
|
| pinned,
|
| style,
|
| loading = "lazy",
|
| } = Astro.props;
|
| const className = Astro.props.class;
|
|
|
| // 处理随机图:如果image为"api",则从配置的API获取随机图
|
| const processedImage = processCoverImageSync(image, entry.id);
|
| const hasCover =
|
| processedImage !== undefined &&
|
| processedImage !== null &&
|
| processedImage !== "";
|
|
|
| const coverWidth = "30%";
|
|
|
| const { remarkPluginFrontmatter } = await render(entry);
|
| ---
|
|
|
| <div
|
| class:list={[
|
| "post-card-wrapper",
|
| hasCover ? "has-cover" : "no-cover",
|
| pinned ? "pinned" : "",
|
| "card-base flex flex-col-reverse md:flex-col w-full rounded-(--radius-large) overflow-hidden relative",
|
| className,
|
| ]}
|
| style={style}
|
| >
|
| <!-- pinned icon -->
|
| <div
|
| class:list={[
|
| "post-card-content",
|
| "pl-6 md:pl-9 pr-6 md:pr-2 pt-6 md:pt-7 pb-6 md:pb-7 relative flex flex-col h-full",
|
| {
|
| "w-full md:w-[calc(100%-52px-12px)]": !hasCover,
|
| "w-full md:w-[calc(100%-var(--coverWidth)-1.5rem)]": hasCover,
|
| },
|
| ]}
|
| >
|
|
|
|
|
| <a
|
| href={url}
|
| class="post-card-title transition group w-full block font-bold mb-3 text-3xl text-90
|
| hover:text-(--primary) dark:hover:text-(--primary)
|
| active:text-(--title-active) dark:active:text-(--title-active)
|
| before:w-1 before:h-5 before:rounded-md before:bg-(--primary)
|
| before:absolute before:top-[35px] before:left-[18px] before:hidden md:before:block"
|
| >
|
| <!-- {
|
| pinned && (
|
| <>
|
| <Icon
|
| name="mdi:pin"
|
| class="inline text-(--primary) text-2xl mr-2 -translate-y-0.5"
|
| />
|
| </>
|
| )
|
| } -->
|
| {title}<Icon
|
| class="inline-block text-[2rem] text-(--primary) md:hidden align-middle -translate-y-[2px] -ml-1"
|
| name="material-symbols:chevron-right-rounded"
|
| /><Icon
|
| class="text-(--primary) text-[2rem] transition hidden md:inline-block opacity-0 group-hover:opacity-100 -translate-x-2 group-hover:-translate-x-1 align-middle -translate-y-[2px]"
|
| name="material-symbols:chevron-right-rounded"
|
| />
|
| </a>
|
|
|
| <!-- metadata -->
|
| <PostMetadata
|
| published={published}
|
| updated={updated}
|
| tags={tags}
|
| category={category || undefined}
|
| hideTagsForMobile={true}
|
| hideUpdateDate={true}
|
| words={remarkPluginFrontmatter.words}
|
| minutes={remarkPluginFrontmatter.minutes}
|
| showWordCount={true}
|
| pinned={pinned}
|
| className="mb-4 post-meta"
|
| />
|
|
|
| <!-- description -->
|
| <div
|
| class:list={[
|
| "transition text-75 mb-3.5 md:pr-4 description grow",
|
| { "line-clamp-2 md:line-clamp-1": !description },
|
| ]}
|
| >
|
| {description || remarkPluginFrontmatter.excerpt}
|
| </div>
|
|
|
| <!-- tags -->
|
| <div
|
| class="text-sm text-black/30 dark:text-white/30 flex flex-wrap gap-2 transition stats mt-auto"
|
| >
|
| {
|
| tags &&
|
| tags.length > 0 &&
|
| tags.map((tag, _i) => (
|
| <a
|
| href={getTagUrl(tag)}
|
| aria-label={`View all posts with the ${tag.trim()} tag`}
|
| class="btn-regular h-6 text-xs px-2 py-1 rounded-md
|
| transition-all duration-200 hover:scale-105 active:scale-95"
|
| >
|
|
|
| </a>
|
| ))
|
| }
|
| {
|
| !(tags && tags.length > 0) && (
|
| <span class="text-(--content-meta) text-xs">
|
|
|
| </span>
|
| )
|
| }
|
| </div>
|
| </div>
|
|
|
| {
|
| hasCover && (
|
| <a
|
| href={url}
|
| aria-label={title}
|
| class:list={[
|
| "post-card-image",
|
| "group",
|
| "w-full md:w-(--coverWidth)",
|
| "aspect-2/1 md:aspect-auto",
|
| "relative md:absolute md:top-4 md:bottom-4 md:right-4",
|
| "rounded-t-(--radius-large) md:rounded-xl overflow-hidden",
|
| ]}
|
| >
|
| <div class="absolute pointer-events-none z-10 w-full h-full group-hover:bg-black/30 group-active:bg-black/50 transition" />
|
| <div class="absolute pointer-events-none z-20 w-full h-full flex items-center justify-center ">
|
| <Icon
|
| name="material-symbols:chevron-right-rounded"
|
| class="transition opacity-0 group-hover:opacity-100 scale-50 group-hover:scale-100 text-white text-5xl"
|
| />
|
| </div>
|
| <CoverImage
|
| src={processedImage}
|
| basePath={getFileDirFromPath(entry.filePath || '')}
|
| alt="Cover Image of the Post"
|
| class="w-full h-full transition-transform group-active:scale-105"
|
| preview={true}
|
| loading={loading}
|
| />
|
| </a>
|
| )
|
| }
|
|
|
| {
|
| !hasCover && (
|
| <a
|
| href={url}
|
| aria-label={title}
|
| class:list={[
|
| "post-card-enter-btn",
|
| "hidden md:flex btn-regular w-13",
|
| "absolute right-3 top-3 bottom-3 rounded-xl bg-(--enter-btn-bg)",
|
| "hover:bg-(--enter-btn-bg-hover) active:bg-(--enter-btn-bg-active) active:scale-95",
|
| ]}
|
| >
|
| <Icon
|
| name="material-symbols:chevron-right-rounded"
|
| class="transition text-(--primary) text-4xl mx-auto"
|
| />
|
| </a>
|
| )
|
| }
|
| </div>
|
|
|
| <style define:vars={{ coverWidth }}>
|
| /* Grid Mode Styles */
|
| :global(.grid-mode) .post-card-wrapper {
|
| flex-direction: column-reverse !important;
|
| height: 100% !important;
|
| justify-content: flex-end !important;
|
| }
|
|
|
| :global(.grid-mode) .post-card-content {
|
| width: 100% !important;
|
| height: auto !important;
|
| flex-grow: 1 !important;
|
| }
|
|
|
| :global(.grid-mode) .post-card-image {
|
| position: relative !important;
|
| top: auto !important;
|
| bottom: auto !important;
|
| right: auto !important;
|
| width: 100% !important;
|
| margin: 0 !important;
|
| border-radius: var(--radius-large) var(--radius-large) 0 0 !important;
|
| max-height: none !important;
|
| aspect-ratio: 2/1 !important;
|
| }
|
|
|
| :global(.grid-mode) .description {
|
| flex-grow: 0 !important;
|
| }
|
|
|
| :global(.grid-mode) .stats {
|
| margin-top: auto !important;
|
| }
|
|
|
| :global(.grid-mode) .has-cover .post-card-content {
|
| padding-top: 0.8rem !important;
|
| }
|
| :global(.grid-mode) .has-cover .post-card-title::before {
|
| top: 1.3rem !important;
|
| }
|
|
|
| </style>
|
|
|