feat!: AstroPaper v6 (#631)

* feat: add initial configuration and social icons for AstroPaper theme

* feat: add theme styles and design tokens

* chore: configure Astro with sitemap, i18n, fonts, and tooling

- Set site URL, i18n (single locale, no prefix), sitemap integration
- Add Google Sans Code font via stable fonts API
- Configure Shiki dual-theme markdown (min-light / night-owl)
- Fix astro-paper.config.ts to use relative import (alias not
  available when Vite loads root-level config files)
- Add @astrojs/sitemap and @typescript-eslint/parser dependencies
- Wire @typescript-eslint/parser into ESLint so TypeScript syntax
  in Astro frontmatter (type Props, etc.) parses correctly

* feat: add base styles, dark variant, and typography

- global.css: dark variant via [data-theme], base layer for * / a /
  button selectors, scrollbar tokens, layout utilities (max-w-app,
  app-layout), active-nav as @utility for Tailwind variant support
- html and body classes moved directly onto elements in BaseLayout
  (Tailwind-native approach) so only truly global selectors remain here
- typography.css: prose overrides, Shiki light/dark code block
  switching, diff/highlight/word-highlight transformer styles

* feat: add i18n types, English strings, and useTranslations helper

Plain typed object approach — no third-party i18n library needed.
Paraglide.js was considered but requires SSR output (hard blocker
for a static Astro site).

- UIStrings interface with nav, post, pagination, and a11y groups
- en.ts with `satisfies UIStrings` for literal-type preservation
- useTranslations(locale) helper falls back to English automatically

Adding a new language is a one-file addition; full routing stays
as a registry add-on.

* feat: add client-side theme toggle script

- Reads window.__theme.value set by the inline FOUC-prevention script
  to avoid re-detecting the theme on first load
- Toggles data-theme on <html> and persists to localStorage
- Fills <meta name="theme-color"> with the computed background colour
  so Android's browser chrome matches the page
- Re-runs setup on astro:after-swap for View Transitions navigation
- Carries theme-color across astro:before-swap to prevent navbar
  flicker on Android during page transitions
- Syncs with OS prefers-color-scheme changes at runtime

* feat: add BaseLayout and PostLayout

BaseLayout (replaces scaffold Layout.astro):
- Font, canonical URL via Astro.site, RSS autodiscovery
- FOUC-prevention inline script sets data-theme before first paint
  and exposes window.__theme.value for theme.ts to reuse
- html/body classes on elements directly (Tailwind-native)
- <slot name="head"> for child layout injection
- ClientRouter for View Transitions (respects lightAndDarkMode flag)
- googleVerification rendered from config instead of .env — it is a
  public string, not a secret, and not environment-specific

PostLayout (new):
- Wraps BaseLayout; injects article-specific head content via slot
- Overrides og:type to "article"
- Adds article:published_time / article:modified_time conditionally
- JSON-LD BlogPosting with conditional datePublished/dateModified
  (fixes old bug where absent dates produced "undefined" string)
- Structured data only on actual post pages (not home/archives/tags)

SiteConfig: add optional googleVerification field

* chore: replace scaffold index page with BaseLayout placeholder

* feat: add Header component

* feat: add Footer and Socials component

* chore: install dayjs, slugify, zod, @astrojs/mdx and add MDX integration

* feat: add resolved config that applies defaults in one place

Defaults for posts, features, socials, and shareLinks are merged in
src/config.ts so all consuming code gets non-optional types with no
scattered ?? fallbacks.

* feat: add content schema, blog path, and post utilities

Renames collection blog→posts, moves content to src/content/posts/,
adds .mdx support, and adds a typed pages collection. Includes
getSortedPosts, postFilter, getPath, and slugify utilities.

* feat: add Datetime and Card components

* refactor: switch layouts and components to resolved config

Replaces @/astro-paper.config imports with @/config and removes
optional chaining on feature flags now that the resolved config
guarantees non-optional values.

* feat: add home and footer i18n strings

* feat: implement home page with hero, featured, and recent posts

Replaces the BaseLayout placeholder with the full index page:
hero section with RSS link and social links, featured posts section,
recent posts capped at posts.perIndex, and an All Posts link. All
section labels come from i18n.

* feat: add pagination component

* feat: add Breadcrumb component with locale support

* feat: add Main component

* refactor: update code formatting in Card.astro

* feat: add posts page

* fix: add missing @astrojs/mdx dependency

* refactor: rename meta to pages in i18n

* refactor: improve code formatting in Breadcrumb

* refactor: destructure props in Main component for clarity

* feat: add tag pages

* feat: add About page

* chore: update i18n configuration to use site language from config

* feat: add Archives page

* feat: integrate Pagefind search functionality

* feat: add dynamic Open Graph image generation for posts

* feat: add post detail page

* feat: add Shiki transformers

* chore: remove unused edit link text

* chore: update .gitignore to include generated Pagefind directory

* chore: update site URL in configuration to production link

* chore: replace favicon and remove unused SVG assets, add new Open Graph image

* feat: update site config with default values

* feat: add default OG image path resolution

* fix: ensure edit post feature checks for enabled status correctly

* feat: implement dynamic Open Graph image generation

* feat: add RSS feed generation functionality

* feat: add 404 Not Found page

* feat: add robots.txt generation for SEO optimization

* refactor: change getSortedPosts to a named export

* refactor: change postFilter to a named export

* refactor: rename BaseLayout to Layout

* refactor: standardize locale handling across components

* refactor: update getPostsByGroupCondition location

* chore: remove unused Welcome component

* refactor(i18n): update links using getRelativeLocaleUrl

* refactor(i18n): add dynamic locale loading

* fix: update language attribute to use current locale

* fix: correct wording for updatedAt in post translations

* fix: update layout titles to use correct translation keys for tags pages

* chore: use new colors for better DX and a11y

* feat: add underline icon for active icon nav

* feat: add toc for markdown contents

* refactor: remove unused feature flags in config

* refactor(i18n): enhance locale handling and path management

* fix: ts unused variable hint

* refactor: extract locale stripping logic into a util func

* refactor(i18n): improve locale handling in tag pages

* refactor: update post URL helper functions

* refactor(i18n): replace pathWithBase with getRelativeLocaleUrl for locale handling

* refactor: rename fileWithBase to getAssetPath

* refactor: use runtime agnostic APIs for file paths

* refactor: use transition directive for heading transition

* refactor: remove unused title element from tag page

* fix: decouple ClientRouter from lightAndDarkMode flag

* refactor: rename `postPaths.ts` to `getPostPaths.ts`

* refactor: remove unused `minutesRead` string from post translations

* refactor: simplify AdjacentPostNav component by passing prevPost and nextPost directly

* refactor: remove unused code declaration

* docs: add existing blog posts

* chore: update astro and @astrojs/check dependencies to latest versions

* build: use experimental svg optimizer

* chore(repo): restore project meta files and Docker configs

* refactor: update code formatting with Prettier

* docs: update README and docs

* docs: add JSDoc comments to utility functions

* docs: add JSDoc comments to resolved config file

* fix(og): avoid double slashes when appending index.png

* chore(ci): update Node.js version to 22.12.0 in CI workflow

* feat: improve og image generation with Astro's getFontFileURL API (#632)

* feat: improve og image generation with Astro's getFontFileURL API

- remove @resvg/resvg-js and use sharp to convert SVG to PNG
- update og image generation to use Astro's getFontFileURL API for better font handling

* docs: update README and dynamic OG images documentation

- remove outdated content and clarify dynamic OG image generation process in README.
- add new image example and update instructions for handling non-Latin characters in dynamic OG images.
- adjust copyright year in README to 2026.

* fix(header): adjust icon button alignment in mobile

* feat(docs): add ResponsiveTable to use it in MDX posts (#634)

* feat(docs): add ResponsiveTable to use it in MDX posts

Introduce ResponsiveTable with variant-based styling. Rename docs posts to
.mdx, wrap tables in the component, and remove redundant table `code` rules
from prose typography.

* chore(prettier): ignore .mdx files when checking format

* chore(ci): update Node.js version to 24 in CI workflow

* bump: upgrade Astro and dependencies

* fix: remove redundant backUrl script bug

* fix(header): adjust button and icon alignment

* fix(a11y): improve muted-foreground color contrast

* docs: update predefined color schemes

* chore: support site verification env as fallback for googleVerification

* docs: add astro paper v6 updates in docs

* docs: add AstroPaper-v6.png in astro-paper-6 blog post

* fix: update back-to-top button DOM placement (#641)

Closes #626, #131, #493
This commit is contained in:
Sat Naing
2026-05-17 16:17:58 +07:00
committed by GitHub
parent f3005328e5
commit f0b644de3a
122 changed files with 5387 additions and 4561 deletions
+59 -110
View File
@@ -1,176 +1,125 @@
---
import { Font } from "astro:assets";
import { ClientRouter } from "astro:transitions";
import { PUBLIC_GOOGLE_SITE_VERIFICATION } from "astro:env/client";
import { SITE } from "@/config";
import { getRelativeLocaleUrl } from "astro:i18n";
import { resolveDefaultOgImagePath } from "@/utils/resolveDefaultOgImagePath";
import { getAssetPath } from "@/utils/withBase";
import config from "@/config";
import "@/styles/global.css";
type Props = {
title?: string;
author?: string;
profile?: string;
description?: string;
ogImage?: string;
canonicalURL?: string;
pubDatetime?: Date;
modDatetime?: Date | null;
scrollSmooth?: boolean;
};
const { site } = config;
const {
title = SITE.title,
author = SITE.author,
profile = SITE.profile,
description = SITE.desc,
ogImage = SITE.ogImage ? `/${SITE.ogImage}` : "/og.png",
canonicalURL = new URL(Astro.url.pathname, Astro.url),
pubDatetime,
modDatetime,
scrollSmooth = false,
title = site.title,
description = site.description,
ogImage = resolveDefaultOgImagePath(config),
canonicalURL = new URL(Astro.url.pathname, Astro.site).href,
} = Astro.props;
const socialImageURL = new URL(ogImage, Astro.url);
const structuredData = {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: `${title}`,
image: `${socialImageURL}`,
datePublished: `${pubDatetime?.toISOString()}`,
...(modDatetime && { dateModified: modDatetime.toISOString() }),
author: [
{
"@type": "Person",
name: `${author}`,
...(profile && { url: profile }),
},
],
};
const socialImageURL = new URL(ogImage, Astro.site ?? Astro.url);
const rssHref = getRelativeLocaleUrl(
Astro.currentLocale ?? config.site.lang,
"rss.xml"
);
---
<!doctype html>
<html
dir={SITE.dir}
lang=`${SITE.lang ?? "en"}`
class={`${scrollSmooth && "scroll-smooth"}`}
dir={site.dir ?? "ltr"}
lang={Astro.currentLocale ?? site.lang}
class="overflow-y-scroll scroll-smooth"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/svg+xml" href={getAssetPath("favicon.svg")} />
<link rel="icon" href={getAssetPath("favicon.ico")} />
<link rel="canonical" href={canonicalURL} />
<meta name="generator" content={Astro.generator} />
<!-- Load Font -->
<!-- Font -->
<Font
cssVariable="--font-google-sans-code"
preload={[{ subset: "latin", weight: 400, style: "normal" }]}
/>
<!-- General Meta Tags -->
<!-- Primary meta -->
<title>{title}</title>
<meta name="title" content={title} />
<meta name="description" content={description} />
<meta name="author" content={author} />
<link rel="sitemap" href="/sitemap-index.xml" />
<meta name="author" content={site.author} />
<link rel="sitemap" href={getAssetPath("sitemap-index.xml")} />
<!-- Open Graph / Facebook -->
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:site_name" content={site.title} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonicalURL} />
<meta property="og:image" content={socialImageURL} />
<!-- Article Published/Modified time -->
{
pubDatetime && (
<meta
property="article:published_time"
content={pubDatetime.toISOString()}
/>
)
}
{
modDatetime && (
<meta
property="article:modified_time"
content={modDatetime.toISOString()}
/>
)
}
<!-- Twitter -->
<!-- Twitter / X -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={canonicalURL} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={socialImageURL} />
<!-- Google JSON-LD Structured data -->
<script
type="application/ld+json"
is:inline
set:html={JSON.stringify(structuredData)}
/>
<!-- Enable RSS feed auto-discovery -->
<!-- https://docs.astro.build/en/recipes/rss/#enabling-rss-feed-auto-discovery -->
<!-- RSS autodiscovery -->
<link
rel="alternate"
type="application/rss+xml"
title={SITE.title}
href={new URL("rss.xml", Astro.site)}
title={site.title}
href={new URL(rssHref, Astro.site)}
/>
<!-- Filled at runtime by theme.ts to match the current background colour -->
<meta name="theme-color" content="" />
<!-- Extra head content injected by child layouts (e.g. JSON-LD, article meta) -->
<slot name="head" />
{
// If PUBLIC_GOOGLE_SITE_VERIFICATION is set in the environment variable,
// include google-site-verification tag in the heading
// Learn more: https://support.google.com/webmasters/answer/9008080#meta_tag_verification&zippy=%2Chtml-tag
PUBLIC_GOOGLE_SITE_VERIFICATION && (
site.googleVerification && (
<meta
name="google-site-verification"
content={PUBLIC_GOOGLE_SITE_VERIFICATION}
content={site.googleVerification}
/>
)
}
<ClientRouter />
<!-- Minimal inline script to prevent FOUC - sets theme immediately -->
<!--
Inline FOUC-prevention script: sets data-theme on <html> before
the browser paints. Runs synchronously, no defer/async.
-->
<script is:inline>
(function () {
const initialColorScheme = ""; // "light" | "dark"
const currentTheme = localStorage.getItem("theme");
function getPreferTheme() {
if (currentTheme) return currentTheme;
if (initialColorScheme) return initialColorScheme;
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}
const themeValue = getPreferTheme();
// Set theme immediately to prevent flash
document.firstElementChild?.setAttribute("data-theme", themeValue);
// Export minimal API for external script
window.theme = {
themeValue: themeValue,
getTheme: () => window.theme.themeValue,
setTheme: val => {
window.theme.themeValue = val;
},
};
const stored = localStorage.getItem("theme");
const prefersDark = window.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
const theme = stored ?? (prefersDark ? "dark" : "light");
document.firstElementChild?.setAttribute("data-theme", theme);
// Expose value so theme.ts can skip re-detection.
window.__theme = { value: theme };
})();
</script>
</head>
<body>
<slot />
<!-- Load full theme logic -->
<script src="../scripts/theme.ts"></script>
<ClientRouter />
</head>
<body
class="bg-background font-app text-foreground selection:bg-accent/75 selection:text-accent-foreground flex min-h-svh flex-col"
>
<slot />
<script>
import "@/scripts/theme";
</script>
</body>
</html>