Merge branch 'astro-v2'

This commit is contained in:
satnaing
2023-01-31 20:22:09 +06:30
48 changed files with 1656 additions and 3077 deletions
+3
View File
@@ -18,3 +18,6 @@ pnpm-debug.log*
# macOS-specific files # macOS-specific files
.DS_Store .DS_Store
# ignore .astro directory
.astro
+2 -1
View File
@@ -9,4 +9,5 @@
!astro.config.mjs !astro.config.mjs
!package.json !package.json
!.prettierrc !.prettierrc
!.eslintrc.json !.eslintrc.json
!README.md
+23 -18
View File
@@ -14,6 +14,7 @@ This theme is self-documented \_ which means articles/posts in this theme can al
## 🔥 Features ## 🔥 Features
- [x] type-safe markdown
- [x] super fast performance - [x] super fast performance
- [x] accessible (Keyboard/VoiceOver) - [x] accessible (Keyboard/VoiceOver)
- [x] responsive (mobile ~ desktops) - [x] responsive (mobile ~ desktops)
@@ -54,8 +55,11 @@ Inside of AstroPaper, you'll see the following folders and files:
│ ├── assets/ │ ├── assets/
│ │ └── socialIcons.ts │ │ └── socialIcons.ts
│ ├── components/ │ ├── components/
│ ├── contents/ │ ├── content/
│ │ └── some-blog-posts.md │ │ | blog/
│ │ | └── some-blog-posts.md
│ │ └── _schemas.ts
│ │ └── config.ts
│ ├── layouts/ │ ├── layouts/
│ └── pages/ │ └── pages/
│ └── styles/ │ └── styles/
@@ -69,16 +73,18 @@ Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page
Any static assets, like images, can be placed in the `public/` directory. Any static assets, like images, can be placed in the `public/` directory.
All blog posts are stored in `src/contents/` directory. All blog posts are stored in `src/content/blog` directory.
## 📖 Documentation ## 📖 Documentation
Documentation can be read in two formats\_ _markdown_ & _blog post_. Documentation can be read in two formats\_ _markdown_ & _blog post_.
- Configuration - [markdown](src/contents/how-to-configure-astropaper-theme.md) | [blog post](https://astro-paper.pages.dev/posts/how-to-configure-astropaper-theme/) - Configuration - [markdown](src/content/blog/how-to-configure-astropaper-theme.md) | [blog post](https://astro-paper.pages.dev/posts/how-to-configure-astropaper-theme/)
- Add Posts - [markdown](src/contents/adding-new-post.md) | [blog post](https://astro-paper.pages.dev/posts/adding-new-posts-in-astropaper-theme/) - Add Posts - [markdown](src/content/blog/adding-new-post.md) | [blog post](https://astro-paper.pages.dev/posts/adding-new-posts-in-astropaper-theme/)
- Customize Color Schemes - [markdown](src/contents/customizing-astropaper-theme-color-schemes.md) | [blog post](https://astro-paper.pages.dev/posts/customizing-astropaper-theme-color-schemes/) - Customize Color Schemes - [markdown](src/content/blog/customizing-astropaper-theme-color-schemes.md) | [blog post](https://astro-paper.pages.dev/posts/customizing-astropaper-theme-color-schemes/)
- Predefined Color Schemes - [markdown](src/contents/predefined-color-schemes.md) | [blog post](https://astro-paper.pages.dev/posts/predefined-color-schemes/) - Predefined Color Schemes - [markdown](src/content/blog/predefined-color-schemes.md) | [blog post](https://astro-paper.pages.dev/posts/predefined-color-schemes/)
> For AstroPaper v1, check out [this branch](https://github.com/satnaing/astro-paper/tree/astro-paper-v1) and this [live URL](https://astro-paper-v1.astro-paper.pages.dev/)
## 💻 Tech Stack ## 💻 Tech Stack
@@ -121,17 +127,16 @@ PUBLIC_GOOGLE_SITE_VERIFICATION=your-google-site-verification-value
All commands are run from the root of the project, from a terminal: All commands are run from the root of the project, from a terminal:
| Command | Action | | Command | Action |
| :--------------------- | :------------------------------------------------- | | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------- |
| `npm install` | Installs dependencies | | `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:3000` | | `npm run dev` | Starts local dev server at `localhost:3000` |
| `npm run build` | Build your production site to `./dist/` | | `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying | | `npm run preview` | Preview your build locally, before deploying |
| `npm run format:check` | Check code format with Prettier | | `npm run format:check` | Check code format with Prettier |
| `npm run format` | Format codes with Prettier | | `npm run format` | Format codes with Prettier |
| `npm run cz` | Commit code changes with commitizen | | `npm run sync` | Generates TypeScript types for all Astro modules. [Learn more](https://docs.astro.build/en/reference/cli-reference/#astro-sync). |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` | | `npm run cz` | Commit code changes with commitizen |
| `npm run astro --help` | Get help using the Astro CLI |
## ✨ Feedback & Suggestions ## ✨ Feedback & Suggestions
+1171 -2721
View File
File diff suppressed because it is too large Load Diff
+8 -6
View File
@@ -7,6 +7,7 @@
"start": "astro dev", "start": "astro dev",
"build": "astro build", "build": "astro build",
"preview": "astro preview", "preview": "astro preview",
"sync": "astro sync",
"astro": "astro", "astro": "astro",
"format:check": "prettier --check .", "format:check": "prettier --check .",
"format": "prettier --write .", "format": "prettier --write .",
@@ -14,19 +15,19 @@
"prepare": "husky install" "prepare": "husky install"
}, },
"dependencies": { "dependencies": {
"@astrojs/rss": "^1.0.3", "@astrojs/rss": "^2.1.0",
"astro": "^1.9.2", "astro": "^2.0.4",
"fuse.js": "^6.6.2", "fuse.js": "^6.6.2",
"github-slugger": "^2.0.0", "github-slugger": "^2.0.0",
"remark-collapse": "^0.1.2", "remark-collapse": "^0.1.2",
"remark-toc": "^8.0.1", "remark-toc": "^8.0.1",
"satori": "^0.1.1", "satori": "^0.1.2",
"tailwindcss": "^3.2.4" "tailwindcss": "^3.2.4"
}, },
"devDependencies": { "devDependencies": {
"@astrojs/react": "^1.2.2", "@astrojs/react": "^2.0.2",
"@astrojs/sitemap": "^1.0.0", "@astrojs/sitemap": "^1.0.1",
"@astrojs/tailwind": "^2.1.3", "@astrojs/tailwind": "^3.0.1",
"@tailwindcss/typography": "^0.5.9", "@tailwindcss/typography": "^0.5.9",
"@types/github-slugger": "^1.3.0", "@types/github-slugger": "^1.3.0",
"@types/react": "^18.0.27", "@types/react": "^18.0.27",
@@ -35,6 +36,7 @@
"husky": "^8.0.3", "husky": "^8.0.3",
"lint-staged": "^13.1.0", "lint-staged": "^13.1.0",
"prettier": "^2.8.3", "prettier": "^2.8.3",
"prettier-plugin-tailwindcss": "^0.2.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0"
}, },
+1 -1
View File
@@ -1,4 +1,4 @@
import type { SocialIcons } from "src/types"; import type { SocialIcons } from "@types";
const socialIcons: SocialIcons = { const socialIcons: SocialIcons = {
Github: `<svg Github: `<svg
+10 -4
View File
@@ -22,7 +22,11 @@ breadcrumbList[0] === "posts" &&
breadcrumbList.map((breadcrumb, index) => breadcrumbList.map((breadcrumb, index) =>
index + 1 === breadcrumbList.length ? ( index + 1 === breadcrumbList.length ? (
<li> <li>
<span class={`${index > 0 ? "lowercase" : ""}`} aria-current="page"> <span
class={`${index > 0 ? "lowercase" : "capitalize"}`}
aria-current="page"
>
{/* make the last part lowercase in Home > Tags > some-tag */}
{breadcrumb} {breadcrumb}
</span> </span>
</li> </li>
@@ -39,14 +43,16 @@ breadcrumbList[0] === "posts" &&
<style> <style>
.breadcrumb { .breadcrumb {
@apply max-w-3xl mx-auto px-4 w-full mt-8 mb-1; @apply mx-auto mt-8 mb-1 w-full max-w-3xl px-4;
} }
.breadcrumb ul li { .breadcrumb ul li {
@apply inline; @apply inline;
} }
.breadcrumb ul li a, .breadcrumb ul li a {
@apply capitalize opacity-70;
}
.breadcrumb ul li span { .breadcrumb ul li span {
@apply opacity-70 capitalize; @apply opacity-70;
} }
.breadcrumb ul li:not(:last-child) a { .breadcrumb ul li:not(:last-child) a {
@apply hover:opacity-100; @apply hover:opacity-100;
+17 -16
View File
@@ -1,31 +1,32 @@
import Datetime from "./Datetime"; import Datetime from "./Datetime";
import type { Frontmatter } from "src/types"; import type { BlogFrontmatter } from "@content/_schemas";
export interface Props { export interface Props {
href?: string; href?: string;
post: Frontmatter; frontmatter: BlogFrontmatter;
secHeading?: boolean; secHeading?: boolean;
} }
const styles = { export default function Card({ href, frontmatter, secHeading = true }: Props) {
cardContainer: "my-6", const { title, pubDatetime, description } = frontmatter;
titleLink:
"text-skin-accent font-medium text-lg underline-offset-4 decoration-dashed focus-visible:no-underline focus-visible:underline-offset-0 inline-block",
titleHeading: "font-medium text-lg decoration-dashed hover:underline",
};
export default function Card({ href, post, secHeading = true }: Props) {
return ( return (
<li className={styles.cardContainer}> <li className="my-6">
<a href={href} className={styles.titleLink}> <a
href={href}
className="inline-block text-lg font-medium text-skin-accent decoration-dashed underline-offset-4 focus-visible:no-underline focus-visible:underline-offset-0"
>
{secHeading ? ( {secHeading ? (
<h2 className={styles.titleHeading}>{post.title}</h2> <h2 className="text-lg font-medium decoration-dashed hover:underline">
{title}
</h2>
) : ( ) : (
<h3 className={styles.titleHeading}>{post.title}</h3> <h3 className="text-lg font-medium decoration-dashed hover:underline">
{title}
</h3>
)} )}
</a> </a>
<Datetime datetime={post.datetime} /> <Datetime datetime={pubDatetime} />
<p>{post.description}</p> <p>{description}</p>
</li> </li>
); );
} }
+4 -4
View File
@@ -1,17 +1,17 @@
export interface Props { export interface Props {
datetime: string; datetime: string | Date;
size?: "sm" | "lg"; size?: "sm" | "lg";
className?: string; className?: string;
} }
export default function Datetime({ datetime, size = "sm", className }: Props) { export default function Datetime({ datetime, size = "sm", className }: Props) {
return ( return (
<div className={`opacity-80 flex items-center space-x-2 ${className}`}> <div className={`flex items-center space-x-2 opacity-80 ${className}`}>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
className={`${ className={`${
size === "sm" ? "scale-90" : "scale-100" size === "sm" ? "scale-90" : "scale-100"
} w-6 h-6 inline-block fill-skin-base`} } inline-block h-6 w-6 fill-skin-base`}
aria-hidden="true" aria-hidden="true"
> >
<path d="M7 11h2v2H7zm0 4h2v2H7zm4-4h2v2h-2zm0 4h2v2h-2zm4-4h2v2h-2zm0 4h2v2h-2z"></path> <path d="M7 11h2v2H7zm0 4h2v2H7zm4-4h2v2h-2zm0 4h2v2h-2zm4-4h2v2h-2zm0 4h2v2h-2z"></path>
@@ -25,7 +25,7 @@ export default function Datetime({ datetime, size = "sm", className }: Props) {
); );
} }
const FormattedDatetime = ({ datetime }: { datetime: string }) => { const FormattedDatetime = ({ datetime }: { datetime: string | Date }) => {
const myDatetime = new Date(datetime); const myDatetime = new Date(datetime);
const date = myDatetime.toLocaleDateString([], { const date = myDatetime.toLocaleDateString([], {
+3 -3
View File
@@ -28,16 +28,16 @@ const { noMarginTop = false } = Astro.props;
@apply w-full; @apply w-full;
} }
.footer-wrapper { .footer-wrapper {
@apply py-6 sm:py-4 flex flex-col sm:flex-row-reverse justify-between items-center; @apply flex flex-col items-center justify-between py-6 sm:flex-row-reverse sm:py-4;
} }
.link-button { .link-button {
@apply p-2 my-1 hover:rotate-6; @apply my-1 p-2 hover:rotate-6;
} }
.link-button svg { .link-button svg {
@apply scale-125; @apply scale-125;
} }
.copyright-wrapper { .copyright-wrapper {
@apply my-2 flex flex-col sm:flex-row items-center whitespace-nowrap; @apply my-2 flex flex-col items-center whitespace-nowrap sm:flex-row;
} }
.separator { .separator {
@apply hidden sm:inline; @apply hidden sm:inline;
+15 -15
View File
@@ -1,5 +1,5 @@
--- ---
import { LOGO_IMAGE, SITE } from "src/config"; import { LOGO_IMAGE, SITE } from "@config";
import Hr from "./Hr.astro"; import Hr from "./Hr.astro";
import LinkButton from "./LinkButton.astro"; import LinkButton from "./LinkButton.astro";
@@ -107,35 +107,35 @@ const { activeNav } = Astro.props;
<style> <style>
#skip-to-content { #skip-to-content {
@apply bg-skin-accent text-skin-inverted py-2 px-3 absolute -top-full focus:top-4 left-16 z-50 transition-all; @apply absolute -top-full left-16 z-50 bg-skin-accent py-2 px-3 text-skin-inverted transition-all focus:top-4;
} }
.nav-container { .nav-container {
@apply max-w-3xl mx-auto flex flex-col sm:flex-row justify-between items-center; @apply mx-auto flex max-w-3xl flex-col items-center justify-between sm:flex-row;
} }
.top-nav-wrap { .top-nav-wrap {
@apply relative w-full p-4 sm:py-8 flex justify-between items-start sm:items-center; @apply relative flex w-full items-start justify-between p-4 sm:items-center sm:py-8;
} }
.logo { .logo {
@apply absolute sm:static font-semibold text-xl sm:text-2xl py-1; @apply absolute py-1 text-xl font-semibold sm:static sm:text-2xl;
} }
.hamburger-menu { .hamburger-menu {
@apply self-end p-2 sm:hidden; @apply self-end p-2 sm:hidden;
} }
.hamburger-menu svg { .hamburger-menu svg {
@apply fill-skin-base w-6 h-6 scale-125; @apply h-6 w-6 scale-125 fill-skin-base;
} }
nav { nav {
@apply w-full sm:py-0 sm:ml-2 flex flex-col items-center sm:justify-end sm:flex-row sm:space-x-4 bg-skin-fill; @apply flex w-full flex-col items-center bg-skin-fill sm:ml-2 sm:flex-row sm:justify-end sm:space-x-4 sm:py-0;
} }
nav ul { nav ul {
@apply mt-4 w-44 grid grid-rows-4 grid-cols-2 gap-y-2 gap-x-2 sm:gap-y-0 sm:mt-0 sm:ml-0 sm:w-auto sm:gap-x-5; @apply mt-4 grid w-44 grid-cols-2 grid-rows-4 gap-y-2 gap-x-2 sm:mt-0 sm:ml-0 sm:w-auto sm:gap-y-0 sm:gap-x-5;
} }
nav ul li { nav ul li {
@apply col-span-2 flex justify-center items-center; @apply col-span-2 flex items-center justify-center;
} }
nav ul li a { nav ul li a {
@apply px-4 py-3 w-full text-center sm:px-2 sm:py-1 sm:my-0 font-medium hover:text-skin-accent; @apply w-full px-4 py-3 text-center font-medium hover:text-skin-accent sm:my-0 sm:px-2 sm:py-1;
} }
nav ul li:nth-child(4) a { nav ul li:nth-child(4) a {
@apply w-auto; @apply w-auto;
@@ -145,7 +145,7 @@ const { activeNav } = Astro.props;
@apply col-span-1; @apply col-span-1;
} }
nav a.active { nav a.active {
@apply underline underline-offset-4 decoration-wavy decoration-2; @apply underline decoration-wavy decoration-2 underline-offset-4;
} }
nav a.active svg { nav a.active svg {
@apply fill-skin-accent; @apply fill-skin-accent;
@@ -155,20 +155,20 @@ const { activeNav } = Astro.props;
@apply p-1; @apply p-1;
} }
nav button svg { nav button svg {
@apply w-6 h-6 fill-skin-base hover:fill-skin-accent; @apply h-6 w-6 fill-skin-base hover:fill-skin-accent;
} }
#theme-btn { #theme-btn {
@apply p-3 sm:p-1; @apply p-3 sm:p-1;
} }
#theme-btn svg { #theme-btn svg {
@apply hover:rotate-12 scale-125 sm:scale-100; @apply scale-125 hover:rotate-12 sm:scale-100;
} }
.icon-container { .icon-container {
@apply w-6 h-5 flex-col justify-between items-end; @apply h-5 w-6 flex-col items-end justify-between;
} }
.icon-container div { .icon-container div {
@apply bg-skin-inverted h-0.5 transition-all; @apply h-0.5 bg-skin-inverted transition-all;
} }
#first-line { #first-line {
@apply w-full; @apply w-full;
+12 -13
View File
@@ -2,13 +2,12 @@ import Fuse from "fuse.js";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import Card from "@components/Card"; import Card from "@components/Card";
import slugify from "@utils/slugify"; import slugify from "@utils/slugify";
import type { Frontmatter } from "src/types"; import type { BlogFrontmatter } from "@content/_schemas";
type SearchItem = { export type SearchItem = {
title: string; title: string;
description: string; description: string;
headings: string[]; data: BlogFrontmatter;
frontmatter: Frontmatter;
}; };
interface Props { interface Props {
@@ -32,7 +31,7 @@ export default function SearchBar({ searchList }: Props) {
}; };
const fuse = new Fuse(searchList, { const fuse = new Fuse(searchList, {
keys: ["title", "description", "headings"], keys: ["title", "description"],
includeMatches: true, includeMatches: true,
minMatchCharLength: 2, minMatchCharLength: 2,
threshold: 0.5, threshold: 0.5,
@@ -79,14 +78,14 @@ export default function SearchBar({ searchList }: Props) {
</svg> </svg>
</span> </span>
<input <input
className="placeholder:italic placeholder:text-opacity-75 py-3 pl-10 pr-3 className="block w-full rounded border border-skin-fill
block bg-skin-fill w-full rounded border-opacity-40 bg-skin-fill py-3 pl-10
border border-skin-fill border-opacity-40 pr-3 placeholder:italic placeholder:text-opacity-75
focus:outline-none focus:border-skin-accent" focus:border-skin-accent focus:outline-none"
placeholder="Search for anything..." placeholder="Search for anything..."
type="text" type="text"
name="search" name="search"
defaultValue={inputVal} value={inputVal}
onChange={handleChange} onChange={handleChange}
autoComplete="off" autoComplete="off"
autoFocus autoFocus
@@ -108,9 +107,9 @@ export default function SearchBar({ searchList }: Props) {
{searchResults && {searchResults &&
searchResults.map(({ item, refIndex }) => ( searchResults.map(({ item, refIndex }) => (
<Card <Card
post={item.frontmatter} href={`/posts/${slugify(item.data)}`}
href={`/posts/${slugify(item.frontmatter)}`} frontmatter={item.data}
key={`${refIndex}-${slugify(item.frontmatter)}`} key={`${refIndex}-${slugify(item.data)}`}
/> />
))} ))}
</ul> </ul>
+2 -2
View File
@@ -1,5 +1,5 @@
--- ---
import { SOCIALS } from "src/config"; import { SOCIALS } from "@config";
import LinkButton from "./LinkButton.astro"; import LinkButton from "./LinkButton.astro";
import socialIcons from "@assets/socialIcons"; import socialIcons from "@assets/socialIcons";
@@ -29,6 +29,6 @@ const { centered = false } = Astro.props;
@apply flex-wrap justify-center gap-1; @apply flex-wrap justify-center gap-1;
} }
.link-button { .link-button {
@apply p-2 sm:p-1 hover:rotate-6; @apply p-2 hover:rotate-6 sm:p-1;
} }
</style> </style>
+5 -5
View File
@@ -13,8 +13,8 @@ const { name, size = "sm" } = Astro.props;
}`} }`}
> >
<a <a
href={`/tags/${name}`} href={`/tags/${name.toLowerCase()}`}
class={`${size === "sm" ? "pr-1 text-sm" : "pr-2 text-lg"} group`} class={`${size === "sm" ? "text-sm" : "text-lg"} pr-2 group`}
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -23,15 +23,15 @@ const { name, size = "sm" } = Astro.props;
d="M16.018 3.815 15.232 8h-4.966l.716-3.815-1.964-.37L8.232 8H4v2h3.857l-.751 4H3v2h3.731l-.714 3.805 1.965.369L8.766 16h4.966l-.714 3.805 1.965.369.783-4.174H20v-2h-3.859l.751-4H21V8h-3.733l.716-3.815-1.965-.37zM14.106 14H9.141l.751-4h4.966l-.752 4z" d="M16.018 3.815 15.232 8h-4.966l.716-3.815-1.964-.37L8.232 8H4v2h3.857l-.751 4H3v2h3.731l-.714 3.805 1.965.369L8.766 16h4.966l-.714 3.805 1.965.369.783-4.174H20v-2h-3.859l.751-4H21V8h-3.733l.716-3.815-1.965-.37zM14.106 14H9.141l.751-4h4.966l-.752 4z"
></path> ></path>
</svg> </svg>
&nbsp;<span>{name}</span> &nbsp;<span>{name.toLowerCase()}</span>
</a> </a>
</li> </li>
<style> <style>
a { a {
@apply underline decoration-dashed hover:text-skin-accent relative hover:-top-0.5 focus-visible:p-1; @apply relative underline decoration-dashed hover:-top-0.5 hover:text-skin-accent focus-visible:p-1;
} }
a svg { a svg {
@apply w-6 h-6 text-skin-base scale-95 opacity-80 -mr-5 group-hover:fill-skin-accent; @apply -mr-5 h-6 w-6 scale-95 text-skin-base opacity-80 group-hover:fill-skin-accent;
} }
</style> </style>
+15
View File
@@ -0,0 +1,15 @@
import { z } from "astro:content";
export const blogSchema = z.object({
author: z.string().optional(),
pubDatetime: z.date(),
title: z.string(),
postSlug: z.string().optional(),
featured: z.boolean().optional(),
draft: z.boolean().optional(),
tags: z.array(z.string()).default(["others"]),
ogImage: z.string().optional(),
description: z.string(),
});
export type BlogFrontmatter = z.infer<typeof blogSchema>;
@@ -1,8 +1,8 @@
--- ---
author: Sat Naing author: Sat Naing
datetime: 2022-09-23T15:22:00Z pubDatetime: 2022-09-23T15:22:00Z
title: Adding new posts in AstroPaper theme title: Adding new posts in AstroPaper theme
slug: adding-new-posts-in-astropaper-theme postSlug: adding-new-posts-in-astropaper-theme
featured: true featured: true
draft: false draft: false
tags: tags:
@@ -23,33 +23,48 @@ Frontmatter is the main place to store some important information about the post
Here is the list of frontmatter property for each post. Here is the list of frontmatter property for each post.
| Property | Description | Remark | | Property | Description | Remark |
| ----------------- | ----------------------------------------------------------------------------------------- | ------------------------- | | ----------------- | ------------------------------------------------------------------------------- | --------------------------------------------- |
| **_title_** | Title of the post. (h1) | required<sup>\*</sup> | | **_title_** | Title of the post. (h1) | required<sup>\*</sup> |
| **_description_** | Description of the post. Used in post excerpt and site description of the post. | default = SITE.desc | | **_description_** | Description of the post. Used in post excerpt and site description of the post. | required<sup>\*</sup> |
| **_author_** | Author of the post. | default = SITE.author | | **_pubDatetime_** | Published datetime in ISO 8601 format. | required<sup>\*</sup> |
| **_datetime_** | Published datetime in ISO 8601 format. | | | **_author_** | Author of the post. | default = SITE.author |
| **_slug_** | Slug for the post. Usually the all lowercase title seperated in `-` instead of whtiespace | default = slugified title | | **_postSlug_** | Slug for the post. Will automatically be slugified. | default = slugified title |
| **_featured_** | Whether or not display this post in featured section of home page | default = false | | **_featured_** | Whether or not display this post in featured section of home page | default = false |
| **_draft_** | Mark this post 'unpublished'. | default = false | | **_draft_** | Mark this post 'unpublished'. | default = false |
| **_tags_** | Related keywords for this post. Written in array yaml format. | | | **_tags_** | Related keywords for this post. Written in array yaml format. | default = others |
| **_ogImage_** | OG image of the post. Useful for social media sharing and SEO. | default = SITE.ogImage | | **_ogImage_** | OG image of the post. Useful for social media sharing and SEO. | default = SITE.ogImage or generated SVG image |
`title` and `slug` fields in frontmatter must be specified. Only `title`, `description` and `pubDatetime` fields in frontmatter must be specified.
Title is the title of the post and it is very important for search engine optimization (SEO). Title and description (excerpt) are important for search engine optimization (SEO) and thus AstroPaper encourages to include these in blog posts.
`slug` is the unique identifier of the url. Thus, `slug` must be unique and different from other posts. The whitespace of `slug` needs to be separated with `-` or `_` but `-` is recommended. If slug is not specified, the slugified title of the post will be used as slug. `slug` is the unique identifier of the url. Thus, `slug` must be unique and different from other posts. The whitespace of `slug` needs to be separated with `-` or `_` but `-` is recommended. However, even if you don't write the correct slug, AstroPaper will automatically slugify your incorrect slug. If slug is not specified, the slugified title of the post will be used as slug.
Here is the sample frontmatter for the post. If you omit `tags` in a blog post (in other words, if no tag is specified), the default tag `others` will be used as a tag for that post. You can set the default tag in the `/src/content/_schemas.ts` file.
```ts
// src/contents/_schemas.ts
export const blogSchema = z.object({
// ---
// replace "others" with whatever you want
tags: z.array(z.string()).default(["others"]),
ogImage: z.string().optional(),
description: z.string(),
});
```
### Sample Frontmatter
Here is the sample frontmatter for a post.
```yaml ```yaml
# src/contents/sample-post.md # src/contents/sample-post.md
--- ---
title: The title of the post title: The title of the post
author: your name author: your name
datetime: 2022-09-21T05:17:19Z pubDatetime: 2022-09-21T05:17:19Z
slug: the-title-of-the-post postSlug: the-title-of-the-post
featured: true featured: true
draft: false draft: false
tags: tags:
@@ -101,3 +116,5 @@ My recommendation for image compression sites.
### OG Image ### OG Image
The default OG image will be placed if a post does not specify the OG image. Though not required, OG image related to the post should be specify in the frontmatter. The recommended size for OG image is **_1200 X 640_** px. The default OG image will be placed if a post does not specify the OG image. Though not required, OG image related to the post should be specify in the frontmatter. The recommended size for OG image is **_1200 X 640_** px.
> Since AstroPaper v1.4.0, OG images will be generated automatically if not specified. Check out [the announcement](https://astro-paper.pages.dev/posts/dynamic-og-image-generation-in-astropaper-blog-posts/).
+91
View File
@@ -0,0 +1,91 @@
---
author: Sat Naing
pubDatetime: 2023-01-30T15:57:52.737Z
title: AstroPaper 2.0
postSlug: astro-paper-2
featured: true
ogImage: https://user-images.githubusercontent.com/53733092/215771435-25408246-2309-4f8b-a781-1f3d93bdf0ec.png
tags:
- release
description: AstroPaper with the enhancements of Astro v2. Type-safe markdown contents, bug fixes and better dev experience etc.
---
Astro 2.0 has been released with some cool features, breaking changes, DX improvements, better error overlay and so on. AstroPaper takes advantage of those cool features, especially Content Collections API.
<!-- ![Introducing AstroPaper 2.0](https://user-images.githubusercontent.com/53733092/215683840-dc2502f5-8c5a-44f0-a26c-4e7180455056.png) -->
![Introducing AstroPaper 2.0](https://user-images.githubusercontent.com/53733092/215771435-25408246-2309-4f8b-a781-1f3d93bdf0ec.png)
## Table of contents
## Features & Changes
### Type-safe Frontmatters and Redefined Blog Schema
Frontmatter of AstroPaper 2.0 markdown contents are now type-safe thanks to Astros Content Collections. Blog schema is defined inside the `src/content/_schemas.ts` file.
### New Home for Blog contents
All the blog posts were moved from `src/contents` to `src/content/blog` directory.
### New Fetch API
Contents are now fetched with `getCollection` function. No relative path to the content needs to be specified anymore.
```ts
// old content fetching method
- const postImportResult = import.meta.glob<MarkdownInstance<Frontmatter>>(
"../contents/**/**/*.md",);
// new content fetching method
+ const postImportResult = await getCollection("blog");
```
### Modified Search Logic for better Search Result
In the older version of AstroPaper, when someone search some article, the search cirtea keys that will be searched are `title`, `description` and `headings` (heading means all the headings h1 ~ h6 of the blog post). In AstroPaper v2, only `title` and `description` will be searched as the user types.
### Renamed Frontmatter Properties
The following frontmatter properties are renamed.
| Old Names | New Names |
| --------- | ----------- |
| datetime | pubDatetime |
| slug | postSlug |
### Default Tag for blog post
If a blog post doesn't have any tag (in other words, frontmatter property `tags` is not specified), the default tag `others` will be used for that blog post. But you can set the default tag in the `/src/content/_schemas.ts` file.
```ts
// src/contents/_schemas.ts
export const blogSchema = z.object({
// ---
// replace "others" with whatever you want
tags: z.array(z.string()).default(["others"]),
ogImage: z.string().optional(),
description: z.string(),
});
```
### New Predefined Dark Color Scheme
AstroPaper v2 has a new dark color scheme (high contrast & low contrast) which is based on Astro's dark logo. Check out [this link](https://astro-paper.pages.dev/posts/predefined-color-schemes#astro-dark) for more info.
![New Predefined Dark Color Scheme](https://user-images.githubusercontent.com/53733092/215680520-59427bb0-f4cb-48c0-bccc-f182a428d72d.svg)
### Automatic Class Sorting
AstroPaper 2.0 includes automatic class sorting with [TailwindCSS Prettier plugin](https://tailwindcss.com/blog/automatic-class-sorting-with-prettier)
### Updated Docs & README
All the [#docs](https://astro-paper.pages.dev/tags/docs/) blog posts and [README](https://github.com/satnaing/astro-paper#readme) are updated for this AstroPaper v2.
## Bug Fixes
- fix broken tags in the Blog Post page
- in a tag page, the last part of the breadcrumb is now updated to lower-case for consistency
- exclude draft posts in a tag page
- fix 'onChange value not updating issue' after a page reload
@@ -1,8 +1,8 @@
--- ---
author: Sat Naing author: Sat Naing
datetime: 2022-09-25T15:20:35Z pubDatetime: 2022-09-25T15:20:35Z
title: Customizing AstroPaper theme color schemes title: Customizing AstroPaper theme color schemes
slug: "" postSlug: ""
featured: false featured: false
draft: false draft: false
tags: tags:
@@ -130,3 +130,5 @@ Here is an example of changing the light color scheme.
} }
} }
``` ```
> Check out some [predefined color schemes](https://astro-paper.pages.dev/posts/predefined-color-schemes/) AstroPaper has already crafted for you.
@@ -1,8 +1,8 @@
--- ---
author: Sat Naing author: Sat Naing
datetime: 2022-12-28T04:59:04.866Z pubDatetime: 2022-12-28T04:59:04.866Z
title: Dynamic OG image generation in AstroPaper blog posts title: Dynamic OG image generation in AstroPaper blog posts
slug: dynamic-og-image-generation-in-astropaper-blog-posts postSlug: dynamic-og-image-generation-in-astropaper-blog-posts
featured: false featured: false
draft: false draft: false
tags: tags:
@@ -1,8 +1,8 @@
--- ---
author: Sat Naing author: Sat Naing
datetime: 2022-09-23T04:58:53Z pubDatetime: 2022-09-23T04:58:53Z
title: How to configure AstroPaper theme title: How to configure AstroPaper theme
slug: how-to-configure-astropaper-theme postSlug: how-to-configure-astropaper-theme
featured: true featured: true
draft: false draft: false
tags: tags:
@@ -37,15 +37,15 @@ export const SITE = {
Here are SITE configuration options Here are SITE configuration options
| Options | Description | | Options | Description |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `website` | Your deployed website url | | `website` | Your deployed website url |
| `author` | Your name | | `author` | Your name |
| `desc` | Your site description. Useful for SEO and social media sharing. | | `desc` | Your site description. Useful for SEO and social media sharing. |
| `title` | Your site name | | `title` | Your site name |
| `ogImage` | Your default OG image for the site. Useful for social media sharing. OG image can be updated under `/public` directory. | | `ogImage` | Your default OG image for the site. Useful for social media sharing. OG images can be an external image url or they can be placed under `/public` directory. |
| `lightAndDarkMode` | Enable or disable `light & dark mode` for the website. If disabled, primary color scheme will be used. This option is enabled by default. | | `lightAndDarkMode` | Enable or disable `light & dark mode` for the website. If disabled, primary color scheme will be used. This option is enabled by default. |
| `postPerPage` | You can specify how many posts will be displayed in each posts page. (eg: if you set SITE.postPerPage to 3, each page will only show 3 posts per page) | | `postPerPage` | You can specify how many posts will be displayed in each posts page. (eg: if you set SITE.postPerPage to 3, each page will only show 3 posts per page) |
## Configuring logo or title ## Configuring logo or title
@@ -1,9 +1,9 @@
--- ---
title: How to connect AstroPaper blog with Forestry CMS title: How to connect AstroPaper blog with Forestry CMS
author: Sat Naing author: Sat Naing
datetime: 2022-09-21T05:17:19Z pubDatetime: 2022-09-21T05:17:19Z
slug: how-to-connect-astro-paper-blog-with-forestry-cms postSlug: how-to-connect-astro-paper-blog-with-forestry-cms
featured: true featured: false
draft: false draft: false
tags: tags:
- docs - docs
@@ -15,6 +15,8 @@ description:
Headless CMS. Headless CMS.
--- ---
> Important!!! Forestry is going to be discontinued on April 22nd, 2023. You can [read their announcement](https://forestry.io/blog/forestry.io-end-of-life/) for more info.
In this article, I will explain step by step process of connecting AstroPaper theme with the Forestry headless CMS. So, let's get started 🎉 In this article, I will explain step by step process of connecting AstroPaper theme with the Forestry headless CMS. So, let's get started 🎉
## Table of contents ## Table of contents
@@ -1,8 +1,8 @@
--- ---
title: How Do I Develop My Portfolio Website & Blog title: How Do I Develop My Portfolio Website & Blog
author: Sat Naing author: Sat Naing
datetime: 2022-03-25T16:55:12.000+00:00 pubDatetime: 2022-03-25T16:55:12.000+00:00
slug: how-do-i-develop-my-portfolio-and-blog postSlug: how-do-i-develop-my-portfolio-and-blog
featured: false featured: false
draft: false draft: false
tags: tags:
@@ -10,6 +10,7 @@ tags:
- TailwindCSS - TailwindCSS
- HeadlessCMS - HeadlessCMS
- Blog - Blog
ogImage: ""
description: description:
"EXAMPLE POST: My experience about developing my first portfolio website and a blog "EXAMPLE POST: My experience about developing my first portfolio website and a blog
using NextJS and a headless CMS." using NextJS and a headless CMS."
@@ -1,8 +1,8 @@
--- ---
author: Sat Naing author: Sat Naing
datetime: 2022-09-26T12:13:24Z pubDatetime: 2022-09-26T12:13:24Z
title: Predefined color schemes title: Predefined color schemes
slug: predefined-color-schemes postSlug: predefined-color-schemes
featured: false featured: false
draft: false draft: false
tags: tags:
@@ -75,6 +75,21 @@ html[data-theme="light"] {
Light color scheme has to be defined as `html[data-theme="dark"]`. Light color scheme has to be defined as `html[data-theme="dark"]`.
### AstroPaper 1 original Dark Theme
![AstroPaper 1 default dark theme](https://user-images.githubusercontent.com/53733092/215769153-13b0ad8d-5ba2-44b1-af06-e5ae61293f62.png)
```css
html[data-theme="dark"] {
--color-fill: 47, 55, 65;
--color-text-base: 230, 230, 230;
--color-accent: 26, 217, 217;
--color-card: 63, 75, 90;
--color-card-muted: 89, 107, 129;
--color-border: 59, 70, 85;
}
```
### Deep Oyster ### Deep Oyster
![deep-oyster-color-scheme](https://user-images.githubusercontent.com/53733092/192314524-45ec5904-3d8f-450a-9edf-1e32c5e11d6c.png) ![deep-oyster-color-scheme](https://user-images.githubusercontent.com/53733092/192314524-45ec5904-3d8f-450a-9edf-1e32c5e11d6c.png)
@@ -104,3 +119,34 @@ html[data-theme="dark"] {
--color-border: 134, 67, 107; --color-border: 134, 67, 107;
} }
``` ```
### Astro dark (High Contrast)
![astro-dark-color-scheme](https://user-images.githubusercontent.com/53733092/215680520-59427bb0-f4cb-48c0-bccc-f182a428d72d.svg)
```css
html[data-theme="dark"] {
--color-fill: 16, 23, 42; /* higher contrast bgColor */
--color-fill: 33, 39, 55;
--color-text-base: 234, 237, 243;
--color-accent: 255, 107, 1;
--color-card: 27, 39, 70;
--color-card-muted: 138, 51, 2;
--color-border: 171, 75, 8;
}
```
### Astro dark (New default dark theme in AstroPaper 2)
![new dark color scheme - low contrast](https://user-images.githubusercontent.com/53733092/215772856-d5b7ae35-ddaa-4ed6-b0bf-3fa5dbcf834c.png)
```css
html[data-theme="dark"] {
--color-fill: 33, 39, 55; /* lower contrast bgColor */
--color-text-base: 234, 237, 243;
--color-accent: 255, 107, 1;
--color-card: 52, 63, 96;
--color-card-muted: 138, 51, 2;
--color-border: 171, 75, 8;
}
```
@@ -1,8 +1,8 @@
--- ---
title: My recent article 2 title: My recent article 2
author: Sat Naing author: Sat Naing
datetime: 2022-06-06T04:06:31Z pubDatetime: 2022-06-06T04:06:31Z
slug: my-recent-2 postSlug: my-recent-2
featured: false featured: false
draft: true draft: true
tags: tags:
@@ -1,12 +1,13 @@
--- ---
title: Tailwind Typography Plugin title: Tailwind Typography Plugin
author: Sat Naing author: Sat Naing
datetime: 2022-07-05T02:05:51Z pubDatetime: 2022-07-05T02:05:51Z
featured: false featured: false
draft: false draft: false
tags: tags:
- TypeScript - TypeScript
- Astro - Astro
ogImage: ""
description: "EXAMPLE POST: About Tailwind Typography Plugin and how you can use it effectively." description: "EXAMPLE POST: About Tailwind Typography Plugin and how you can use it effectively."
--- ---
@@ -1,8 +1,8 @@
--- ---
title: How Do I Develop My Terminal Portfolio Website with React title: How Do I Develop My Terminal Portfolio Website with React
author: Sat Naing author: Sat Naing
datetime: 2022-06-09T03:42:51Z pubDatetime: 2022-06-09T03:42:51Z
slug: how-do-i-develop-my-terminal-portfolio-website-with-react postSlug: how-do-i-develop-my-terminal-portfolio-website-with-react
featured: false featured: false
draft: false draft: false
tags: tags:
+8
View File
@@ -0,0 +1,8 @@
import { defineCollection } from "astro:content";
import { blogSchema } from "./_schemas";
const blog = defineCollection({
schema: blogSchema,
});
export const collections = { blog };
+3 -3
View File
@@ -1,5 +1,5 @@
--- ---
import { SITE } from "src/config"; import { SITE } from "@config";
import Breadcrumbs from "@components/Breadcrumbs.astro"; import Breadcrumbs from "@components/Breadcrumbs.astro";
import Footer from "@components/Footer.astro"; import Footer from "@components/Footer.astro";
import Header from "@components/Header.astro"; import Header from "@components/Header.astro";
@@ -19,8 +19,8 @@ const { frontmatter } = Astro.props;
<Header activeNav="about" /> <Header activeNav="about" />
<Breadcrumbs /> <Breadcrumbs />
<main id="main-content"> <main id="main-content">
<section id="about" class="mb-28 prose max-w-3xl prose-img:border-0"> <section id="about" class="prose mb-28 max-w-3xl prose-img:border-0">
<h1 class="text-2xl sm:text-3xl tracking-wider">{frontmatter.title}</h1> <h1 class="text-2xl tracking-wider sm:text-3xl">{frontmatter.title}</h1>
<slot /> <slot />
</section> </section>
</main> </main>
+1 -1
View File
@@ -1,5 +1,5 @@
--- ---
import { SITE } from "src/config"; import { SITE } from "@config";
import "../styles/base.css"; import "../styles/base.css";
const googleSiteVerification = import.meta.env.PUBLIC_GOOGLE_SITE_VERIFICATION; const googleSiteVerification = import.meta.env.PUBLIC_GOOGLE_SITE_VERIFICATION;
+2 -2
View File
@@ -18,10 +18,10 @@ const { pageTitle, pageDesc } = Astro.props;
<style> <style>
#main-content { #main-content {
@apply max-w-3xl mx-auto px-4 pb-12 w-full; @apply mx-auto w-full max-w-3xl px-4 pb-12;
} }
#main-content h1 { #main-content h1 {
@apply font-semibold text-2xl sm:text-3xl; @apply text-2xl font-semibold sm:text-3xl;
} }
#main-content p { #main-content p {
@apply mt-2 mb-6 italic; @apply mt-2 mb-6 italic;
+12 -11
View File
@@ -4,16 +4,17 @@ import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro"; import Footer from "@components/Footer.astro";
import Tag from "@components/Tag.astro"; import Tag from "@components/Tag.astro";
import Datetime from "@components/Datetime"; import Datetime from "@components/Datetime";
import type { MarkdownInstance } from "astro"; import type { CollectionEntry } from "astro:content";
import type { Frontmatter } from "src/types";
export interface Props { export interface Props {
post: MarkdownInstance<Frontmatter>; post: CollectionEntry<"blog">;
} }
const { frontmatter, Content } = Astro.props.post; const { post } = Astro.props;
const { title, author, description, ogImage, datetime, tags } = frontmatter; const { title, author, description, ogImage, pubDatetime, tags } = post.data;
const { Content } = await post.render();
const ogUrl = new URL(ogImage ? ogImage : `${title}.svg`, Astro.url.origin) const ogUrl = new URL(ogImage ? ogImage : `${title}.svg`, Astro.url.origin)
.href; .href;
@@ -21,9 +22,9 @@ const ogUrl = new URL(ogImage ? ogImage : `${title}.svg`, Astro.url.origin)
<Layout title={title} author={author} description={description} ogImage={ogUrl}> <Layout title={title} author={author} description={description} ogImage={ogUrl}>
<Header /> <Header />
<div class="max-w-3xl mx-auto w-full px-2 flex justify-start"> <div class="mx-auto flex w-full max-w-3xl justify-start px-2">
<button <button
class="mt-8 mb-2 hover:opacity-75 flex focus-outline" class="focus-outline mt-8 mb-2 flex hover:opacity-75"
onclick="history.back()" onclick="history.back()"
> >
<svg xmlns="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"
@@ -35,8 +36,8 @@ const ogUrl = new URL(ogImage ? ogImage : `${title}.svg`, Astro.url.origin)
</div> </div>
<main id="main-content"> <main id="main-content">
<h1 class="post-title">{title}</h1> <h1 class="post-title">{title}</h1>
<Datetime datetime={datetime} size="lg" className="my-2" /> <Datetime datetime={pubDatetime} size="lg" className="my-2" />
<article id="article" role="article" class="mx-auto max-w-3xl mt-8 prose"> <article id="article" role="article" class="prose mx-auto mt-8 max-w-3xl">
<Content /> <Content />
</article> </article>
@@ -49,10 +50,10 @@ const ogUrl = new URL(ogImage ? ogImage : `${title}.svg`, Astro.url.origin)
<style> <style>
main { main {
@apply max-w-3xl mx-auto px-4 pb-12 w-full; @apply mx-auto w-full max-w-3xl px-4 pb-12;
} }
.post-title { .post-title {
@apply font-semibold text-2xl text-skin-accent; @apply text-2xl font-semibold text-skin-accent;
} }
.tags-container { .tags-container {
@apply my-8; @apply my-8;
+8 -11
View File
@@ -1,5 +1,5 @@
--- ---
import { SITE } from "src/config"; import { SITE } from "@config";
import Layout from "@layouts/Layout.astro"; import Layout from "@layouts/Layout.astro";
import Main from "@layouts/Main.astro"; import Main from "@layouts/Main.astro";
import Header from "@components/Header.astro"; import Header from "@components/Header.astro";
@@ -7,13 +7,12 @@ import Footer from "@components/Footer.astro";
import Card from "@components/Card"; import Card from "@components/Card";
import LinkButton from "@components/LinkButton.astro"; import LinkButton from "@components/LinkButton.astro";
import slugify from "@utils/slugify"; import slugify from "@utils/slugify";
import type { MarkdownInstance } from "astro"; import type { CollectionEntry } from "astro:content";
import type { Frontmatter } from "src/types";
export interface Props { export interface Props {
pageNum: number; pageNum: number;
totalPages: number; totalPages: number;
posts: MarkdownInstance<Frontmatter>[]; posts: CollectionEntry<"blog">[];
} }
const { pageNum, totalPages, posts } = Astro.props; const { pageNum, totalPages, posts } = Astro.props;
@@ -27,11 +26,9 @@ const next = pageNum < totalPages ? "" : "disabled";
<Main pageTitle="Posts" pageDesc="All the articles I've posted."> <Main pageTitle="Posts" pageDesc="All the articles I've posted.">
<ul> <ul>
{ {
posts.map(({ frontmatter }) => { posts.map(({ data }) => (
return ( <Card href={`/posts/${slugify(data)}`} frontmatter={data} />
<Card href={`/posts/${slugify(frontmatter)}`} post={frontmatter} /> ))
);
})
} }
</ul> </ul>
</Main> </Main>
@@ -69,10 +66,10 @@ const next = pageNum < totalPages ? "" : "disabled";
<style> <style>
.pagination-wrapper { .pagination-wrapper {
@apply flex justify-center mt-auto mb-8; @apply mt-auto mb-8 flex justify-center;
} }
.disabled { .disabled {
@apply opacity-50 hover:text-skin-base group-hover:fill-skin-base pointer-events-none select-none; @apply pointer-events-none select-none opacity-50 hover:text-skin-base group-hover:fill-skin-base;
} }
.disabled-svg { .disabled-svg {
@apply group-hover:!fill-skin-base; @apply group-hover:!fill-skin-base;
+5 -5
View File
@@ -1,5 +1,5 @@
--- ---
import { SITE } from "src/config"; import { SITE } from "@config";
import Layout from "@layouts/Layout.astro"; import Layout from "@layouts/Layout.astro";
import Header from "@components/Header.astro"; import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro"; import Footer from "@components/Footer.astro";
@@ -28,15 +28,15 @@ import LinkButton from "@components/LinkButton.astro";
<style> <style>
#main-content { #main-content {
@apply max-w-3xl mx-auto flex-1 justify-center items-center flex; @apply mx-auto flex max-w-3xl flex-1 items-center justify-center;
} }
.not-found-wrapper { .not-found-wrapper {
@apply flex flex-col items-center justify-center mb-14; @apply mb-14 flex flex-col items-center justify-center;
} }
.not-found-wrapper h1 { .not-found-wrapper h1 {
@apply font-bold text-9xl text-skin-accent; @apply text-9xl font-bold text-skin-accent;
} }
.not-found-wrapper p { .not-found-wrapper p {
@apply text-2xl sm:text-3xl mt-4; @apply mt-4 text-2xl sm:text-3xl;
} }
</style> </style>
+6 -12
View File
@@ -1,24 +1,18 @@
import type { APIRoute, MarkdownInstance } from "astro"; import { getCollection } from "astro:content";
import generateOgImage from "@utils/generateOgImage"; import generateOgImage from "@utils/generateOgImage";
import type { Frontmatter } from "@types"; import type { APIRoute } from "astro";
export const get: APIRoute = async ({ params }) => ({ export const get: APIRoute = async ({ params }) => ({
body: await generateOgImage(params.ogTitle), body: await generateOgImage(params.ogTitle),
}); });
const postImportResult = import.meta.glob<MarkdownInstance<Frontmatter>>( const postImportResult = await getCollection("blog", ({ data }) => !data.draft);
"../contents/**/**/*.md",
{
eager: true,
}
);
const posts = Object.values(postImportResult); const posts = Object.values(postImportResult);
export function getStaticPaths() { export function getStaticPaths() {
return posts return posts
.filter(({ frontmatter }) => !frontmatter.draft) .filter(({ data }) => !data.ogImage)
.filter(({ frontmatter }) => !frontmatter.ogImage) .map(({ data }) => ({
.map(({ frontmatter }) => ({ params: { ogTitle: data.title },
params: { ogTitle: frontmatter.title },
})); }));
} }
+16 -18
View File
@@ -1,22 +1,20 @@
--- ---
import { getCollection } from "astro:content";
import Layout from "@layouts/Layout.astro"; import Layout from "@layouts/Layout.astro";
import Header from "@components/Header.astro"; import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro"; import Footer from "@components/Footer.astro";
import LinkButton from "@components/LinkButton.astro"; import LinkButton from "@components/LinkButton.astro";
import Hr from "@components/Hr.astro"; import Hr from "@components/Hr.astro";
import Card from "@components/Card"; import Card from "@components/Card";
import Socials from "@components/Socials.astro";
import getSortedPosts from "@utils/getSortedPosts"; import getSortedPosts from "@utils/getSortedPosts";
import slugify from "@utils/slugify"; import slugify from "@utils/slugify";
import type { Frontmatter } from "src/types";
import Socials from "@components/Socials.astro";
import { SOCIALS } from "@config"; import { SOCIALS } from "@config";
const posts = await Astro.glob<Frontmatter>("../contents/**/*.md"); const posts = await getCollection("blog");
const sortedPosts = getSortedPosts(posts); const sortedPosts = getSortedPosts(posts);
const featuredPosts = sortedPosts.filter( const featuredPosts = sortedPosts.filter(({ data }) => data.featured);
({ frontmatter }) => frontmatter.featured
);
const socialCount = SOCIALS.filter(social => social.active).length; const socialCount = SOCIALS.filter(social => social.active).length;
--- ---
@@ -76,10 +74,10 @@ const socialCount = SOCIALS.filter(social => social.active).length;
<section id="featured"> <section id="featured">
<h2>Featured</h2> <h2>Featured</h2>
<ul> <ul>
{featuredPosts.map(({ frontmatter }) => ( {featuredPosts.map(({ data }) => (
<Card <Card
href={`/posts/${slugify(frontmatter)}`} href={`/posts/${slugify(data)}`}
post={frontmatter} frontmatter={data}
secHeading={false} secHeading={false}
/> />
))} ))}
@@ -95,11 +93,11 @@ const socialCount = SOCIALS.filter(social => social.active).length;
<ul> <ul>
{ {
sortedPosts.map( sortedPosts.map(
({ frontmatter }, index) => ({ data }, index) =>
index < 4 && ( index < 4 && (
<Card <Card
href={`/posts/${slugify(frontmatter)}`} href={`/posts/${slugify(data)}`}
post={frontmatter} frontmatter={data}
secHeading={false} secHeading={false}
/> />
) )
@@ -128,22 +126,22 @@ const socialCount = SOCIALS.filter(social => social.active).length;
@apply pt-8 pb-6; @apply pt-8 pb-6;
} }
#hero h1 { #hero h1 {
@apply inline-block font-bold my-4 sm:my-8 text-3xl sm:text-5xl; @apply my-4 inline-block text-3xl font-bold sm:my-8 sm:text-5xl;
} }
#hero .rss-link { #hero .rss-link {
@apply mb-6; @apply mb-6;
} }
#hero .rss-icon { #hero .rss-icon {
@apply h-6 w-6 scale-110 sm:scale-125 mb-2 sm:mb-3 fill-skin-accent; @apply mb-2 h-6 w-6 scale-110 fill-skin-accent sm:mb-3 sm:scale-125;
} }
#hero p { #hero p {
@apply my-2; @apply my-2;
} }
.social-wrapper { .social-wrapper {
@apply flex flex-col sm:flex-row sm:items-center mt-4; @apply mt-4 flex flex-col sm:flex-row sm:items-center;
} }
.social-links { .social-links {
@apply whitespace-nowrap mr-2 sm:mb-0 mb-1; @apply mr-2 mb-1 whitespace-nowrap sm:mb-0;
} }
/* ===== Featured & Recent Posts Sections ===== */ /* ===== Featured & Recent Posts Sections ===== */
@@ -153,9 +151,9 @@ const socialCount = SOCIALS.filter(social => social.active).length;
} }
#featured h2, #featured h2,
#recent-posts h2 { #recent-posts h2 {
@apply font-semibold text-2xl tracking-wide; @apply text-2xl font-semibold tracking-wide;
} }
.all-posts-btn-wrapper { .all-posts-btn-wrapper {
@apply text-center my-8; @apply my-8 text-center;
} }
</style> </style>
+12 -38
View File
@@ -1,53 +1,27 @@
--- ---
import { SITE } from "src/config"; import { CollectionEntry, getCollection } from "astro:content";
import Posts from "@layouts/Posts.astro"; import Posts from "@layouts/Posts.astro";
import PostDetails from "@layouts/PostDetails.astro"; import PostDetails from "@layouts/PostDetails.astro";
import getSortedPosts from "@utils/getSortedPosts"; import getSortedPosts from "@utils/getSortedPosts";
import getPageNumbers from "@utils/getPageNumbers"; import getPageNumbers from "@utils/getPageNumbers";
import type { MarkdownInstance } from "astro";
import type { Frontmatter } from "src/types";
import slugify from "@utils/slugify"; import slugify from "@utils/slugify";
import { SITE } from "@config";
export interface Props { export interface Props {
post: MarkdownInstance<Frontmatter>; post: CollectionEntry<"blog">;
} }
type PostResult = {
params: {
slug: string | number;
};
props?: {
post: MarkdownInstance<Record<string, any>>;
};
}[];
type PagePaths = {
params: {
slug: string;
};
}[];
export async function getStaticPaths() { export async function getStaticPaths() {
const posts = await Astro.glob<Frontmatter>("../../contents/**/*.md"); const posts = await getCollection("blog", ({ data }) => !data.draft);
const filteredPosts = posts.filter(({ frontmatter }) => !frontmatter.draft); const postResult = posts.map(post => ({
params: { slug: slugify(post.data) },
props: { post },
}));
let postResult: PostResult = filteredPosts.map(post => { const pagePaths = getPageNumbers(posts.length).map(pageNum => ({
return { params: { slug: String(pageNum) },
params: { }));
slug: slugify(post.frontmatter),
},
props: {
post,
},
};
});
const pagePaths: PagePaths = getPageNumbers(filteredPosts.length).map(
pageNum => ({
params: { slug: String(pageNum) },
})
);
return [...postResult, ...pagePaths]; return [...postResult, ...pagePaths];
} }
@@ -55,7 +29,7 @@ export async function getStaticPaths() {
const { slug } = Astro.params; const { slug } = Astro.params;
const { post } = Astro.props; const { post } = Astro.props;
const posts = await Astro.glob<Frontmatter>("../../contents/**/*.md"); const posts = await getCollection("blog");
const sortedPosts = getSortedPosts(posts); const sortedPosts = getSortedPosts(posts);
+4 -3
View File
@@ -1,11 +1,12 @@
--- ---
import { SITE } from "src/config"; import { SITE } from "@config";
import Posts from "@layouts/Posts.astro"; import Posts from "@layouts/Posts.astro";
import getSortedPosts from "@utils/getSortedPosts"; import getSortedPosts from "@utils/getSortedPosts";
import getPageNumbers from "@utils/getPageNumbers"; import getPageNumbers from "@utils/getPageNumbers";
import type { Frontmatter } from "src/types";
const posts = await Astro.glob<Frontmatter>("../../contents/**/*.md"); import { getCollection } from "astro:content";
const posts = await getCollection("blog");
const sortedPosts = getSortedPosts(posts); const sortedPosts = getSortedPosts(posts);
+12 -21
View File
@@ -1,28 +1,19 @@
import { SITE } from "src/config";
import rss from "@astrojs/rss"; import rss from "@astrojs/rss";
import type { Frontmatter } from "src/types"; import { getCollection } from "astro:content";
import type { MarkdownInstance } from "astro"; import { SITE } from "@config";
import slugify from "@utils/slugify"; import slugify from "@utils/slugify";
const postImportResult = import.meta.glob<MarkdownInstance<Frontmatter>>( export async function get() {
"../contents/**/**/*.md", const posts = await getCollection("blog", ({ data }) => !data.draft);
{ return rss({
eager: true,
}
);
const posts = Object.values(postImportResult);
export const get = () =>
rss({
title: SITE.title, title: SITE.title,
description: SITE.desc, description: SITE.desc,
site: SITE.website, site: SITE.website,
items: posts items: posts.map(({ data }) => ({
.filter(({ frontmatter }) => !frontmatter.draft) link: `posts/${slugify(data)}`,
.map(({ frontmatter }) => ({ title: data.title,
link: `posts/${slugify(frontmatter)}`, description: data.description,
title: frontmatter.title, pubDate: new Date(data.pubDatetime),
description: frontmatter.description, })),
pubDate: new Date(frontmatter.datetime),
})),
}); });
}
+8 -11
View File
@@ -1,24 +1,21 @@
--- ---
import { SITE } from "src/config"; import { getCollection } from "astro:content";
import { SITE } from "@config";
import Layout from "@layouts/Layout.astro"; import Layout from "@layouts/Layout.astro";
import Main from "@layouts/Main.astro"; import Main from "@layouts/Main.astro";
import Header from "@components/Header.astro"; import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro"; import Footer from "@components/Footer.astro";
import Search from "@components/Search"; import Search from "@components/Search";
import type { Frontmatter } from "src/types";
// Retrieve all articles // Retrieve all articles
const posts = await Astro.glob<Frontmatter>("../contents/**/*.md"); const posts = await getCollection("blog", ({ data }) => !data.draft);
// List of items to search in // List of items to search in
const searchList = posts const searchList = posts.map(({ data }) => ({
.filter(({ frontmatter }) => !frontmatter.draft) title: data.title,
.map(post => ({ description: data.description,
title: post.frontmatter.title, data,
description: post.frontmatter.description, }));
headings: post.getHeadings().map(h => h.text),
frontmatter: post.frontmatter,
}));
--- ---
<Layout title={`Search | ${SITE.title}`}> <Layout title={`Search | ${SITE.title}`}>
+9 -18
View File
@@ -1,5 +1,5 @@
--- ---
import { SITE } from "src/config"; import { CollectionEntry, getCollection } from "astro:content";
import Layout from "@layouts/Layout.astro"; import Layout from "@layouts/Layout.astro";
import Main from "@layouts/Main.astro"; import Main from "@layouts/Main.astro";
import Header from "@components/Header.astro"; import Header from "@components/Header.astro";
@@ -7,39 +7,30 @@ import Footer from "@components/Footer.astro";
import Card from "@components/Card"; import Card from "@components/Card";
import getUniqueTags from "@utils/getUniqueTags"; import getUniqueTags from "@utils/getUniqueTags";
import getPostsByTag from "@utils/getPostsByTag"; import getPostsByTag from "@utils/getPostsByTag";
import type { MarkdownInstance } from "astro";
import type { Frontmatter } from "src/types";
import slugify from "@utils/slugify"; import slugify from "@utils/slugify";
import { SITE } from "@config";
export interface Props { export interface Props {
post: MarkdownInstance<Frontmatter>; post: CollectionEntry<"blog">;
tag: string; tag: string;
} }
export async function getStaticPaths() { export async function getStaticPaths() {
const posts: MarkdownInstance<Frontmatter>[] = await Astro.glob( const posts = await getCollection("blog");
"../../contents/**/*.md"
);
const tags = getUniqueTags(posts); const tags = getUniqueTags(posts);
return tags.map(tag => { return tags.map(tag => {
return { return {
params: { params: { tag },
tag, props: { tag },
},
props: {
tag,
},
}; };
}); });
} }
const { tag } = Astro.props; const { tag } = Astro.props;
const posts: MarkdownInstance<Frontmatter>[] = await Astro.glob( const posts = await getCollection("blog", ({ data }) => !data.draft);
"../../contents/**/*.md"
);
const tagPosts = getPostsByTag(posts, tag); const tagPosts = getPostsByTag(posts, tag);
--- ---
@@ -52,8 +43,8 @@ const tagPosts = getPostsByTag(posts, tag);
> >
<ul> <ul>
{ {
tagPosts.map(({ frontmatter }) => ( tagPosts.map(({ data }) => (
<Card href={`/posts/${slugify(frontmatter)}`} post={frontmatter} /> <Card href={`/posts/${slugify(data)}`} frontmatter={data} />
)) ))
} }
</ul> </ul>
+3 -3
View File
@@ -1,14 +1,14 @@
--- ---
import { SITE } from "src/config"; import { getCollection } from "astro:content";
import Header from "@components/Header.astro"; import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro"; import Footer from "@components/Footer.astro";
import Layout from "@layouts/Layout.astro"; import Layout from "@layouts/Layout.astro";
import Main from "@layouts/Main.astro"; import Main from "@layouts/Main.astro";
import Tag from "@components/Tag.astro"; import Tag from "@components/Tag.astro";
import getUniqueTags from "@utils/getUniqueTags"; import getUniqueTags from "@utils/getUniqueTags";
import type { Frontmatter } from "src/types"; import { SITE } from "@config";
const posts = await Astro.glob<Frontmatter>("../../contents/**/*.md"); const posts = await getCollection("blog");
let tags = getUniqueTags(posts); let tags = getUniqueTags(posts);
--- ---
+34 -34
View File
@@ -13,12 +13,12 @@
--color-border: 236, 233, 233; --color-border: 236, 233, 233;
} }
html[data-theme="dark"] { html[data-theme="dark"] {
--color-fill: 47, 55, 65; --color-fill: 33, 39, 55;
--color-text-base: 230, 230, 230; --color-text-base: 234, 237, 243;
--color-accent: 26, 217, 217; --color-accent: 255, 107, 1;
--color-card: 63, 75, 90; --color-card: 52, 63, 96;
--color-card-muted: 89, 107, 129; --color-card-muted: 138, 51, 2;
--color-border: 59, 70, 85; --color-border: 171, 75, 8;
} }
#sun-svg, #sun-svg,
html[data-theme="dark"] #moon-svg { html[data-theme="dark"] #moon-svg {
@@ -29,45 +29,45 @@
display: block; display: block;
} }
body { body {
@apply font-mono bg-skin-fill text-skin-base min-h-screen flex flex-col @apply flex min-h-screen flex-col bg-skin-fill font-mono text-skin-base
selection:bg-skin-accent selection:text-skin-inverted selection:bg-opacity-70; selection:bg-skin-accent selection:bg-opacity-70 selection:text-skin-inverted;
} }
section, section,
footer { footer {
@apply max-w-3xl mx-auto px-4; @apply mx-auto max-w-3xl px-4;
} }
a { a {
@apply outline-offset-1 outline-skin-fill outline-2 @apply outline-2 outline-offset-1 outline-skin-fill
focus-visible:outline-dashed focus-visible:no-underline; focus-visible:no-underline focus-visible:outline-dashed;
} }
svg { svg {
@apply w-6 h-6 inline-block fill-skin-base group-hover:fill-skin-accent; @apply inline-block h-6 w-6 fill-skin-base group-hover:fill-skin-accent;
} }
svg.icon-tabler { svg.icon-tabler {
@apply w-6 h-6 scale-125 sm:scale-110 opacity-90 @apply inline-block h-6 w-6 scale-125 fill-transparent
inline-block fill-transparent stroke-2 stroke-current stroke-current stroke-2 opacity-90 group-hover:fill-transparent
group-hover:fill-transparent; sm:scale-110;
} }
.prose { .prose {
@apply prose-a:underline-offset-8 prose-ul:overflow-x-clip @apply prose-headings:!mb-3 prose-headings:!text-skin-base
prose-a:!decoration-dashed hover:prose-a:text-skin-accent prose-h3:italic prose-p:!text-skin-base
prose-img:mx-auto prose-img:border-2 prose-img:border-skin-line prose-a:!text-skin-base prose-a:!decoration-dashed prose-a:underline-offset-8
prose-img:!mt-2 prose-headings:!mb-3 hover:prose-a:text-skin-accent prose-blockquote:!border-l-skin-accent
prose-th:border prose-th:border-skin-line prose-blockquote:border-opacity-50 prose-blockquote:opacity-80
prose-td:border prose-td:border-skin-line prose-figcaption:!text-skin-base prose-figcaption:opacity-70
prose-h3:italic prose-strong:!text-skin-base
prose-p:!text-skin-base prose-headings:!text-skin-base prose-code:rounded prose-code:bg-skin-card
prose-ol:!text-skin-base prose-ul:!text-skin-base prose-blockquote:opacity-80 prose-code:bg-opacity-75 prose-code:p-1 prose-code:!text-skin-base
prose-figcaption:!text-skin-base prose-figcaption:opacity-70 prose-code:before:!content-[''] prose-code:after:!content-['']
prose-code:bg-skin-card prose-code:bg-opacity-75 prose-pre:!text-skin-base prose-ol:!text-skin-base
prose-code:!text-skin-base prose-code:p-1 prose-code:rounded prose-ul:overflow-x-clip prose-ul:!text-skin-base prose-li:marker:!text-skin-accent
prose-code:after:!content-[''] prose-code:before:!content-[''] prose-table:text-skin-base prose-th:border
prose-strong:!text-skin-base prose-table:text-skin-base prose-th:border-skin-line prose-td:border
prose-pre:!text-skin-base prose-a:!text-skin-base prose-td:border-skin-line prose-img:mx-auto
prose-hr:!border-skin-line prose-li:marker:!text-skin-accent prose-img:!mt-2 prose-img:border-2
prose-blockquote:!border-l-skin-accent prose-blockquote:border-opacity-50; prose-img:border-skin-line prose-hr:!border-skin-line;
} }
.prose a { .prose a {
@apply hover:!text-skin-accent; @apply hover:!text-skin-accent;
@@ -81,7 +81,7 @@
@apply mb-2; @apply mb-2;
} }
.prose details { .prose details {
@apply text-skin-base cursor-pointer inline-block select-none; @apply inline-block cursor-pointer select-none text-skin-base;
} }
.prose summary { .prose summary {
@apply focus-outline; @apply focus-outline;
@@ -121,6 +121,6 @@
@apply hidden; @apply hidden;
} }
.focus-outline { .focus-outline {
@apply outline-offset-1 outline-skin-fill outline-2 focus-visible:outline-dashed focus-visible:no-underline; @apply outline-2 outline-offset-1 outline-skin-fill focus-visible:no-underline focus-visible:outline-dashed;
} }
} }
-12
View File
@@ -1,15 +1,3 @@
export interface Frontmatter {
title: string;
ogImage?: string;
description: string;
author: string;
datetime: string;
slug: string;
featured: boolean;
draft: boolean;
tags: string[];
}
export type SocialObjects = { export type SocialObjects = {
name: SocialMedia; name: SocialMedia;
href: string; href: string;
+1 -1
View File
@@ -1,4 +1,4 @@
import { SITE } from "src/config"; import { SITE } from "@config";
const getPageNumbers = (numberOfPosts: number) => { const getPageNumbers = (numberOfPosts: number) => {
const numberOfPages = numberOfPosts / Number(SITE.postPerPage); const numberOfPages = numberOfPosts / Number(SITE.postPerPage);
+3 -4
View File
@@ -1,8 +1,7 @@
import { slufigyAll } from "./slugify"; import { slufigyAll } from "./slugify";
import type { MarkdownInstance } from "astro"; import type { CollectionEntry } from "astro:content";
import type { Frontmatter } from "../types";
const getPostsByTag = (posts: MarkdownInstance<Frontmatter>[], tag: string) => const getPostsByTag = (posts: CollectionEntry<"blog">[], tag: string) =>
posts.filter(post => slufigyAll(post.frontmatter.tags).includes(tag)); posts.filter(post => slufigyAll(post.data.tags).includes(tag));
export default getPostsByTag; export default getPostsByTag;
+5 -6
View File
@@ -1,13 +1,12 @@
import type { MarkdownInstance } from "astro"; import type { CollectionEntry } from "astro:content";
import type { Frontmatter } from "../types";
const getSortedPosts = (posts: MarkdownInstance<Frontmatter>[]) => const getSortedPosts = (posts: CollectionEntry<"blog">[]) =>
posts posts
.filter(({ frontmatter }) => !frontmatter.draft) .filter(({ data }) => !data.draft)
.sort( .sort(
(a, b) => (a, b) =>
Math.floor(new Date(b.frontmatter.datetime).getTime() / 1000) - Math.floor(new Date(b.data.pubDatetime).getTime() / 1000) -
Math.floor(new Date(a.frontmatter.datetime).getTime() / 1000) Math.floor(new Date(a.data.pubDatetime).getTime() / 1000)
); );
export default getSortedPosts; export default getSortedPosts;
+4 -5
View File
@@ -1,12 +1,11 @@
import { slugifyStr } from "./slugify"; import { slugifyStr } from "./slugify";
import type { MarkdownInstance } from "astro"; import type { CollectionEntry } from "astro:content";
import type { Frontmatter } from "../types";
const getUniqueTags = (posts: MarkdownInstance<Frontmatter>[]) => { const getUniqueTags = (posts: CollectionEntry<"blog">[]) => {
let tags: string[] = []; let tags: string[] = [];
const filteredPosts = posts.filter(({ frontmatter }) => !frontmatter.draft); const filteredPosts = posts.filter(({ data }) => !data.draft);
filteredPosts.forEach(post => { filteredPosts.forEach(post => {
tags = [...tags, ...post.frontmatter.tags] tags = [...tags, ...post.data.tags]
.map(tag => slugifyStr(tag)) .map(tag => slugifyStr(tag))
.filter( .filter(
(value: string, index: number, self: string[]) => (value: string, index: number, self: string[]) =>
+3 -3
View File
@@ -1,10 +1,10 @@
import { slug as slugger } from "github-slugger"; import { slug as slugger } from "github-slugger";
import type { Frontmatter } from "src/types"; import type { BlogFrontmatter } from "@content/_schemas";
export const slugifyStr = (str: string) => slugger(str); export const slugifyStr = (str: string) => slugger(str);
const slugify = (frontmatter: Frontmatter) => const slugify = (post: BlogFrontmatter) =>
frontmatter.slug ? slugger(frontmatter.slug) : slugger(frontmatter.title); post.postSlug ? slugger(post.postSlug) : slugger(post.title);
export const slufigyAll = (arr: string[]) => arr.map(str => slugifyStr(str)); export const slufigyAll = (arr: string[]) => arr.map(str => slugifyStr(str));