- {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 ? (
-
+
) : 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 (

);
@@ -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 ? (
-
+
) : 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 (

);
@@ -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 ? (
-
+
) : 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 (

);
@@ -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 ? (
-
+
) : 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 (

);
@@ -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 ? (
-
+
) : 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 (

);
@@ -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_
+
+
+
+_An example where the current cost is worse than the optimal cost._
+
+
+
+_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 ? (
-
+
) : 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 (
+

+ );
+ }
+
+ return (
+
+
+ {displayTitle}
+
+
+ );
+ };
+
+ if (!featuredPost) {
+ return (
+
+ No posts yet.
+
+ );
+ }
return (
-
+
@@ -35,22 +77,18 @@ const BlogPosts = ({
-

+
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}
+ {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$/, "")}/`,
})),
});
}