Initial commit: astro-micro scaffold
Amp-Thread-ID: https://ampcode.com/threads/T-019c7839-0936-729b-aa87-92b35d3d68fe Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
commit
93917727fb
108 changed files with 12180 additions and 0 deletions
42
src/components/ArrowCard.astro
Normal file
42
src/components/ArrowCard.astro
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
import type { CollectionEntry } from "astro:content";
|
||||
|
||||
type Props = {
|
||||
entry: CollectionEntry<"blog"> | CollectionEntry<"projects">;
|
||||
};
|
||||
|
||||
const { entry } = Astro.props as {
|
||||
entry: CollectionEntry<"blog"> | CollectionEntry<"projects">;
|
||||
};
|
||||
---
|
||||
|
||||
<a
|
||||
href={`/${entry.collection}/${entry.id}`}
|
||||
class="not-prose group relative flex flex-nowrap rounded-lg border border-black/15 px-4 py-3 pr-10 transition-colors duration-300 ease-in-out hover:bg-black/5 hover:text-black focus-visible:bg-black/5 focus-visible:text-black dark:border-white/20 dark:hover:bg-white/5 dark:hover:text-white dark:focus-visible:bg-white/5 dark:focus-visible:text-white"
|
||||
>
|
||||
<div class="flex flex-1 flex-col truncate">
|
||||
<div class="font-semibold">
|
||||
{entry.data.title}
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
{entry.data.description}
|
||||
</div>
|
||||
</div>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
class="absolute right-2 top-1/2 size-5 -translate-y-1/2 fill-none stroke-current stroke-2"
|
||||
>
|
||||
<line
|
||||
x1="5"
|
||||
y1="12"
|
||||
x2="19"
|
||||
y2="12"
|
||||
class="translate-x-3 scale-x-0 transition-transform duration-300 ease-in-out group-hover:translate-x-0 group-hover:scale-x-100 group-focus-visible:translate-x-0 group-focus-visible:scale-x-100"
|
||||
></line>
|
||||
<polyline
|
||||
points="12 5 19 12 12 19"
|
||||
class="-translate-x-1 transition-transform duration-300 ease-in-out group-hover:translate-x-0 group-focus-visible:translate-x-0"
|
||||
></polyline>
|
||||
</svg>
|
||||
</a>
|
||||
33
src/components/BackToPrevious.astro
Normal file
33
src/components/BackToPrevious.astro
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
type Props = {
|
||||
href: string;
|
||||
};
|
||||
|
||||
const { href } = Astro.props;
|
||||
---
|
||||
|
||||
<a
|
||||
href={href}
|
||||
class="not-prose group relative flex w-fit flex-nowrap rounded-sm border border-black/15 py-1.5 pl-7 pr-3 transition-colors duration-300 ease-in-out hover:bg-black/5 hover:text-black focus-visible:bg-black/5 focus-visible:text-black dark:border-white/20 dark:hover:bg-white/5 dark:hover:text-white dark:focus-visible:bg-white/5 dark:focus-visible:text-white"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
class="absolute left-2 top-1/2 size-4 -translate-y-1/2 fill-none stroke-current stroke-2"
|
||||
>
|
||||
<line
|
||||
x1="5"
|
||||
y1="12"
|
||||
x2="19"
|
||||
y2="12"
|
||||
class="translate-x-2 scale-x-0 transition-transform duration-300 ease-in-out group-hover:translate-x-0 group-hover:scale-x-100 group-focus-visible:translate-x-0 group-focus-visible:scale-x-100"
|
||||
></line>
|
||||
<polyline
|
||||
points="12 5 5 12 12 19"
|
||||
class="translate-x-1 transition-transform duration-300 ease-in-out group-hover:translate-x-0 group-focus-visible:translate-x-0"
|
||||
></polyline>
|
||||
</svg>
|
||||
<div class="text-sm">
|
||||
<slot />
|
||||
</div>
|
||||
</a>
|
||||
40
src/components/BackToTop.astro
Normal file
40
src/components/BackToTop.astro
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
|
||||
---
|
||||
|
||||
<script>
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const btn = document.getElementById('back-to-top');
|
||||
if (btn) {
|
||||
btn.addEventListener('click', () => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
id="back-to-top"
|
||||
class="group relative flex w-fit flex-nowrap rounded-sm border border-black/15 py-1.5 pl-8 pr-3 transition-colors duration-300 ease-in-out hover:bg-black/5 hover:text-black focus-visible:bg-black/5 focus-visible:text-black dark:border-white/20 dark:hover:bg-white/5 dark:hover:text-white dark:focus-visible:bg-white/5 dark:focus-visible:text-white"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
class="absolute left-2 top-1/2 size-4 -translate-y-1/2 rotate-90 fill-none stroke-current stroke-2"
|
||||
>
|
||||
<line
|
||||
x1="5"
|
||||
y1="12"
|
||||
x2="19"
|
||||
y2="12"
|
||||
class="translate-x-2 scale-x-0 transition-transform duration-300 ease-in-out group-hover:translate-x-0 group-hover:scale-x-100 group-focus-visible:translate-x-0 group-focus-visible:scale-x-100"
|
||||
></line>
|
||||
<polyline
|
||||
points="12 5 5 12 12 19"
|
||||
class="translate-x-1 transition-transform duration-300 ease-in-out group-hover:translate-x-0 group-focus-visible:translate-x-0"
|
||||
></polyline>
|
||||
</svg>
|
||||
<div class="text-sm">Back to top</div>
|
||||
</button>
|
||||
30
src/components/Callout.astro
Normal file
30
src/components/Callout.astro
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
interface Props {
|
||||
type?: "default" | "info" | "warning" | "error";
|
||||
}
|
||||
const { type = "default" } = Astro.props;
|
||||
let emoji = "💡";
|
||||
if (type === "info") {
|
||||
emoji = "ℹ️";
|
||||
} else if (type === "warning") {
|
||||
emoji = "⚠️";
|
||||
} else if (type === "error") {
|
||||
emoji = "🚨";
|
||||
}
|
||||
|
||||
const baseClasses = "relative my-4 flex rounded border p-3";
|
||||
const typeClasses = {
|
||||
default:
|
||||
"border-orange-800 bg-orange-100 text-orange-950 dark:border-orange-200/20 dark:bg-orange-950/20 dark:text-orange-200",
|
||||
info: "border-blue-800 bg-blue-100 text-blue-950 dark:border-blue-200/20 dark:bg-blue-950/20 dark:text-blue-200",
|
||||
warning:
|
||||
"border-yellow-800 bg-yellow-100 text-yellow-950 dark:border-yellow-200/20 dark:bg-yellow-950/20 dark:text-yellow-200",
|
||||
error:
|
||||
"border-red-800 bg-red-100 text-red-950 dark:border-red-200/20 dark:bg-red-950/20 dark:text-red-200",
|
||||
};
|
||||
---
|
||||
|
||||
<div class:list={["not-prose", baseClasses, typeClasses[type]]}>
|
||||
<span class="pointer-events-none pr-3 text-xl select-none">{emoji}</span>
|
||||
<slot />
|
||||
</div>
|
||||
5
src/components/Container.astro
Normal file
5
src/components/Container.astro
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
|
||||
---
|
||||
|
||||
<div class="mx-auto max-w-(--breakpoint-sm) px-3"><slot /></div>
|
||||
90
src/components/Footer.astro
Normal file
90
src/components/Footer.astro
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
import Container from "@components/Container.astro";
|
||||
import { SITE } from "@consts";
|
||||
import BackToTop from "@components/BackToTop.astro";
|
||||
---
|
||||
|
||||
<footer class="animate">
|
||||
<Container>
|
||||
<div class="relative">
|
||||
<div class="absolute -top-12 right-0">
|
||||
<BackToTop />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>© {new Date().getFullYear()} • {SITE.TITLE} 🔬</div>
|
||||
<div class="flex flex-wrap items-center gap-1.5">
|
||||
<button
|
||||
id="light-theme-button"
|
||||
aria-label="Light theme"
|
||||
class="group flex size-9 items-center justify-center rounded-sm border border-black/15 hover:bg-black/5 focus-visible:bg-black/5 dark:border-white/20 dark:hover:bg-white/5 dark:focus-visible:bg-white/5"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="transition-colors duration-300 ease-in-out group-hover:animate-pulse group-hover:stroke-black group-focus-visible:animate-pulse group-focus-visible:stroke-black dark:group-hover:stroke-white dark:group-focus-visible:stroke-white"
|
||||
>
|
||||
<circle cx="12" cy="12" r="5"></circle>
|
||||
<line x1="12" y1="1" x2="12" y2="3"></line>
|
||||
<line x1="12" y1="21" x2="12" y2="23"></line>
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
||||
<line x1="1" y1="12" x2="3" y2="12"></line>
|
||||
<line x1="21" y1="12" x2="23" y2="12"></line>
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
id="dark-theme-button"
|
||||
aria-label="Dark theme"
|
||||
class="group flex size-9 items-center justify-center rounded-sm border border-black/15 hover:bg-black/5 focus-visible:bg-black/5 dark:border-white/20 dark:hover:bg-white/5 dark:focus-visible:bg-white/5"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="transition-colors duration-300 ease-in-out group-hover:animate-pulse group-hover:stroke-black group-focus-visible:animate-pulse group-focus-visible:stroke-black dark:group-hover:stroke-white dark:group-focus-visible:stroke-white"
|
||||
>
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
id="system-theme-button"
|
||||
aria-label="System theme"
|
||||
class="group flex size-9 items-center justify-center rounded-sm border border-black/15 hover:bg-black/5 focus-visible:bg-black/5 dark:border-white/20 dark:hover:bg-white/5 dark:focus-visible:bg-white/5"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="transition-colors duration-300 ease-in-out group-hover:animate-pulse group-hover:stroke-black group-focus-visible:animate-pulse group-focus-visible:stroke-black dark:group-hover:stroke-white dark:group-focus-visible:stroke-white"
|
||||
>
|
||||
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
|
||||
<line x1="8" y1="21" x2="16" y2="21"></line>
|
||||
<line x1="12" y1="17" x2="12" y2="21"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</footer>
|
||||
17
src/components/FormattedDate.astro
Normal file
17
src/components/FormattedDate.astro
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
interface Props {
|
||||
date: Date;
|
||||
}
|
||||
|
||||
const { date } = Astro.props;
|
||||
---
|
||||
|
||||
<time datetime={date.toISOString()}>
|
||||
{
|
||||
date.toLocaleDateString("en-US", {
|
||||
month: "long",
|
||||
day: "2-digit",
|
||||
year: "numeric",
|
||||
})
|
||||
}
|
||||
</time>
|
||||
20
src/components/Giscus.astro
Normal file
20
src/components/Giscus.astro
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<div class="giscus"></div>
|
||||
|
||||
<script
|
||||
is:inline
|
||||
data-astro-rerun
|
||||
src="https://giscus.app/client.js"
|
||||
data-repo="trevortylerlee/astro-micro"
|
||||
data-repo-id="R_kgDOL_6l9Q"
|
||||
data-category="Announcements"
|
||||
data-category-id="DIC_kwDOL_6l9c4Cfk55"
|
||||
data-mapping="pathname"
|
||||
data-strict="0"
|
||||
data-reactions-enabled="1"
|
||||
data-emit-metadata="0"
|
||||
data-input-position="top"
|
||||
data-theme="preferred_color_scheme"
|
||||
data-lang="en"
|
||||
data-loading="lazy"
|
||||
crossorigin="anonymous"
|
||||
async></script>
|
||||
267
src/components/Head.astro
Normal file
267
src/components/Head.astro
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
---
|
||||
import "../styles/global.css";
|
||||
import { ClientRouter } from "astro:transitions";
|
||||
|
||||
import "@fontsource/geist-sans/100.css";
|
||||
import "@fontsource/geist-sans/200.css";
|
||||
import "@fontsource/geist-sans/300.css";
|
||||
import "@fontsource/geist-sans/400.css";
|
||||
import "@fontsource/geist-sans/500.css";
|
||||
import "@fontsource/geist-sans/600.css";
|
||||
import "@fontsource/geist-sans/700.css";
|
||||
import "@fontsource/geist-sans/800.css";
|
||||
import "@fontsource/geist-sans/900.css";
|
||||
import "@fontsource/geist-mono/100.css";
|
||||
import "@fontsource/geist-mono/200.css";
|
||||
import "@fontsource/geist-mono/300.css";
|
||||
import "@fontsource/geist-mono/400.css";
|
||||
import "@fontsource/geist-mono/500.css";
|
||||
import "@fontsource/geist-mono/600.css";
|
||||
import "@fontsource/geist-mono/700.css";
|
||||
import "@fontsource/geist-mono/800.css";
|
||||
import "@fontsource/geist-mono/900.css";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
||||
|
||||
const { title, description, image = "/astro-micro.jpg" } = Astro.props;
|
||||
---
|
||||
|
||||
<!-- Global Metadata -->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link
|
||||
rel="icon"
|
||||
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🔬</text></svg>"
|
||||
/>
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
|
||||
<!-- Canonical URL -->
|
||||
<link rel="canonical" href={canonicalURL} />
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>{title}</title>
|
||||
<meta name="title" content={title} />
|
||||
<meta name="description" content={description} />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={Astro.url} />
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:image" content={new URL(image, Astro.url)} />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content={Astro.url} />
|
||||
<meta property="twitter:title" content={title} />
|
||||
<meta property="twitter:description" content={description} />
|
||||
<meta property="twitter:image" content={new URL(image, Astro.url)} />
|
||||
|
||||
<!-- PageFind -->
|
||||
<link href="/pagefind/pagefind-ui.css" rel="stylesheet" />
|
||||
<script is:inline src="/pagefind/pagefind-ui.js"></script>
|
||||
|
||||
<ClientRouter />
|
||||
|
||||
<script is:inline>
|
||||
function init() {
|
||||
preloadTheme();
|
||||
onScroll();
|
||||
animate();
|
||||
updateThemeButtons();
|
||||
addCopyCodeButtons();
|
||||
setGiscusTheme();
|
||||
|
||||
const backToTop = document.getElementById("back-to-top");
|
||||
backToTop?.addEventListener("click", (event) => scrollToTop(event));
|
||||
|
||||
const backToPrev = document.getElementById("back-to-prev");
|
||||
backToPrev?.addEventListener("click", () => window.history.back());
|
||||
|
||||
const lightThemeButton = document.getElementById("light-theme-button");
|
||||
lightThemeButton?.addEventListener("click", () => {
|
||||
localStorage.setItem("theme", "light");
|
||||
toggleTheme(false);
|
||||
updateThemeButtons();
|
||||
});
|
||||
|
||||
const darkThemeButton = document.getElementById("dark-theme-button");
|
||||
darkThemeButton?.addEventListener("click", () => {
|
||||
localStorage.setItem("theme", "dark");
|
||||
toggleTheme(true);
|
||||
updateThemeButtons();
|
||||
});
|
||||
|
||||
const systemThemeButton = document.getElementById("system-theme-button");
|
||||
systemThemeButton?.addEventListener("click", () => {
|
||||
localStorage.setItem("theme", "system");
|
||||
toggleTheme(window.matchMedia("(prefers-color-scheme: dark)").matches);
|
||||
updateThemeButtons();
|
||||
});
|
||||
|
||||
window
|
||||
.matchMedia("(prefers-color-scheme: dark)")
|
||||
.addEventListener("change", (event) => {
|
||||
if (localStorage.theme === "system") {
|
||||
toggleTheme(event.matches);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("scroll", onScroll);
|
||||
}
|
||||
|
||||
function updateThemeButtons() {
|
||||
const theme = localStorage.getItem("theme");
|
||||
const lightThemeButton = document.getElementById("light-theme-button");
|
||||
const darkThemeButton = document.getElementById("dark-theme-button");
|
||||
const systemThemeButton = document.getElementById("system-theme-button");
|
||||
|
||||
function removeActiveButtonTheme(button) {
|
||||
button?.classList.remove("bg-black/5");
|
||||
button?.classList.remove("dark:bg-white/5");
|
||||
}
|
||||
|
||||
function addActiveButtonTheme(button) {
|
||||
button?.classList.add("bg-black/5");
|
||||
button?.classList.add("dark:bg-white/5");
|
||||
}
|
||||
|
||||
removeActiveButtonTheme(lightThemeButton);
|
||||
removeActiveButtonTheme(darkThemeButton);
|
||||
removeActiveButtonTheme(systemThemeButton);
|
||||
|
||||
if (theme === "light") {
|
||||
addActiveButtonTheme(lightThemeButton);
|
||||
} else if (theme === "dark") {
|
||||
addActiveButtonTheme(darkThemeButton);
|
||||
} else {
|
||||
addActiveButtonTheme(systemThemeButton);
|
||||
}
|
||||
}
|
||||
|
||||
function animate() {
|
||||
const animateElements = document.querySelectorAll(".animate");
|
||||
|
||||
animateElements.forEach((element, index) => {
|
||||
setTimeout(() => {
|
||||
element.classList.add("show");
|
||||
}, index * 100);
|
||||
});
|
||||
}
|
||||
|
||||
function onScroll() {
|
||||
if (window.scrollY > 0) {
|
||||
document.documentElement.classList.add("scrolled");
|
||||
} else {
|
||||
document.documentElement.classList.remove("scrolled");
|
||||
}
|
||||
}
|
||||
|
||||
function scrollToTop(event) {
|
||||
event.preventDefault();
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
|
||||
function toggleTheme(dark) {
|
||||
const css = document.createElement("style");
|
||||
|
||||
css.appendChild(
|
||||
document.createTextNode(
|
||||
`* {
|
||||
-webkit-transition: none !important;
|
||||
-moz-transition: none !important;
|
||||
-o-transition: none !important;
|
||||
-ms-transition: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
`,
|
||||
),
|
||||
);
|
||||
|
||||
document.head.appendChild(css);
|
||||
|
||||
if (dark) {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
|
||||
window.getComputedStyle(css).opacity;
|
||||
document.head.removeChild(css);
|
||||
|
||||
setGiscusTheme();
|
||||
}
|
||||
|
||||
function preloadTheme() {
|
||||
const userTheme = localStorage.theme;
|
||||
|
||||
if (userTheme === "light" || userTheme === "dark") {
|
||||
toggleTheme(userTheme === "dark");
|
||||
} else {
|
||||
toggleTheme(window.matchMedia("(prefers-color-scheme: dark)").matches);
|
||||
}
|
||||
}
|
||||
|
||||
function addCopyCodeButtons() {
|
||||
let copyButtonLabel = "📋";
|
||||
let codeBlocks = Array.from(document.querySelectorAll("pre"));
|
||||
|
||||
async function copyCode(codeBlock, copyButton) {
|
||||
const codeText = codeBlock.innerText;
|
||||
const buttonText = copyButton.innerText;
|
||||
const textToCopy = codeText.replace(buttonText, "");
|
||||
|
||||
await navigator.clipboard.writeText(textToCopy);
|
||||
copyButton.innerText = "✅";
|
||||
|
||||
setTimeout(() => {
|
||||
copyButton.innerText = copyButtonLabel;
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
for (let codeBlock of codeBlocks) {
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.style.position = "relative";
|
||||
|
||||
const copyButton = document.createElement("button");
|
||||
copyButton.innerText = copyButtonLabel;
|
||||
copyButton.classList = "copy-code";
|
||||
|
||||
codeBlock.setAttribute("tabindex", "0");
|
||||
codeBlock.appendChild(copyButton);
|
||||
|
||||
codeBlock.parentNode.insertBefore(wrapper, codeBlock);
|
||||
wrapper.appendChild(codeBlock);
|
||||
|
||||
copyButton?.addEventListener("click", async () => {
|
||||
await copyCode(codeBlock, copyButton);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const setGiscusTheme = () => {
|
||||
const giscus = document.querySelector(".giscus-frame");
|
||||
|
||||
const isDark = document.documentElement.classList.contains("dark");
|
||||
|
||||
if (giscus) {
|
||||
const url = new URL(giscus.src);
|
||||
url.searchParams.set("theme", isDark ? "dark" : "light");
|
||||
giscus.src = url.toString();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => init());
|
||||
document.addEventListener("astro:after-swap", () => init());
|
||||
preloadTheme();
|
||||
</script>
|
||||
46
src/components/Header.astro
Normal file
46
src/components/Header.astro
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
import Container from "@components/Container.astro";
|
||||
import Link from "@components/Link.astro";
|
||||
import { SITE } from "@consts";
|
||||
---
|
||||
|
||||
<header transition:persist>
|
||||
<Container>
|
||||
<div class="flex flex-wrap justify-between gap-y-2">
|
||||
<Link href="/" underline={false}>
|
||||
<div class="font-semibold">
|
||||
{SITE.TITLE} 🔬
|
||||
</div>
|
||||
</Link>
|
||||
<nav class="flex items-center gap-1 text-sm">
|
||||
<Link href="/blog">blog</Link>
|
||||
<span>
|
||||
{`/`}
|
||||
</span>
|
||||
<Link href="/projects">projects</Link>
|
||||
<span>
|
||||
{`/`}
|
||||
</span>
|
||||
<button
|
||||
id="magnifying-glass"
|
||||
aria-label="Search"
|
||||
class="flex items-center rounded-sm border border-black/15 bg-neutral-100 px-2 py-1 text-xs transition-colors duration-300 ease-in-out hover:bg-black/5 hover:text-black focus-visible:bg-black/5 focus-visible:text-black dark:border-white/20 dark:bg-neutral-900 dark:hover:bg-white/5 dark:hover:text-white dark:focus-visible:bg-white/5 dark:focus-visible:text-white"
|
||||
>
|
||||
<svg
|
||||
height="16"
|
||||
stroke-linejoin="round"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
style="color: currentcolor;"
|
||||
><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M3.5 7C3.5 5.067 5.067 3.5 7 3.5C8.933 3.5 10.5 5.067 10.5 7C10.5 7.88461 10.1718 8.69256 9.63058 9.30876L9.30876 9.63058C8.69256 10.1718 7.88461 10.5 7 10.5C5.067 10.5 3.5 8.933 3.5 7ZM9.96544 11.0261C9.13578 11.6382 8.11014 12 7 12C4.23858 12 2 9.76142 2 7C2 4.23858 4.23858 2 7 2C9.76142 2 12 4.23858 12 7C12 8.11014 11.6382 9.13578 11.0261 9.96544L14.0303 12.9697L14.5607 13.5L13.5 14.5607L12.9697 14.0303L9.96544 11.0261Z"
|
||||
fill="currentColor"></path></svg
|
||||
>
|
||||
Search
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</Container>
|
||||
</header>
|
||||
31
src/components/Link.astro
Normal file
31
src/components/Link.astro
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
import { cn } from "@lib/utils";
|
||||
|
||||
type Props = {
|
||||
href: string;
|
||||
external?: boolean;
|
||||
underline?: boolean;
|
||||
group?: boolean;
|
||||
};
|
||||
|
||||
const {
|
||||
href,
|
||||
external,
|
||||
underline = true,
|
||||
group = false,
|
||||
...rest
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<a
|
||||
href={href}
|
||||
target={external ? "_blank" : "_self"}
|
||||
class={cn(
|
||||
"inline-block decoration-black/30 dark:decoration-white/30 hover:decoration-black/50 focus-visible:decoration-black/50 dark:hover:decoration-white/50 dark:focus-visible:decoration-white/50 text-current hover:text-black focus-visible:text-black dark:hover:text-white dark:focus-visible:text-white transition-colors duration-300 ease-in-out",
|
||||
underline && "underline underline-offset-[3px]",
|
||||
group && "group"
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<slot />
|
||||
</a>
|
||||
132
src/components/PageFind.astro
Normal file
132
src/components/PageFind.astro
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
---
|
||||
import Search from "astro-pagefind/components/Search";
|
||||
---
|
||||
|
||||
<aside data-pagefind-ignore>
|
||||
<div
|
||||
transition:persist
|
||||
id="backdrop"
|
||||
class="bg-[rgba(0, 0, 0, 0.5] invisible fixed left-0 top-0 z-50 flex h-screen w-full justify-center p-6 backdrop-blur-xs"
|
||||
>
|
||||
<div
|
||||
id="pagefind-container"
|
||||
class="m-0 flex h-fit max-h-[80%] w-full max-w-(--breakpoint-sm) flex-col overflow-auto rounded-sm border border-black/15 bg-neutral-100 p-2 px-4 py-3 shadow-lg dark:border-white/20 dark:bg-neutral-900"
|
||||
>
|
||||
<Search
|
||||
id="search"
|
||||
className="pagefind-ui"
|
||||
uiOptions={{
|
||||
showImages: false,
|
||||
excerptLength: 15,
|
||||
resetStyles: false,
|
||||
}}
|
||||
/>
|
||||
<div class="mr-2 pb-1 pt-4 text-right text-xs dark:prose-invert">
|
||||
Press <span class="prose text-xs dark:prose-invert"
|
||||
><kbd class="">Esc</kbd></span
|
||||
> or click anywhere to close
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<script is:inline>
|
||||
const magnifyingGlass = document.getElementById("magnifying-glass");
|
||||
const backdrop = document.getElementById("backdrop");
|
||||
|
||||
function openPagefind() {
|
||||
const searchDiv = document.getElementById("search");
|
||||
const search = searchDiv.querySelector("input");
|
||||
setTimeout(() => {
|
||||
search.focus();
|
||||
}, 0);
|
||||
backdrop?.classList.remove("invisible");
|
||||
backdrop?.classList.add("visible");
|
||||
}
|
||||
|
||||
function closePagefind() {
|
||||
const search = document.getElementById("search");
|
||||
search.value = "";
|
||||
backdrop?.classList.remove("visible");
|
||||
backdrop?.classList.add("invisible");
|
||||
}
|
||||
|
||||
// open pagefind
|
||||
magnifyingGlass?.addEventListener("click", () => {
|
||||
openPagefind();
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "/") {
|
||||
e.preventDefault();
|
||||
openPagefind();
|
||||
} else if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
||||
e.preventDefault();
|
||||
openPagefind();
|
||||
}
|
||||
});
|
||||
|
||||
// close pagefind
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape") {
|
||||
closePagefind();
|
||||
}
|
||||
});
|
||||
|
||||
// close pagefind when searched result(link) clicked
|
||||
document.addEventListener("click", (event) => {
|
||||
if (event.target.classList.contains("pagefind-ui__result-link")) {
|
||||
closePagefind();
|
||||
}
|
||||
});
|
||||
|
||||
backdrop?.addEventListener("click", (event) => {
|
||||
if (!event.target.closest("#pagefind-container")) {
|
||||
closePagefind();
|
||||
}
|
||||
});
|
||||
|
||||
// prevent form submission
|
||||
const form = document.getElementById("form");
|
||||
form?.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style is:global>
|
||||
:root {
|
||||
--pagefind-ui-scale: 0.75;
|
||||
--pagefind-ui-border-width: 1px;
|
||||
--pagefind-ui-border-radius: 3px;
|
||||
--pagefind-ui-font: "Geist", sans-serif;
|
||||
--pagefind-ui-primary: #3d3d3d;
|
||||
--pagefind-ui-text: #3d3d3d;
|
||||
--pagefind-ui-background: #ffffff;
|
||||
--pagefind-ui-border: #d0d0d0;
|
||||
--pagefind-ui-tag: #f5f5f5;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--pagefind-ui-primary: #d4d4d4;
|
||||
--pagefind-ui-text: #d4d4d4;
|
||||
--pagefind-ui-background: #171717;
|
||||
--pagefind-ui-border: #404040;
|
||||
}
|
||||
|
||||
#search input {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#search p {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#search .pagefind-ui__result-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#search .pagefind-ui__message {
|
||||
padding: 0;
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
65
src/components/PostNavigation.astro
Normal file
65
src/components/PostNavigation.astro
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
const { prevPost, nextPost } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="grid grid-cols-2 gap-1.5 sm:gap-3">
|
||||
{
|
||||
prevPost?.id ? (
|
||||
<a
|
||||
href={`/blog/${prevPost?.id}`}
|
||||
class="group relative flex flex-nowrap rounded-lg border border-black/15 px-4 py-3 pl-10 no-underline transition-colors duration-300 ease-in-out hover:bg-black/5 hover:text-black focus-visible:bg-black/5 focus-visible:text-black dark:border-white/20 dark:hover:bg-white/5 dark:hover:text-white dark:focus-visible:bg-white/5 dark:focus-visible:text-white"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
class="absolute left-2 top-1/2 size-5 -translate-y-1/2 fill-none stroke-current stroke-2"
|
||||
>
|
||||
<line
|
||||
x1="5"
|
||||
y1="12"
|
||||
x2="19"
|
||||
y2="12"
|
||||
class="translate-x-3 scale-x-0 transition-transform duration-300 ease-in-out group-hover:translate-x-0 group-hover:scale-x-100 group-focus-visible:translate-x-0 group-focus-visible:scale-x-100"
|
||||
/>
|
||||
<polyline
|
||||
points="12 5 5 12 12 19"
|
||||
class="translate-x-1 transition-transform duration-300 ease-in-out group-hover:translate-x-0 group-focus-visible:translate-x-0"
|
||||
/>
|
||||
</svg>
|
||||
<div class="flex items-center text-sm">{prevPost?.data.title}</div>
|
||||
</a>
|
||||
) : (
|
||||
<div class="invisible" />
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
nextPost?.id ? (
|
||||
<a
|
||||
href={`/blog/${nextPost?.id}`}
|
||||
class="group relative flex grow flex-row-reverse flex-nowrap rounded-lg border border-black/15 px-4 py-4 pr-10 no-underline transition-colors duration-300 ease-in-out hover:bg-black/5 hover:text-black focus-visible:bg-black/5 focus-visible:text-black dark:border-white/20 dark:hover:bg-white/5 dark:hover:text-white dark:focus-visible:bg-white/5 dark:focus-visible:text-white"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
class="absolute right-2 top-1/2 size-5 -translate-y-1/2 fill-none stroke-current stroke-2"
|
||||
>
|
||||
<line
|
||||
x1="5"
|
||||
y1="12"
|
||||
x2="19"
|
||||
y2="12"
|
||||
class="translate-x-3 scale-x-0 transition-transform duration-300 ease-in-out group-hover:translate-x-0 group-hover:scale-x-100 group-focus-visible:translate-x-0 group-focus-visible:scale-x-100"
|
||||
/>
|
||||
<polyline
|
||||
points="12 5 19 12 12 19"
|
||||
class="-translate-x-1 transition-transform duration-300 ease-in-out group-hover:translate-x-0 group-focus-visible:translate-x-0"
|
||||
/>
|
||||
</svg>
|
||||
<div class="flex items-center text-sm">{nextPost?.data.title}</div>
|
||||
</a>
|
||||
) : (
|
||||
<div class="invisible" />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
43
src/components/TableOfContents.astro
Normal file
43
src/components/TableOfContents.astro
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
import TableOfContentsHeading from "./TableOfContentsHeading.astro";
|
||||
|
||||
// https://kld.dev/building-table-of-contents/
|
||||
const { headings } = Astro.props;
|
||||
const toc = buildToc(headings);
|
||||
|
||||
export interface Heading {
|
||||
depth: number;
|
||||
slug: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
function buildToc(headings: Heading[]) {
|
||||
const toc: Heading[] = [];
|
||||
const parentHeadings = new Map();
|
||||
headings.forEach((h) => {
|
||||
const heading = { ...h, subheadings: [] };
|
||||
parentHeadings.set(heading.depth, heading);
|
||||
if (heading.depth === 2) {
|
||||
toc.push(heading);
|
||||
} else {
|
||||
parentHeadings.get(heading.depth - 1).subheadings.push(heading);
|
||||
}
|
||||
});
|
||||
return toc;
|
||||
}
|
||||
---
|
||||
|
||||
<details
|
||||
open
|
||||
class="animate group rounded-lg border border-black/15 dark:border-white/20"
|
||||
>
|
||||
<summary
|
||||
class="cursor-pointer rounded-t-lg px-3 py-1.5 font-medium transition-colors group-open:bg-black/5 hover:bg-black/5 dark:group-open:bg-white/5 hover:dark:bg-white/5"
|
||||
>Table of Contents</summary
|
||||
>
|
||||
<nav>
|
||||
<ul class="py-3">
|
||||
{toc.map((heading) => <TableOfContentsHeading heading={heading} />)}
|
||||
</ul>
|
||||
</nav>
|
||||
</details>
|
||||
23
src/components/TableOfContentsHeading.astro
Normal file
23
src/components/TableOfContentsHeading.astro
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
import type { Heading } from "./TableOfContents.astro";
|
||||
import Link from "./Link.astro";
|
||||
|
||||
// https://kld.dev/building-table-of-contents/
|
||||
|
||||
const { heading } = Astro.props;
|
||||
---
|
||||
|
||||
<li class="list-inside list-disc px-6 py-1.5 text-sm">
|
||||
<Link href={"#" + heading.slug} underline>
|
||||
{heading.text}
|
||||
</Link>
|
||||
{
|
||||
heading.subheadings.length > 0 && (
|
||||
<ul class="translate-x-3">
|
||||
{heading.subheadings.map((subheading: Heading) => (
|
||||
<Astro.self heading={subheading} />
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
</li>
|
||||
10
src/components/TagCloud.astro
Normal file
10
src/components/TagCloud.astro
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
import { getCollection } from "astro:content";
|
||||
|
||||
const allPosts = await getCollection("blog");
|
||||
const allTags = allPosts.map((tag) => tag.data.tags).flat();
|
||||
|
||||
console.log(allTags);
|
||||
---
|
||||
|
||||
<span> </span>
|
||||
40
src/consts.ts
Normal file
40
src/consts.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import type { Metadata, Site, Socials } from "@types";
|
||||
|
||||
export const SITE: Site = {
|
||||
TITLE: "Micro",
|
||||
DESCRIPTION: "Neeldhara's micro blog.",
|
||||
EMAIL: "neeldhara.mishra@gmail.com",
|
||||
NUM_POSTS_ON_HOMEPAGE: 5,
|
||||
NUM_PROJECTS_ON_HOMEPAGE: 3,
|
||||
};
|
||||
|
||||
export const HOME: Metadata = {
|
||||
TITLE: "Home",
|
||||
DESCRIPTION: "Astro Micro is an accessible theme for Astro.",
|
||||
};
|
||||
|
||||
export const BLOG: Metadata = {
|
||||
TITLE: "Blog",
|
||||
DESCRIPTION: "A collection of articles on topics I am passionate about.",
|
||||
};
|
||||
|
||||
export const PROJECTS: Metadata = {
|
||||
TITLE: "Projects",
|
||||
DESCRIPTION:
|
||||
"A collection of my projects with links to repositories and live demos.",
|
||||
};
|
||||
|
||||
export const SOCIALS: Socials = [
|
||||
{
|
||||
NAME: "X (formerly Twitter)",
|
||||
HREF: "https://twitter.com/boogerbuttcheek",
|
||||
},
|
||||
{
|
||||
NAME: "GitHub",
|
||||
HREF: "https://github.com/trevortylerlee",
|
||||
},
|
||||
{
|
||||
NAME: "Website",
|
||||
HREF: "https://trevortylerlee.com",
|
||||
},
|
||||
];
|
||||
27
src/content.config.ts
Normal file
27
src/content.config.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { defineCollection, z } from "astro:content";
|
||||
import { glob } from 'astro/loaders';
|
||||
|
||||
const blog = defineCollection({
|
||||
loader: glob({ pattern: '**/*.{md,mdx}', base: "./src/content/blog" }),
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
date: z.coerce.date(),
|
||||
draft: z.boolean().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
const projects = defineCollection({
|
||||
loader: glob({ pattern: '**/*.{md,mdx}', base: "./src/content/projects" }),
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
date: z.coerce.date(),
|
||||
draft: z.boolean().optional(),
|
||||
demoURL: z.string().optional(),
|
||||
repoURL: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = { blog, projects };
|
||||
112
src/content/blog/00-micro-changelog/index.mdx
Normal file
112
src/content/blog/00-micro-changelog/index.mdx
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
---
|
||||
title: "Everything new in Astro Micro"
|
||||
description: "Features, enhancements, and changes."
|
||||
date: "2024-05-09"
|
||||
---
|
||||
|
||||
import Callout from "@/components/Callout.astro";
|
||||
|
||||
---
|
||||
|
||||
## Pagefind search 🔎
|
||||
|
||||
[Pagefind](https://pagefind.app) is a search library for static websites. Micro uses [Sergey Shishkin's](https://github.com/shishkin) [astro-pagefind](https://github.com/shishkin/astro-pagefind) integration. This integration simplifies development and does not require any changes to the default build script.
|
||||
|
||||
Press <kbd>/</kbd> or <kbd>CTRL</kbd> + <kbd>K</kbd> to open the search dialog. For Mac users, <kbd>⌘</kbd> + <kbd>K</kbd> can also be used. To dismiss the search dialog, press <kbd>Esc</kbd> or click on an area outside the component.
|
||||
|
||||
### Build and develop
|
||||
|
||||
<Callout type="error">
|
||||
The site **must** be built at least once so Pagefind can index the content.
|
||||
</Callout>
|
||||
|
||||
```bash
|
||||
# Pagefind must index the site to function
|
||||
npm run build
|
||||
```
|
||||
|
||||
When developing you can continue to use `npm run dev` and Pagefind will use the index from the last available build.
|
||||
|
||||
---
|
||||
|
||||
## Giscus comments 💬
|
||||
|
||||
[Giscus](https://giscus.app) leverages Github discussions to act as a comments system. To get Giscus working on your own website, see [here](/blog/01-getting-started#deploy-the-site).
|
||||
|
||||
---
|
||||
|
||||
## Callout component 🆕
|
||||
|
||||
<Callout>
|
||||
Adipisicing et officia reprehenderit fugiat occaecat cupidatat exercitation
|
||||
labore consequat ullamco nostrud non.
|
||||
</Callout>
|
||||
|
||||
<Callout type="info">
|
||||
Adipisicing et officia reprehenderit fugiat occaecat cupidatat exercitation
|
||||
labore consequat ullamco nostrud non.
|
||||
</Callout>
|
||||
|
||||
<Callout type="warning">
|
||||
Adipisicing et officia reprehenderit fugiat occaecat cupidatat exercitation
|
||||
labore consequat ullamco nostrud non.
|
||||
</Callout>
|
||||
|
||||
<Callout type="error">
|
||||
Adipisicing et officia reprehenderit fugiat occaecat cupidatat exercitation
|
||||
labore consequat ullamco nostrud non.
|
||||
</Callout>
|
||||
|
||||
---
|
||||
|
||||
## UI enhancements 🎨
|
||||
|
||||
- Elements are styled and animate on focus
|
||||
- Increased contrast in light mode
|
||||
- Active theme is indicated by theme buttons
|
||||
- Separate syntax highlight themes for light and dark mode
|
||||
- Code blocks have a copy button
|
||||
- Add pagination to the bottom of blog posts
|
||||
- Create 404 page
|
||||
- Add ToC component to posts
|
||||
|
||||
---
|
||||
|
||||
## Other changes
|
||||
|
||||
- Change fonts to Geist Sans and Geist Mono
|
||||
- Switch base color from "stone" to "neutral"
|
||||
- Change formatted date to use "long" option for month
|
||||
- Minor spacing changes throughout
|
||||
- Remove "work" collection and components
|
||||
- If desired, you can get the code from [Astro Nano](https://github.com/markhorn-dev/astro-nano)
|
||||
- Slightly increased link decoration offset
|
||||
- Slightly sped-up animations
|
||||
- Reversed animation
|
||||
- Ensure posts use an h1 tag for post titles
|
||||
- Tweaked typography
|
||||
|
||||
---
|
||||
|
||||
## Issues ⚠️
|
||||
|
||||
### Active issues
|
||||
|
||||
No active issues!
|
||||
|
||||
### Closed issues
|
||||
|
||||
- Fixed by [blopker](https://github.com/blopker): [ToC links are obscured by Header when scrolled to](https://github.com/trevortylerlee/astro-micro/issues/4)
|
||||
- Fixed by [blopker](https://github.com/blopker): [Pagefind requires a refresh to function when ViewTransitions is enabled](https://github.com/trevortylerlee/astro-micro/issues/7)
|
||||
- Fixed by [arastoonet](https://github.com/arastoonet): [Fix typo in README](https://github.com/trevortylerlee/astro-micro/pull/19)
|
||||
- Fixed by [cgranier](https://github.com/cgranier): [Pagination links advance by slug/folder](https://github.com/trevortylerlee/astro-micro/issues/26)
|
||||
- Fixed by [cgranier](https://github.com/cgranier): [Hides Table of Contents when there are no headings](https://github.com/trevortylerlee/astro-micro/pull/30)
|
||||
- Fixed: [Giscus does not appear when switching blogs via post navigation](https://github.com/trevortylerlee/astro-micro/issues/32)
|
||||
- Fixed: [Geist font renders incorrectly on Windows](https://github.com/trevortylerlee/astro-micro/issues/33)
|
||||
- Fixed: [Pagination fails in Cloudflare Pages](https://github.com/trevortylerlee/astro-micro/issues/39)
|
||||
- Fixed by [luciancah](https://github.com/luciancah): [Prevent Pagefind from double-indexing results](https://github.com/trevortylerlee/astro-micro/issues/40)
|
||||
- Fixed by [luciancah](https://github.com/luciancah): [Pagefind should close if a result is clicked](https://github.com/trevortylerlee/astro-micro/issues/43)
|
||||
- Fixed: [Implement tags](https://github.com/trevortylerlee/astro-micro/issues/70)
|
||||
- Fixed by [anaxite](https://github.com/anaxite): [Update Astro to 5.0](https://github.com/trevortylerlee/astro-micro/issues/73)
|
||||
- Fixed by [MoyaF](https://github.com/MoyaF): [Table of Contents links not working](https://github.com/trevortylerlee/astro-micro/issues/75)
|
||||
- Fixed by [antoniovalentini](https://github.com/antoniovalentini): [Possible bugs due to outdated Astro version 5.0.3](https://github.com/trevortylerlee/astro-micro/issues/78)
|
||||
182
src/content/blog/01-getting-started/index.md
Normal file
182
src/content/blog/01-getting-started/index.md
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
---
|
||||
title: "Getting started"
|
||||
description: "Hit the ground running."
|
||||
date: "2024-03-22"
|
||||
tags:
|
||||
- tutorial
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Install astro-micro
|
||||
|
||||
Clone the [Astro Micro repository](https://github.com/trevortylerlee/astro-micro.git).
|
||||
|
||||
```sh
|
||||
git clone https://github.com/trevortylerlee/astro-micro.git my-astro-micro
|
||||
```
|
||||
|
||||
```sh
|
||||
cd my-astro-micro
|
||||
```
|
||||
|
||||
```sh
|
||||
npm i
|
||||
```
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Customize the website metadata
|
||||
|
||||
To change the website metadata, edit `src/consts.ts`.
|
||||
|
||||
```ts
|
||||
// src/consts.ts
|
||||
|
||||
export const SITE: Site = {
|
||||
NAME: "Astro Micro",
|
||||
DESCRIPTION: "Astro Micro is an accessible theme for Astro.",
|
||||
EMAIL: "trevortylerlee@gmail.com",
|
||||
NUM_POSTS_ON_HOMEPAGE: 3,
|
||||
NUM_PROJECTS_ON_HOMEPAGE: 3,
|
||||
};
|
||||
```
|
||||
|
||||
| Field | Req | Description |
|
||||
| :----------- | :-- | :--------------------------------------------------- |
|
||||
| TITLE | Yes | Displayed in header and footer. Used in SEO and RSS. |
|
||||
| DESCRIPTION | Yes | Used in SEO and RSS. |
|
||||
| EMAIL | Yes | Displayed in contact section. |
|
||||
| NUM_POSTS | Yes | Limit number of posts on home page. |
|
||||
| NUM_PROJECTS | Yes | Limit number of projects on home page. |
|
||||
|
||||
---
|
||||
|
||||
## Customize metadata for individual pages
|
||||
|
||||
```ts
|
||||
// src/consts.ts
|
||||
|
||||
export const ABOUT: Metadata = {
|
||||
TITLE: "About",
|
||||
DESCRIPTION: "Astro Micro is a fork of Astro Nano.",
|
||||
};
|
||||
```
|
||||
|
||||
| Field | Req | Description |
|
||||
| :---------- | :-- | :--------------------------------------------- |
|
||||
| TITLE | Yes | Displayed in browser tab. Used in SEO and RSS. |
|
||||
| DESCRIPTION | Yes | Used in SEO and RSS. |
|
||||
|
||||
---
|
||||
|
||||
## Add your social media links
|
||||
|
||||
```ts
|
||||
// src/consts.ts
|
||||
|
||||
export const SOCIALS: Socials = [
|
||||
{
|
||||
NAME: "twitter-x",
|
||||
HREF: "https://twitter.com/boogerbuttcheeks",
|
||||
},
|
||||
{
|
||||
NAME: "github",
|
||||
HREF: "https://github.com/trevortylerlee",
|
||||
},
|
||||
{
|
||||
NAME: "linkedin",
|
||||
HREF: "https://www.linkedin.com/in/trevortylerlee",
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
| Field | Req | Description |
|
||||
| :---- | :-- | :-------------------------------------- |
|
||||
| NAME | Yes | Displayed in contact section as a link. |
|
||||
| HREF | Yes | External url to social media profile. |
|
||||
|
||||
## Deploy the site
|
||||
|
||||
To set up RSS and Giscus, it's easier if the site is deployed and has a URL for you to use. Instantly deploy to Vercel or Netlify by clicking the buttons below.
|
||||
|
||||
<div class="flex gap-2">
|
||||
<a target="_blank" aria-label="Deploy with Vercel" href="https://vercel.com/new/clone?repository-url=https://github.com/trevortylerlee/astro-micro">
|
||||
<img src="/deploy_vercel.svg" />
|
||||
</a>
|
||||
<a target="_blank" aria-label="Deploy with Netlify" href="https://app.netlify.com/start/deploy?repository=https://github.com/trevortylerlee/astro-micro">
|
||||
<img src="/deploy_netlify.svg" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
To deploy manually see [Astro's docs](https://docs.astro.build/en/guides/deploy/).
|
||||
|
||||
## Set up RSS
|
||||
|
||||
Change the `site` option to the deployed site's URL.
|
||||
|
||||
```js
|
||||
// astro.config.mjs
|
||||
|
||||
export default defineConfig({
|
||||
site: "https://astro-micro.vercel.app",
|
||||
integrations: [tailwind(), sitemap(), mdx(), pagefind()],
|
||||
markdown: {
|
||||
shikiConfig: {
|
||||
theme: "css-variables",
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Set up Giscus
|
||||
|
||||
Follow the steps at [giscus.app](https://giscus.app). Once you get your custom Giscus script from that site, go to `Giscus.astro` and replace that script with your own.
|
||||
|
||||
```js
|
||||
// src/components/Giscus.astro
|
||||
|
||||
<script
|
||||
is:inline
|
||||
src="https://giscus.app/client.js"
|
||||
data-repo="trevortylerlee/astro-micro"
|
||||
data-repo-id="R_kgDOL_6l9Q"
|
||||
data-category="Announcements"
|
||||
data-category-id="DIC_kwDOL_6l9c4Cfk55"
|
||||
data-mapping="pathname"
|
||||
data-strict="0"
|
||||
data-reactions-enabled="1"
|
||||
data-emit-metadata="0"
|
||||
data-input-position="top"
|
||||
data-theme="preferred_color_scheme"
|
||||
data-lang="en"
|
||||
data-loading="lazy"
|
||||
crossorigin="anonymous"
|
||||
async
|
||||
></script>
|
||||
```
|
||||
|
||||
To change the Giscus themes used, edit the `setGiscusTheme` function in `Head.astro`.
|
||||
|
||||
```js
|
||||
// src/components/Head.astro
|
||||
|
||||
const setGiscusTheme = () => {
|
||||
const giscus = document.querySelector(".giscus-frame");
|
||||
|
||||
const isDark = document.documentElement.classList.contains("dark");
|
||||
|
||||
if (giscus) {
|
||||
const url = new URL(giscus.src);
|
||||
// Change "dark" and "light" to other Giscus themes
|
||||
url.searchParams.set("theme", isDark ? "dark" : "light");
|
||||
giscus.src = url.toString();
|
||||
}
|
||||
};
|
||||
```
|
||||
69
src/content/blog/02-blog-collection/index.md
Normal file
69
src/content/blog/02-blog-collection/index.md
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
---
|
||||
title: "Blog Collection"
|
||||
description: "How to add posts to the blog."
|
||||
date: "2024-03-21"
|
||||
tags:
|
||||
- tutorial
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Working with the `blog` collection:
|
||||
|
||||
The `blog` collection is found in `src/content/blog`.
|
||||
|
||||
```
|
||||
📁 /src/content/blog
|
||||
└── 📁 post-1
|
||||
└── 📄 index.md
|
||||
└── 📁 post-2
|
||||
└── 📄 index.mdx
|
||||
```
|
||||
|
||||
In the above example, two blog posts will be generated with the folder name representing the id.
|
||||
|
||||
- `https://example.com/blog/post-1`
|
||||
- `https://example.com/blog/post-2`
|
||||
|
||||
---
|
||||
|
||||
## Provide metadata
|
||||
|
||||
Metadata is required for each post.
|
||||
|
||||
```astro
|
||||
---
|
||||
title: "Blog Collection";
|
||||
description: "How to add posts to the blog.";
|
||||
date: "2024-03-21";
|
||||
draft: false;
|
||||
tags:
|
||||
- tutorial
|
||||
---
|
||||
```
|
||||
|
||||
| Field | Req | Type | Remarks |
|
||||
| :---------- | :-- | :------ | :----------------------------------------------- |
|
||||
| title | Yes | string | Title of the content. Used in SEO and RSS. |
|
||||
| description | Yes | string | Description of the content. Used in SEO and RSS. |
|
||||
| date | Yes | string | Must be a valid date string (able to be parsed). |
|
||||
| draft | No | boolean | If draft: true, content will not be published. |
|
||||
| tags | No | string array | Tags to organize content |
|
||||
|
||||
---
|
||||
|
||||
All that's left to do is write the content under the metadata.
|
||||
|
||||
```astro
|
||||
---
|
||||
title: "Blog Collection";
|
||||
description: "How to add posts to the blog.";
|
||||
date: "2024-03-21";
|
||||
draft: false;
|
||||
tags:
|
||||
- tutorial
|
||||
---
|
||||
|
||||
## Working with the blog collection
|
||||
<!-- content -->
|
||||
```
|
||||
64
src/content/blog/03-projects-collection/index.md
Normal file
64
src/content/blog/03-projects-collection/index.md
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
---
|
||||
title: "Projects Collection"
|
||||
description: "How to add projects to your portfolio."
|
||||
date: "2024-03-20"
|
||||
tags:
|
||||
- tutorial
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Working with the `projects` collection
|
||||
|
||||
The `projects` collections is found in `src/content/projects`.
|
||||
|
||||
```
|
||||
📁 /src/content/projects
|
||||
└── 📁 project-1
|
||||
└── 📄 index.md
|
||||
└── 📁 project-2
|
||||
└── 📄 index.mdx
|
||||
```
|
||||
|
||||
In the above example, two project posts will be generated with the folder name representing the id.
|
||||
|
||||
- `https://example.com/projects/project-1`
|
||||
- `https://example.com/projects/project-2`
|
||||
|
||||
---
|
||||
|
||||
## Provide metadata
|
||||
|
||||
```astro
|
||||
---
|
||||
title: "Astro Micro";
|
||||
description: "Astro Micro is an accessible theme for Astro.";
|
||||
date: "2024-03-20";
|
||||
draft: false;
|
||||
---
|
||||
```
|
||||
|
||||
| Field | Req | Type | Remarks |
|
||||
| :---------- | :-- | :------ | :----------------------------------------------- |
|
||||
| title | Yes | string | Title of the content. Used in SEO and RSS. |
|
||||
| description | Yes | string | Description of the content. Used in SEO and RSS. |
|
||||
| date | Yes | string | Must be a valid date string (able to be parsed). |
|
||||
| draft | No | boolean | If draft: true, content will not be published. |
|
||||
| demoURL | No | string | Link to live project demo, if available. |
|
||||
| repoURL | No | string | Link to project repo, if available. |
|
||||
|
||||
---
|
||||
|
||||
All that's left to do is write the content under the metadata.
|
||||
|
||||
```astro
|
||||
---
|
||||
title: "Astro Micro";
|
||||
description: "Astro Micro is an accessible theme for Astro";
|
||||
date: "2024-03-20";
|
||||
draft: false;
|
||||
---
|
||||
|
||||
## Astro Micro 🔬
|
||||
<!-- content -->
|
||||
```
|
||||
400
src/content/blog/04-markdown-syntax/index.mdx
Normal file
400
src/content/blog/04-markdown-syntax/index.mdx
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
---
|
||||
title: "Markdown syntax guide"
|
||||
description: "Get started writing content in Markdown."
|
||||
date: "2024-03-17"
|
||||
tag:
|
||||
- reference
|
||||
---
|
||||
|
||||
import Callout from "@/components/Callout.astro";
|
||||
|
||||
---
|
||||
|
||||
## Headings
|
||||
|
||||
To create headings, use hash symbols (#) followed by a space. The number of hash symbols indicates the heading level.
|
||||
|
||||
<Callout>
|
||||
Use `h2` tags instead of `h1` tags in the post. Too many `h1` tags on a single
|
||||
page can impact SEO. The post title serves as the `h1`.
|
||||
</Callout>
|
||||
|
||||
```md
|
||||
# Heading 1
|
||||
|
||||
## Heading 2
|
||||
|
||||
### Heading 3
|
||||
|
||||
#### Heading 4
|
||||
|
||||
##### Heading 5
|
||||
|
||||
###### Heading 6
|
||||
```
|
||||
|
||||
<h1>Heading 1</h1>
|
||||
<h2>Heading 2</h2>
|
||||
<h3>Heading 3</h3>
|
||||
<h4>Heading 4</h4>
|
||||
<h5>Heading 5</h5>
|
||||
<h6>Heading 6</h6>
|
||||
|
||||
---
|
||||
|
||||
## Paragraphs
|
||||
|
||||
To create paragraphs, use a blank line to separate one or more lines of text.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```md
|
||||
<!-- empty line -->
|
||||
I love Star Wars.
|
||||
<!-- empty line -->
|
||||
My favourite is Episode III – Revenge of the Sith.
|
||||
<!-- empty line -->
|
||||
```
|
||||
|
||||
I love Star Wars.
|
||||
|
||||
My favourite is Episode III – Revenge of the Sith.
|
||||
|
||||
---
|
||||
|
||||
## Italic
|
||||
|
||||
Use one asterisk \(\*\) or underscore \(\_\) to italicize text.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```md
|
||||
I *love* Star Wars.
|
||||
My _favourite_ is Episode III – Revenge of the Sith.
|
||||
```
|
||||
|
||||
I _love_ Star Wars.
|
||||
My _favourite_ is Episode III – Revenge of the Sith.
|
||||
|
||||
---
|
||||
|
||||
## Bold
|
||||
|
||||
Use two asterisks \(\*\) or underscores \(\_\) to bold text.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```md
|
||||
I **love** Star Wars.
|
||||
My __favourite__ is Episode III – Revenge of the Sith.
|
||||
```
|
||||
|
||||
I **love** Star Wars.
|
||||
My **favourite** is Episode III – Revenge of the Sith.
|
||||
|
||||
---
|
||||
|
||||
## Italic and Bold
|
||||
|
||||
Use three asterisks \(\*\) or underscores \(\_\) to both bold and italicize text.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```md
|
||||
I ***love*** Star Wars.
|
||||
My ___favourite___ is Episode III – Revenge of the Sith.
|
||||
```
|
||||
|
||||
I **_love_** Star Wars.
|
||||
My **_favourite_** is Episode III – Revenge of the Sith.
|
||||
|
||||
---
|
||||
|
||||
## Horizontal Rule
|
||||
|
||||
Use three hyphens \(\-\), asterisks \(\*\), or underscores \(\_\) to create a horizontal rule.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```md
|
||||
<!-- empty line -->
|
||||
---
|
||||
<!-- empty line -->
|
||||
***
|
||||
<!-- empty line -->
|
||||
___
|
||||
<!-- empty line -->
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Links
|
||||
|
||||
To create a link, the link text in brackets \(\[\]\) and then follow it immediately with the URL in parentheses \(\(\)\).
|
||||
|
||||
```md
|
||||
Micro is a fork of [astro-nano](https://github.com/markhorn-dev/astro-nano).
|
||||
```
|
||||
|
||||
Micro is a fork of [astro-nano](https://github.com/markhorn-dev/astro-nano).
|
||||
|
||||
---
|
||||
|
||||
## Ordered Lists
|
||||
|
||||
To create an ordered list, add line items with numbers followed by periods. Use an indent to create a nested list.
|
||||
|
||||
```md
|
||||
1. Item 1
|
||||
2. Item 2
|
||||
1. Sub item 1
|
||||
2. Sub item 2
|
||||
3. Item 3
|
||||
```
|
||||
|
||||
1. Item 1
|
||||
2. Item 2
|
||||
1. Sub item 1
|
||||
2. Sub item 2
|
||||
3. Item 3
|
||||
|
||||
---
|
||||
|
||||
## Unordered List
|
||||
|
||||
To create an unordered list, add a hyphen \(\-\), an asterisk \(\*\), or a plus sign \(\+\) in front of line items. Don't mix. Use an indent to create a nested list.
|
||||
|
||||
```md
|
||||
- Item 1
|
||||
- Item 2
|
||||
- Sub item 1
|
||||
- Sub item 2
|
||||
- Item 3
|
||||
```
|
||||
|
||||
- Item 1
|
||||
- Item 2
|
||||
- Sub item 1
|
||||
- Sub item 2
|
||||
- Item 3
|
||||
|
||||
---
|
||||
|
||||
## Images
|
||||
|
||||
To add an image, add an exclamation mark (!), followed by alt text in brackets ([]), and the path or URL to the image asset in parentheses.
|
||||
|
||||
```md
|
||||

|
||||
```
|
||||
|
||||
### Relative
|
||||
|
||||
Use the `` pattern relative to the same folder as the markdown file. Notice the period.
|
||||
|
||||
```md
|
||||

|
||||
```
|
||||
|
||||

|
||||
|
||||
### Public Image
|
||||
|
||||
Use the `` pattern relative to the public folder. No period.
|
||||
|
||||
```md
|
||||

|
||||
```
|
||||
|
||||

|
||||
|
||||
### External Image
|
||||
|
||||
Use the `` pattern.
|
||||
|
||||
```md
|
||||

|
||||
```
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Blockquotes
|
||||
|
||||
To add a blockquote add the greater-than character \(\>\) before a paragraph. For multi-line blockquotes, add additional greater-than character for each line and include an empty spacer line.
|
||||
|
||||
```md
|
||||
> So this is how liberty dies...
|
||||
>
|
||||
> with thunderous applause.
|
||||
```
|
||||
|
||||
> So this is how liberty dies...
|
||||
>
|
||||
> with thunderous applause.
|
||||
|
||||
---
|
||||
|
||||
## Strikethrough
|
||||
|
||||
Use a tilde \(\~\) symbol to strikethrough text.
|
||||
|
||||
```md
|
||||
~I don't like sand.~ It's coarse and rough and irritating.
|
||||
```
|
||||
|
||||
~I don't like sand.~ It's coarse and rough and irritating.
|
||||
|
||||
---
|
||||
|
||||
## Subscript
|
||||
|
||||
Use the `<sub>` tag to denote subscript.
|
||||
|
||||
```md
|
||||
H<sub>2</sub>O
|
||||
```
|
||||
|
||||
H<sub>2</sub>O
|
||||
|
||||
---
|
||||
|
||||
## Superscript
|
||||
|
||||
Use the `<sup>` tag to denote superscript.
|
||||
|
||||
```md
|
||||
E=mc<sup>2</sup>
|
||||
```
|
||||
|
||||
E=mc<sup>2</sup>
|
||||
|
||||
---
|
||||
|
||||
## Keyboard
|
||||
|
||||
Use the `<kbd>` tag to denote keys on the keyboard.
|
||||
|
||||
```md
|
||||
<kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>Delete</kbd>
|
||||
```
|
||||
|
||||
<kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>Delete</kbd>
|
||||
|
||||
---
|
||||
|
||||
## Abbreviate
|
||||
|
||||
Use the `<abbr>` tag to denote abbreviation.
|
||||
|
||||
```md
|
||||
<abbr title="Graphics Interchange Format">GIF</abbr>
|
||||
```
|
||||
|
||||
<abbr title="Graphics Interchange Format">GIF</abbr>
|
||||
|
||||
---
|
||||
|
||||
### Highlight
|
||||
|
||||
Use the `<mark>` tag to denote highlighted text.
|
||||
|
||||
```md
|
||||
Do or do not. <mark>There is no try.</mark>
|
||||
```
|
||||
|
||||
Do or do not. <mark>There is no try.</mark>
|
||||
|
||||
---
|
||||
|
||||
## Task Lists
|
||||
|
||||
Combine a list with square brackets ([]) representing a checkbox. Typing `x` inside the brackets marks the task as complete.
|
||||
|
||||
```md
|
||||
- [x] Build a lightsaber
|
||||
- [ ] Pass the Jedi Trials
|
||||
- [ ] Train a padawan
|
||||
```
|
||||
|
||||
- [x] Build a lightsaber
|
||||
- [ ] Pass the Jedi Trials
|
||||
- [ ] Train a padawan
|
||||
|
||||
---
|
||||
|
||||
## Tables
|
||||
|
||||
Use three or more hyphens (-) for the column headers and use pipes (|) to separate each column. You can align text in the columns to the left, right, or center by adding a colon (:) to the left, right, or on both side of the hyphens.
|
||||
|
||||
```md
|
||||
| Item | Count |
|
||||
| :----- | ----: |
|
||||
| X-Wing | 1 |
|
||||
| Y-Wing | 2 |
|
||||
| A-Wing | 3 |
|
||||
```
|
||||
|
||||
| Item | Count |
|
||||
| :----- | ----: |
|
||||
| X-Wing | 1 |
|
||||
| Y-Wing | 2 |
|
||||
| A-Wing | 3 |
|
||||
|
||||
---
|
||||
|
||||
## Footnotes
|
||||
|
||||
Add a caret (^) and an identifier inside brackets \(\[\^1\]\). Identifiers can be numbers or words but can't contain spaces or tabs.
|
||||
|
||||
```md
|
||||
Here's a footnote, [^1] and here's another one. [^2]
|
||||
[^1]: This is the first footnote.
|
||||
[^2]: This is the second footnote.
|
||||
```
|
||||
|
||||
Here's a footnote, [^1] and here's another one. [^2]
|
||||
[^1]: This is the first footnote.
|
||||
[^2]: This is the second footnote.
|
||||
|
||||
See the bottom of this page to view the footnotes.
|
||||
|
||||
---
|
||||
|
||||
## Code
|
||||
|
||||
To denote a word or phrase as code, enclose it in backticks (`).
|
||||
|
||||
```md
|
||||
`package.json`
|
||||
```
|
||||
|
||||
`package.json`
|
||||
|
||||
---
|
||||
|
||||
## Code Blocks
|
||||
|
||||
Denote a code block by wrapping a section of valid code in triple backticks (`). To enable syntax highlighting, type the shorthand symbol for the language after the first three backticks. [Reference for shorthand symbols](https://shiki.style/languages).
|
||||
|
||||
````
|
||||
```js
|
||||
function hello() {
|
||||
console.log("hello world");
|
||||
}
|
||||
```
|
||||
````
|
||||
|
||||
```js
|
||||
function hello() {
|
||||
console.log("hello world");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
Refer to [markdownguide.org](https://www.markdownguide.org/) for best practices as well as extended syntax.
|
||||
|
||||
---
|
||||
BIN
src/content/blog/04-markdown-syntax/x-wing.jpeg
Normal file
BIN
src/content/blog/04-markdown-syntax/x-wing.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 203 KiB |
10
src/content/blog/05-mdx-syntax/component.astro
Normal file
10
src/content/blog/05-mdx-syntax/component.astro
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
|
||||
---
|
||||
|
||||
<button
|
||||
onclick="alert(`You clicked the button!`)"
|
||||
class="border border-black/10 p-2 transition-colors duration-300 ease-in-out hover:bg-black/5 dark:border-white/10 dark:hover:bg-white/10"
|
||||
>
|
||||
Relative Button
|
||||
</button>
|
||||
73
src/content/blog/05-mdx-syntax/index.mdx
Normal file
73
src/content/blog/05-mdx-syntax/index.mdx
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
---
|
||||
title: "MDX syntax guide"
|
||||
description: "Use interactive components in Markdown."
|
||||
date: "2024-03-16"
|
||||
tags:
|
||||
- reference
|
||||
---
|
||||
|
||||
import Callout from "@/components/Callout.astro";
|
||||
|
||||
---
|
||||
|
||||
MDX is an extension of Markdown with the ability to import `.astro`,
|
||||
`.jsx`, `.tsx` and other framework components you have integrated.
|
||||
|
||||
This guide covers the basics of MDX syntax and how to use it, as well as a few examples.
|
||||
|
||||
## Example 1
|
||||
|
||||
Importing a component from the `/components` directory.
|
||||
|
||||
This component accepts a Javascript date object and format it as a string.
|
||||
|
||||
```astro
|
||||
import DateComp from "../../../components/FormattedDate.astro";
|
||||
|
||||
<DateComp date={new Date()} />
|
||||
```
|
||||
|
||||
import FormattedDate from "../../../components/FormattedDate.astro";
|
||||
|
||||
<FormattedDate date={new Date()} />
|
||||
|
||||
---
|
||||
|
||||
## Example 2
|
||||
|
||||
Importing a component from a relative path to your content.
|
||||
|
||||
This component displays an alert when the button is clicked.
|
||||
|
||||
```astro
|
||||
import RelativeComponent from "./component.astro";
|
||||
|
||||
<RelativeComponent />
|
||||
```
|
||||
|
||||
import RelativeComponent from "./component.astro";
|
||||
|
||||
<RelativeComponent />
|
||||
|
||||
---
|
||||
|
||||
By default Micro has zero frameworks installed. If you install a framework, components of that framework can be used in MDX files.
|
||||
|
||||
<Callout>
|
||||
Don't forget to use [client
|
||||
directives](https://docs.astro.build/en/reference/directives-reference/#client-directives)
|
||||
to make framework components interactive.
|
||||
</Callout>
|
||||
|
||||
```astro
|
||||
<ReactComponent client:load />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## More Links
|
||||
|
||||
- [MDX Syntax Documentation](https://mdxjs.com/docs/what-is-mdx)
|
||||
- [Astro Framework Integrations](https://docs.astro.build/en/guides/integrations-guide)
|
||||
- [Astro Usage Documentation](https://docs.astro.build/en/guides/markdown-content/#markdown-and-mdx-pages)
|
||||
- [Client Directives](https://docs.astro.build/en/reference/directives-reference/#client-directives)
|
||||
9
src/content/blog/06-year-sorting-example/index.md
Normal file
9
src/content/blog/06-year-sorting-example/index.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
title: "Year sorting example"
|
||||
description: "Nano groups posts by year."
|
||||
date: "2023-12-31"
|
||||
tags:
|
||||
- example
|
||||
---
|
||||
|
||||
This post is to demonstrate the year sorting capabilities.
|
||||
12
src/content/blog/07-draft-example/index.md
Normal file
12
src/content/blog/07-draft-example/index.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: "Draft example"
|
||||
description: "Setting draft flag to true to hide this post."
|
||||
date: "2022-12-31"
|
||||
draft: false
|
||||
tags:
|
||||
- example
|
||||
---
|
||||
|
||||
This post also demonstrates the year sorting capabilities.
|
||||
|
||||
Try setting this file's metadata to `draft: true`.
|
||||
11
src/content/blog/08-prev-next-order-example/index.md
Normal file
11
src/content/blog/08-prev-next-order-example/index.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
title: "Chronological pagination example"
|
||||
description: "Pagination works regardless of folder name."
|
||||
date: "2024-03-21"
|
||||
tags:
|
||||
- example
|
||||
---
|
||||
|
||||
This post should show up in proper chronological order even though its folder comes last in the `content/blog` directory.
|
||||
|
||||
The `Previous Post` and `Next Post` buttons under each blog post should also keep the proper chronological order, based on the frontmatter `date` field.
|
||||
79
src/content/projects/project-1/index.md
Normal file
79
src/content/projects/project-1/index.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
title: "Astro Sphere"
|
||||
description: "Portfolio and blog build with astro."
|
||||
date: "03/18/2024"
|
||||
demoURL: "https://astro-sphere-demo.vercel.app"
|
||||
repoURL: "https://github.com/markhorn-dev/astro-sphere"
|
||||
---
|
||||
|
||||

|
||||
|
||||
Astro Sphere is a static, minimalist, lightweight, lightning fast portfolio and blog theme based on Mark Horn's personal website.
|
||||
|
||||
It is primarily Astro, Tailwind and Typescript, with a very small amount of SolidJS for stateful components.
|
||||
|
||||
## 🚀 Deploy your own
|
||||
|
||||
<div class="flex gap-2">
|
||||
<a target="_blank" aria-label="Deploy with Vercel" href="https://vercel.com/new/clone?repository-url=https://github.com/markhorn-dev/astro-sphere">
|
||||
<img src="/deploy_vercel.svg" />
|
||||
</a>
|
||||
<a target="_blank" aria-label="Deploy with Netlify" href="https://app.netlify.com/start/deploy?repository=https://github.com/markhorn-dev/astro-sphere">
|
||||
<img src="/deploy_netlify.svg" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## 📋 Features
|
||||
|
||||
- ✅ 100/100 Lighthouse performance
|
||||
- ✅ Responsive
|
||||
- ✅ Accessible
|
||||
- ✅ SEO-friendly
|
||||
- ✅ Typesafe
|
||||
- ✅ Minimal style
|
||||
- ✅ Light/Dark Theme
|
||||
- ✅ Animated UI
|
||||
- ✅ Tailwind styling
|
||||
- ✅ Auto generated sitemap
|
||||
- ✅ Auto generated RSS Feed
|
||||
- ✅ Markdown support
|
||||
- ✅ MDX Support (components in your markdown)
|
||||
- ✅ Searchable content (posts and projects)
|
||||
|
||||
## 💯 Lighthouse score
|
||||
|
||||

|
||||
|
||||
## 🕊️ Lightweight
|
||||
|
||||
All pages under 100kb (including fonts)
|
||||
|
||||
## ⚡︎ Fast
|
||||
|
||||
Rendered in ~40ms on localhost
|
||||
|
||||
## 📄 Configuration
|
||||
|
||||
The blog posts on the demo serve as the documentation and configuration.
|
||||
|
||||
## 💻 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
Replace npm with your package manager of choice. `npm`, `pnpm`, `yarn`, `bun`, etc
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :------------------------------------------------ |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
||||
| `npm run sync` | Generates TypeScript types for all Astro modules. |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||
| `npm run lint` | Run ESLint |
|
||||
| `npm run lint:fix` | Auto-fix ESLint issues |
|
||||
|
||||
## 🏛️ License
|
||||
|
||||
MIT
|
||||
82
src/content/projects/project-2/index.md
Normal file
82
src/content/projects/project-2/index.md
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
title: "Astro Nano"
|
||||
description: "Minimal portfolio and blog build with astro and no frameworks."
|
||||
date: "2024-03-26"
|
||||
demoURL: "https://astro-nano-demo.vercel.app"
|
||||
repoURL: "https://github.com/markhorn-dev/astro-nano"
|
||||
---
|
||||
|
||||

|
||||
|
||||
Astro Nano is a static, minimalist, lightweight, lightning fast portfolio and blog theme.
|
||||
|
||||
Built with Astro, Tailwind and Typescript, and no frameworks.
|
||||
|
||||
It was designed as an even more minimal theme than Mark Horn's popular theme [Astro Sphere](https://github.com/markhorn-dev/astro-sphere)
|
||||
|
||||
## 🚀 Deploy your own
|
||||
|
||||
<div class="flex gap-2">
|
||||
<a target="_blank" aria-label="Deploy with Vercel" href="https://vercel.com/new/clone?repository-url=https://github.com/markhorn-dev/astro-nano">
|
||||
<img src="/deploy_vercel.svg" />
|
||||
</a>
|
||||
<a target="_blank" aria-label="Deploy with Netlify" href="https://app.netlify.com/start/deploy?repository=https://github.com/markhorn-dev/astro-nano">
|
||||
<img src="/deploy_netlify.svg" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## 📋 Features
|
||||
|
||||
- ✅ 100/100 Lighthouse performance
|
||||
- ✅ Responsive
|
||||
- ✅ Accessible
|
||||
- ✅ SEO-friendly
|
||||
- ✅ Typesafe
|
||||
- ✅ Minimal style
|
||||
- ✅ Light/Dark Theme
|
||||
- ✅ Animated UI
|
||||
- ✅ Tailwind styling
|
||||
- ✅ Auto generated sitemap
|
||||
- ✅ Auto generated RSS Feed
|
||||
- ✅ Markdown support
|
||||
- ✅ MDX Support (components in your markdown)
|
||||
|
||||
## 💯 Lighthouse score
|
||||
|
||||

|
||||
|
||||
## 🕊️ Lightweight
|
||||
|
||||
No frameworks or added bulk
|
||||
|
||||
## ⚡︎ Fast
|
||||
|
||||
Rendered in ~40ms on localhost
|
||||
|
||||
## 📄 Configuration
|
||||
|
||||
The blog posts on the demo serve as the documentation and configuration.
|
||||
|
||||
## 💻 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
Replace npm with your package manager of choice. `npm`, `pnpm`, `yarn`, `bun`, etc
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :------------------------------------------------ |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
||||
| `npm run dev:network` | Starts local dev server on local network |
|
||||
| `npm run sync` | Generates TypeScript types for all Astro modules. |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run preview:network` | Preview build on local network |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||
| `npm run lint` | Run ESLint |
|
||||
| `npm run lint:fix` | Auto-fix ESLint issues |
|
||||
|
||||
## 🏛️ License
|
||||
|
||||
MIT
|
||||
45
src/content/projects/project-3/index.md
Normal file
45
src/content/projects/project-3/index.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
title: "Astro Micro"
|
||||
description: "Astro Micro is an accessible and lightweight blog."
|
||||
date: "2024-05-26"
|
||||
demoURL: "https://astro-micro.vercel.app"
|
||||
repoURL: "https://github.com/trevortylerlee/astro-micro"
|
||||
---
|
||||
|
||||

|
||||
|
||||
Astro Micro is an accessible theme for Astro. It's a fork of Mark Horn's popular theme Astro Nano. Like Nano, Micro comes with zero frameworks installed.
|
||||
|
||||
Micro adds features like [Pagefind](https://pagefind.app) for search, [Giscus](https://giscus.app) for comments, and more. For a full list of changes, see this [blog post](/blog/00-micro-changelog).
|
||||
|
||||
Micro still comes with everything great about Nano — full type safety, a sitemap, an RSS feed, and Markdown + MDX support. Styled with TailwindCSS and preconfigured with system, light, and dark themes.
|
||||
|
||||
Visit [Astro Micro on Github](https://github.com/trevortylerlee/astro-micro) to get started.
|
||||
|
||||
## 🚀 Deploy your own
|
||||
|
||||
<div class="flex gap-2">
|
||||
<a target="_blank" aria-label="Deploy with Vercel" href="https://vercel.com/new/clone?repository-url=https://github.com/trevortylerlee/astro-micro">
|
||||
<img src="/deploy_vercel.svg" />
|
||||
</a>
|
||||
<a target="_blank" aria-label="Deploy with Netlify" href="https://app.netlify.com/start/deploy?repository=https://github.com/trevortylerlee/astro-micro">
|
||||
<img src="/deploy_netlify.svg" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## 📋 Features
|
||||
|
||||
Everything in [Astro Nano](https://github.com/markhorn-dev/astro-nano) plus:
|
||||
|
||||
- Pagefind search
|
||||
- Giscus comments
|
||||
- Callout component
|
||||
- Table of contents component
|
||||
- Pagination component
|
||||
- 404 page
|
||||
|
||||
and [more](/blog/00-micro-changelog).
|
||||
|
||||
## 💯 Lighthouse score
|
||||
|
||||

|
||||
37
src/layouts/Layout.astro
Normal file
37
src/layouts/Layout.astro
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
import Head from "@components/Head.astro";
|
||||
import Header from "@components/Header.astro";
|
||||
import Footer from "@components/Footer.astro";
|
||||
import Pagefind from "@components/PageFind.astro";
|
||||
import { SITE } from "@consts";
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
const { title, description } = Astro.props;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<Head title={`${title} | ${SITE.TITLE}`} description={description} />
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<style>
|
||||
.animate{
|
||||
opacity:1;
|
||||
translate: var(--tw-translate-x) calc(var(--spacing) * 0);
|
||||
}
|
||||
</style>
|
||||
</noscript>
|
||||
<Header />
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
<Footer />
|
||||
<Pagefind />
|
||||
</body>
|
||||
</html>
|
||||
21
src/lib/utils.ts
Normal file
21
src/lib/utils.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export function formatDate(date: Date) {
|
||||
return Intl.DateTimeFormat("en-US", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
export function readingTime(html: string) {
|
||||
const textOnly = html.replace(/<[^>]+>/g, "");
|
||||
const wordCount = textOnly.split(/\s+/).length;
|
||||
const readingTimeMinutes = (wordCount / 200 + 1).toFixed();
|
||||
return `${readingTimeMinutes} min read`;
|
||||
}
|
||||
19
src/pages/404.astro
Normal file
19
src/pages/404.astro
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
import Container from "@components/Container.astro";
|
||||
import BackToPrevious from "@components/BackToPrevious.astro";
|
||||
import { SITE } from "@consts";
|
||||
---
|
||||
|
||||
<Layout title="404" description={SITE.DESCRIPTION}>
|
||||
<Container>
|
||||
<div class="mt-16 grid place-items-center gap-3">
|
||||
<h4 class="animate text-2xl font-semibold text-black dark:text-white">
|
||||
404: Page not found
|
||||
</h4>
|
||||
<span class="animate">
|
||||
<BackToPrevious href="/">Go to home page</BackToPrevious>
|
||||
</span>
|
||||
</div>
|
||||
</Container>
|
||||
</Layout>
|
||||
100
src/pages/blog/[...id].astro
Normal file
100
src/pages/blog/[...id].astro
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
---
|
||||
import { type CollectionEntry, getCollection, render } from "astro:content";
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
import Container from "@components/Container.astro";
|
||||
import FormattedDate from "@components/FormattedDate.astro";
|
||||
import { readingTime } from "@lib/utils";
|
||||
import BackToPrevious from "@components/BackToPrevious.astro";
|
||||
import PostNavigation from "@components/PostNavigation.astro";
|
||||
import TableOfContents from "@components/TableOfContents.astro";
|
||||
import Giscus from "@components/Giscus.astro";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = (await getCollection("blog"))
|
||||
.filter((post) => !post.data.draft)
|
||||
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
|
||||
return posts.map((post) => ({
|
||||
params: { id: post.id },
|
||||
props: post,
|
||||
}));
|
||||
}
|
||||
type Props = CollectionEntry<"blog">;
|
||||
|
||||
const posts = (await getCollection("blog"))
|
||||
.filter((post) => !post.data.draft)
|
||||
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
|
||||
|
||||
function getNextPost() {
|
||||
let postIndex;
|
||||
for (const post of posts) {
|
||||
if (post.id === Astro.params.id) {
|
||||
postIndex = posts.indexOf(post);
|
||||
return posts[postIndex + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getPrevPost() {
|
||||
let postIndex;
|
||||
for (const post of posts) {
|
||||
if (post.id === Astro.params.id) {
|
||||
postIndex = posts.indexOf(post);
|
||||
return posts[postIndex - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const nextPost = getNextPost();
|
||||
const prevPost = getPrevPost();
|
||||
|
||||
const post = Astro.props;
|
||||
const { Content, headings } = await render(post);
|
||||
---
|
||||
|
||||
<Layout title={post.data.title} description={post.data.description}>
|
||||
<Container>
|
||||
<div class="animate">
|
||||
<BackToPrevious href="/blog">Back to blog</BackToPrevious>
|
||||
</div>
|
||||
<div class="my-10 space-y-1">
|
||||
<div class="animate flex items-center gap-1.5">
|
||||
<div class="font-base text-sm">
|
||||
<FormattedDate date={post.data.date} />
|
||||
</div>
|
||||
•
|
||||
{post.body && (
|
||||
<div class="font-base text-sm">
|
||||
{readingTime(post.body)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<h1 class="animate text-3xl font-semibold text-black dark:text-white">
|
||||
{post.data.title}
|
||||
</h1>
|
||||
{
|
||||
post.data.tags && post.data.tags?.length > 0 ? (
|
||||
<div class="animate flex gap-2 pt-1">
|
||||
{post.data.tags.map((tag) => (
|
||||
<a
|
||||
href={`/tags/${tag}`}
|
||||
class="rounded-sm border border-black/15 px-2 py-1 text-sm transition-colors duration-300 ease-in-out hover:bg-black/5 hover:text-black focus-visible:bg-black/5 focus-visible:text-black dark:border-white/20 dark:hover:bg-white/5 dark:hover:text-white dark:focus-visible:bg-white/5 dark:focus-visible:text-white"
|
||||
>
|
||||
{tag}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
</div>
|
||||
{headings.length > 0 && <TableOfContents headings={headings} />}
|
||||
<article class="animate">
|
||||
<Content />
|
||||
<div class="mt-24">
|
||||
<PostNavigation prevPost={prevPost} nextPost={nextPost} />
|
||||
</div>
|
||||
<div class="mt-24">
|
||||
<Giscus />
|
||||
</div>
|
||||
</article>
|
||||
</Container>
|
||||
</Layout>
|
||||
55
src/pages/blog/index.astro
Normal file
55
src/pages/blog/index.astro
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
import { type CollectionEntry, getCollection } from "astro:content";
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
import Container from "@components/Container.astro";
|
||||
import ArrowCard from "@components/ArrowCard.astro";
|
||||
import { BLOG } from "@consts";
|
||||
|
||||
const data = (await getCollection("blog"))
|
||||
.filter((post) => !post.data.draft)
|
||||
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
|
||||
|
||||
type Acc = {
|
||||
[year: string]: CollectionEntry<"blog">[];
|
||||
};
|
||||
|
||||
const posts = data.reduce((acc: Acc, post) => {
|
||||
const year = post.data.date.getFullYear().toString();
|
||||
if (!acc[year]) {
|
||||
acc[year] = [];
|
||||
}
|
||||
acc[year].push(post);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const years = Object.keys(posts).sort((a, b) => parseInt(b) - parseInt(a));
|
||||
---
|
||||
|
||||
<Layout title={BLOG.TITLE} description={BLOG.DESCRIPTION}>
|
||||
<Container>
|
||||
<aside data-pagefind-ignore>
|
||||
<div class="space-y-10">
|
||||
<div class="space-y-4">
|
||||
{
|
||||
years.map((year) => (
|
||||
<section class="animate space-y-4">
|
||||
<div class="font-semibold text-black dark:text-white">
|
||||
{year}
|
||||
</div>
|
||||
<div>
|
||||
<ul class="not-prose flex flex-col gap-4">
|
||||
{posts[year].map((post) => (
|
||||
<li>
|
||||
<ArrowCard entry={post} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</Container>
|
||||
</Layout>
|
||||
145
src/pages/index.astro
Normal file
145
src/pages/index.astro
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
---
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
import Container from "@components/Container.astro";
|
||||
import { SITE, HOME, SOCIALS } from "@consts";
|
||||
import ArrowCard from "@components/ArrowCard.astro";
|
||||
import Link from "@components/Link.astro";
|
||||
import { getCollection } from "astro:content";
|
||||
import type { CollectionEntry } from "astro:content";
|
||||
|
||||
const blog = (await getCollection("blog"))
|
||||
.filter((post) => !post.data.draft)
|
||||
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
|
||||
.slice(0, SITE.NUM_POSTS_ON_HOMEPAGE);
|
||||
|
||||
const projects: CollectionEntry<"projects">[] = (
|
||||
await getCollection("projects")
|
||||
)
|
||||
.filter((project) => !project.data.draft)
|
||||
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
|
||||
.slice(0, SITE.NUM_PROJECTS_ON_HOMEPAGE);
|
||||
---
|
||||
|
||||
<Layout title={HOME.TITLE} description={HOME.DESCRIPTION}>
|
||||
<Container>
|
||||
<aside data-pagefind-ignore>
|
||||
<h1 class="font-semibold text-black dark:text-white">
|
||||
Introducing Astro Micro 🔬
|
||||
</h1>
|
||||
<div class="space-y-16">
|
||||
<section>
|
||||
<article class="space-y-4">
|
||||
<span>
|
||||
<p>
|
||||
Astro Micro is an accessible theme for <Link
|
||||
href="https://astro.build/">Astro</Link
|
||||
>. It's a fork of
|
||||
<Link href="https://github.com/markhorn-dev">
|
||||
Mark Horn's
|
||||
</Link> popular theme <Link
|
||||
href="https://astro.build/themes/details/astronano/"
|
||||
>Astro Nano</Link
|
||||
>. Like Nano, Micro comes with zero frameworks installed.
|
||||
</p>
|
||||
<p>
|
||||
Micro adds features like <Link href="https://pagefind.app/"
|
||||
>Pagefind</Link
|
||||
> for search, <Link href="https://giscus.app">Giscus</Link> for comments,
|
||||
and more. For a full list of changes, see this <Link
|
||||
href="/blog/00-micro-changelog">blog post</Link
|
||||
>.
|
||||
</p>
|
||||
</span>
|
||||
<span class="animate">
|
||||
<p>
|
||||
Micro still comes with everything great about Nano — full type
|
||||
safety, a sitemap, an RSS feed, and Markdown + MDX support.
|
||||
Styled with TailwindCSS and preconfigured with system, light,
|
||||
and dark themes.
|
||||
</p>
|
||||
<p>
|
||||
Visit
|
||||
<Link href="https://github.com/trevortylerlee/astro-micro">
|
||||
Astro Micro on GitHub
|
||||
</Link>
|
||||
to fork the repository to get started.
|
||||
</p>
|
||||
</span>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="animate space-y-6">
|
||||
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||
<h2 class="font-semibold text-black dark:text-white">
|
||||
Latest posts
|
||||
</h2>
|
||||
<Link href="/blog"> See all posts </Link>
|
||||
</div>
|
||||
<ul class="not-prose flex flex-col gap-4">
|
||||
{
|
||||
blog.map((post) => (
|
||||
<li>
|
||||
<ArrowCard entry={post} />
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="animate space-y-6">
|
||||
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||
<h2 class="font-semibold text-black dark:text-white">
|
||||
Recent projects
|
||||
</h2>
|
||||
<Link href="/projects"> See all projects </Link>
|
||||
</div>
|
||||
<ul class="not-prose flex flex-col gap-4">
|
||||
{
|
||||
projects.map((project) => (
|
||||
<li>
|
||||
<ArrowCard entry={project} />
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="animate space-y-4">
|
||||
<h2 class="font-semibold text-black dark:text-white">
|
||||
Let's Connect
|
||||
</h2>
|
||||
<article>
|
||||
<p>
|
||||
If you want to get in touch with me about something or just to say
|
||||
hi, reach out on social media or send me an email.
|
||||
</p>
|
||||
</article>
|
||||
<ul class="not-prose flex flex-wrap gap-2">
|
||||
{
|
||||
SOCIALS.map((SOCIAL) => (
|
||||
<li class="flex gap-x-2 text-nowrap">
|
||||
<Link
|
||||
href={SOCIAL.HREF}
|
||||
external
|
||||
aria-label={`${SITE.TITLE} on ${SOCIAL.NAME}`}
|
||||
>
|
||||
{SOCIAL.NAME}
|
||||
</Link>
|
||||
{"/"}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
<li class="line-clamp-1">
|
||||
<Link
|
||||
href={`mailto:${SITE.EMAIL}`}
|
||||
aria-label={`Email ${SITE.TITLE}`}
|
||||
>
|
||||
{SITE.EMAIL}
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</aside>
|
||||
</Container>
|
||||
</Layout>
|
||||
69
src/pages/projects/[...id].astro
Normal file
69
src/pages/projects/[...id].astro
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
---
|
||||
import { type CollectionEntry, getCollection, render } from "astro:content";
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
import Container from "@components/Container.astro";
|
||||
import FormattedDate from "@components/FormattedDate.astro";
|
||||
import { readingTime } from "@lib/utils";
|
||||
import BackToPrevious from "@components/BackToPrevious.astro";
|
||||
import Link from "@components/Link.astro";
|
||||
import TableOfContents from "@components/TableOfContents.astro";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const projects = (await getCollection("projects"))
|
||||
.filter((post) => !post.data.draft)
|
||||
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
|
||||
return projects.map((project) => ({
|
||||
params: { id: project.id },
|
||||
props: project,
|
||||
}));
|
||||
}
|
||||
type Props = CollectionEntry<"projects">;
|
||||
|
||||
const project = Astro.props;
|
||||
const { Content, headings } = await render(project);
|
||||
---
|
||||
|
||||
<Layout title={project.data.title} description={project.data.description}>
|
||||
<Container>
|
||||
<div class="animate">
|
||||
<BackToPrevious href="/projects">Back to projects</BackToPrevious>
|
||||
</div>
|
||||
<div class="animate my-10 space-y-1">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<div class="font-base text-sm">
|
||||
<FormattedDate date={project.data.date} />
|
||||
</div>
|
||||
•
|
||||
{project.body && (
|
||||
<div class="font-base text-sm">
|
||||
{readingTime(project.body)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<h1 class="text-3xl font-semibold text-black dark:text-white">
|
||||
{project.data.title}
|
||||
</h1>
|
||||
{
|
||||
(project.data.demoURL || project.data.repoURL) && (
|
||||
<nav class="flex gap-1">
|
||||
{project.data.demoURL && (
|
||||
<Link href={project.data.demoURL} external>
|
||||
demo
|
||||
</Link>
|
||||
)}
|
||||
{project.data.demoURL && project.data.repoURL && <span>/</span>}
|
||||
{project.data.repoURL && (
|
||||
<Link href={project.data.repoURL} external>
|
||||
repo
|
||||
</Link>
|
||||
)}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<TableOfContents headings={headings} />
|
||||
<article class="animate">
|
||||
<Content />
|
||||
</article>
|
||||
</Container>
|
||||
</Layout>
|
||||
32
src/pages/projects/index.astro
Normal file
32
src/pages/projects/index.astro
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
import { getCollection } from "astro:content";
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
import Container from "@components/Container.astro";
|
||||
import ArrowCard from "@components/ArrowCard.astro";
|
||||
import { PROJECTS } from "@consts";
|
||||
|
||||
const projects = (await getCollection("projects"))
|
||||
.filter((project) => !project.data.draft)
|
||||
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
|
||||
---
|
||||
|
||||
<Layout title={PROJECTS.TITLE} description={PROJECTS.DESCRIPTION}>
|
||||
<Container>
|
||||
<aside data-pagefind-ignore>
|
||||
<div class="space-y-10">
|
||||
<div class="animate font-semibold text-black dark:text-white">
|
||||
Projects
|
||||
</div>
|
||||
<ul class="animate not-prose flex flex-col gap-4">
|
||||
{
|
||||
projects.map((project) => (
|
||||
<li>
|
||||
<ArrowCard entry={project} />
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
</Container>
|
||||
</Layout>
|
||||
27
src/pages/rss.xml.js
Normal file
27
src/pages/rss.xml.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import rss from "@astrojs/rss";
|
||||
import { SITE } from "@consts";
|
||||
import { getCollection } from "astro:content";
|
||||
|
||||
export async function GET(context) {
|
||||
const blog = (await getCollection("blog")).filter((post) => !post.data.draft);
|
||||
|
||||
const projects = (await getCollection("projects")).filter(
|
||||
(project) => !project.data.draft,
|
||||
);
|
||||
|
||||
const items = [...blog, ...projects].sort(
|
||||
(a, b) => new Date(b.data.date).valueOf() - new Date(a.data.date).valueOf(),
|
||||
);
|
||||
|
||||
return rss({
|
||||
title: SITE.TITLE,
|
||||
description: SITE.DESCRIPTION,
|
||||
site: context.site,
|
||||
items: items.map((item) => ({
|
||||
title: item.data.title,
|
||||
description: item.data.description,
|
||||
pubDate: item.data.date,
|
||||
link: `/${item.collection}/${item.id}/`,
|
||||
})),
|
||||
});
|
||||
}
|
||||
44
src/pages/tags/[...id].astro
Normal file
44
src/pages/tags/[...id].astro
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
import { getCollection } from "astro:content";
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
import Container from "@components/Container.astro";
|
||||
import ArrowCard from "@components/ArrowCard.astro";
|
||||
import BackToPrevious from "@components/BackToPrevious.astro";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection("blog", ({ data }) => !data.draft);
|
||||
|
||||
// Get unique tags
|
||||
const tags = [...new Set(posts.flatMap((post) => post.data.tags || []))];
|
||||
|
||||
// Create paths for each tag
|
||||
return tags.map((tag) => ({
|
||||
params: { id: tag },
|
||||
props: {
|
||||
posts: posts.filter((post) => post.data.tags?.includes(tag)),
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
const { id } = Astro.params;
|
||||
const { posts } = Astro.props;
|
||||
|
||||
// Sort posts by date, most recent first
|
||||
const sortedPosts = posts.sort(
|
||||
(a, b) => new Date(b.data.date).getTime() - new Date(a.data.date).getTime(),
|
||||
);
|
||||
---
|
||||
|
||||
<Layout title={`Tag: ${id}`} description={`Posts with the tag: ${id}`}>
|
||||
<Container>
|
||||
<div class="space-y-10" data-pagefind-ignore>
|
||||
<BackToPrevious href="/tags"> All tags </BackToPrevious>
|
||||
<h1 class="animate font-semibold text-black dark:text-white">
|
||||
Posts tagged with "{id}"
|
||||
</h1>
|
||||
<ul class="animate flex flex-col gap-4">
|
||||
{sortedPosts.map((post) => <ArrowCard entry={post} />)}
|
||||
</ul>
|
||||
</div>
|
||||
</Container>
|
||||
</Layout>
|
||||
31
src/pages/tags/index.astro
Normal file
31
src/pages/tags/index.astro
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
import { getCollection } from "astro:content";
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
import Container from "@components/Container.astro";
|
||||
|
||||
const posts = await getCollection("blog", ({ data }) => !data.draft);
|
||||
const tags = [...new Set(posts.flatMap((post) => post.data.tags || []))].sort();
|
||||
---
|
||||
|
||||
<Layout title="Tags" description="List of tags used.">
|
||||
<Container>
|
||||
<div class="space-y-10">
|
||||
<h1 class="animate font-semibold">All Tags</h1>
|
||||
<div class="animate flex flex-wrap gap-2">
|
||||
{
|
||||
tags.map((tag) => (
|
||||
<a
|
||||
href={`/tags/${tag}`}
|
||||
class="rounded-sm border border-black/15 px-2 py-1 text-sm transition-colors duration-300 ease-in-out hover:bg-black/5 hover:text-black focus-visible:bg-black/5 focus-visible:text-black dark:border-white/20 dark:hover:bg-white/5 dark:hover:text-white dark:focus-visible:bg-white/5 dark:focus-visible:text-white"
|
||||
>
|
||||
{tag}{" "}
|
||||
<span class="text-sm text-gray-600">
|
||||
({posts.filter((post) => post.data.tags?.includes(tag)).length})
|
||||
</span>
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</Layout>
|
||||
149
src/styles/global.css
Normal file
149
src/styles/global.css
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
@import 'tailwindcss';
|
||||
|
||||
@plugin '@tailwindcss/typography';
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme {
|
||||
--font-sans: Geist Sans, ui-sans-serif, system-ui, sans-serif,
|
||||
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
--font-mono: Geist Mono, ui-monospace, SFMono-Regular, Menlo, Monaco,
|
||||
Consolas, 'Liberation Mono', 'Courier New', monospace;
|
||||
}
|
||||
|
||||
/*
|
||||
The default border color has changed to `currentColor` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
html {
|
||||
overflow-y: auto;
|
||||
color-scheme: light;
|
||||
scroll-padding-top: 100px;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
@apply size-full;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply font-sans antialiased;
|
||||
@apply flex flex-col;
|
||||
@apply bg-neutral-100 dark:bg-neutral-900;
|
||||
@apply text-black/75 dark:text-white/75;
|
||||
}
|
||||
|
||||
header {
|
||||
@apply fixed left-0 right-0 top-0 z-50 py-6;
|
||||
@apply bg-neutral-100/75 dark:bg-neutral-900/75;
|
||||
@apply saturate-200 backdrop-blur-xs;
|
||||
}
|
||||
|
||||
main {
|
||||
@apply flex-1 py-32;
|
||||
}
|
||||
|
||||
footer {
|
||||
@apply py-6 text-sm;
|
||||
}
|
||||
|
||||
article {
|
||||
@apply prose prose-neutral max-w-full dark:prose-invert prose-img:mx-auto prose-img:my-auto;
|
||||
@apply prose-headings:font-semibold;
|
||||
@apply prose-headings:text-black dark:prose-headings:text-white;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
article a {
|
||||
@apply font-sans text-current underline underline-offset-[3px];
|
||||
@apply decoration-black/30 dark:decoration-white/30;
|
||||
@apply transition-colors duration-300 ease-in-out;
|
||||
}
|
||||
article a:hover {
|
||||
@apply text-black dark:text-white;
|
||||
@apply decoration-black/50 dark:decoration-white/50;
|
||||
}
|
||||
}
|
||||
|
||||
.animate {
|
||||
@apply -translate-y-3 opacity-0;
|
||||
@apply transition-all duration-300 ease-out;
|
||||
}
|
||||
|
||||
.animate.show {
|
||||
@apply translate-y-0 opacity-100;
|
||||
}
|
||||
|
||||
html #back-to-top {
|
||||
@apply pointer-events-none opacity-0;
|
||||
}
|
||||
|
||||
html.scrolled #back-to-top {
|
||||
@apply pointer-events-auto opacity-100;
|
||||
}
|
||||
|
||||
/* shiki config */
|
||||
pre {
|
||||
@apply border border-black/15 py-5 dark:border-white/20;
|
||||
}
|
||||
|
||||
:root {
|
||||
--astro-code-foreground: #09090b;
|
||||
--astro-code-background: #fafafa;
|
||||
--astro-code-token-comment: #a19595;
|
||||
--astro-code-token-keyword: #f47067;
|
||||
--astro-code-token-string: #00a99a;
|
||||
--astro-code-token-function: #429996;
|
||||
--astro-code-token-constant: #2b70c5;
|
||||
--astro-code-token-parameter: #4e8fdf;
|
||||
--astro-code-token-string-expression: #ae42a0;
|
||||
--astro-code-token-punctuation: #8996a3;
|
||||
--astro-code-token-link: #8d85ff;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--astro-code-foreground: #fafafa;
|
||||
--astro-code-background: #09090b;
|
||||
--astro-code-token-comment: #a19595;
|
||||
--astro-code-token-keyword: #f47067;
|
||||
--astro-code-token-string: #00a99a;
|
||||
--astro-code-token-function: #6eafad;
|
||||
--astro-code-token-constant: #b3cceb;
|
||||
--astro-code-token-parameter: #4e8fdf;
|
||||
--astro-code-token-string-expression: #bf7db6;
|
||||
--astro-code-token-punctuation: #8996a3;
|
||||
--astro-code-token-link: #8d85ff;
|
||||
}
|
||||
|
||||
/* copy code button on codeblocks */
|
||||
.copy-code {
|
||||
@apply absolute right-3 top-3 grid size-9 place-content-center rounded-sm border border-black/15 bg-neutral-100 text-center duration-300 ease-in-out dark:border-white/20 dark:bg-neutral-900;
|
||||
}
|
||||
|
||||
.copy-code:hover {
|
||||
@apply bg-[#E9E9E9] transition-colors dark:bg-[#232323];
|
||||
}
|
||||
|
||||
.copy-code:active {
|
||||
@apply scale-90 transition-transform;
|
||||
}
|
||||
17
src/types.ts
Normal file
17
src/types.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
export type Site = {
|
||||
TITLE: string;
|
||||
DESCRIPTION: string;
|
||||
EMAIL: string;
|
||||
NUM_POSTS_ON_HOMEPAGE: number;
|
||||
NUM_PROJECTS_ON_HOMEPAGE: number;
|
||||
};
|
||||
|
||||
export type Metadata = {
|
||||
TITLE: string;
|
||||
DESCRIPTION: string;
|
||||
};
|
||||
|
||||
export type Socials = {
|
||||
NAME: string;
|
||||
HREF: string;
|
||||
}[];
|
||||
Loading…
Add table
Add a link
Reference in a new issue