|
1 | 1 | --- |
2 | 2 | import { format } from "date-fns"; |
3 | | -import noImage from "../images/no-image.svg"; |
4 | | -import type { Article } from "../schema"; |
5 | 3 | import { Image } from "astro:assets"; |
| 4 | +import { getCollection } from "astro:content"; |
| 5 | +import { markdownToTxt } from "markdown-to-txt"; |
6 | 6 |
|
7 | 7 | interface Props { |
8 | | - variant?: "normal" | "compact"; |
9 | | - articles: { data: Article; body?: string }[]; |
| 8 | + variant?: "full" | "compact"; |
10 | 9 | class?: string; |
11 | 10 | } |
12 | 11 | const props = Astro.props; |
13 | | -const { variant = "normal", articles } = props; |
| 12 | +const { variant = "full" } = props; |
| 13 | +
|
| 14 | +const articles = (await getCollection("articles")).sort( |
| 15 | + (a, b) => b.data.date.getTime() - a.data.date.getTime(), |
| 16 | +); |
| 17 | +if (variant === "compact") { |
| 18 | + articles.splice(3); |
| 19 | +} |
| 20 | +
|
| 21 | +const MDXSpecialSyntax = /^import ActionButton from ".+";/; |
| 22 | +
|
| 23 | +const articlesWithExcerpts = await Promise.all( |
| 24 | + articles.map(async (article) => { |
| 25 | + if (!article.body) throw new Error("article doesn't have body!"); |
| 26 | + // 😇 performance |
| 27 | + const text = markdownToTxt(article.body); |
| 28 | + return { |
| 29 | + article, |
| 30 | + text: text.replace(MDXSpecialSyntax, ""), |
| 31 | + }; |
| 32 | + }), |
| 33 | +); |
14 | 34 | --- |
15 | 35 |
|
16 | | -<!-- |
17 | | - fragment ArticleListArticle on Mdx { |
18 | | - id |
19 | | - frontmatter { |
20 | | - date |
21 | | - slug |
22 | | - title |
23 | | - image { |
24 | | - childImageSharp { |
25 | | - gatsbyImageData( |
26 | | - width: 600 |
27 | | - height: 400 |
28 | | - transformOptions: { fit: INSIDE } |
29 | | - ) |
30 | | - largeGatsbyImageData: gatsbyImageData( |
31 | | - width: 1200 |
32 | | - height: 800 |
33 | | - transformOptions: { fit: INSIDE } |
34 | | - ) |
35 | | - } |
36 | | - } |
37 | | - } |
38 | | - excerpt(pruneLength: 60) |
39 | | - longExcerpt: excerpt(pruneLength: 200) |
40 | | - } |
41 | | ---> |
42 | 36 | <ul |
43 | 37 | class:list={[ |
44 | 38 | "grid grid-flow-dense gap-9 md:grid-cols-2 lg:grid-cols-3", |
45 | | - variant === "normal" && "xl:grid-cols-4", |
| 39 | + variant === "full" ? "variant-full xl:grid-cols-4" : "variant-compact", |
46 | 40 | props.class, |
47 | 41 | ]} |
48 | 42 | > |
49 | 43 | { |
50 | | - articles.map((article, i) => { |
51 | | - const isFeatured = variant === "normal" && i % 11 === 0; // 11 記事ごとに大きく表示する |
| 44 | + articlesWithExcerpts.map(({ article, text }, i) => { |
| 45 | + const isFeatured = variant === "full" && i % 11 === 0; // 11 記事ごとに大きく表示する |
52 | 46 | const additionalProps = isFeatured |
53 | 47 | ? { |
54 | 48 | cellClassName: "md:col-span-2 md:row-span-2", |
55 | | - imageClassName: "h-[350px] md:h-[450px]", |
56 | | - excerpt: article.body?.slice(0, 120), |
| 49 | + imageClassName: "max-h-[350px] md:max-h-[450px]", |
| 50 | + excerpt: text.slice(0, 300), |
57 | 51 | } |
58 | 52 | : { |
59 | 53 | cellClassName: "", |
60 | | - imageClassName: "h-[200px]", |
61 | | - excerpt: article.body?.slice(0, 120), |
| 54 | + imageClassName: "max-h-[200px]", |
| 55 | + excerpt: text.slice(0, 80), |
62 | 56 | }; |
63 | 57 | return ( |
64 | 58 | <li class="contents"> |
65 | 59 | <a |
66 | | - href={`/articles/${article.data.slug}`} |
| 60 | + href={`/articles/${article.id}`} |
67 | 61 | class:list={[ |
68 | | - "block hover:opacity-80", |
| 62 | + "relative rounded-xl p-1 shadow-md hover:brightness-110 active:top-0.5", |
69 | 63 | additionalProps.cellClassName, |
70 | 64 | ]} |
71 | 65 | > |
72 | | - <div |
| 66 | + <Image |
| 67 | + loading={i < 5 ? "eager" : "lazy"} |
| 68 | + alt="カバー画像" |
| 69 | + height={48 * 4 * 4} |
| 70 | + width={48 * 4 * 4} |
| 71 | + src={article.data.image} |
73 | 72 | class:list={[ |
74 | | - "isolate h-48 w-full overflow-clip rounded-xl object-cover", |
| 73 | + "animate-move w-full rounded-xl object-contain object-top", |
75 | 74 | additionalProps.imageClassName, |
76 | 75 | ]} |
77 | | - > |
78 | | - <Image |
79 | | - alt="カバー画像" |
80 | | - height={48 * 4 * 4} |
81 | | - width={48 * 4 * 4} |
82 | | - src={article.data.image} |
83 | | - class="h-full w-full object-cover" |
84 | | - /> |
85 | | - </div> |
| 76 | + /> |
86 | 77 | <div class="mt-4"> |
87 | 78 | <time class="block text-sm text-gray-500"> |
88 | 79 | {format(article.data.date, "yyyy/MM/dd HH:mm")} |
89 | 80 | </time> |
90 | 81 | <h3 class="text-lg font-bold">{article.data.title}</h3> |
91 | | - <p class="prose mt-2 max-w-none">{additionalProps.excerpt}</p> |
| 82 | + <p class="prose mt-2 max-w-none">{additionalProps.excerpt}...</p> |
92 | 83 | </div> |
93 | 84 | </a> |
94 | 85 | </li> |
95 | 86 | ); |
96 | 87 | }) |
97 | 88 | } |
98 | 89 | </ul> |
| 90 | +<style> |
| 91 | + @import "tailwindcss"; |
| 92 | + @media (max-width: 64rem) { |
| 93 | + .variant-compact :nth-child(n + 3) a { |
| 94 | + @apply hidden; |
| 95 | + } |
| 96 | + } |
| 97 | + @media (max-width: 80rem) { |
| 98 | + .variant-compact :nth-child(n + 4) a { |
| 99 | + @apply hidden; |
| 100 | + } |
| 101 | + } |
| 102 | +</style> |
0 commit comments