🚀

Zenn を Astro で雑クローン

2022/09/15に公開

Astro の Markdown の機能を使ってみたかったのでローカルの Zenn 的なものを作ることにした。

セットアップ

Astro と Tailwind CSS のテンプレートリポジトリを作っておいたのでこれをクローンして使います。

$ git clone https://github.com/ogty/astro-tailwindcss-template
$ cd astro-tailwindcss-template
$ pnpm install

記事作成

記事作成を簡略化したいので Makefile を作ります。
記事は./src/pages/postsに配置することにします。

name        ?= $(shell uuidgen)
today       := $(shell date '+%Y/%m/%d')
POSTS_PATH  := ./src/pages/posts
LAYOUT_PATH := ../../layouts/ArticleLayout.astro
TEMPLATE    := $(shell echo '---\\nlayout: $(LAYOUT_PATH)\\ntitle:\\nemoji:\\ntype:\\ndate: $(today)\\n---')

article:
	@echo $(TEMPLATE) > $(POSTS_PATH)/$(name).md

新しい記事は ↓ で作成できます。nameを指定しない場合はuuidがファイル名になります。

$ make article
$ make article name=article1

実行すると ↓ のような内容になっているので、

---
layout: ../../layouts/ArticleLayout.astro
title: 
emoji: 
type: 
date: 2022/09/15
---

適当に書き足します。

---
layout: ../../layouts/ArticleLayout.astro
title: 記事1
emoji: 🥺
type: tech
date: 2022/09/15
---

処理

作成された./src/pages/posts/*.mdの記事を Zenn 風にしていきます。

./src/index.astro

---
import Layout from '@layouts/Layout.astro';
import Card from '../components/Card.astro';

export type Type = 'TECH' | 'IDEA';

interface Post {
  title: string;
  emoji: string;
  type: Type;
  date: string;
  url: string;
}

const posts = await Astro.glob('../pages/posts/*.md');
const postsList = posts.map((post): Post => {
  const { title, date, emoji } = post.frontmatter;
  const type = post.frontmatter.type.toUpperCase();
  const url = post.url as string;

  return {
    title,
    emoji,
    type,
    date,
    url
  };
});
---

<Layout title="Memo" backgroundColor="#F1F5F9">
  <main class="mt-0 mx-auto pt-16 px-4 sm:px-6 md:px-10 lg:px-10" style="max-width: 960px;">
    <div
      class="grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 gap-4 sm:gap-7 md:gap-7 lg:gap-7"
    >
      {
        postsList.map((post: Post) => {
          return <Card {...post} />;
        })
      }
    </div>
  </main>
</Layout>

↓ で記事の---で囲った情報が取れるので、これをPost型に合うようにいい感じに処理します。

---
const posts = await Astro.glob('../pages/posts/*.md');
---

./src/components/Card.astro

---
export type Type = 'TECH' | 'IDEA';

export interface Props {
  title: string;
  emoji: string;
  type: Type;
  date: string;
  url: string;
}

const { title, emoji, type, date, url } = Astro.props;
---

<article
  class="flex flex-col relative max-w-sm bg-white rounded-xl border border-gray-200 shadow-lg"
>
  <a class="absolute top-0 left-0 h-full w-full flex-1" href={url}>&nbsp;</a>
  <div class="absolute top-3 left-3 flex space-x-2 justify-center">
    <span
      style={`background-color: ${type === 'TECH' ? '#3EA8FF' : '#807AFF'}; font-size: 10px;`}
      class="text-xs inline-block py-1 px-1.5 leading-none text-center whitespace-nowrap align-baseline font-semibold text-white rounded-full"
    >
      {type.toUpperCase()}
    </span>
  </div>
  <div
    class="py-7 text-center text-5xl rounded-t-lg"
    style={`background-color: ${type === 'TECH' ? '#CFE5FF' : '#DBDFFF'};`}
  >
    {emoji}
  </div>
  <div class="pt-4 px-4 flex-1">
    <h5 class="text-md font-semibold tracking-tigh">
      {title}
    </h5>
  </div>
  <div class="pb-3 px-4 pt-1">
    <span class="text-xs" style="color: #93A5B1;">
      {date}
    </span>
  </div>
</article>

index.astroから受け取ったデータを展開します。

記事用のレイアウト作成

次に記事用のレイアウトを作成します。

./layouts/ArticleLayout.astro

---
import Layout from './Layout.astro';

const { frontmatter } = Astro.props;
const { title, emoji } = frontmatter;
---

<Layout title={title} backgroundColor="#edf2f7">
  <header class="text-center p-14 max-w-4xl my-0 mx-auto">
    <span class="text-7xl">{emoji}</span>
    <h1 class="mt-6 text-3xl font-bold">{title}</h1>
  </header>
  <div class="mt-0 mx-auto bg-white rounded-xl p-8" style="max-width: 960px;">
    <slot />
  </div>
</Layout>

<style is:global>
  ...
</style>

記事のlayoutArticleLayout.astroを指定すれば、<slot />に記事の内容が展開されます。

ただ、Tailwind CSS で元々のタグのデザインがなくなっているので、<style is:global>に記事用の CSS を書く必要があります。
Markdown 内でも問題なく Tailwind CSS は適応されるので、記事ごとにデザインを変えられますが、面倒なのでグローバルに書きます。

結構良さそうです。

Discussion