From 632543db4d1d22f2aa0cfec1604ce4267c9cd116 Mon Sep 17 00:00:00 2001 From: Neeldhara Misra Date: Thu, 25 Jun 2026 05:21:34 +0530 Subject: [PATCH] Fix local images and em dash rendering --- sites/art/astro.config.mjs | 2 + sites/art/src/components/BaseHead.astro | 5 +- .../art/src/components/sections/blog-post.tsx | 6 +- .../src/components/sections/blog-posts.tsx | 15 ++-- sites/art/src/lib/typography.ts | 5 ++ sites/art/src/pages/rss.xml.js | 5 +- sites/art/src/remark/typography.mjs | 22 ++++++ sites/poetry/astro.config.mjs | 13 +++- sites/poetry/src/components/BaseHead.astro | 5 +- .../src/components/sections/blog-post.tsx | 4 +- .../src/components/sections/blog-posts.tsx | 15 ++-- sites/poetry/src/lib/typography.ts | 5 ++ sites/poetry/src/pages/rss.xml.js | 5 +- sites/poetry/src/remark/typography.mjs | 22 ++++++ sites/puzzles/astro.config.mjs | 2 + sites/puzzles/src/components/BaseHead.astro | 5 +- .../src/components/sections/blog-post.tsx | 6 +- .../src/components/sections/blog-posts.tsx | 15 ++-- sites/puzzles/src/lib/typography.ts | 5 ++ sites/puzzles/src/pages/rss.xml.js | 5 +- sites/puzzles/src/remark/typography.mjs | 22 ++++++ sites/reflections/astro.config.mjs | 2 + .../reflections/src/components/BaseHead.astro | 5 +- .../src/components/sections/blog-post.tsx | 6 +- .../src/components/sections/blog-posts.tsx | 15 ++-- sites/reflections/src/lib/typography.ts | 5 ++ sites/reflections/src/pages/rss.xml.js | 5 +- sites/reflections/src/remark/typography.mjs | 22 ++++++ sites/research/astro.config.mjs | 2 + sites/research/src/components/BaseHead.astro | 5 +- .../src/components/sections/blog-post.tsx | 6 +- .../src/components/sections/blog-posts.tsx | 15 ++-- sites/research/src/lib/typography.ts | 5 ++ sites/research/src/pages/rss.xml.js | 5 +- sites/research/src/remark/typography.mjs | 22 ++++++ sites/reviews/astro.config.mjs | 2 + sites/reviews/src/components/BaseHead.astro | 5 +- .../src/components/sections/blog-post.tsx | 6 +- .../src/components/sections/blog-posts.tsx | 15 ++-- sites/reviews/src/lib/typography.ts | 5 ++ sites/reviews/src/pages/rss.xml.js | 5 +- sites/reviews/src/remark/typography.mjs | 22 ++++++ sites/vibes/astro.config.mjs | 2 + sites/vibes/src/components/BaseHead.astro | 5 +- .../src/components/sections/blog-post.tsx | 6 +- .../src/components/sections/blog-posts.tsx | 15 ++-- .../vibes/building-interactives/index.md | 27 +++----- sites/vibes/src/lib/typography.ts | 5 ++ sites/vibes/src/pages/rss.xml.js | 5 +- sites/vibes/src/remark/typography.mjs | 22 ++++++ src/components/BaseHead.astro | 7 +- src/components/sections/blog-post.tsx | 6 +- src/components/sections/blog-posts.tsx | 68 ++++++++++++++----- src/lib/typography.ts | 5 ++ src/pages/rss.xml.js | 7 +- 55 files changed, 432 insertions(+), 117 deletions(-) create mode 100644 sites/art/src/lib/typography.ts create mode 100644 sites/art/src/remark/typography.mjs create mode 100644 sites/poetry/src/lib/typography.ts create mode 100644 sites/poetry/src/remark/typography.mjs create mode 100644 sites/puzzles/src/lib/typography.ts create mode 100644 sites/puzzles/src/remark/typography.mjs create mode 100644 sites/reflections/src/lib/typography.ts create mode 100644 sites/reflections/src/remark/typography.mjs create mode 100644 sites/research/src/lib/typography.ts create mode 100644 sites/research/src/remark/typography.mjs create mode 100644 sites/reviews/src/lib/typography.ts create mode 100644 sites/reviews/src/remark/typography.mjs create mode 100644 sites/vibes/src/lib/typography.ts create mode 100644 sites/vibes/src/remark/typography.mjs create mode 100644 src/lib/typography.ts diff --git a/sites/art/astro.config.mjs b/sites/art/astro.config.mjs index 5efa078..17d8510 100644 --- a/sites/art/astro.config.mjs +++ b/sites/art/astro.config.mjs @@ -12,6 +12,7 @@ import tailwindcss from "@tailwindcss/vite"; import remarkCallouts from "./src/remark/callouts.mjs"; import remarkCodeFenceLanguages from "./src/remark/code-fence-languages.mjs"; import remarkInlineFootnotes from "./src/remark/inline-footnotes.mjs"; +import remarkTypography from "./src/remark/typography.mjs"; import { ACTIVE_BLOG_SITE } from "./src/blog-sites.js"; const remarkPlugins = [ @@ -19,6 +20,7 @@ const remarkPlugins = [ remarkInlineFootnotes, remarkCodeFenceLanguages, remarkMath, + remarkTypography, remarkCallouts, ]; const rehypePlugins = [rehypeKatex]; diff --git a/sites/art/src/components/BaseHead.astro b/sites/art/src/components/BaseHead.astro index e519a3a..a0639ec 100644 --- a/sites/art/src/components/BaseHead.astro +++ b/sites/art/src/components/BaseHead.astro @@ -3,6 +3,7 @@ // all pages through the use of the component. import '../styles/global.css'; import { SITE_TITLE, SITE_METADATA } from '../consts'; +import { typographicText } from '@/lib/typography'; interface Props { title: string; @@ -13,8 +14,8 @@ interface Props { const canonicalURL = new URL(Astro.url.pathname, Astro.site); const { title, description, image } = Astro.props; -const finalTitle = title || SITE_METADATA.title.default; -const finalDescription = description || SITE_METADATA.description; +const finalTitle = typographicText(title || SITE_METADATA.title.default); +const finalDescription = typographicText(description || SITE_METADATA.description); const finalImage = image || SITE_METADATA.openGraph.images[0].url; const imageURL = new URL(finalImage, Astro.url); --- diff --git a/sites/art/src/components/sections/blog-post.tsx b/sites/art/src/components/sections/blog-post.tsx index 21082ce..3aa1a13 100644 --- a/sites/art/src/components/sections/blog-post.tsx +++ b/sites/art/src/components/sections/blog-post.tsx @@ -1,5 +1,6 @@ import type { ReactNode } from "react"; import { format } from "date-fns"; +import { typographicText } from "@/lib/typography"; const getReadingTime = (body: string | undefined) => { const words = (body ?? "") @@ -18,6 +19,7 @@ const BlogPost = ({ children: ReactNode; }) => { const { title, image, pubDate } = post.data; + const displayTitle = typographicText(title); const readingTime = getReadingTime(post.body); return ( @@ -30,12 +32,12 @@ const BlogPost = ({ {readingTime} min read -

{title}

+

{displayTitle}

{image ? (
- {title} + {displayTitle}
) : null} diff --git a/sites/art/src/components/sections/blog-posts.tsx b/sites/art/src/components/sections/blog-posts.tsx index c77c76a..3b7ecf6 100644 --- a/sites/art/src/components/sections/blog-posts.tsx +++ b/sites/art/src/components/sections/blog-posts.tsx @@ -4,6 +4,7 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { Calendar, Clock, User } from "lucide-react"; +import { typographicText } from "@/lib/typography"; import { PlusSigns } from "../icons/plus-signs"; const BlogPosts = ({ @@ -27,11 +28,13 @@ const BlogPosts = ({ post: any; featured?: boolean; }) => { + const displayTitle = typographicText(post.data.title); + if (post.data.image) { return ( {post.data.title} ); @@ -44,7 +47,7 @@ const BlogPosts = ({ featured ? "font-fraunces text-2xl" : "font-fraunces text-lg" } > - {post.data.title} + {displayTitle} ); @@ -82,10 +85,10 @@ const BlogPosts = ({ Featured Post

- {featuredPost.data.title} + {typographicText(featuredPost.data.title)}

- {featuredPost.data.description} + {typographicText(featuredPost.data.description)}

@@ -128,10 +131,10 @@ const BlogPosts = ({

- {post.data.title} + {typographicText(post.data.title)}

- {post.data.description} + {typographicText(post.data.description)}

diff --git a/sites/art/src/lib/typography.ts b/sites/art/src/lib/typography.ts new file mode 100644 index 0000000..2aabeb8 --- /dev/null +++ b/sites/art/src/lib/typography.ts @@ -0,0 +1,5 @@ +const EM_DASH_PATTERN = /---/g; + +export function typographicText(value: string | null | undefined) { + return (value ?? "").replace(EM_DASH_PATTERN, "—"); +} diff --git a/sites/art/src/pages/rss.xml.js b/sites/art/src/pages/rss.xml.js index 918375e..0a688e3 100644 --- a/sites/art/src/pages/rss.xml.js +++ b/sites/art/src/pages/rss.xml.js @@ -2,6 +2,7 @@ import rss from "@astrojs/rss"; import { getCollection } from "astro:content"; import { SITE_TITLE, SITE_DESCRIPTION } from "../consts"; import { ACTIVE_BLOG_SITE } from "../blog-sites.js"; +import { typographicText } from "@/lib/typography"; export async function GET(context) { const posts = (await getCollection(ACTIVE_BLOG_SITE.key)).sort( @@ -10,10 +11,12 @@ export async function GET(context) { return rss({ title: SITE_TITLE, - description: SITE_DESCRIPTION, + description: typographicText(SITE_DESCRIPTION), site: context.site, items: posts.map((post) => ({ ...post.data, + title: typographicText(post.data.title), + description: typographicText(post.data.description), link: `/${post.id.replace(/\/index$/, "")}/`, })), }); diff --git a/sites/art/src/remark/typography.mjs b/sites/art/src/remark/typography.mjs new file mode 100644 index 0000000..93f846a --- /dev/null +++ b/sites/art/src/remark/typography.mjs @@ -0,0 +1,22 @@ +const EM_DASH_PATTERN = /---/g; + +function visitTextNodes(node) { + if (!node || typeof node !== "object") return; + + if (node.type === "text" && typeof node.value === "string") { + node.value = node.value.replace(EM_DASH_PATTERN, "—"); + return; + } + + if (!Array.isArray(node.children)) return; + + for (const child of node.children) { + visitTextNodes(child); + } +} + +export default function remarkTypography() { + return (tree) => { + visitTextNodes(tree); + }; +} diff --git a/sites/poetry/astro.config.mjs b/sites/poetry/astro.config.mjs index a38215d..17d8510 100644 --- a/sites/poetry/astro.config.mjs +++ b/sites/poetry/astro.config.mjs @@ -2,6 +2,7 @@ import { defineConfig } from "astro/config"; import mdx from "@astrojs/mdx"; import rehypeKatex from "rehype-katex"; +import remarkGfm from "remark-gfm"; import remarkMath from "remark-math"; import sitemap from "@astrojs/sitemap"; @@ -9,9 +10,19 @@ import react from "@astrojs/react"; // @ts-ignore - TailwindCSS v4 Vite plugin has type compatibility issues with Astro import tailwindcss from "@tailwindcss/vite"; import remarkCallouts from "./src/remark/callouts.mjs"; +import remarkCodeFenceLanguages from "./src/remark/code-fence-languages.mjs"; +import remarkInlineFootnotes from "./src/remark/inline-footnotes.mjs"; +import remarkTypography from "./src/remark/typography.mjs"; import { ACTIVE_BLOG_SITE } from "./src/blog-sites.js"; -const remarkPlugins = [remarkMath, remarkCallouts]; +const remarkPlugins = [ + remarkGfm, + remarkInlineFootnotes, + remarkCodeFenceLanguages, + remarkMath, + remarkTypography, + remarkCallouts, +]; const rehypePlugins = [rehypeKatex]; // https://astro.build/config diff --git a/sites/poetry/src/components/BaseHead.astro b/sites/poetry/src/components/BaseHead.astro index e519a3a..a0639ec 100644 --- a/sites/poetry/src/components/BaseHead.astro +++ b/sites/poetry/src/components/BaseHead.astro @@ -3,6 +3,7 @@ // all pages through the use of the component. import '../styles/global.css'; import { SITE_TITLE, SITE_METADATA } from '../consts'; +import { typographicText } from '@/lib/typography'; interface Props { title: string; @@ -13,8 +14,8 @@ interface Props { const canonicalURL = new URL(Astro.url.pathname, Astro.site); const { title, description, image } = Astro.props; -const finalTitle = title || SITE_METADATA.title.default; -const finalDescription = description || SITE_METADATA.description; +const finalTitle = typographicText(title || SITE_METADATA.title.default); +const finalDescription = typographicText(description || SITE_METADATA.description); const finalImage = image || SITE_METADATA.openGraph.images[0].url; const imageURL = new URL(finalImage, Astro.url); --- diff --git a/sites/poetry/src/components/sections/blog-post.tsx b/sites/poetry/src/components/sections/blog-post.tsx index 560e9f0..2b07555 100644 --- a/sites/poetry/src/components/sections/blog-post.tsx +++ b/sites/poetry/src/components/sections/blog-post.tsx @@ -1,4 +1,5 @@ import { format } from "date-fns"; +import { typographicText } from "@/lib/typography"; const getReadingTime = (body: string | undefined) => { const words = (body ?? "") @@ -17,12 +18,13 @@ const BlogPost = ({ children: React.ReactNode; }) => { const { title, pubDate } = post.data; + const displayTitle = typographicText(title); const readingTime = getReadingTime(post.body); return (
-

{title}

+

{displayTitle}

); @@ -82,10 +85,10 @@ const BlogPosts = ({ Featured Post

- {featuredPost.data.title} + {typographicText(featuredPost.data.title)}

- {featuredPost.data.description} + {typographicText(featuredPost.data.description)}

@@ -128,10 +131,10 @@ const BlogPosts = ({

- {post.data.title} + {typographicText(post.data.title)}

- {post.data.description} + {typographicText(post.data.description)}

diff --git a/sites/poetry/src/lib/typography.ts b/sites/poetry/src/lib/typography.ts new file mode 100644 index 0000000..2aabeb8 --- /dev/null +++ b/sites/poetry/src/lib/typography.ts @@ -0,0 +1,5 @@ +const EM_DASH_PATTERN = /---/g; + +export function typographicText(value: string | null | undefined) { + return (value ?? "").replace(EM_DASH_PATTERN, "—"); +} diff --git a/sites/poetry/src/pages/rss.xml.js b/sites/poetry/src/pages/rss.xml.js index 918375e..0a688e3 100644 --- a/sites/poetry/src/pages/rss.xml.js +++ b/sites/poetry/src/pages/rss.xml.js @@ -2,6 +2,7 @@ import rss from "@astrojs/rss"; import { getCollection } from "astro:content"; import { SITE_TITLE, SITE_DESCRIPTION } from "../consts"; import { ACTIVE_BLOG_SITE } from "../blog-sites.js"; +import { typographicText } from "@/lib/typography"; export async function GET(context) { const posts = (await getCollection(ACTIVE_BLOG_SITE.key)).sort( @@ -10,10 +11,12 @@ export async function GET(context) { return rss({ title: SITE_TITLE, - description: SITE_DESCRIPTION, + description: typographicText(SITE_DESCRIPTION), site: context.site, items: posts.map((post) => ({ ...post.data, + title: typographicText(post.data.title), + description: typographicText(post.data.description), link: `/${post.id.replace(/\/index$/, "")}/`, })), }); diff --git a/sites/poetry/src/remark/typography.mjs b/sites/poetry/src/remark/typography.mjs new file mode 100644 index 0000000..93f846a --- /dev/null +++ b/sites/poetry/src/remark/typography.mjs @@ -0,0 +1,22 @@ +const EM_DASH_PATTERN = /---/g; + +function visitTextNodes(node) { + if (!node || typeof node !== "object") return; + + if (node.type === "text" && typeof node.value === "string") { + node.value = node.value.replace(EM_DASH_PATTERN, "—"); + return; + } + + if (!Array.isArray(node.children)) return; + + for (const child of node.children) { + visitTextNodes(child); + } +} + +export default function remarkTypography() { + return (tree) => { + visitTextNodes(tree); + }; +} diff --git a/sites/puzzles/astro.config.mjs b/sites/puzzles/astro.config.mjs index 5efa078..17d8510 100644 --- a/sites/puzzles/astro.config.mjs +++ b/sites/puzzles/astro.config.mjs @@ -12,6 +12,7 @@ import tailwindcss from "@tailwindcss/vite"; import remarkCallouts from "./src/remark/callouts.mjs"; import remarkCodeFenceLanguages from "./src/remark/code-fence-languages.mjs"; import remarkInlineFootnotes from "./src/remark/inline-footnotes.mjs"; +import remarkTypography from "./src/remark/typography.mjs"; import { ACTIVE_BLOG_SITE } from "./src/blog-sites.js"; const remarkPlugins = [ @@ -19,6 +20,7 @@ const remarkPlugins = [ remarkInlineFootnotes, remarkCodeFenceLanguages, remarkMath, + remarkTypography, remarkCallouts, ]; const rehypePlugins = [rehypeKatex]; diff --git a/sites/puzzles/src/components/BaseHead.astro b/sites/puzzles/src/components/BaseHead.astro index e519a3a..a0639ec 100644 --- a/sites/puzzles/src/components/BaseHead.astro +++ b/sites/puzzles/src/components/BaseHead.astro @@ -3,6 +3,7 @@ // all pages through the use of the component. import '../styles/global.css'; import { SITE_TITLE, SITE_METADATA } from '../consts'; +import { typographicText } from '@/lib/typography'; interface Props { title: string; @@ -13,8 +14,8 @@ interface Props { const canonicalURL = new URL(Astro.url.pathname, Astro.site); const { title, description, image } = Astro.props; -const finalTitle = title || SITE_METADATA.title.default; -const finalDescription = description || SITE_METADATA.description; +const finalTitle = typographicText(title || SITE_METADATA.title.default); +const finalDescription = typographicText(description || SITE_METADATA.description); const finalImage = image || SITE_METADATA.openGraph.images[0].url; const imageURL = new URL(finalImage, Astro.url); --- diff --git a/sites/puzzles/src/components/sections/blog-post.tsx b/sites/puzzles/src/components/sections/blog-post.tsx index 21082ce..3aa1a13 100644 --- a/sites/puzzles/src/components/sections/blog-post.tsx +++ b/sites/puzzles/src/components/sections/blog-post.tsx @@ -1,5 +1,6 @@ import type { ReactNode } from "react"; import { format } from "date-fns"; +import { typographicText } from "@/lib/typography"; const getReadingTime = (body: string | undefined) => { const words = (body ?? "") @@ -18,6 +19,7 @@ const BlogPost = ({ children: ReactNode; }) => { const { title, image, pubDate } = post.data; + const displayTitle = typographicText(title); const readingTime = getReadingTime(post.body); return ( @@ -30,12 +32,12 @@ const BlogPost = ({ {readingTime} min read
-

{title}

+

{displayTitle}

{image ? (
- {title} + {displayTitle}
) : null} diff --git a/sites/puzzles/src/components/sections/blog-posts.tsx b/sites/puzzles/src/components/sections/blog-posts.tsx index c77c76a..3b7ecf6 100644 --- a/sites/puzzles/src/components/sections/blog-posts.tsx +++ b/sites/puzzles/src/components/sections/blog-posts.tsx @@ -4,6 +4,7 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { Calendar, Clock, User } from "lucide-react"; +import { typographicText } from "@/lib/typography"; import { PlusSigns } from "../icons/plus-signs"; const BlogPosts = ({ @@ -27,11 +28,13 @@ const BlogPosts = ({ post: any; featured?: boolean; }) => { + const displayTitle = typographicText(post.data.title); + if (post.data.image) { return ( {post.data.title} ); @@ -44,7 +47,7 @@ const BlogPosts = ({ featured ? "font-fraunces text-2xl" : "font-fraunces text-lg" } > - {post.data.title} + {displayTitle}
); @@ -82,10 +85,10 @@ const BlogPosts = ({ Featured Post

- {featuredPost.data.title} + {typographicText(featuredPost.data.title)}

- {featuredPost.data.description} + {typographicText(featuredPost.data.description)}

@@ -128,10 +131,10 @@ const BlogPosts = ({

- {post.data.title} + {typographicText(post.data.title)}

- {post.data.description} + {typographicText(post.data.description)}

diff --git a/sites/puzzles/src/lib/typography.ts b/sites/puzzles/src/lib/typography.ts new file mode 100644 index 0000000..2aabeb8 --- /dev/null +++ b/sites/puzzles/src/lib/typography.ts @@ -0,0 +1,5 @@ +const EM_DASH_PATTERN = /---/g; + +export function typographicText(value: string | null | undefined) { + return (value ?? "").replace(EM_DASH_PATTERN, "—"); +} diff --git a/sites/puzzles/src/pages/rss.xml.js b/sites/puzzles/src/pages/rss.xml.js index 918375e..0a688e3 100644 --- a/sites/puzzles/src/pages/rss.xml.js +++ b/sites/puzzles/src/pages/rss.xml.js @@ -2,6 +2,7 @@ import rss from "@astrojs/rss"; import { getCollection } from "astro:content"; import { SITE_TITLE, SITE_DESCRIPTION } from "../consts"; import { ACTIVE_BLOG_SITE } from "../blog-sites.js"; +import { typographicText } from "@/lib/typography"; export async function GET(context) { const posts = (await getCollection(ACTIVE_BLOG_SITE.key)).sort( @@ -10,10 +11,12 @@ export async function GET(context) { return rss({ title: SITE_TITLE, - description: SITE_DESCRIPTION, + description: typographicText(SITE_DESCRIPTION), site: context.site, items: posts.map((post) => ({ ...post.data, + title: typographicText(post.data.title), + description: typographicText(post.data.description), link: `/${post.id.replace(/\/index$/, "")}/`, })), }); diff --git a/sites/puzzles/src/remark/typography.mjs b/sites/puzzles/src/remark/typography.mjs new file mode 100644 index 0000000..93f846a --- /dev/null +++ b/sites/puzzles/src/remark/typography.mjs @@ -0,0 +1,22 @@ +const EM_DASH_PATTERN = /---/g; + +function visitTextNodes(node) { + if (!node || typeof node !== "object") return; + + if (node.type === "text" && typeof node.value === "string") { + node.value = node.value.replace(EM_DASH_PATTERN, "—"); + return; + } + + if (!Array.isArray(node.children)) return; + + for (const child of node.children) { + visitTextNodes(child); + } +} + +export default function remarkTypography() { + return (tree) => { + visitTextNodes(tree); + }; +} diff --git a/sites/reflections/astro.config.mjs b/sites/reflections/astro.config.mjs index 5efa078..17d8510 100644 --- a/sites/reflections/astro.config.mjs +++ b/sites/reflections/astro.config.mjs @@ -12,6 +12,7 @@ import tailwindcss from "@tailwindcss/vite"; import remarkCallouts from "./src/remark/callouts.mjs"; import remarkCodeFenceLanguages from "./src/remark/code-fence-languages.mjs"; import remarkInlineFootnotes from "./src/remark/inline-footnotes.mjs"; +import remarkTypography from "./src/remark/typography.mjs"; import { ACTIVE_BLOG_SITE } from "./src/blog-sites.js"; const remarkPlugins = [ @@ -19,6 +20,7 @@ const remarkPlugins = [ remarkInlineFootnotes, remarkCodeFenceLanguages, remarkMath, + remarkTypography, remarkCallouts, ]; const rehypePlugins = [rehypeKatex]; diff --git a/sites/reflections/src/components/BaseHead.astro b/sites/reflections/src/components/BaseHead.astro index e519a3a..a0639ec 100644 --- a/sites/reflections/src/components/BaseHead.astro +++ b/sites/reflections/src/components/BaseHead.astro @@ -3,6 +3,7 @@ // all pages through the use of the component. import '../styles/global.css'; import { SITE_TITLE, SITE_METADATA } from '../consts'; +import { typographicText } from '@/lib/typography'; interface Props { title: string; @@ -13,8 +14,8 @@ interface Props { const canonicalURL = new URL(Astro.url.pathname, Astro.site); const { title, description, image } = Astro.props; -const finalTitle = title || SITE_METADATA.title.default; -const finalDescription = description || SITE_METADATA.description; +const finalTitle = typographicText(title || SITE_METADATA.title.default); +const finalDescription = typographicText(description || SITE_METADATA.description); const finalImage = image || SITE_METADATA.openGraph.images[0].url; const imageURL = new URL(finalImage, Astro.url); --- diff --git a/sites/reflections/src/components/sections/blog-post.tsx b/sites/reflections/src/components/sections/blog-post.tsx index 21082ce..3aa1a13 100644 --- a/sites/reflections/src/components/sections/blog-post.tsx +++ b/sites/reflections/src/components/sections/blog-post.tsx @@ -1,5 +1,6 @@ import type { ReactNode } from "react"; import { format } from "date-fns"; +import { typographicText } from "@/lib/typography"; const getReadingTime = (body: string | undefined) => { const words = (body ?? "") @@ -18,6 +19,7 @@ const BlogPost = ({ children: ReactNode; }) => { const { title, image, pubDate } = post.data; + const displayTitle = typographicText(title); const readingTime = getReadingTime(post.body); return ( @@ -30,12 +32,12 @@ const BlogPost = ({ {readingTime} min read
-

{title}

+

{displayTitle}

{image ? (
- {title} + {displayTitle}
) : null} diff --git a/sites/reflections/src/components/sections/blog-posts.tsx b/sites/reflections/src/components/sections/blog-posts.tsx index c77c76a..3b7ecf6 100644 --- a/sites/reflections/src/components/sections/blog-posts.tsx +++ b/sites/reflections/src/components/sections/blog-posts.tsx @@ -4,6 +4,7 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { Calendar, Clock, User } from "lucide-react"; +import { typographicText } from "@/lib/typography"; import { PlusSigns } from "../icons/plus-signs"; const BlogPosts = ({ @@ -27,11 +28,13 @@ const BlogPosts = ({ post: any; featured?: boolean; }) => { + const displayTitle = typographicText(post.data.title); + if (post.data.image) { return ( {post.data.title} ); @@ -44,7 +47,7 @@ const BlogPosts = ({ featured ? "font-fraunces text-2xl" : "font-fraunces text-lg" } > - {post.data.title} + {displayTitle}
); @@ -82,10 +85,10 @@ const BlogPosts = ({ Featured Post

- {featuredPost.data.title} + {typographicText(featuredPost.data.title)}

- {featuredPost.data.description} + {typographicText(featuredPost.data.description)}

@@ -128,10 +131,10 @@ const BlogPosts = ({

- {post.data.title} + {typographicText(post.data.title)}

- {post.data.description} + {typographicText(post.data.description)}

diff --git a/sites/reflections/src/lib/typography.ts b/sites/reflections/src/lib/typography.ts new file mode 100644 index 0000000..2aabeb8 --- /dev/null +++ b/sites/reflections/src/lib/typography.ts @@ -0,0 +1,5 @@ +const EM_DASH_PATTERN = /---/g; + +export function typographicText(value: string | null | undefined) { + return (value ?? "").replace(EM_DASH_PATTERN, "—"); +} diff --git a/sites/reflections/src/pages/rss.xml.js b/sites/reflections/src/pages/rss.xml.js index 918375e..0a688e3 100644 --- a/sites/reflections/src/pages/rss.xml.js +++ b/sites/reflections/src/pages/rss.xml.js @@ -2,6 +2,7 @@ import rss from "@astrojs/rss"; import { getCollection } from "astro:content"; import { SITE_TITLE, SITE_DESCRIPTION } from "../consts"; import { ACTIVE_BLOG_SITE } from "../blog-sites.js"; +import { typographicText } from "@/lib/typography"; export async function GET(context) { const posts = (await getCollection(ACTIVE_BLOG_SITE.key)).sort( @@ -10,10 +11,12 @@ export async function GET(context) { return rss({ title: SITE_TITLE, - description: SITE_DESCRIPTION, + description: typographicText(SITE_DESCRIPTION), site: context.site, items: posts.map((post) => ({ ...post.data, + title: typographicText(post.data.title), + description: typographicText(post.data.description), link: `/${post.id.replace(/\/index$/, "")}/`, })), }); diff --git a/sites/reflections/src/remark/typography.mjs b/sites/reflections/src/remark/typography.mjs new file mode 100644 index 0000000..93f846a --- /dev/null +++ b/sites/reflections/src/remark/typography.mjs @@ -0,0 +1,22 @@ +const EM_DASH_PATTERN = /---/g; + +function visitTextNodes(node) { + if (!node || typeof node !== "object") return; + + if (node.type === "text" && typeof node.value === "string") { + node.value = node.value.replace(EM_DASH_PATTERN, "—"); + return; + } + + if (!Array.isArray(node.children)) return; + + for (const child of node.children) { + visitTextNodes(child); + } +} + +export default function remarkTypography() { + return (tree) => { + visitTextNodes(tree); + }; +} diff --git a/sites/research/astro.config.mjs b/sites/research/astro.config.mjs index 5efa078..17d8510 100644 --- a/sites/research/astro.config.mjs +++ b/sites/research/astro.config.mjs @@ -12,6 +12,7 @@ import tailwindcss from "@tailwindcss/vite"; import remarkCallouts from "./src/remark/callouts.mjs"; import remarkCodeFenceLanguages from "./src/remark/code-fence-languages.mjs"; import remarkInlineFootnotes from "./src/remark/inline-footnotes.mjs"; +import remarkTypography from "./src/remark/typography.mjs"; import { ACTIVE_BLOG_SITE } from "./src/blog-sites.js"; const remarkPlugins = [ @@ -19,6 +20,7 @@ const remarkPlugins = [ remarkInlineFootnotes, remarkCodeFenceLanguages, remarkMath, + remarkTypography, remarkCallouts, ]; const rehypePlugins = [rehypeKatex]; diff --git a/sites/research/src/components/BaseHead.astro b/sites/research/src/components/BaseHead.astro index e519a3a..a0639ec 100644 --- a/sites/research/src/components/BaseHead.astro +++ b/sites/research/src/components/BaseHead.astro @@ -3,6 +3,7 @@ // all pages through the use of the component. import '../styles/global.css'; import { SITE_TITLE, SITE_METADATA } from '../consts'; +import { typographicText } from '@/lib/typography'; interface Props { title: string; @@ -13,8 +14,8 @@ interface Props { const canonicalURL = new URL(Astro.url.pathname, Astro.site); const { title, description, image } = Astro.props; -const finalTitle = title || SITE_METADATA.title.default; -const finalDescription = description || SITE_METADATA.description; +const finalTitle = typographicText(title || SITE_METADATA.title.default); +const finalDescription = typographicText(description || SITE_METADATA.description); const finalImage = image || SITE_METADATA.openGraph.images[0].url; const imageURL = new URL(finalImage, Astro.url); --- diff --git a/sites/research/src/components/sections/blog-post.tsx b/sites/research/src/components/sections/blog-post.tsx index 21082ce..3aa1a13 100644 --- a/sites/research/src/components/sections/blog-post.tsx +++ b/sites/research/src/components/sections/blog-post.tsx @@ -1,5 +1,6 @@ import type { ReactNode } from "react"; import { format } from "date-fns"; +import { typographicText } from "@/lib/typography"; const getReadingTime = (body: string | undefined) => { const words = (body ?? "") @@ -18,6 +19,7 @@ const BlogPost = ({ children: ReactNode; }) => { const { title, image, pubDate } = post.data; + const displayTitle = typographicText(title); const readingTime = getReadingTime(post.body); return ( @@ -30,12 +32,12 @@ const BlogPost = ({ {readingTime} min read
-

{title}

+

{displayTitle}

{image ? (
- {title} + {displayTitle}
) : null} diff --git a/sites/research/src/components/sections/blog-posts.tsx b/sites/research/src/components/sections/blog-posts.tsx index c77c76a..3b7ecf6 100644 --- a/sites/research/src/components/sections/blog-posts.tsx +++ b/sites/research/src/components/sections/blog-posts.tsx @@ -4,6 +4,7 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { Calendar, Clock, User } from "lucide-react"; +import { typographicText } from "@/lib/typography"; import { PlusSigns } from "../icons/plus-signs"; const BlogPosts = ({ @@ -27,11 +28,13 @@ const BlogPosts = ({ post: any; featured?: boolean; }) => { + const displayTitle = typographicText(post.data.title); + if (post.data.image) { return ( {post.data.title} ); @@ -44,7 +47,7 @@ const BlogPosts = ({ featured ? "font-fraunces text-2xl" : "font-fraunces text-lg" } > - {post.data.title} + {displayTitle}
); @@ -82,10 +85,10 @@ const BlogPosts = ({ Featured Post

- {featuredPost.data.title} + {typographicText(featuredPost.data.title)}

- {featuredPost.data.description} + {typographicText(featuredPost.data.description)}

@@ -128,10 +131,10 @@ const BlogPosts = ({

- {post.data.title} + {typographicText(post.data.title)}

- {post.data.description} + {typographicText(post.data.description)}

diff --git a/sites/research/src/lib/typography.ts b/sites/research/src/lib/typography.ts new file mode 100644 index 0000000..2aabeb8 --- /dev/null +++ b/sites/research/src/lib/typography.ts @@ -0,0 +1,5 @@ +const EM_DASH_PATTERN = /---/g; + +export function typographicText(value: string | null | undefined) { + return (value ?? "").replace(EM_DASH_PATTERN, "—"); +} diff --git a/sites/research/src/pages/rss.xml.js b/sites/research/src/pages/rss.xml.js index 918375e..0a688e3 100644 --- a/sites/research/src/pages/rss.xml.js +++ b/sites/research/src/pages/rss.xml.js @@ -2,6 +2,7 @@ import rss from "@astrojs/rss"; import { getCollection } from "astro:content"; import { SITE_TITLE, SITE_DESCRIPTION } from "../consts"; import { ACTIVE_BLOG_SITE } from "../blog-sites.js"; +import { typographicText } from "@/lib/typography"; export async function GET(context) { const posts = (await getCollection(ACTIVE_BLOG_SITE.key)).sort( @@ -10,10 +11,12 @@ export async function GET(context) { return rss({ title: SITE_TITLE, - description: SITE_DESCRIPTION, + description: typographicText(SITE_DESCRIPTION), site: context.site, items: posts.map((post) => ({ ...post.data, + title: typographicText(post.data.title), + description: typographicText(post.data.description), link: `/${post.id.replace(/\/index$/, "")}/`, })), }); diff --git a/sites/research/src/remark/typography.mjs b/sites/research/src/remark/typography.mjs new file mode 100644 index 0000000..93f846a --- /dev/null +++ b/sites/research/src/remark/typography.mjs @@ -0,0 +1,22 @@ +const EM_DASH_PATTERN = /---/g; + +function visitTextNodes(node) { + if (!node || typeof node !== "object") return; + + if (node.type === "text" && typeof node.value === "string") { + node.value = node.value.replace(EM_DASH_PATTERN, "—"); + return; + } + + if (!Array.isArray(node.children)) return; + + for (const child of node.children) { + visitTextNodes(child); + } +} + +export default function remarkTypography() { + return (tree) => { + visitTextNodes(tree); + }; +} diff --git a/sites/reviews/astro.config.mjs b/sites/reviews/astro.config.mjs index 5efa078..17d8510 100644 --- a/sites/reviews/astro.config.mjs +++ b/sites/reviews/astro.config.mjs @@ -12,6 +12,7 @@ import tailwindcss from "@tailwindcss/vite"; import remarkCallouts from "./src/remark/callouts.mjs"; import remarkCodeFenceLanguages from "./src/remark/code-fence-languages.mjs"; import remarkInlineFootnotes from "./src/remark/inline-footnotes.mjs"; +import remarkTypography from "./src/remark/typography.mjs"; import { ACTIVE_BLOG_SITE } from "./src/blog-sites.js"; const remarkPlugins = [ @@ -19,6 +20,7 @@ const remarkPlugins = [ remarkInlineFootnotes, remarkCodeFenceLanguages, remarkMath, + remarkTypography, remarkCallouts, ]; const rehypePlugins = [rehypeKatex]; diff --git a/sites/reviews/src/components/BaseHead.astro b/sites/reviews/src/components/BaseHead.astro index e519a3a..a0639ec 100644 --- a/sites/reviews/src/components/BaseHead.astro +++ b/sites/reviews/src/components/BaseHead.astro @@ -3,6 +3,7 @@ // all pages through the use of the component. import '../styles/global.css'; import { SITE_TITLE, SITE_METADATA } from '../consts'; +import { typographicText } from '@/lib/typography'; interface Props { title: string; @@ -13,8 +14,8 @@ interface Props { const canonicalURL = new URL(Astro.url.pathname, Astro.site); const { title, description, image } = Astro.props; -const finalTitle = title || SITE_METADATA.title.default; -const finalDescription = description || SITE_METADATA.description; +const finalTitle = typographicText(title || SITE_METADATA.title.default); +const finalDescription = typographicText(description || SITE_METADATA.description); const finalImage = image || SITE_METADATA.openGraph.images[0].url; const imageURL = new URL(finalImage, Astro.url); --- diff --git a/sites/reviews/src/components/sections/blog-post.tsx b/sites/reviews/src/components/sections/blog-post.tsx index 21082ce..3aa1a13 100644 --- a/sites/reviews/src/components/sections/blog-post.tsx +++ b/sites/reviews/src/components/sections/blog-post.tsx @@ -1,5 +1,6 @@ import type { ReactNode } from "react"; import { format } from "date-fns"; +import { typographicText } from "@/lib/typography"; const getReadingTime = (body: string | undefined) => { const words = (body ?? "") @@ -18,6 +19,7 @@ const BlogPost = ({ children: ReactNode; }) => { const { title, image, pubDate } = post.data; + const displayTitle = typographicText(title); const readingTime = getReadingTime(post.body); return ( @@ -30,12 +32,12 @@ const BlogPost = ({ {readingTime} min read
-

{title}

+

{displayTitle}

{image ? (
- {title} + {displayTitle}
) : null} diff --git a/sites/reviews/src/components/sections/blog-posts.tsx b/sites/reviews/src/components/sections/blog-posts.tsx index c77c76a..3b7ecf6 100644 --- a/sites/reviews/src/components/sections/blog-posts.tsx +++ b/sites/reviews/src/components/sections/blog-posts.tsx @@ -4,6 +4,7 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { Calendar, Clock, User } from "lucide-react"; +import { typographicText } from "@/lib/typography"; import { PlusSigns } from "../icons/plus-signs"; const BlogPosts = ({ @@ -27,11 +28,13 @@ const BlogPosts = ({ post: any; featured?: boolean; }) => { + const displayTitle = typographicText(post.data.title); + if (post.data.image) { return ( {post.data.title} ); @@ -44,7 +47,7 @@ const BlogPosts = ({ featured ? "font-fraunces text-2xl" : "font-fraunces text-lg" } > - {post.data.title} + {displayTitle}
); @@ -82,10 +85,10 @@ const BlogPosts = ({ Featured Post

- {featuredPost.data.title} + {typographicText(featuredPost.data.title)}

- {featuredPost.data.description} + {typographicText(featuredPost.data.description)}

@@ -128,10 +131,10 @@ const BlogPosts = ({

- {post.data.title} + {typographicText(post.data.title)}

- {post.data.description} + {typographicText(post.data.description)}

diff --git a/sites/reviews/src/lib/typography.ts b/sites/reviews/src/lib/typography.ts new file mode 100644 index 0000000..2aabeb8 --- /dev/null +++ b/sites/reviews/src/lib/typography.ts @@ -0,0 +1,5 @@ +const EM_DASH_PATTERN = /---/g; + +export function typographicText(value: string | null | undefined) { + return (value ?? "").replace(EM_DASH_PATTERN, "—"); +} diff --git a/sites/reviews/src/pages/rss.xml.js b/sites/reviews/src/pages/rss.xml.js index 918375e..0a688e3 100644 --- a/sites/reviews/src/pages/rss.xml.js +++ b/sites/reviews/src/pages/rss.xml.js @@ -2,6 +2,7 @@ import rss from "@astrojs/rss"; import { getCollection } from "astro:content"; import { SITE_TITLE, SITE_DESCRIPTION } from "../consts"; import { ACTIVE_BLOG_SITE } from "../blog-sites.js"; +import { typographicText } from "@/lib/typography"; export async function GET(context) { const posts = (await getCollection(ACTIVE_BLOG_SITE.key)).sort( @@ -10,10 +11,12 @@ export async function GET(context) { return rss({ title: SITE_TITLE, - description: SITE_DESCRIPTION, + description: typographicText(SITE_DESCRIPTION), site: context.site, items: posts.map((post) => ({ ...post.data, + title: typographicText(post.data.title), + description: typographicText(post.data.description), link: `/${post.id.replace(/\/index$/, "")}/`, })), }); diff --git a/sites/reviews/src/remark/typography.mjs b/sites/reviews/src/remark/typography.mjs new file mode 100644 index 0000000..93f846a --- /dev/null +++ b/sites/reviews/src/remark/typography.mjs @@ -0,0 +1,22 @@ +const EM_DASH_PATTERN = /---/g; + +function visitTextNodes(node) { + if (!node || typeof node !== "object") return; + + if (node.type === "text" && typeof node.value === "string") { + node.value = node.value.replace(EM_DASH_PATTERN, "—"); + return; + } + + if (!Array.isArray(node.children)) return; + + for (const child of node.children) { + visitTextNodes(child); + } +} + +export default function remarkTypography() { + return (tree) => { + visitTextNodes(tree); + }; +} diff --git a/sites/vibes/astro.config.mjs b/sites/vibes/astro.config.mjs index 5efa078..17d8510 100644 --- a/sites/vibes/astro.config.mjs +++ b/sites/vibes/astro.config.mjs @@ -12,6 +12,7 @@ import tailwindcss from "@tailwindcss/vite"; import remarkCallouts from "./src/remark/callouts.mjs"; import remarkCodeFenceLanguages from "./src/remark/code-fence-languages.mjs"; import remarkInlineFootnotes from "./src/remark/inline-footnotes.mjs"; +import remarkTypography from "./src/remark/typography.mjs"; import { ACTIVE_BLOG_SITE } from "./src/blog-sites.js"; const remarkPlugins = [ @@ -19,6 +20,7 @@ const remarkPlugins = [ remarkInlineFootnotes, remarkCodeFenceLanguages, remarkMath, + remarkTypography, remarkCallouts, ]; const rehypePlugins = [rehypeKatex]; diff --git a/sites/vibes/src/components/BaseHead.astro b/sites/vibes/src/components/BaseHead.astro index e519a3a..a0639ec 100644 --- a/sites/vibes/src/components/BaseHead.astro +++ b/sites/vibes/src/components/BaseHead.astro @@ -3,6 +3,7 @@ // all pages through the use of the component. import '../styles/global.css'; import { SITE_TITLE, SITE_METADATA } from '../consts'; +import { typographicText } from '@/lib/typography'; interface Props { title: string; @@ -13,8 +14,8 @@ interface Props { const canonicalURL = new URL(Astro.url.pathname, Astro.site); const { title, description, image } = Astro.props; -const finalTitle = title || SITE_METADATA.title.default; -const finalDescription = description || SITE_METADATA.description; +const finalTitle = typographicText(title || SITE_METADATA.title.default); +const finalDescription = typographicText(description || SITE_METADATA.description); const finalImage = image || SITE_METADATA.openGraph.images[0].url; const imageURL = new URL(finalImage, Astro.url); --- diff --git a/sites/vibes/src/components/sections/blog-post.tsx b/sites/vibes/src/components/sections/blog-post.tsx index 21082ce..3aa1a13 100644 --- a/sites/vibes/src/components/sections/blog-post.tsx +++ b/sites/vibes/src/components/sections/blog-post.tsx @@ -1,5 +1,6 @@ import type { ReactNode } from "react"; import { format } from "date-fns"; +import { typographicText } from "@/lib/typography"; const getReadingTime = (body: string | undefined) => { const words = (body ?? "") @@ -18,6 +19,7 @@ const BlogPost = ({ children: ReactNode; }) => { const { title, image, pubDate } = post.data; + const displayTitle = typographicText(title); const readingTime = getReadingTime(post.body); return ( @@ -30,12 +32,12 @@ const BlogPost = ({ {readingTime} min read
-

{title}

+

{displayTitle}

{image ? (
- {title} + {displayTitle}
) : null} diff --git a/sites/vibes/src/components/sections/blog-posts.tsx b/sites/vibes/src/components/sections/blog-posts.tsx index c77c76a..3b7ecf6 100644 --- a/sites/vibes/src/components/sections/blog-posts.tsx +++ b/sites/vibes/src/components/sections/blog-posts.tsx @@ -4,6 +4,7 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { Calendar, Clock, User } from "lucide-react"; +import { typographicText } from "@/lib/typography"; import { PlusSigns } from "../icons/plus-signs"; const BlogPosts = ({ @@ -27,11 +28,13 @@ const BlogPosts = ({ post: any; featured?: boolean; }) => { + const displayTitle = typographicText(post.data.title); + if (post.data.image) { return ( {post.data.title} ); @@ -44,7 +47,7 @@ const BlogPosts = ({ featured ? "font-fraunces text-2xl" : "font-fraunces text-lg" } > - {post.data.title} + {displayTitle}
); @@ -82,10 +85,10 @@ const BlogPosts = ({ Featured Post

- {featuredPost.data.title} + {typographicText(featuredPost.data.title)}

- {featuredPost.data.description} + {typographicText(featuredPost.data.description)}

@@ -128,10 +131,10 @@ const BlogPosts = ({

- {post.data.title} + {typographicText(post.data.title)}

- {post.data.description} + {typographicText(post.data.description)}

diff --git a/sites/vibes/src/content/vibes/building-interactives/index.md b/sites/vibes/src/content/vibes/building-interactives/index.md index 3f74ce7..6f533b4 100644 --- a/sites/vibes/src/content/vibes/building-interactives/index.md +++ b/sites/vibes/src/content/vibes/building-interactives/index.md @@ -397,22 +397,17 @@ For visual clarity we also follow up with the following suggestion. > for smaller ones)? -
-
- -
The initial configuration
-
-
- -
An example where the current cost is worse than the optimal -cost.
-
-
- -
An example where the current and optimal costs are the -same.
-
-
+![The initial configuration](image003-a.png) + +_The initial configuration_ + +![An example where the current cost is worse than the optimal cost.](image003-b.png) + +_An example where the current cost is worse than the optimal cost._ + +![An example where the current and optimal costs are the same.](image003-c.png) + +_An example where the current and optimal costs are the same._ The current version of this implementation is [here](https://filestorage2.netlify.app/), and it's still a little quirky in terms of the first couple of times you drag the slider, diff --git a/sites/vibes/src/lib/typography.ts b/sites/vibes/src/lib/typography.ts new file mode 100644 index 0000000..2aabeb8 --- /dev/null +++ b/sites/vibes/src/lib/typography.ts @@ -0,0 +1,5 @@ +const EM_DASH_PATTERN = /---/g; + +export function typographicText(value: string | null | undefined) { + return (value ?? "").replace(EM_DASH_PATTERN, "—"); +} diff --git a/sites/vibes/src/pages/rss.xml.js b/sites/vibes/src/pages/rss.xml.js index 918375e..0a688e3 100644 --- a/sites/vibes/src/pages/rss.xml.js +++ b/sites/vibes/src/pages/rss.xml.js @@ -2,6 +2,7 @@ import rss from "@astrojs/rss"; import { getCollection } from "astro:content"; import { SITE_TITLE, SITE_DESCRIPTION } from "../consts"; import { ACTIVE_BLOG_SITE } from "../blog-sites.js"; +import { typographicText } from "@/lib/typography"; export async function GET(context) { const posts = (await getCollection(ACTIVE_BLOG_SITE.key)).sort( @@ -10,10 +11,12 @@ export async function GET(context) { return rss({ title: SITE_TITLE, - description: SITE_DESCRIPTION, + description: typographicText(SITE_DESCRIPTION), site: context.site, items: posts.map((post) => ({ ...post.data, + title: typographicText(post.data.title), + description: typographicText(post.data.description), link: `/${post.id.replace(/\/index$/, "")}/`, })), }); diff --git a/sites/vibes/src/remark/typography.mjs b/sites/vibes/src/remark/typography.mjs new file mode 100644 index 0000000..93f846a --- /dev/null +++ b/sites/vibes/src/remark/typography.mjs @@ -0,0 +1,22 @@ +const EM_DASH_PATTERN = /---/g; + +function visitTextNodes(node) { + if (!node || typeof node !== "object") return; + + if (node.type === "text" && typeof node.value === "string") { + node.value = node.value.replace(EM_DASH_PATTERN, "—"); + return; + } + + if (!Array.isArray(node.children)) return; + + for (const child of node.children) { + visitTextNodes(child); + } +} + +export default function remarkTypography() { + return (tree) => { + visitTextNodes(tree); + }; +} diff --git a/src/components/BaseHead.astro b/src/components/BaseHead.astro index 19ca5c4..a0639ec 100644 --- a/src/components/BaseHead.astro +++ b/src/components/BaseHead.astro @@ -3,6 +3,7 @@ // all pages through the use of the component. import '../styles/global.css'; import { SITE_TITLE, SITE_METADATA } from '../consts'; +import { typographicText } from '@/lib/typography'; interface Props { title: string; @@ -13,8 +14,8 @@ interface Props { const canonicalURL = new URL(Astro.url.pathname, Astro.site); const { title, description, image } = Astro.props; -const finalTitle = title || SITE_METADATA.title.default; -const finalDescription = description || SITE_METADATA.description; +const finalTitle = typographicText(title || SITE_METADATA.title.default); +const finalDescription = typographicText(description || SITE_METADATA.description); const finalImage = image || SITE_METADATA.openGraph.images[0].url; const imageURL = new URL(finalImage, Astro.url); --- @@ -27,7 +28,7 @@ const imageURL = new URL(finalImage, Astro.url); - + diff --git a/src/components/sections/blog-post.tsx b/src/components/sections/blog-post.tsx index 21082ce..3aa1a13 100644 --- a/src/components/sections/blog-post.tsx +++ b/src/components/sections/blog-post.tsx @@ -1,5 +1,6 @@ import type { ReactNode } from "react"; import { format } from "date-fns"; +import { typographicText } from "@/lib/typography"; const getReadingTime = (body: string | undefined) => { const words = (body ?? "") @@ -18,6 +19,7 @@ const BlogPost = ({ children: ReactNode; }) => { const { title, image, pubDate } = post.data; + const displayTitle = typographicText(title); const readingTime = getReadingTime(post.body); return ( @@ -30,12 +32,12 @@ const BlogPost = ({ {readingTime} min read
-

{title}

+

{displayTitle}

{image ? (
- {title} + {displayTitle}
) : null} diff --git a/src/components/sections/blog-posts.tsx b/src/components/sections/blog-posts.tsx index 00f8ec8..3b7ecf6 100644 --- a/src/components/sections/blog-posts.tsx +++ b/src/components/sections/blog-posts.tsx @@ -4,6 +4,7 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { Calendar, Clock, User } from "lucide-react"; +import { typographicText } from "@/lib/typography"; import { PlusSigns } from "../icons/plus-signs"; const BlogPosts = ({ @@ -16,11 +17,52 @@ const BlogPosts = ({ // Get the first post as the featured post const featuredPost = posts[0]; const remainingPosts = posts.slice(1); + const slugFor = (post: any) => post.id.replace(/\/index$/, ""); const hrefFor = (post: any) => - collection ? `/${collection}/${post.id}/` : `/${post.id}/`; + collection ? `/${collection}/${slugFor(post)}/` : `/${slugFor(post)}/`; + + const PostVisual = ({ + post, + featured = false, + }: { + post: any; + featured?: boolean; + }) => { + const displayTitle = typographicText(post.data.title); + + if (post.data.image) { + return ( + {displayTitle} + ); + } + + return ( +
+ + {displayTitle} + +
+ ); + }; + + if (!featuredPost) { + return ( +
+ No posts yet. +
+ ); + } return ( -
+
@@ -35,22 +77,18 @@ const BlogPosts = ({
- {featuredPost.data.title} +
Featured Post -

- {featuredPost.data.title} +

+ {typographicText(featuredPost.data.title)}

- {featuredPost.data.description} + {typographicText(featuredPost.data.description)}

@@ -89,18 +127,14 @@ const BlogPosts = ({ href={hrefFor(post)} >
- {post.data.title} +

- {post.data.title} + {typographicText(post.data.title)}

- {post.data.description} + {typographicText(post.data.description)}

diff --git a/src/lib/typography.ts b/src/lib/typography.ts new file mode 100644 index 0000000..2aabeb8 --- /dev/null +++ b/src/lib/typography.ts @@ -0,0 +1,5 @@ +const EM_DASH_PATTERN = /---/g; + +export function typographicText(value: string | null | undefined) { + return (value ?? "").replace(EM_DASH_PATTERN, "—"); +} diff --git a/src/pages/rss.xml.js b/src/pages/rss.xml.js index ec2ce2d..0a688e3 100644 --- a/src/pages/rss.xml.js +++ b/src/pages/rss.xml.js @@ -2,6 +2,7 @@ import rss from "@astrojs/rss"; import { getCollection } from "astro:content"; import { SITE_TITLE, SITE_DESCRIPTION } from "../consts"; import { ACTIVE_BLOG_SITE } from "../blog-sites.js"; +import { typographicText } from "@/lib/typography"; export async function GET(context) { const posts = (await getCollection(ACTIVE_BLOG_SITE.key)).sort( @@ -10,11 +11,13 @@ export async function GET(context) { return rss({ title: SITE_TITLE, - description: SITE_DESCRIPTION, + description: typographicText(SITE_DESCRIPTION), site: context.site, items: posts.map((post) => ({ ...post.data, - link: `/${post.id}/`, + title: typographicText(post.data.title), + description: typographicText(post.data.description), + link: `/${post.id.replace(/\/index$/, "")}/`, })), }); }